diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index c9fa02e2f4683c9..1b2e33c7c6e5386 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -1208,6 +1208,7 @@ export default async function getBaseWebpackConfig( assetPrefix: config.assetPrefix || '', sassOptions: config.sassOptions, productionBrowserSourceMaps: config.productionBrowserSourceMaps, + future: config.future, }) let originalDevtool = webpackConfig.devtool @@ -1330,7 +1331,7 @@ export default async function getBaseWebpackConfig( (e) => (e as any).__next_css_remove !== true ) } - } else { + } else if (!config.future.strictPostcssConfiguration) { await __overrideCssConfiguration(dir, !dev, webpackConfig) } diff --git a/packages/next/build/webpack/config/blocks/css/index.ts b/packages/next/build/webpack/config/blocks/css/index.ts index 1d4b85b7dfb2743..549e061cb05fd4d 100644 --- a/packages/next/build/webpack/config/blocks/css/index.ts +++ b/packages/next/build/webpack/config/blocks/css/index.ts @@ -78,10 +78,7 @@ export const css = curry(async function css( const postCssPlugins = await getPostCssPlugins( ctx.rootDirectory, ctx.isProduction, - // TODO: In the future, we should stop supporting old CSS setups and - // unconditionally inject ours. When that happens, we should remove this - // function argument. - true + !ctx.future.strictPostcssConfiguration ) // CSS cannot be imported in _document. This comes before everything because diff --git a/packages/next/build/webpack/config/index.ts b/packages/next/build/webpack/config/index.ts index 7b110b8ddc11c31..ece56c922d31031 100644 --- a/packages/next/build/webpack/config/index.ts +++ b/packages/next/build/webpack/config/index.ts @@ -1,4 +1,5 @@ import webpack from 'webpack' +import { NextConfig } from '../../../next-server/server/config' import { base } from './blocks/base' import { css } from './blocks/css' import { ConfigurationContext, pipe } from './utils' @@ -13,6 +14,7 @@ export async function build( assetPrefix, sassOptions, productionBrowserSourceMaps, + future, }: { rootDirectory: string customAppFile: string | null @@ -21,6 +23,7 @@ export async function build( assetPrefix: string sassOptions: any productionBrowserSourceMaps: boolean + future: NextConfig['future'] } ): Promise { const ctx: ConfigurationContext = { @@ -37,6 +40,7 @@ export async function build( : '', sassOptions, productionBrowserSourceMaps, + future, } const fn = pipe(base(ctx), css(ctx)) diff --git a/packages/next/build/webpack/config/utils.ts b/packages/next/build/webpack/config/utils.ts index 28c08317e5466d3..d15a3f1d3db96bf 100644 --- a/packages/next/build/webpack/config/utils.ts +++ b/packages/next/build/webpack/config/utils.ts @@ -1,4 +1,5 @@ import webpack from 'webpack' +import { NextConfig } from '../../../next-server/server/config' export type ConfigurationContext = { rootDirectory: string @@ -14,6 +15,8 @@ export type ConfigurationContext = { sassOptions: any productionBrowserSourceMaps: boolean + + future: NextConfig['future'] } export type ConfigurationFn = ( diff --git a/packages/next/next-server/server/config.ts b/packages/next/next-server/server/config.ts index 8b8a50b1b1473d2..ba8b664dae66bbf 100644 --- a/packages/next/next-server/server/config.ts +++ b/packages/next/next-server/server/config.ts @@ -31,6 +31,11 @@ export type NextConfig = { [key: string]: any } & { redirects?: () => Promise trailingSlash?: boolean + + future: { + strictPostcssConfiguration: boolean + excludeDefaultMomentLocales: boolean + } } const defaultConfig: NextConfig = { @@ -83,6 +88,7 @@ const defaultConfig: NextConfig = { scriptLoader: false, }, future: { + strictPostcssConfiguration: false, excludeDefaultMomentLocales: false, }, serverRuntimeConfig: {}, diff --git a/test/integration/css-customization/test/index.test.js b/test/integration/css-customization/test/index.test.js index 015d5bfd20dcb17..54034b9dd3cd336 100644 --- a/test/integration/css-customization/test/index.test.js +++ b/test/integration/css-customization/test/index.test.js @@ -108,6 +108,39 @@ describe('Legacy Next-CSS Customization', () => { }) }) +describe('Custom CSS Customization via Webpack', () => { + const appDir = join(fixturesDir, 'custom-configuration-webpack') + + beforeAll(async () => { + await remove(join(appDir, '.next')) + }) + + it('should compile successfully', async () => { + const { code, stdout, stderr } = await nextBuild(appDir, [], { + stdout: true, + stderr: true, + }) + expect(code).toBe(0) + expect(stdout).toMatch(/Compiled successfully/) + expect(stderr).not.toMatch( + /Built-in CSS support is being disabled due to custom CSS configuration being detected/ + ) + }) + + it(`should've compiled and prefixed`, async () => { + const cssFolder = join(appDir, '.next/static/css') + + const files = await readdir(cssFolder) + const cssFiles = files.filter((f) => /\.css$/.test(f)) + + expect(cssFiles.length).toBe(1) + const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') + expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatchInlineSnapshot( + `"@media (480px <= width < 768px){::placeholder{color:green}}.video{max-width:400px;max-height:300px}"` + ) + }) +}) + describe('CSS Customization Array', () => { const appDir = join(fixturesDir, 'custom-configuration-arr') diff --git a/test/integration/css-fixtures/custom-configuration-webpack/next.config.js b/test/integration/css-fixtures/custom-configuration-webpack/next.config.js new file mode 100644 index 000000000000000..567e9738538e0b3 --- /dev/null +++ b/test/integration/css-fixtures/custom-configuration-webpack/next.config.js @@ -0,0 +1,36 @@ +module.exports = { + onDemandEntries: { + maxInactiveAge: 1000 * 60 * 60, + }, + webpack(config) { + modifyLoaderConfig( + config.module.rules, + [/(? { + if (!Array.isArray(rule.use)) return + rule.use.forEach((u) => { + if (u.options.postcssOptions) { + u.options.postcssOptions.plugins = [ + require('postcss-short-size')({ + // Add a prefix to test that configuration is passed + prefix: 'xyz', + }), + ] + } + }) + } + ) + + return config + }, + future: { strictPostcssConfiguration: true }, +} + +function modifyLoaderConfig(rules, regexes, cb) { + rules.forEach((rule) => { + if (rule.oneOf) return modifyLoaderConfig(rule.oneOf, regexes, cb) + regexes.forEach((regex) => { + if (rule.test && rule.test.toString() === regex.toString()) cb(rule) + }) + }) +} diff --git a/test/integration/css-fixtures/custom-configuration-webpack/pages/_app.js b/test/integration/css-fixtures/custom-configuration-webpack/pages/_app.js new file mode 100644 index 000000000000000..17a2196742e95d7 --- /dev/null +++ b/test/integration/css-fixtures/custom-configuration-webpack/pages/_app.js @@ -0,0 +1,12 @@ +import React from 'react' +import App from 'next/app' +import '../styles/global.css' + +class MyApp extends App { + render() { + const { Component, pageProps } = this.props + return + } +} + +export default MyApp diff --git a/test/integration/css-fixtures/custom-configuration-webpack/pages/index.js b/test/integration/css-fixtures/custom-configuration-webpack/pages/index.js new file mode 100644 index 000000000000000..b3ba78da2d5e17c --- /dev/null +++ b/test/integration/css-fixtures/custom-configuration-webpack/pages/index.js @@ -0,0 +1,3 @@ +export default function Home() { + return
+} diff --git a/test/integration/css-fixtures/custom-configuration-webpack/styles/global.css b/test/integration/css-fixtures/custom-configuration-webpack/styles/global.css new file mode 100644 index 000000000000000..f942036ad16ef0b --- /dev/null +++ b/test/integration/css-fixtures/custom-configuration-webpack/styles/global.css @@ -0,0 +1,11 @@ +/* this should pass through untransformed */ +@media (480px <= width < 768px) { + ::placeholder { + color: green; + } +} + +/* this should be transformed to width/height */ +.video { + -xyz-max-size: 400px 300px; +}