Skip to content

Commit

Permalink
feat(css) add experimental webpack CSS support
Browse files Browse the repository at this point in the history
  • Loading branch information
noreiller committed Aug 8, 2022
1 parent 8ebd1a2 commit 645b1e2
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 3 deletions.
25 changes: 22 additions & 3 deletions packages/next/build/webpack-config.ts
Expand Up @@ -1237,6 +1237,7 @@ export default async function getBaseWebpackConfig(
dev ? '' : appDir ? '-[chunkhash]' : '-[contenthash]'
}.js`,
library: isClient || isEdgeServer ? '_N_E' : undefined,
uniqueName: config.experimental.webpackCssExperiment ? '_N_E' : undefined,
libraryTarget: isClient || isEdgeServer ? 'assign' : 'commonjs2',
hotUpdateChunkFilename: 'static/webpack/[id].[fullhash].hot-update.js',
hotUpdateMainFilename:
Expand Down Expand Up @@ -1751,6 +1752,21 @@ export default async function getBaseWebpackConfig(
].filter<[Feature, boolean]>(Boolean as any)
)
),
...(config.experimental.webpackCssExperiment
? [
new webpack.ids.DeterministicModuleIdsPlugin({
maxLength: 5,
failOnConflict: true,
fixedLength: true,
test: (m) => m.type.startsWith('css'),
}),
new webpack.experiments.ids.SyncModuleIdsPlugin({
test: (m) => m.type.startsWith('css'),
path: path.resolve(distDir, 'module-ids.json'),
mode: 'create',
}),
]
: []),
].filter(Boolean as any as ExcludesFalse),
}

Expand Down Expand Up @@ -1802,6 +1818,7 @@ export default async function getBaseWebpackConfig(
...config.experimental.urlImports,
}
: undefined,
css: config.experimental.webpackCssExperiment,
}

webpack5Config.module!.parser = {
Expand Down Expand Up @@ -2162,9 +2179,11 @@ export default async function getBaseWebpackConfig(
}

const hasUserCssConfig =
webpackConfig.module?.rules.some(
(rule) => canMatchCss(rule.test) || canMatchCss(rule.include)
) ?? false
(config.experimental.webpackCssExperiment ||
webpackConfig.module?.rules.some(
(rule) => canMatchCss(rule.test) || canMatchCss(rule.include)
)) ??
false

if (hasUserCssConfig) {
// only show warning for one build
Expand Down
19 changes: 19 additions & 0 deletions packages/next/build/webpack/config/blocks/css/index.ts
Expand Up @@ -502,6 +502,25 @@ export const css = curry(async function css(
)
}

if (ctx.experimental.webpackCssExperiment) {
fns.push(
loader({
oneOf: [
{
test: regexSassModules,
use: require.resolve('next/dist/compiled/sass-loader'),
type: 'css/module',
},
{
test: regexSassGlobal,
use: require.resolve('next/dist/compiled/sass-loader'),
type: 'css',
},
],
})
)
}

const fn = pipe(...fns)
return fn(config)
})
1 change: 1 addition & 0 deletions packages/next/server/config-shared.ts
Expand Up @@ -98,6 +98,7 @@ export interface ExperimentalConfig {
// Use Record<string, unknown> as critters doesn't export its Option type
// https://github.com/GoogleChromeLabs/critters/blob/a590c05f9197b656d2aeaae9369df2483c26b072/packages/critters/src/index.d.ts
optimizeCss?: boolean | Record<string, unknown>
webpackCssExperiment?: boolean
nextScriptWorkers?: boolean
scrollRestoration?: boolean
externalDir?: boolean
Expand Down
87 changes: 87 additions & 0 deletions packages/next/types/webpack.d.ts
Expand Up @@ -161,6 +161,7 @@ declare module 'webpack4' {
/** Optimization options */
optimization?: Options.Optimization
experiments?: {
css: boolean | { exportsOnly?: boolean }
layers: boolean
}
}
Expand Down Expand Up @@ -244,6 +245,8 @@ declare module 'webpack4' {
libraryExport?: string | string[]
/** If output.libraryTarget is set to umd and output.library is set, setting this to true will name the AMD module. */
umdNamedDefine?: boolean
/** A unique name of the webpack build to avoid multiple webpack runtimes to conflict when using globals. */
uniqueName?: string
/** The output directory as absolute path. */
path?: string
/** Include comments with information about the modules. */
Expand Down Expand Up @@ -319,6 +322,7 @@ declare module 'webpack4' {
generator?: {
asset?: any
}
type?: any
}

interface Resolve {
Expand Down Expand Up @@ -571,6 +575,8 @@ declare module 'webpack4' {
| 'json'
| 'webassembly/experimental'
| 'asset/resource'
| 'css'
| 'css/module'
/**
* Match the resource path of the module
*/
Expand Down Expand Up @@ -2071,6 +2077,87 @@ declare module 'webpack4' {

namespace dependencies {}

namespace ids {
class DeterministicModuleIdsPlugin extends Plugin {
constructor(options?: {
/**
* context relative to which module identifiers are computed
*/
context?: string
/**
* selector function for modules
*/
test?: (arg0: Module) => boolean
/**
* maximum id length in digits (used as starting point)
*/
maxLength?: number
/**
* hash salt for ids
*/
salt?: number
/**
* do not increase the maxLength to find an optimal id space size
*/
fixedLength?: boolean
/**
* throw an error when id conflicts occur (instead of rehashing)
*/
failOnConflict?: boolean
})
options: {
/**
* context relative to which module identifiers are computed
*/
context?: string
/**
* selector function for modules
*/
test?: (arg0: Module) => boolean
/**
* maximum id length in digits (used as starting point)
*/
maxLength?: number
/**
* hash salt for ids
*/
salt?: number
/**
* do not increase the maxLength to find an optimal id space size
*/
fixedLength?: boolean
/**
* throw an error when id conflicts occur (instead of rehashing)
*/
failOnConflict?: boolean
}
}
}
namespace experiments {
namespace ids {
class SyncModuleIdsPlugin extends Plugin {
constructor(options: {
/**
* path to file
*/
path: string
/**
* context for module names
*/
context?: string
/**
* selector for modules
*/
test: (arg0: Module) => boolean
/**
* operation mode (defaults to merge)
*/
mode?: 'read' | 'create' | 'merge' | 'update'
})
}
}
}

namespace loader {
interface Loader extends Function {
(
Expand Down
90 changes: 90 additions & 0 deletions test/e2e/css-webpack-experiment/index.test.ts
@@ -0,0 +1,90 @@
import { createNext } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import webdriver from 'next-webdriver'

describe('CSS webpack Experiment', () => {
describe('with basic CSS', () => {
let next: NextInstance

beforeAll(async () => {
next = await createNext({
files: {
'styles/global.css': `
p {
color: green;
font-size: 2rem;
}
`,
'styles/styles.module.css': `
.hello {
color: red;
}
`,
'styles/styles.module.sass': `
$color: blue
.hello
color: $color
`,
'pages/_app.js': `
import '../styles/global.css';
export default function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
`,
'pages/index.js': `
export default function Page() {
return <p>hello world</p>
}
`,
'pages/css-modules.js': `
import * as styles from '../styles/styles.module.css';
export default function Page() {
return <p className={styles.hello}>hello world</p>
}
`,
'pages/sass.js': `
import * as styles from '../styles/styles.module.sass';
export default function Page() {
return <p className={styles.hello}>hello world</p>
}
`,
},
nextConfig: {
experimental: {
webpackCssExperiment: true,
},
},
dependencies: {
sass: '1.52.3',
},
})
})

afterAll(() => next.destroy())

it('should work with basic CSS', async () => {
const browser = await webdriver(next.url, `/`)
const element = await browser.elementByCss('p')
const color = await element.getComputedCss('color')

expect(color).toBe('rgb(0, 128, 0)')
})

it('should work with CSS modules', async () => {
const browser = await webdriver(next.url, `/css-modules`)
const element = await browser.elementByCss('p')
const color = await element.getComputedCss('color')

expect(color).toBe('rgb(255, 0, 0)')
})

it('should work with SASS', async () => {
const browser = await webdriver(next.url, `/sass`)
const element = await browser.elementByCss('p')
const color = await element.getComputedCss('color')

expect(color).toBe('rgb(0, 0, 255)')
})
})
})

0 comments on commit 645b1e2

Please sign in to comment.