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' ;
4
5
import { findPagesDir } from 'next/dist/lib/find-pages-dir' ;
6
+ import { lazyPostCSS } from 'next/dist/build/webpack/config/blocks/css' ;
5
7
import { 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' ;
7
8
8
9
import 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 ] ;
11
16
12
17
const NextMiniCssExtractPlugin = NextMiniCssExtractPluginDefault as any ;
13
18
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 ) {
16
21
try {
17
- browsers = browserslist . loadConfig ( {
22
+ return browserslist . loadConfig ( {
18
23
path : dir ,
19
24
env : isDevelopment ? 'development' : 'production' ,
20
25
} ) ;
21
- } catch { }
22
-
23
- return browsers ;
26
+ } catch ( _ ) {
27
+ return undefined ;
28
+ }
24
29
}
25
30
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
29
33
const getVanillaExtractCssLoaders = (
30
34
options : WebpackConfigContext ,
31
35
assetPrefix : string ,
32
36
) => {
33
37
const loaders : webpack . RuleSetUseItem [ ] = [ ] ;
34
38
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
36
41
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
38
43
// next-style-loader will mess up css order in development mode.
39
44
// Next.js appDir doesn't use next-style-loader either.
40
45
// So we always use css-loader here, to simplify things and get proper order of output CSS
@@ -54,7 +59,7 @@ const getVanillaExtractCssLoaders = (
54
59
undefined ,
55
60
) ;
56
61
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
58
63
loaders . push ( {
59
64
loader : require . resolve ( 'next/dist/build/webpack/loaders/css-loader/src' ) ,
60
65
options : {
@@ -76,7 +81,7 @@ const getVanillaExtractCssLoaders = (
76
81
} ,
77
82
} ) ;
78
83
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
80
85
loaders . push ( {
81
86
loader : require . resolve (
82
87
'next/dist/build/webpack/loaders/postcss-loader/src' ,
@@ -86,102 +91,109 @@ const getVanillaExtractCssLoaders = (
86
91
} ,
87
92
} ) ;
88
93
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
+
89
99
return loaders ;
90
100
} ;
91
101
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' ) ,
134
137
) ,
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' ;
176
167
177
168
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
+ } ) ,
179
185
) ;
186
+ }
180
187
181
- if ( typeof nextConfig . webpack === 'function' ) {
182
- return nextConfig . webpack ( config , options ) ;
183
- }
188
+ config . plugins . push (
189
+ new VanillaExtractPlugin ( { outputCss , ... pluginOptions } ) ,
190
+ ) ;
184
191
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