Skip to content

Commit 89224fe

Browse files
syfxlinaskoufis
andauthored
fix(next-plugin): build not outputting css on windows (#1180)
* fix(next-plugin): build not outputting css on windows * Create wise-oranges-talk.md * Format code --------- Co-authored-by: Adam Skoufis <[email protected]> Co-authored-by: Adam Skoufis <[email protected]>
1 parent 58005eb commit 89224fe

File tree

14 files changed

+363
-272
lines changed

14 files changed

+363
-272
lines changed

.changeset/wise-oranges-talk.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@vanilla-extract/next-plugin": patch
3+
"@vanilla-extract/webpack-plugin": patch
4+
---
5+
6+
Fixes Next.js 13 CSS output on Windows when using React Server Components

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ test-results
99

1010
.pnp.*
1111
.next
12-
.DS_Store
12+
.DS_Store
13+
.idea

packages/next-plugin/package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
"description": "Zero-runtime Stylesheets-in-TypeScript",
55
"main": "dist/vanilla-extract-next-plugin.cjs.js",
66
"module": "dist/vanilla-extract-next-plugin.esm.js",
7+
"preconstruct": {
8+
"entrypoints": [
9+
"index.ts"
10+
]
11+
},
712
"files": [
813
"/dist"
914
],
@@ -15,8 +20,7 @@
1520
"author": "SEEK",
1621
"license": "MIT",
1722
"dependencies": {
18-
"@vanilla-extract/webpack-plugin": "^2.3.0",
19-
"browserslist": "^4.19.1"
23+
"@vanilla-extract/webpack-plugin": "^2.3.0"
2024
},
2125
"peerDependencies": {
2226
"next": ">=12.1.7"

packages/next-plugin/src/index.ts

Lines changed: 121 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,45 @@
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';
45
import { findPagesDir } from 'next/dist/lib/find-pages-dir';
6+
import { lazyPostCSS } from 'next/dist/build/webpack/config/blocks/css';
57
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';
78

89
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];
1116

1217
const 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
2933
const 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: /\.vanilla\.css$/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: /vanilla\.virtual\.css/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+
};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"main": "dist/vanilla-extract-webpack-plugin-next.cjs.js",
3+
"module": "dist/vanilla-extract-webpack-plugin-next.esm.js"
4+
}

packages/webpack-plugin/package.json

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,33 @@
1717
"./virtualFileLoader": {
1818
"module": "./virtualFileLoader/dist/vanilla-extract-webpack-plugin-virtualFileLoader.esm.js",
1919
"default": "./virtualFileLoader/dist/vanilla-extract-webpack-plugin-virtualFileLoader.cjs.js"
20+
},
21+
"./next": {
22+
"module": "./next/dist/vanilla-extract-webpack-plugin-next.esm.js",
23+
"default": "./next/dist/vanilla-extract-webpack-plugin-next.cjs.js"
24+
},
25+
"./virtualNextFileLoader": {
26+
"module": "./virtualNextFileLoader/dist/vanilla-extract-webpack-plugin-virtualNextFileLoader.esm.js",
27+
"default": "./virtualNextFileLoader/dist/vanilla-extract-webpack-plugin-virtualNextFileLoader.cjs.js"
2028
}
2129
},
2230
"preconstruct": {
2331
"entrypoints": [
2432
"index.ts",
2533
"loader.ts",
26-
"virtualFileLoader.ts"
34+
"virtualFileLoader.ts",
35+
"next.ts",
36+
"virtualNextFileLoader.ts"
2737
]
2838
},
2939
"files": [
3040
"/dist",
3141
"/loader",
3242
"/virtualFileLoader",
33-
"extracted.js"
43+
"/next",
44+
"/virtualNextFileLoader",
45+
"extracted.js",
46+
"vanilla.virtual.css"
3447
],
3548
"repository": {
3649
"type": "git",

0 commit comments

Comments
 (0)