Repository: vanilla-extract-css/vanilla-extract (ts)

Issue:
Internal dependency update (debug-ids 1.0.2, integration 6.1.1) lead to crash accessing "styles"
### Describe the bug

Our project is a mono-repo with a shared UI library and different apps. When re-building our pnpm lock file the following changes are applied to the repo:

```
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1a7e909..58ed352 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -872,7 +872,7 @@ packages:
       '@babel/core': ^7.0.0-0
     dependencies:
       '@babel/core': 7.12.9
-      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/helper-plugin-utils': 7.10.4
       '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.12.9
       '@babel/plugin-transform-parameters': 7.20.7_@babel+core@7.12.9
 
@@ -6284,8 +6284,8 @@ packages:
       eslint-visitor-keys: 3.3.0
     dev: false
 
-  /@vanilla-extract/babel-plugin-debug-ids/1.0.1:
-    resolution: {integrity: sha512-ynyKqsJiMzM1/yiIJ6QdqpWKlK4IMJJWREpPtaemZrE1xG1B4E/Nfa6YazuDWjDkCJC1tRIpEGnVs+pMIjUxyw==}
+  /@vanilla-extract/babel-plugin-debug-ids/1.0.2:
+    resolution: {integrity: sha512-LjnbQWGeMwaydmovx8jWUR8BxLtLiPyq0xz5C8G5OvFhsuJxvavLdrBHNNizvr1dq7/3qZGlPv0znsvU4P44YA==}
     dependencies:
       '@babel/core': 7.21.0
     transitivePeerDependencies:
@@ -6306,12 +6306,12 @@ packages:
       media-query-parser: 2.0.2
       outdent: 0.8.0
 
-  /@vanilla-extract/integration/6.1.0:
-    resolution: {integrity: sha512-7gDkOibk/DraG35ZpiAYqWd33wLA6YRnieC5vw7ItoFEzCv9bUaS9c+ZyktyWW3nRnL+e7Pc6FS6l7MKgEsX1w==}
+  /@vanilla-extract/integration/6.1.1:
+    resolution: {integrity: sha512-JGlynmtCju+NWU5ITgs81szCo8A4hFfW1E2oAsn++JHvgU7+wyg9N0TwByZOB1/nbrHVKISzRvl1M0BeB0mZXQ==}
     dependencies:
       '@babel/core': 7.21.0
       '@babel/plugin-syntax-typescript': 7.20.0_@babel+core@7.21.0
-      '@vanilla-extract/babel-plugin-debug-ids': 1.0.1
+      '@vanilla-extract/babel-plugin-debug-ids': 1.0.2
       '@vanilla-extract/css': 1.9.5
       esbuild: 0.16.17
       eval: 0.1.6
@@ -6326,7 +6326,7 @@ packages:
   /@vanilla-extract/jest-transform/1.1.0:
     resolution: {integrity: sha512-yjk/IXMOGrvFXW48eF/JmMua/KumbT8HI1OtCHfod4MFiJq9F7i0qF+UCoNwAMTwywu0eFWnHbTU/3jd8phUjA==}
     dependencies:
-      '@vanilla-extract/integration': 6.1.0
+      '@vanilla-extract/integration': 6.1.1
       esbuild: 0.16.17
     transitivePeerDependencies:
       - supports-color
@@ -6348,7 +6348,7 @@ packages:
     peerDependencies:
       vite: ^2.2.3 || ^3.0.0 || ^4.0.3
     dependencies:
-      '@vanilla-extract/integration': 6.1.0
+      '@vanilla-extract/integration': 6.1.1
       outdent: 0.8.0
       postcss: 8.4.21
       postcss-load-config: 3.1.4_postcss@8.4.21
@@ -6363,7 +6363,7 @@ packages:
     peerDependencies:
       webpack: ^4.30.0 || ^5.20.2
     dependencies:
-      '@vanilla-extract/integration': 6.1.0
+      '@vanilla-extract/integration': 6.1.1
       chalk: 4.1.2
       debug: 4.3.4
       loader-utils: 2.0.4
````

As soon as these changes have been applied the following crash happens somewhere during our call to create sprinkles:

```
react.development.js:1407 Uncaught TypeError: Cannot read properties of undefined (reading 'styles')
    at createSprinkles-29d354c5.esm.js:47:62
    at Array.map (<anonymous>)
    at createSprinkles-29d354c5.esm.js:47:51
    at createSprinkles3 (vanilla-extract-sprinkles-createRuntimeSprinkles.esm.js:6:41)
    at Text.css.ts:3:24
```

This is the line which crashes:

```
  var sprinklesStyles = Object.assign({}, ...args.map(a => a.styles));
````

inside of this block:

```
var createSprinkles = composeStyles => function () {
  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
    args[_key] = arguments[_key];
  }

  var sprinklesStyles = Object.assign({}, ...args.map(a => a.styles));
  var sprinklesKeys = Object.keys(sprinklesStyles);
  var shorthandNames = sprinklesKeys.filter(property => 'mappings' in sprinklesStyles[property]);
```

That's what we are doing:

```
import { createSprinkles, defineProperties } from "@vanilla-extract/sprinkles"

import { fontSizes, fontWeights, letterSpacings, lineHeights } from "./styles"

export const textProperties = defineProperties({
  properties: {
    fontSize: fontSizes,
    fontWeight: fontWeights,
    letterSpacing: letterSpacings,
    lineHeight: lineHeights
  }
})

export const textAtoms = createSprinkles(textProperties)
```

Anybody has an idea what happens here. From what I see in the repo it's not obvious to me. Thanks for any help!

### Reproduction

working on it

### System Info

```shell
System:
    OS: macOS 13.2.1
    CPU: (20) arm64 Apple M1 Ultra
    Memory: 1.47 GB / 64.00 GB
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 16.19.1 - /opt/homebrew/opt/node@16/bin/node
    npm: 8.19.3 - /opt/homebrew/opt/node@16/bin/npm
  Browsers:
    Chrome: 110.0.5481.177
    Firefox: 109.0.1
    Safari: 16.3
  npmPackages:
    @vanilla-extract/css: ^1.9.5 => 1.9.5
    @vanilla-extract/jest-transform: ^1.1.0 => 1.1.0
    @vanilla-extract/sprinkles: ^1.5.1 => 1.5.1
    @vanilla-extract/vite-plugin: ^3.8.0 => 3.8.0
    vite: ^4.1.4 => 4.1.4
```


### Used Package Manager

pnpm

### Logs

_No response_

### Validations

- [X] Check that there isn't [already an issue](https://github.com/vanilla-extract-css/vanilla-extract/issues) that reports the same bug to avoid creating a duplicate.
- [X] The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug.

Pull request description:
integration: Sort serialized module exports
The `eval` result we use to serialize modules is an object containing the module's exports, so its order can't be reliably depended on. This was resulting in module exports sometimes being serialized in the wrong order. In [my reproduction](https://github.com/askoufis/vanilla-extract-issue-1027-repro), the effect of this was `createSprinkles` being called on a variable that was declared on the line below, causing a runtime error.

```js
import "/src/App.css.ts.vanilla.js";
import { createSprinkles as _ad221 } from "/node_modules/.vite/deps/@vanilla-extract_sprinkles_createRuntimeSprinkles.js?v=0be76525";

// Referencing `textProperties` before it's defined
export var textAtoms = _ad221(textProperties);
export var textProperties = { /* */ };
```

We can take advantage of [the new exports variable sharing](https://github.com/vanilla-extract-css/vanilla-extract/releases/tag/%40vanilla-extract%2Fintegration%406.1.1) to build a dependency graph, which we can then use to sort the exports in a valid order.

Fixes #1027.

Patch:
diff --git a/.changeset/shiny-socks-lick.md b/.changeset/shiny-socks-lick.md
new file mode 100644
index 0000000..ec6a402
--- /dev/null
+++ b/.changeset/shiny-socks-lick.md
@@ -0,0 +1,7 @@
+---
+'@vanilla-extract/integration': patch
+---
+
+Sort serialized module exports
+
+Fixes a Vanilla module serialization bug that sometimes resulted in variables being used before they were declared
diff --git a/packages/integration/src/processVanillaFile.ts b/packages/integration/src/processVanillaFile.ts
index cc6c34f..c01b360 100644
--- a/packages/integration/src/processVanillaFile.ts
+++ b/packages/integration/src/processVanillaFile.ts
@@ -166,6 +166,7 @@ function stringifyExports(
   unusedCompositionRegex: RegExp | null,
   key: string,
   exportLookup: Map<any, string>,
+  exportDependencyGraph: DependencyGraph,
 ): any {
   return stringify(
     value,
@@ -185,6 +186,7 @@ function stringifyExports(
         const reusedExport = exportLookup.get(value);
 
         if (reusedExport && reusedExport !== key) {
+          exportDependencyGraph.addDependency(key, reusedExport);
           return reusedExport;
         }
         return next(value);
@@ -236,6 +238,7 @@ function stringifyExports(
                 unusedCompositionRegex,
                 key,
                 exportLookup,
+                exportDependencyGraph,
               ),
             )
             .join(',')})`;
@@ -263,6 +266,48 @@ function stringifyExports(
 
 const defaultExportName = '__default__';
 
+class DependencyGraph {
+  graph: Map<string, Set<string>>;
+
+  public constructor() {
+    this.graph = new Map();
+  }
+
+  /**
+   * Creates a "depends on" relationship between `key` and `dependency`
+   */
+  public addDependency(key: string, dependency: string) {
+    const dependencies = this.graph.get(key);
+
+    if (dependencies) {
+      dependencies.add(dependency);
+    } else {
+      this.graph.set(key, new Set([dependency]));
+    }
+  }
+
+  /**
+   * Whether or not `key` depends on `dependency`
+   */
+  public dependsOn(key: string, dependency: string): boolean {
+    const dependencies = this.graph.get(key);
+
+    if (dependencies) {
+      if (dependencies?.has(dependency)) {
+        return true;
+      }
+
+      for (const [dep] of dependencies.entries()) {
+        if (this.dependsOn(dep, dependency)) {
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }
+}
+
 export function serializeVanillaModule(
   cssImports: Array<string>,
   exports: Record<string, unknown>,
@@ -276,29 +321,49 @@ export function serializeVanillaModule(
     ]),
   );
 
-  const moduleExports = Object.keys(exports).map((key) => {
+  const exportDependencyGraph = new DependencyGraph();
+
+  const moduleExports = Object.entries(exports).map(([key, value]) => {
     const serializedExport = stringifyExports(
       functionSerializationImports,
-      exports[key],
+      value,
       unusedCompositionRegex,
       key === 'default' ? defaultExportName : key,
       exportLookup,
+      exportDependencyGraph,
     );
 
     if (key === 'default') {
       return [
-        `var ${defaultExportName} = ${serializedExport};`,
-        `export default ${defaultExportName};`,
-      ].join('\n');
+        defaultExportName,
+        [
+          `var ${defaultExportName} = ${serializedExport};`,
+          `export default ${defaultExportName};`,
+        ].join('\n'),
+      ];
     }
 
-    return `export var ${key} = ${serializedExport};`;
+    return [key, `export var ${key} = ${serializedExport};`];
   });
 
+  const sortedModuleExports = moduleExports
+    .sort(([key1], [key2]) => {
+      if (exportDependencyGraph.dependsOn(key1, key2)) {
+        return 1;
+      }
+
+      if (exportDependencyGraph.dependsOn(key2, key1)) {
+        return -1;
+      }
+
+      return 0;
+    })
+    .map(([, s]) => s);
+
   const outputCode = [
     ...cssImports,
     ...functionSerializationImports,
-    ...moduleExports,
+    ...sortedModuleExports,
   ];
 
   return outputCode.join('\n');


Test patch:
diff --git a/packages/integration/src/processVanillaFile.test.ts b/packages/integration/src/processVanillaFile.test.ts
index 9407610..926fc07 100644
--- a/packages/integration/src/processVanillaFile.test.ts
+++ b/packages/integration/src/processVanillaFile.test.ts
@@ -180,4 +180,59 @@ describe('serializeVanillaModule', () => {
       export var sprinkles = _86bce(__default__,otherComplexExport);"
     `);
   });
+
+  test('should order exports correctly', () => {
+    const simpleObjectExport = { hello: 'world' };
+
+    const complexExport = {
+      my: {
+        very: {
+          complex: {
+            arg: simpleObjectExport,
+          },
+        },
+      },
+    };
+
+    const otherComplexExport = {
+      other: {
+        complex: [1, 2, 3],
+      },
+    };
+
+    const someOtherExport = [4, 5, 6];
+
+    const reExport = otherComplexExport;
+    const reReExport = reExport;
+
+    const sprinkles = () => {};
+    sprinkles.__function_serializer__ = {
+      importPath: 'my-package',
+      importName: 'myFunction',
+      args: [complexExport, otherComplexExport, someOtherExport, reReExport],
+    };
+    const exports = {
+      sprinkles,
+      complexExport,
+      simpleObjectExport,
+      reReExport,
+      otherComplexExport,
+      default: someOtherExport,
+      reExport,
+    };
+
+    expect(serializeVanillaModule(['import "./styles.css"'], exports, null))
+      .toMatchInlineSnapshot(`
+      "import "./styles.css"
+      import { myFunction as _86bce } from 'my-package';
+      export var simpleObjectExport = {hello:'world'};
+      export var complexExport = {my:{very:{complex:{arg:simpleObjectExport}}}};
+      var __default__ = [4,5,6];
+      export default __default__;
+      export var reExport = {other:{complex:[1,2,3]}};
+      export var sprinkles = _86bce(complexExport,reExport,__default__,reExport);
+      export var reReExport = reExport;
+      export var otherComplexExport = reExport;"
+    `);
+  });
 });