Skip to content

Commit c34e69a

Browse files
fix: processVanillaFile fileScopes race (#1585)
Co-authored-by: Adam Skoufis <[email protected]>
1 parent 9b1bfd0 commit c34e69a

3 files changed

Lines changed: 76 additions & 17 deletions

File tree

.changeset/silly-lights-bet.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@vanilla-extract/integration': patch
3+
---
4+
5+
Fixed a race condition in `processVanillaFile` that could cause missing classnames during CSS serialization

packages/integration/src/processVanillaFile.test.ts

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
import { describe, expect, test } from 'vitest';
1+
import { describe, expect, test, vi } from 'vitest';
22
import { addFunctionSerializer } from '../../css/src/functionSerializer';
3-
import { serializeVanillaModule } from './processVanillaFile';
3+
import {
4+
processVanillaFile,
5+
serializeVanillaModule,
6+
} from './processVanillaFile';
47

58
describe('serializeVanillaModule', () => {
69
test('with plain object exports', () => {
@@ -257,3 +260,60 @@ describe('serializeVanillaModule', () => {
257260
`);
258261
});
259262
});
263+
264+
describe('processVanillaFile', () => {
265+
test('should process vanilla file with correct promise order', async () => {
266+
vi.useFakeTimers();
267+
268+
const serializeVirtualCssPath1 = vi.fn(
269+
({ source }) =>
270+
new Promise<string>((resolve) => {
271+
setTimeout(() => resolve(source), 1);
272+
}),
273+
);
274+
const serializeVirtualCssPath2 = vi.fn(({ source }) =>
275+
Promise.resolve(source),
276+
);
277+
278+
const [result] = await Promise.all([
279+
processVanillaFile({
280+
source: `
281+
const __vanilla_filescope__ = require("@vanilla-extract/css/fileScope");
282+
const { style, globalStyle } = require('@vanilla-extract/css');
283+
__vanilla_filescope__.setFileScope("dependency.css.ts", "test");
284+
exports.x = style({ color: 'blue' });
285+
__vanilla_filescope__.endFileScope();
286+
__vanilla_filescope__.setFileScope("style1.css.ts", "test");
287+
exports.y = style([exports.x]);
288+
globalStyle('body ' + exports.y, { color: 'red' });
289+
__vanilla_filescope__.endFileScope();
290+
`,
291+
filePath: require.resolve('@fixtures/sprinkles'),
292+
serializeVirtualCssPath: serializeVirtualCssPath1,
293+
}),
294+
processVanillaFile({
295+
source: `
296+
const __vanilla_filescope__ = require("@vanilla-extract/css/fileScope");
297+
__vanilla_filescope__.setFileScope("style2.css.ts", "test");
298+
__vanilla_filescope__.endFileScope();
299+
`,
300+
filePath: require.resolve('@fixtures/sprinkles'),
301+
serializeVirtualCssPath: serializeVirtualCssPath2,
302+
}),
303+
vi.runAllTimersAsync(),
304+
]);
305+
306+
expect(result).toMatchInlineSnapshot(`
307+
".dependency__ma8c4x0 {
308+
color: blue;
309+
}
310+
body .style1__emvcy10 {
311+
color: red;
312+
}
313+
export var x = 'dependency__ma8c4x0';
314+
export var y = 'style1__emvcy10 dependency__ma8c4x0';"
315+
`);
316+
317+
vi.useRealTimers();
318+
});
319+
});

packages/integration/src/processVanillaFile.ts

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { FileScope, Adapter } from '@vanilla-extract/css';
22
import { transformCss } from '@vanilla-extract/css/transformCss';
3+
import { removeAdapter, setAdapter } from '@vanilla-extract/css/adapter';
34
import evalCode from 'eval';
45
import { stringify } from 'javascript-stringify';
56
import dedent from 'dedent';
@@ -110,8 +111,13 @@ export async function processVanillaFile({
110111
process.env.NODE_ENV = originalNodeEnv;
111112

112113
const adapterBoundSource = `
113-
require('@vanilla-extract/css/adapter').setAdapter(__adapter__);
114+
const { setAdapter, removeAdapter } = require('@vanilla-extract/css/adapter');
115+
setAdapter(__adapter__);
114116
${source}
117+
// Backwards compat with older versions of @vanilla-extract/css
118+
if (removeAdapter) {
119+
removeAdapter();
120+
}
115121
`;
116122

117123
const evalResult = evalCode(
@@ -127,11 +133,13 @@ export async function processVanillaFile({
127133

128134
for (const [serialisedFileScope, fileScopeCss] of cssByFileScope) {
129135
const fileScope = parseFileScope(serialisedFileScope);
136+
setAdapter(cssAdapter);
130137
const css = transformCss({
131138
localClassNames: Array.from(localClassNames),
132139
composedClassLists,
133140
cssObjs: fileScopeCss,
134141
}).join('\n');
142+
removeAdapter();
135143

136144
const fileName = `${fileScope.filePath}.vanilla.css`;
137145

@@ -158,20 +166,6 @@ export async function processVanillaFile({
158166
cssImports.push(virtualCssFilePath);
159167
}
160168

161-
// We run this code inside eval as jest seems to create a difrerent instance of the adapter file
162-
// for requires executed within the eval and all CSS can be lost.
163-
evalCode(
164-
`const { removeAdapter } = require('@vanilla-extract/css/adapter');
165-
// Backwards compat with older versions of @vanilla-extract/css
166-
if (removeAdapter) {
167-
removeAdapter();
168-
}
169-
`,
170-
filePath,
171-
{ console, process },
172-
true,
173-
);
174-
175169
const unusedCompositions = composedClassLists
176170
.filter(({ identifier }) => !usedCompositions.has(identifier))
177171
.map(({ identifier }) => identifier);

0 commit comments

Comments
 (0)