Skip to content

Commit ed67731

Browse files
authored
fix: Next plugin - pages router style loading (#1365)
1 parent 03ba50d commit ed67731

File tree

12 files changed

+135
-58
lines changed

12 files changed

+135
-58
lines changed

.changeset/honest-pugs-tap.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@vanilla-extract/next-plugin': minor
3+
---
4+
5+
Pages router: use next-style-loader in dev, output css in ssr

fixtures/features/src/html.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import * as styles from './features.css';
2+
import testNodes from '../test-nodes.json';
3+
4+
export default `
5+
<div id="${testNodes.mergedStyle}" class="${styles.mergedStyle}">Merged style</div>
6+
<div id="${testNodes.styleWithComposition}" class="${styles.styleWithComposition}">Style with composition</div>
7+
<div id="${testNodes.styleVariantsWithComposition}" class="${styles.styleVariantsWithComposition.variant}">Style variants with composition</div>
8+
<div id="${testNodes.styleVariantsWithMappedComposition}" class="${styles.styleVariantsWithMappedComposition.variant}">Style variants with mapped composition</div>
9+
<div id="${testNodes.compositionOnly}" class="${styles.compositionOnly}">Composition only</div>
10+
<div id="${testNodes.styleCompositionInSelector}" class="${styles.styleCompositionInSelector}">Style composition in selector</div>
11+
<div id="${testNodes.styleVariantsCompositionInSelector}" class="${styles.styleVariantsCompositionInSelector.variant}">Style variants composition in selector</div>
12+
`;

fixtures/features/src/index.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,7 @@
1-
import * as styles from './features.css';
2-
import testNodes from '../test-nodes.json';
1+
import html from './html';
32

43
function render() {
5-
document.body.innerHTML = `
6-
<div id="${testNodes.mergedStyle}" class="${styles.mergedStyle}">Merged style</div>
7-
<div id="${testNodes.styleWithComposition}" class="${styles.styleWithComposition}">Style with composition</div>
8-
<div id="${testNodes.styleVariantsWithComposition}" class="${styles.styleVariantsWithComposition.variant}">Style variants with composition</div>
9-
<div id="${testNodes.styleVariantsWithMappedComposition}" class="${styles.styleVariantsWithMappedComposition.variant}">Style variants with mapped composition</div>
10-
<div id="${testNodes.compositionOnly}" class="${styles.compositionOnly}">Composition only</div>
11-
<div id="${testNodes.styleCompositionInSelector}" class="${styles.styleCompositionInSelector}">Style composition in selector</div>
12-
<div id="${testNodes.styleVariantsCompositionInSelector}" class="${styles.styleVariantsCompositionInSelector.variant}">Style variants composition in selector</div>
13-
`;
4+
document.body.innerHTML = html;
145
}
156

167
render();
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import html from '@fixtures/features/src/html';
2+
3+
export default function Features() {
4+
return (
5+
<>
6+
<span id="features" />
7+
<div dangerouslySetInnerHTML={{ __html: html }} />
8+
</>
9+
);
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import html from '@fixtures/sprinkles/src/html';
2+
3+
export default function Sprinkles() {
4+
return (
5+
<>
6+
<span id="sprinkles" />
7+
<div dangerouslySetInnerHTML={{ __html: html }} />
8+
</>
9+
);
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import html from '@fixtures/features/src/html';
2+
3+
export default function Features() {
4+
return (
5+
<>
6+
<span id="features" />
7+
<div dangerouslySetInnerHTML={{ __html: html }} />
8+
</>
9+
);
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import html from '@fixtures/sprinkles/src/html';
2+
3+
export default function Sprinkles() {
4+
return (
5+
<>
6+
<span id="sprinkles" />
7+
<div dangerouslySetInnerHTML={{ __html: html }} />
8+
</>
9+
);
10+
}

fixtures/sprinkles/src/html.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import {
2+
sprinkles,
3+
mapResponsiveValue,
4+
normalizeResponsiveValue,
5+
preComposedSprinkles,
6+
preComposedSprinklesUsedInSelector,
7+
container,
8+
} from './styles.css';
9+
import testNodes from '../test-nodes.json';
10+
11+
export default `
12+
<div id="${testNodes.root}" class="${container}">
13+
<div class="${sprinkles({
14+
display: normalizeResponsiveValue('block').mobile,
15+
paddingTop: mapResponsiveValue(
16+
{
17+
mobile: 'small',
18+
desktop: 'medium',
19+
} as const,
20+
(x) => x,
21+
),
22+
})}">
23+
Sprinkles
24+
</div>
25+
<div class="${preComposedSprinkles}">Precomposed sprinkles</div>
26+
<div class="${preComposedSprinklesUsedInSelector}">Precomposed Sprinkles Used In Selector</div>
27+
</div>
28+
`;

fixtures/sprinkles/src/index.ts

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,7 @@
1-
import {
2-
sprinkles,
3-
mapResponsiveValue,
4-
normalizeResponsiveValue,
5-
preComposedSprinkles,
6-
preComposedSprinklesUsedInSelector,
7-
container,
8-
} from './styles.css';
9-
import testNodes from '../test-nodes.json';
1+
import html from './html';
102

113
function render() {
12-
document.body.innerHTML = `
13-
<div id="${testNodes.root}" class="${container}">
14-
<div class="${sprinkles({
15-
display: normalizeResponsiveValue('block').mobile,
16-
paddingTop: mapResponsiveValue(
17-
{
18-
mobile: 'small',
19-
desktop: 'medium',
20-
} as const,
21-
(x) => x,
22-
),
23-
})}">
24-
Sprinkles
25-
</div>
26-
<div class="${preComposedSprinkles}">Precomposed sprinkles</div>
27-
<div class="${preComposedSprinklesUsedInSelector}">Precomposed Sprinkles Used In Selector</div>
28-
</div>
29-
`;
4+
document.body.innerHTML = html;
305
}
316

327
render();

packages/next-plugin/src/index.ts

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,53 @@ function getSupportedBrowsers(dir: string, isDevelopment: boolean) {
3333
const getVanillaExtractCssLoaders = (
3434
options: WebpackConfigContext,
3535
assetPrefix: string,
36+
hasAppDir: boolean,
3637
) => {
3738
const loaders: webpack.RuleSetUseItem[] = [];
3839

3940
// Adopt from Next.js' getClientStyleLoader
4041
// https://github.com/vercel/next.js/blob/6e5b935fd7a61497f6854a81aec7df3a5dbf61ac/packages/next/src/build/webpack/config/blocks/css/loaders/client.ts#L3
4142
if (!options.isServer) {
42-
// https://github.com/vercel/next.js/blob/6e5b935fd7a61497f6854a81aec7df3a5dbf61ac/packages/next/src/build/webpack/config/blocks/css/loaders/client.ts#L44
43-
// next-style-loader will mess up css order in development mode.
44-
// Next.js appDir doesn't use next-style-loader either.
45-
// So we always use css-loader here, to simplify things and get proper order of output CSS
46-
loaders.push({
47-
loader: NextMiniCssExtractPlugin.loader,
48-
options: {
49-
publicPath: `${assetPrefix}/_next/`,
50-
esModule: false,
51-
},
52-
});
43+
// https://github.com/vercel/next.js/blob/6e5b935fd7a61497f6854a81aec7df3a5dbf61ac/packages/next/src/build/webpack/config/blocks/css/loaders/client.ts#L16
44+
// Keep next-style-loader for development mode in `pages/`
45+
if (options.dev && !hasAppDir) {
46+
loaders.push({
47+
loader: 'next-style-loader',
48+
options: {
49+
insert: function (element: Node) {
50+
// By default, style-loader injects CSS into the bottom
51+
// of <head>. This causes ordering problems between dev
52+
// and prod. To fix this, we render a <noscript> tag as
53+
// an anchor for the styles to be placed before. These
54+
// styles will be applied _before_ <style jsx global>.
55+
56+
// These elements should always exist. If they do not,
57+
// this code should fail.
58+
const anchorElement = document.querySelector(
59+
'#__next_css__DO_NOT_USE__',
60+
)!;
61+
const parentNode = anchorElement.parentNode!; // Normally <head>
62+
63+
// Each style tag should be placed right before our
64+
// anchor. By inserting before and not after, we do not
65+
// need to track the last inserted element.
66+
parentNode.insertBefore(element, anchorElement);
67+
},
68+
},
69+
});
70+
} else {
71+
// https://github.com/vercel/next.js/blob/6e5b935fd7a61497f6854a81aec7df3a5dbf61ac/packages/next/src/build/webpack/config/blocks/css/loaders/client.ts#L44
72+
// next-style-loader will mess up css order in development mode.
73+
// Next.js appDir doesn't use next-style-loader either.
74+
// So we always use css-loader here, to simplify things and get proper order of output CSS
75+
loaders.push({
76+
loader: NextMiniCssExtractPlugin.loader,
77+
options: {
78+
publicPath: `${assetPrefix}/_next/`,
79+
esModule: false,
80+
},
81+
});
82+
}
5383
}
5484

5585
const postcss = () =>
@@ -105,7 +135,7 @@ export const createVanillaExtractPlugin = (
105135
return (nextConfig: NextConfig = {}): NextConfig => ({
106136
...nextConfig,
107137
webpack(config: any, options: WebpackConfigContext) {
108-
const { dir, dev, isServer, config: resolvedNextConfig } = options;
138+
const { dir, dev, config: resolvedNextConfig } = options;
109139

110140
// https://github.com/vercel/next.js/blob/1fb4cad2a8329811b5ccde47217b4a6ae739124e/packages/next/build/index.ts#L336
111141
// https://github.com/vercel/next.js/blob/1fb4cad2a8329811b5ccde47217b4a6ae739124e/packages/next/build/webpack-config.ts#L626
@@ -118,11 +148,7 @@ export const createVanillaExtractPlugin = (
118148
// Skip nextConfig check since appDir is stable feature after Next.js 13.4
119149
const hasAppDir = !!(findPagesDirResult && findPagesDirResult.appDir);
120150

121-
const outputCss = hasAppDir
122-
? // Always output css since Next.js App Router needs to collect Server CSS from React Server Components
123-
true
124-
: // There is no appDir, do not output css on server build
125-
!isServer;
151+
const outputCss = true;
126152

127153
// https://github.com/vercel/next.js/blob/6e5b935fd7a61497f6854a81aec7df3a5dbf61ac/packages/next/src/build/webpack/config/helpers.ts#L12-L21
128154
const cssRules = config.module.rules.find(
@@ -143,6 +169,7 @@ export const createVanillaExtractPlugin = (
143169
use: getVanillaExtractCssLoaders(
144170
options,
145171
resolvedNextConfig.assetPrefix,
172+
hasAppDir,
146173
),
147174
});
148175

0 commit comments

Comments
 (0)