Skip to content

Commit 7e876b6

Browse files
authored
integration: Sort serialized module exports (#1036)
1 parent e318f9b commit 7e876b6

File tree

3 files changed

+134
-7
lines changed

3 files changed

+134
-7
lines changed

.changeset/shiny-socks-lick.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@vanilla-extract/integration': patch
3+
---
4+
5+
Sort serialized module exports
6+
7+
Fixes a Vanilla module serialization bug that sometimes resulted in variables being used before they were declared

packages/integration/src/processVanillaFile.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,59 @@ describe('serializeVanillaModule', () => {
180180
export var sprinkles = _86bce(__default__,otherComplexExport);"
181181
`);
182182
});
183+
184+
test('should order exports correctly', () => {
185+
const simpleObjectExport = { hello: 'world' };
186+
187+
const complexExport = {
188+
my: {
189+
very: {
190+
complex: {
191+
arg: simpleObjectExport,
192+
},
193+
},
194+
},
195+
};
196+
197+
const otherComplexExport = {
198+
other: {
199+
complex: [1, 2, 3],
200+
},
201+
};
202+
203+
const someOtherExport = [4, 5, 6];
204+
205+
const reExport = otherComplexExport;
206+
const reReExport = reExport;
207+
208+
const sprinkles = () => {};
209+
sprinkles.__function_serializer__ = {
210+
importPath: 'my-package',
211+
importName: 'myFunction',
212+
args: [complexExport, otherComplexExport, someOtherExport, reReExport],
213+
};
214+
const exports = {
215+
sprinkles,
216+
complexExport,
217+
simpleObjectExport,
218+
reReExport,
219+
otherComplexExport,
220+
default: someOtherExport,
221+
reExport,
222+
};
223+
224+
expect(serializeVanillaModule(['import "./styles.css"'], exports, null))
225+
.toMatchInlineSnapshot(`
226+
"import "./styles.css"
227+
import { myFunction as _86bce } from 'my-package';
228+
export var simpleObjectExport = {hello:'world'};
229+
export var complexExport = {my:{very:{complex:{arg:simpleObjectExport}}}};
230+
var __default__ = [4,5,6];
231+
export default __default__;
232+
export var reExport = {other:{complex:[1,2,3]}};
233+
export var sprinkles = _86bce(complexExport,reExport,__default__,reExport);
234+
export var reReExport = reExport;
235+
export var otherComplexExport = reExport;"
236+
`);
237+
});
183238
});

packages/integration/src/processVanillaFile.ts

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ function stringifyExports(
166166
unusedCompositionRegex: RegExp | null,
167167
key: string,
168168
exportLookup: Map<any, string>,
169+
exportDependencyGraph: DependencyGraph,
169170
): any {
170171
return stringify(
171172
value,
@@ -185,6 +186,7 @@ function stringifyExports(
185186
const reusedExport = exportLookup.get(value);
186187

187188
if (reusedExport && reusedExport !== key) {
189+
exportDependencyGraph.addDependency(key, reusedExport);
188190
return reusedExport;
189191
}
190192
return next(value);
@@ -236,6 +238,7 @@ function stringifyExports(
236238
unusedCompositionRegex,
237239
key,
238240
exportLookup,
241+
exportDependencyGraph,
239242
),
240243
)
241244
.join(',')})`;
@@ -263,6 +266,48 @@ function stringifyExports(
263266

264267
const defaultExportName = '__default__';
265268

269+
class DependencyGraph {
270+
graph: Map<string, Set<string>>;
271+
272+
public constructor() {
273+
this.graph = new Map();
274+
}
275+
276+
/**
277+
* Creates a "depends on" relationship between `key` and `dependency`
278+
*/
279+
public addDependency(key: string, dependency: string) {
280+
const dependencies = this.graph.get(key);
281+
282+
if (dependencies) {
283+
dependencies.add(dependency);
284+
} else {
285+
this.graph.set(key, new Set([dependency]));
286+
}
287+
}
288+
289+
/**
290+
* Whether or not `key` depends on `dependency`
291+
*/
292+
public dependsOn(key: string, dependency: string): boolean {
293+
const dependencies = this.graph.get(key);
294+
295+
if (dependencies) {
296+
if (dependencies?.has(dependency)) {
297+
return true;
298+
}
299+
300+
for (const [dep] of dependencies.entries()) {
301+
if (this.dependsOn(dep, dependency)) {
302+
return true;
303+
}
304+
}
305+
}
306+
307+
return false;
308+
}
309+
}
310+
266311
export function serializeVanillaModule(
267312
cssImports: Array<string>,
268313
exports: Record<string, unknown>,
@@ -276,29 +321,49 @@ export function serializeVanillaModule(
276321
]),
277322
);
278323

279-
const moduleExports = Object.keys(exports).map((key) => {
324+
const exportDependencyGraph = new DependencyGraph();
325+
326+
const moduleExports = Object.entries(exports).map(([key, value]) => {
280327
const serializedExport = stringifyExports(
281328
functionSerializationImports,
282-
exports[key],
329+
value,
283330
unusedCompositionRegex,
284331
key === 'default' ? defaultExportName : key,
285332
exportLookup,
333+
exportDependencyGraph,
286334
);
287335

288336
if (key === 'default') {
289337
return [
290-
`var ${defaultExportName} = ${serializedExport};`,
291-
`export default ${defaultExportName};`,
292-
].join('\n');
338+
defaultExportName,
339+
[
340+
`var ${defaultExportName} = ${serializedExport};`,
341+
`export default ${defaultExportName};`,
342+
].join('\n'),
343+
];
293344
}
294345

295-
return `export var ${key} = ${serializedExport};`;
346+
return [key, `export var ${key} = ${serializedExport};`];
296347
});
297348

349+
const sortedModuleExports = moduleExports
350+
.sort(([key1], [key2]) => {
351+
if (exportDependencyGraph.dependsOn(key1, key2)) {
352+
return 1;
353+
}
354+
355+
if (exportDependencyGraph.dependsOn(key2, key1)) {
356+
return -1;
357+
}
358+
359+
return 0;
360+
})
361+
.map(([, s]) => s);
362+
298363
const outputCode = [
299364
...cssImports,
300365
...functionSerializationImports,
301-
...moduleExports,
366+
...sortedModuleExports,
302367
];
303368

304369
return outputCode.join('\n');

0 commit comments

Comments
 (0)