Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract CSS Loaders into Separate Files #10210

Merged
merged 2 commits into from Jan 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
128 changes: 4 additions & 124 deletions packages/next/build/webpack/config/blocks/css/index.ts
@@ -1,10 +1,10 @@
import curry from 'lodash.curry'
import path from 'path'
import webpack, { Configuration } from 'webpack'
import { Configuration } from 'webpack'
import MiniCssExtractPlugin from '../../../plugins/mini-css-extract-plugin'
import { loader, plugin } from '../../helpers'
import { ConfigurationContext, ConfigurationFn, pipe } from '../../utils'
import { getCssModuleLocalIdent } from './getCssModuleLocalIdent'
import { getCssModuleLoader, getGlobalCssLoader } from './loaders'
import {
getCustomDocumentError,
getGlobalImportError,
Expand All @@ -18,62 +18,6 @@ const regexCssAll = /\.css$/
const regexCssGlobal = /(?<!\.module)\.css$/
const regexCssModules = /\.module\.css$/

function getClientStyleLoader({
isDevelopment,
assetPrefix,
}: {
isDevelopment: boolean
assetPrefix: string
}): 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: { publicPath: `${assetPrefix}/_next/` },
}
}

export const css = curry(async function css(
enabled: boolean,
ctx: ConfigurationContext,
Expand Down Expand Up @@ -145,47 +89,7 @@ export const css = curry(async function css(
include: [ctx.rootDirectory],
exclude: /node_modules/,
},

use: ([
// Add appropriate development more or production mode style
// loader
ctx.isClient &&
getClientStyleLoader({
isDevelopment: ctx.isDevelopment,
assetPrefix: ctx.assetPrefix,
}),

// 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: '__nextjs_postcss',
plugins: postCssPlugins,
sourceMap: true,
},
},
] as webpack.RuleSetUseItem[]).filter(Boolean),
use: getCssModuleLoader(ctx, postCssPlugins),
},
],
})
Expand Down Expand Up @@ -228,31 +132,7 @@ export const css = curry(async function css(
sideEffects: true,
test: regexCssGlobal,
issuer: { include: ctx.customAppFile },

use: [
// Add appropriate development more or production mode style
// loader
getClientStyleLoader({
isDevelopment: ctx.isDevelopment,
assetPrefix: ctx.assetPrefix,
}),

// 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: '__nextjs_postcss',
plugins: postCssPlugins,
sourceMap: true,
},
},
],
use: getGlobalCssLoader(ctx, postCssPlugins),
},
],
})
Expand Down
58 changes: 58 additions & 0 deletions packages/next/build/webpack/config/blocks/css/loaders/client.ts
@@ -0,0 +1,58 @@
import webpack from 'webpack'
import MiniCssExtractPlugin from '../../../../plugins/mini-css-extract-plugin'

export function getClientStyleLoader({
isDevelopment,
assetPrefix,
}: {
isDevelopment: boolean
assetPrefix: string
}): 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: { publicPath: `${assetPrefix}/_next/` },
}
}
40 changes: 40 additions & 0 deletions packages/next/build/webpack/config/blocks/css/loaders/global.ts
@@ -0,0 +1,40 @@
import postcss from 'postcss'
import webpack from 'webpack'
import { ConfigurationContext } from '../../../utils'
import { getClientStyleLoader } from './client'

export function getGlobalCssLoader(
ctx: ConfigurationContext,
postCssPlugins: postcss.AcceptedPlugin[]
): webpack.RuleSetUseItem[] {
const loaders: webpack.RuleSetUseItem[] = []

if (ctx.isClient) {
// Add appropriate development more or production mode style
// loader
loaders.push(
getClientStyleLoader({
isDevelopment: ctx.isDevelopment,
assetPrefix: ctx.assetPrefix,
})
)
}

// Resolve CSS `@import`s and `url()`s
loaders.push({
loader: require.resolve('css-loader'),
options: { importLoaders: 1, sourceMap: true },
})

// Compile CSS
loaders.push({
loader: require.resolve('postcss-loader'),
options: {
ident: '__nextjs_postcss',
plugins: postCssPlugins,
sourceMap: true,
},
})

return loaders
}
@@ -0,0 +1,2 @@
export * from './global'
export * from './modules'
@@ -0,0 +1,56 @@
import postcss from 'postcss'
import webpack from 'webpack'
import { ConfigurationContext } from '../../../utils'
import { getClientStyleLoader } from './client'
import { getCssModuleLocalIdent } from './getCssModuleLocalIdent'

export function getCssModuleLoader(
ctx: ConfigurationContext,
postCssPlugins: postcss.AcceptedPlugin[]
): webpack.RuleSetUseItem[] {
const loaders: webpack.RuleSetUseItem[] = []

if (ctx.isClient) {
// Add appropriate development more or production mode style
// loader
loaders.push(
getClientStyleLoader({
isDevelopment: ctx.isDevelopment,
assetPrefix: ctx.assetPrefix,
})
)
}

// Resolve CSS `@import`s and `url()`s
loaders.push({
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
loaders.push({
loader: require.resolve('postcss-loader'),
options: {
ident: '__nextjs_postcss',
plugins: postCssPlugins,
sourceMap: true,
},
})

return loaders
}