261 lines
8 KiB
TypeScript
261 lines
8 KiB
TypeScript
import curry from 'lodash.curry'
|
|
import path from 'path'
|
|
import webpack, { Configuration } from 'webpack'
|
|
import MiniCssExtractPlugin from '../../../plugins/mini-css-extract-plugin'
|
|
import { loader } from '../../helpers'
|
|
import { ConfigurationContext, ConfigurationFn, pipe } from '../../utils'
|
|
import { getCssModuleLocalIdent } from './getCssModuleLocalIdent'
|
|
import { getGlobalImportError, getModuleImportError } from './messages'
|
|
import { getPostCssPlugins } from './plugins'
|
|
|
|
function getClientStyleLoader({
|
|
isDevelopment,
|
|
}: {
|
|
isDevelopment: boolean
|
|
}): webpack.RuleSetUseItem {
|
|
return isDevelopment
|
|
? {
|
|
loader: require.resolve('style-loader'),
|
|
options: {
|
|
// By default, style-loader injects CSS into the bottom
|
|
// of <head>. This causes ordering problems between dev
|
|
// and prod. To fix this, we render a <noscript> tag as
|
|
// an anchor for the styles to be placed before. These
|
|
// styles will be applied _before_ <style jsx global>.
|
|
insert: function(element: Node) {
|
|
// These elements should always exist. If they do not,
|
|
// this code should fail.
|
|
var anchorElement = document.querySelector(
|
|
'#__next_css__DO_NOT_USE__'
|
|
)!
|
|
var parentNode = anchorElement.parentNode! // Normally <head>
|
|
|
|
// Each style tag should be placed right before our
|
|
// anchor. By inserting before and not after, we do not
|
|
// need to track the last inserted element.
|
|
parentNode.insertBefore(element, anchorElement)
|
|
|
|
// Remember: this is development only code.
|
|
//
|
|
// After styles are injected, we need to remove the
|
|
// <style> tags that set `body { display: none; }`.
|
|
//
|
|
// We use `requestAnimationFrame` as a way to defer
|
|
// this operation since there may be multiple style
|
|
// tags.
|
|
;(self.requestAnimationFrame || setTimeout)(function() {
|
|
for (
|
|
var x = document.querySelectorAll('[data-next-hide-fouc]'),
|
|
i = x.length;
|
|
i--;
|
|
|
|
) {
|
|
x[i].parentNode!.removeChild(x[i])
|
|
}
|
|
})
|
|
},
|
|
},
|
|
}
|
|
: {
|
|
loader: MiniCssExtractPlugin.loader,
|
|
options: {},
|
|
}
|
|
}
|
|
|
|
export const css = curry(async function css(
|
|
enabled: boolean,
|
|
ctx: ConfigurationContext,
|
|
config: Configuration
|
|
) {
|
|
if (!enabled) {
|
|
return config
|
|
}
|
|
|
|
const fns: ConfigurationFn[] = [
|
|
loader({
|
|
oneOf: [
|
|
{
|
|
// Impossible regex expression
|
|
test: /a^/,
|
|
loader: 'noop-loader',
|
|
options: { __next_css_remove: true },
|
|
},
|
|
],
|
|
}),
|
|
]
|
|
|
|
const postCssPlugins = await getPostCssPlugins(ctx.rootDirectory)
|
|
// CSS Modules support must be enabled on the server and client so the class
|
|
// names are availble for SSR or Prerendering.
|
|
fns.push(
|
|
loader({
|
|
oneOf: [
|
|
{
|
|
// CSS Modules should never have side effects. This setting will
|
|
// allow unused CSS to be removed from the production build.
|
|
// We ensure this by disallowing `:global()` CSS at the top-level
|
|
// via the `pure` mode in `css-loader`.
|
|
sideEffects: false,
|
|
// CSS Modules are activated via this specific extension.
|
|
test: /\.module\.css$/,
|
|
// CSS Modules are only supported in the user's application. We're
|
|
// not yet allowing CSS imports _within_ `node_modules`.
|
|
issuer: {
|
|
include: [ctx.rootDirectory],
|
|
exclude: /node_modules/,
|
|
},
|
|
|
|
use: ([
|
|
// Add appropriate development more or production mode style
|
|
// loader
|
|
ctx.isClient &&
|
|
getClientStyleLoader({ isDevelopment: ctx.isDevelopment }),
|
|
|
|
// Resolve CSS `@import`s and `url()`s
|
|
{
|
|
loader: require.resolve('css-loader'),
|
|
options: {
|
|
importLoaders: 1,
|
|
sourceMap: true,
|
|
onlyLocals: ctx.isServer,
|
|
modules: {
|
|
// Disallow global style exports so we can code-split CSS and
|
|
// not worry about loading order.
|
|
mode: 'pure',
|
|
// Generate a friendly production-ready name so it's
|
|
// reasonably understandable. The same name is used for
|
|
// development.
|
|
// TODO: Consider making production reduce this to a single
|
|
// character?
|
|
getLocalIdent: getCssModuleLocalIdent,
|
|
},
|
|
},
|
|
},
|
|
|
|
// Compile CSS
|
|
{
|
|
loader: require.resolve('postcss-loader'),
|
|
options: {
|
|
ident: 'postcss',
|
|
plugins: postCssPlugins,
|
|
sourceMap: true,
|
|
},
|
|
},
|
|
] as webpack.RuleSetUseItem[]).filter(Boolean),
|
|
},
|
|
],
|
|
})
|
|
)
|
|
|
|
// Throw an error for CSS Modules used outside their supported scope
|
|
fns.push(
|
|
loader({
|
|
oneOf: [
|
|
{
|
|
test: /\.module\.css$/,
|
|
use: {
|
|
loader: 'error-loader',
|
|
options: {
|
|
reason: getModuleImportError(),
|
|
},
|
|
},
|
|
},
|
|
],
|
|
})
|
|
)
|
|
|
|
if (ctx.isServer) {
|
|
fns.push(
|
|
loader({
|
|
oneOf: [{ test: /\.css$/, use: require.resolve('ignore-loader') }],
|
|
})
|
|
)
|
|
} else if (ctx.customAppFile) {
|
|
fns.push(
|
|
loader({
|
|
oneOf: [
|
|
{
|
|
// A global CSS import always has side effects. Webpack will tree
|
|
// shake the CSS without this option if the issuer claims to have
|
|
// no side-effects.
|
|
// See https://github.com/webpack/webpack/issues/6571
|
|
sideEffects: true,
|
|
test: /\.css$/,
|
|
issuer: { include: ctx.customAppFile },
|
|
|
|
use: [
|
|
// Add appropriate development more or production mode style
|
|
// loader
|
|
getClientStyleLoader({ isDevelopment: ctx.isDevelopment }),
|
|
|
|
// Resolve CSS `@import`s and `url()`s
|
|
{
|
|
loader: require.resolve('css-loader'),
|
|
options: { importLoaders: 1, sourceMap: true },
|
|
},
|
|
|
|
// Compile CSS
|
|
{
|
|
loader: require.resolve('postcss-loader'),
|
|
options: {
|
|
ident: 'postcss',
|
|
plugins: postCssPlugins,
|
|
sourceMap: true,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
})
|
|
)
|
|
}
|
|
|
|
// Throw an error for Global CSS used outside of our custom <App> file
|
|
fns.push(
|
|
loader({
|
|
oneOf: [
|
|
{
|
|
test: /\.css$/,
|
|
use: {
|
|
loader: 'error-loader',
|
|
options: {
|
|
reason: getGlobalImportError(
|
|
ctx.customAppFile &&
|
|
path.relative(ctx.rootDirectory, ctx.customAppFile)
|
|
),
|
|
},
|
|
},
|
|
},
|
|
],
|
|
})
|
|
)
|
|
|
|
if (ctx.isClient) {
|
|
// Automatically transform references to files (i.e. url()) into URLs
|
|
// e.g. url(./logo.svg)
|
|
fns.push(
|
|
loader({
|
|
oneOf: [
|
|
{
|
|
// This should only be applied to CSS files
|
|
issuer: { test: /\.css$/ },
|
|
// Exclude extensions that webpack handles by default
|
|
exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
|
|
use: {
|
|
// `file-loader` always emits a URL reference, where `url-loader`
|
|
// might inline the asset as a data URI
|
|
loader: require.resolve('file-loader'),
|
|
options: {
|
|
// Hash the file for immutable cacheability
|
|
name: 'static/media/[name].[hash].[ext]',
|
|
},
|
|
},
|
|
},
|
|
],
|
|
})
|
|
)
|
|
}
|
|
|
|
const fn = pipe(...fns)
|
|
return fn(config)
|
|
})
|