1- import { VanillaExtractPlugin } from '@vanilla-extract/webpack-plugin' ;
2- import browserslist from 'browserslist' ;
3- import { lazyPostCSS } from 'next/dist/build/webpack/config/blocks/css' ;
1+ // @ts -expect-error
2+ import browserslist from 'next/dist/compiled/browserslist' ;
3+ import NextMiniCssExtractPluginDefault from 'next/dist/build/webpack/plugins/mini-css-extract-plugin' ;
4+ import { VanillaExtractPlugin } from '@vanilla-extract/webpack-plugin/next' ;
45import { findPagesDir } from 'next/dist/lib/find-pages-dir' ;
6+ import { lazyPostCSS } from 'next/dist/build/webpack/config/blocks/css' ;
57import { cssFileResolve } from 'next/dist/build/webpack/config/blocks/css/loaders/file-resolve' ;
6- import NextMiniCssExtractPluginDefault from 'next/dist/build/webpack/plugins/mini-css-extract-plugin' ;
78
89import type webpack from 'webpack' ;
9- import type { NextConfig } from 'next/types' ;
10- import type { WebpackConfigContext } from 'next/dist/server/config-shared' ;
10+ import type {
11+ NextConfig ,
12+ WebpackConfigContext ,
13+ } from 'next/dist/server/config-shared' ;
14+
15+ type PluginOptions = ConstructorParameters < typeof VanillaExtractPlugin > [ 0 ] ;
1116
1217const NextMiniCssExtractPlugin = NextMiniCssExtractPluginDefault as any ;
1318
14- function getSupportedBrowsers ( dir : any , isDevelopment : any ) {
15- let browsers ;
19+ // Adopted from https://github.com/vercel/next.js/blob/1f1632979c78b3edfe59fd85d8cce62efcdee688/packages/next/build/webpack-config.ts#L60-L72
20+ function getSupportedBrowsers ( dir : string , isDevelopment : boolean ) {
1621 try {
17- browsers = browserslist . loadConfig ( {
22+ return browserslist . loadConfig ( {
1823 path : dir ,
1924 env : isDevelopment ? 'development' : 'production' ,
2025 } ) ;
21- } catch { }
22-
23- return browsers ;
26+ } catch ( _ ) {
27+ return undefined ;
28+ }
2429}
2530
26- type PluginOptions = ConstructorParameters < typeof VanillaExtractPlugin > [ 0 ] ;
27-
28- // https://github.com/vercel/next.js/blob/canary/packages/next/src/build/webpack/config/blocks/css/loaders/global.ts#L7
31+ // Adopt from Next.js' getGlobalCssLoader
32+ // https://github.com/vercel/next.js/blob/6e5b935fd7a61497f6854a81aec7df3a5dbf61ac/packages/next/src/build/webpack/config/blocks/css/loaders/global.ts#L7
2933const getVanillaExtractCssLoaders = (
3034 options : WebpackConfigContext ,
3135 assetPrefix : string ,
3236) => {
3337 const loaders : webpack . RuleSetUseItem [ ] = [ ] ;
3438
35- // https://github.com/vercel/next.js/blob/a4f2bbbe2047d4ed88e9b6f32f6b0adfc8d0c46a/packages/next/src/build/webpack/config/blocks/css/loaders/global.ts#L14
39+ // Adopt from Next.js' getClientStyleLoader
40+ // https://github.com/vercel/next.js/blob/6e5b935fd7a61497f6854a81aec7df3a5dbf61ac/packages/next/src/build/webpack/config/blocks/css/loaders/client.ts#L3
3641 if ( ! options . isServer ) {
37- // https://github.com/vercel/next.js/blob/a4f2bbbe2047d4ed88e9b6f32f6b0adfc8d0c46a /packages/next/src/build/webpack/config/blocks/css/loaders/client.ts#L44
42+ // https://github.com/vercel/next.js/blob/6e5b935fd7a61497f6854a81aec7df3a5dbf61ac /packages/next/src/build/webpack/config/blocks/css/loaders/client.ts#L44
3843 // next-style-loader will mess up css order in development mode.
3944 // Next.js appDir doesn't use next-style-loader either.
4045 // So we always use css-loader here, to simplify things and get proper order of output CSS
@@ -54,7 +59,7 @@ const getVanillaExtractCssLoaders = (
5459 undefined ,
5560 ) ;
5661
57- // https://github.com/vercel/next.js/blob/a4f2bbbe2047d4ed88e9b6f32f6b0adfc8d0c46a /packages/next/src/build/webpack/config/blocks/css/loaders/global.ts#L28
62+ // https://github.com/vercel/next.js/blob/6e5b935fd7a61497f6854a81aec7df3a5dbf61ac /packages/next/src/build/webpack/config/blocks/css/loaders/global.ts#L28
5863 loaders . push ( {
5964 loader : require . resolve ( 'next/dist/build/webpack/loaders/css-loader/src' ) ,
6065 options : {
@@ -76,7 +81,7 @@ const getVanillaExtractCssLoaders = (
7681 } ,
7782 } ) ;
7883
79- // https://github.com/vercel/next.js/blob/a4f2bbbe2047d4ed88e9b6f32f6b0adfc8d0c46a /packages/next/src/build/webpack/config/blocks/css/loaders/global.ts#L43
84+ // https://github.com/vercel/next.js/blob/6e5b935fd7a61497f6854a81aec7df3a5dbf61ac /packages/next/src/build/webpack/config/blocks/css/loaders/global.ts#L29-L38
8085 loaders . push ( {
8186 loader : require . resolve (
8287 'next/dist/build/webpack/loaders/postcss-loader/src' ,
@@ -86,102 +91,109 @@ const getVanillaExtractCssLoaders = (
8691 } ,
8792 } ) ;
8893
94+ // https://github.com/SukkaW/style9-webpack/blob/f51c46bbcd95ea3b988d3559c3b35cc056874366/src/next-appdir/index.ts#L103-L105
95+ loaders . push ( {
96+ loader : VanillaExtractPlugin . loader ,
97+ } ) ;
98+
8999 return loaders ;
90100} ;
91101
92- export const createVanillaExtractPlugin =
93- ( pluginOptions : PluginOptions = { } ) =>
94- ( nextConfig : NextConfig = { } ) : NextConfig =>
95- Object . assign ( { } , nextConfig , {
96- webpack ( config : any , options : WebpackConfigContext ) {
97- const { dir, dev, isServer, config : resolvedNextConfig } = options ;
98- const findPagesDirResult = findPagesDir (
99- dir ,
100- resolvedNextConfig . experimental ?. appDir ,
101- ) ;
102-
103- // https://github.com/vercel/next.js/blob/1fb4cad2a8329811b5ccde47217b4a6ae739124e/packages/next/build/index.ts#L336
104- // https://github.com/vercel/next.js/blob/1fb4cad2a8329811b5ccde47217b4a6ae739124e/packages/next/build/webpack-config.ts#L626
105- // https://github.com/vercel/next.js/pull/43916
106- const hasAppDir =
107- // on Next.js 12, findPagesDirResult is a string. on Next.js 13, findPagesDirResult is an object
108- ! ! resolvedNextConfig . experimental ?. appDir &&
109- ! ! ( findPagesDirResult && findPagesDirResult . appDir ) ;
110-
111- const outputCss = hasAppDir
112- ? // Always output css since Next.js App Router needs to collect Server CSS from React Server Components
113- true
114- : // There is no appDir, do not output css on server build
115- ! isServer ;
116-
117- const cssRules = config . module . rules . find (
118- ( rule : any ) =>
119- Array . isArray ( rule . oneOf ) &&
120- rule . oneOf . some (
121- ( { test } : any ) =>
122- typeof test === 'object' &&
123- typeof test . test === 'function' &&
124- test . test ( 'filename.css' ) ,
125- ) ,
126- ) . oneOf ;
127-
128- cssRules . unshift ( {
129- test : / \. v a n i l l a \. c s s $ / i,
130- sideEffects : true ,
131- use : getVanillaExtractCssLoaders (
132- options ,
133- resolvedNextConfig . assetPrefix ,
102+ export const createVanillaExtractPlugin = (
103+ pluginOptions : PluginOptions = { } ,
104+ ) => {
105+ return ( nextConfig : NextConfig = { } ) : NextConfig => ( {
106+ ...nextConfig ,
107+ webpack ( config : any , options : WebpackConfigContext ) {
108+ const { dir, dev, isServer, config : resolvedNextConfig } = options ;
109+
110+ // https://github.com/vercel/next.js/blob/1fb4cad2a8329811b5ccde47217b4a6ae739124e/packages/next/build/index.ts#L336
111+ // https://github.com/vercel/next.js/blob/1fb4cad2a8329811b5ccde47217b4a6ae739124e/packages/next/build/webpack-config.ts#L626
112+ // https://github.com/vercel/next.js/pull/43916
113+ // on Next.js 12, findPagesDirResult is a string. on Next.js 13, findPagesDirResult is an object
114+ const findPagesDirResult = findPagesDir (
115+ dir ,
116+ resolvedNextConfig . experimental ?. appDir ?? false ,
117+ ) ;
118+ const hasAppDir =
119+ ! ! resolvedNextConfig . experimental ?. appDir &&
120+ ! ! ( findPagesDirResult && findPagesDirResult . appDir ) ;
121+
122+ const outputCss = hasAppDir
123+ ? // Always output css since Next.js App Router needs to collect Server CSS from React Server Components
124+ true
125+ : // There is no appDir, do not output css on server build
126+ ! isServer ;
127+
128+ // https://github.com/vercel/next.js/blob/6e5b935fd7a61497f6854a81aec7df3a5dbf61ac/packages/next/src/build/webpack/config/helpers.ts#L12-L21
129+ const cssRules = config . module . rules . find (
130+ ( rule : any ) =>
131+ Array . isArray ( rule . oneOf ) &&
132+ rule . oneOf . some (
133+ ( { test } : any ) =>
134+ typeof test === 'object' &&
135+ typeof test . test === 'function' &&
136+ test . test ( 'filename.css' ) ,
134137 ) ,
135- } ) ;
136-
137- // vanilla-extract need to emit the css file on both server and client, both during the
138- // development and production.
139- // However, Next.js only add MiniCssExtractPlugin on pages dir + client build + production mode.
140- //
141- // To simplify the logic at our side, we will add MiniCssExtractPlugin based on
142- // the "instanceof" check (We will only add our required MiniCssExtractPlugin if
143- // Next.js hasn't added it yet).
144- // This also prevent multiple MiniCssExtractPlugin being added (which will cause
145- // RealContentHashPlugin to panic)
146- if (
147- ! config . plugins . some (
148- ( plugin : any ) => plugin instanceof NextMiniCssExtractPlugin ,
149- )
150- ) {
151- // HMR reloads the CSS file when the content changes but does not use
152- // the new file name, which means it can't contain a hash.
153- const filename = dev
154- ? 'static/css/[name].css'
155- : 'static/css/[contenthash].css' ;
156-
157- config . plugins . push (
158- new NextMiniCssExtractPlugin ( {
159- filename,
160- chunkFilename : filename ,
161- // Next.js guarantees that CSS order "doesn't matter", due to imposed
162- // restrictions:
163- // 1. Global CSS can only be defined in a single entrypoint (_app)
164- // 2. CSS Modules generate scoped class names by default and cannot
165- // include Global CSS (:global() selector).
166- //
167- // While not a perfect guarantee (e.g. liberal use of `:global()`
168- // selector), this assumption is required to code-split CSS.
169- //
170- // If this warning were to trigger, it'd be unactionable by the user,
171- // but likely not valid -- so just disable it.
172- ignoreOrder : true ,
173- } ) ,
174- ) ;
175- }
138+ ) . oneOf ;
139+
140+ // https://github.com/SukkaW/style9-webpack/blob/f51c46bbcd95ea3b988d3559c3b35cc056874366/src/next-appdir/index.ts#L187-L190
141+ cssRules . unshift ( {
142+ test : / v a n i l l a \. v i r t u a l \. c s s / i,
143+ sideEffects : true ,
144+ use : getVanillaExtractCssLoaders (
145+ options ,
146+ resolvedNextConfig . assetPrefix ,
147+ ) ,
148+ } ) ;
149+
150+ // vanilla-extract need to emit the css file on both server and client, both during the
151+ // development and production.
152+ // However, Next.js only add MiniCssExtractPlugin on pages dir + client build + production mode.
153+ //
154+ // To simplify the logic at our side, we will add MiniCssExtractPlugin based on
155+ // the "instanceof" check (We will only add our required MiniCssExtractPlugin if
156+ // Next.js hasn't added it yet).
157+ // This also prevent multiple MiniCssExtractPlugin being added (which will cause
158+ // RealContentHashPlugin to panic)
159+ if (
160+ ! config . plugins . some ( ( p : any ) => p instanceof NextMiniCssExtractPlugin )
161+ ) {
162+ // HMR reloads the CSS file when the content changes but does not use
163+ // the new file name, which means it can't contain a hash.
164+ const filename = dev
165+ ? 'static/css/[name].css'
166+ : 'static/css/[contenthash].css' ;
176167
177168 config . plugins . push (
178- new VanillaExtractPlugin ( { outputCss, ...pluginOptions } ) ,
169+ new NextMiniCssExtractPlugin ( {
170+ filename,
171+ chunkFilename : filename ,
172+ // Next.js guarantees that CSS order "doesn't matter", due to imposed
173+ // restrictions:
174+ // 1. Global CSS can only be defined in a single entrypoint (_app)
175+ // 2. CSS Modules generate scoped class names by default and cannot
176+ // include Global CSS (:global() selector).
177+ //
178+ // While not a perfect guarantee (e.g. liberal use of `:global()`
179+ // selector), this assumption is required to code-split CSS.
180+ //
181+ // If this warning were to trigger, it'd be unactionable by the user,
182+ // but likely not valid -- so just disable it.
183+ ignoreOrder : true ,
184+ } ) ,
179185 ) ;
186+ }
180187
181- if ( typeof nextConfig . webpack === 'function' ) {
182- return nextConfig . webpack ( config , options ) ;
183- }
188+ config . plugins . push (
189+ new VanillaExtractPlugin ( { outputCss , ... pluginOptions } ) ,
190+ ) ;
184191
185- return config ;
186- } ,
187- } ) ;
192+ if ( typeof nextConfig . webpack === 'function' ) {
193+ return nextConfig . webpack ( config , options ) ;
194+ }
195+
196+ return config ;
197+ } ,
198+ } ) ;
199+ } ;
0 commit comments