@bamboocss/vite 1.35.1 → 1.35.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -176,7 +176,8 @@ const replaceAssetReferences = (bundle, previous, next, sourcemap) => {
176
176
  * would therefore leave two different reachable subsets under one CDN key. The extra final
177
177
  * hash is not cosmetic: it makes late graph reachability cache-safe.
178
178
  */
179
- const optimizeStaticCssAssets = (bundle, session) => {
179
+ const optimizeStaticCssAssets = (bundle, session, options = {}) => {
180
+ const { rename = true } = options;
180
181
  for (const [bundleName, output] of Object.entries(bundle)) {
181
182
  if (output.type !== "asset") continue;
182
183
  const source = typeof output.source === "string" ? output.source : Buffer.from(output.source).toString();
@@ -184,6 +185,7 @@ const optimizeStaticCssAssets = (bundle, session) => {
184
185
  const optimized = pruneStaticCss(source, session);
185
186
  output.source = optimized;
186
187
  if (optimized === source) continue;
188
+ if (!rename) continue;
187
189
  const nextName = output.fileName.replace(/\.css$/, `.b-${(0, _bamboocss_shared.toHash)(optimized)}.css`);
188
190
  if (nextName === output.fileName) continue;
189
191
  if (bundle[nextName] && bundle[nextName] !== output) throw new Error(`bamboocss: final CSS asset name collision at ${JSON.stringify(nextName)}.`);
@@ -208,7 +210,7 @@ const optimizeStaticCssAssets = (bundle, session) => {
208
210
  * process just wrote, which is a race on any watch rebuild.
209
211
  */
210
212
  const bamboocssCss = (options) => {
211
- const { configPath, cwd, session } = options;
213
+ const { configPath, cwd, session, renameCssAsset = true } = options;
212
214
  const builder = new _bamboocss_node.Builder();
213
215
  let server;
214
216
  let command = "build";
@@ -292,7 +294,13 @@ const bamboocssCss = (options) => {
292
294
  generateBundle: {
293
295
  order: "post",
294
296
  handler(_, bundle) {
295
- optimizeStaticCssAssets(bundle, session);
297
+ const rolldown = Boolean(this.meta?.rolldownVersion);
298
+ optimizeStaticCssAssets(bundle, session, { rename: renameCssAsset && !rolldown });
299
+ if (!session.transformedFiles.size) return;
300
+ if (!Object.values(bundle).some((output) => {
301
+ if (output.type !== "asset") return false;
302
+ return (typeof output.source === "string" ? output.source : Buffer.from(output.source).toString()).includes("--made-with-bamboo");
303
+ })) throw new Error(`bamboocss: ${session.transformedFiles.size} module(s) were compiled to Bamboo class values, but no emitted asset carries the generated stylesheet. The build would ship unstyled.\n\nThis happens when another plugin, or the bundler itself, drops or replaces the CSS asset after it is emitted. If you are on Rolldown, report this — the rename that used to cause it is already disabled there. Otherwise look for a plugin running in \`generateBundle\` that rewrites CSS assets.`);
296
304
  }
297
305
  }
298
306
  };
@@ -709,9 +717,10 @@ const propertyKey = (nameNode) => {
709
717
  * there is nothing to match — the host here is any import of the generated css module, which
710
718
  * a file defining a recipe necessarily has, since `cva` came from it.
711
719
  */
712
- const ensureRecipeHelperImport = (imported, call, isBambooCssModule, isGeneratedCssModule, isShadowed, newImportModule) => {
720
+ const ensureRecipeHelperImport = (imported, call, isBambooCssModule, isGeneratedCssModule, isShadowed, newImportModule, helperModuleFromSubpath) => {
713
721
  const sourceFile = call.getSourceFile();
714
722
  let host;
723
+ let subpathModule;
715
724
  for (const declaration of sourceFile.getImportDeclarations()) {
716
725
  const mod = declaration.getModuleSpecifierValue();
717
726
  if (declaration.isTypeOnly()) continue;
@@ -724,8 +733,10 @@ const ensureRecipeHelperImport = (imported, call, isBambooCssModule, isGenerated
724
733
  }
725
734
  }
726
735
  if (!host && isGeneratedCssModule(mod) && declaration.getNamedImports().length > 0) host = declaration;
736
+ if (!subpathModule) subpathModule = helperModuleFromSubpath?.(mod);
727
737
  }
728
- if (!host && !newImportModule) return void 0;
738
+ const fallbackModule = newImportModule ?? subpathModule;
739
+ if (!host && !fallbackModule) return void 0;
729
740
  if (declaredAtModuleScope(sourceFile).has(imported)) return void 0;
730
741
  if (isShadowed(call, imported)) return void 0;
731
742
  if (!host) {
@@ -736,7 +747,7 @@ const ensureRecipeHelperImport = (imported, call, isBambooCssModule, isGenerated
736
747
  insert: {
737
748
  pos: anchor.getEnd(),
738
749
  names: [imported],
739
- module: newImportModule
750
+ module: fallbackModule
740
751
  }
741
752
  };
742
753
  }
@@ -1512,6 +1523,32 @@ const foldSource = (options) => {
1512
1523
  const isBambooCssModule = (mod) => matchesModule(mod, cssModules);
1513
1524
  const isGeneratedCssModule = (mod) => matchesModule(mod, [generatedCssModule]);
1514
1525
  /**
1526
+ * Where the compiler's helpers can be imported from, for a file that imports the generated
1527
+ * css module by *subpath* rather than through its barrel.
1528
+ *
1529
+ * `styled-system/css/cva.js` is a real spelling, and it cannot host the helper: that module
1530
+ * exports `cva`, not `cvaMap`. Matching only the barrel meant no host was found and every
1531
+ * runtime recipe selection in the file declined — a failure whose reported reason said
1532
+ * nothing about import spelling, and whose suggested remedies all pointed elsewhere.
1533
+ *
1534
+ * The sibling `cx` module is what actually exports them. The prefix is verified against the
1535
+ * configured output before anything is derived, so an unrelated `foo/css/bar.js` is left
1536
+ * alone, and the caller's extension is preserved rather than guessed at.
1537
+ */
1538
+ const helperModuleFromSubpath = (mod) => {
1539
+ const normalized = mod.replaceAll("\\", "/");
1540
+ const at = normalized.lastIndexOf("/css/");
1541
+ if (at < 0) return void 0;
1542
+ const prefix = normalized.slice(0, at + 5 - 1);
1543
+ if (!isGeneratedCssModule(prefix)) return void 0;
1544
+ const rest = normalized.slice(at + 5);
1545
+ if (!rest || rest.includes("/")) return void 0;
1546
+ const dot = rest.lastIndexOf(".");
1547
+ const extension = dot > 0 ? rest.slice(dot) : "";
1548
+ if (rest === `cx${extension}`) return void 0;
1549
+ return `${prefix}/cx${extension}`;
1550
+ };
1551
+ /**
1515
1552
  * The generated css entry as spelled beside an imported config recipe.
1516
1553
  *
1517
1554
  * A decision table needs only `cvaMap`, but a module importing a config recipe often has
@@ -1822,7 +1859,7 @@ const foldSource = (options) => {
1822
1859
  }
1823
1860
  const lowered = lowerRecipeCall(call, entry, styleCompiler, isInertExpression, resolvedSelection, inlineSlot, maxRecipeStates);
1824
1861
  if (lowered.kind === "dynamic-style") {
1825
- const helper = ensureRecipeHelperImport(RECIPE_MAP_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed, helperModules.get(name));
1862
+ const helper = ensureRecipeHelperImport(RECIPE_MAP_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed, helperModules.get(name), helperModuleFromSubpath);
1826
1863
  if (helper) {
1827
1864
  candidates.push({
1828
1865
  item,
@@ -1849,7 +1886,7 @@ const foldSource = (options) => {
1849
1886
  continue;
1850
1887
  }
1851
1888
  if (lowered.kind === "slots") {
1852
- const helper = lowered.helper ? ensureRecipeHelperImport(lowered.helper, call, isBambooCssModule, isGeneratedCssModule, isShadowed, helperModules.get(name)) : void 0;
1889
+ const helper = lowered.helper ? ensureRecipeHelperImport(lowered.helper, call, isBambooCssModule, isGeneratedCssModule, isShadowed, helperModules.get(name), helperModuleFromSubpath) : void 0;
1853
1890
  if (!lowered.helper || helper) {
1854
1891
  const replacement = lowered.helper && helper && helper.name !== lowered.helper ? lowered.expression.replaceAll(`${lowered.helper}(`, `${helper.name}(`) : lowered.expression;
1855
1892
  candidates.push({
@@ -2302,7 +2339,7 @@ const foldSource = (options) => {
2302
2339
  const end = call.getEnd();
2303
2340
  if (code.slice(start, end) !== call.getText()) continue;
2304
2341
  if (collides([[start, end]])) continue;
2305
- const helper = ensureRecipeHelperImport(SPLIT_PROPS_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed, helperModules.get(target.getText()));
2342
+ const helper = ensureRecipeHelperImport(SPLIT_PROPS_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed, helperModules.get(target.getText()), helperModuleFromSubpath);
2306
2343
  if (!helper) continue;
2307
2344
  const keys = Object.keys(entry.config.variants ?? {});
2308
2345
  magic.overwrite(start, end, `${helper.name}(${args[0].getText()}, ${JSON.stringify(keys)})`);
@@ -2348,11 +2385,17 @@ const foldSource = (options) => {
2348
2385
  if (skipped.filter((item) => SURVIVES_TO_RUNTIME.has(item.reason) && item.end > item.start).some((item) => nameNode.findReferencesAsNodes().some((ref) => ref.getStart() >= item.start && ref.getStart() < item.end))) continue;
2349
2386
  const survivor = nameNode.findReferencesAsNodes().find((ref) => !applied.some(([from, to]) => ref.getStart() >= from && ref.getStart() < to));
2350
2387
  if (!survivor) continue;
2388
+ const survivorFile = survivor.getSourceFile();
2389
+ const foreign = survivorFile !== sourceFile;
2351
2390
  skipped.push({
2352
2391
  name: binding,
2353
2392
  reason: "runtime-binding",
2354
- start: survivor.getStart(),
2355
- end: survivor.getEnd()
2393
+ start: foreign ? nameNode.getStart() : survivor.getStart(),
2394
+ end: foreign ? nameNode.getEnd() : survivor.getEnd(),
2395
+ ...foreign ? { origin: {
2396
+ filePath: survivorFile.getFilePath(),
2397
+ line: survivor.getStartLineNumber()
2398
+ } } : {}
2356
2399
  });
2357
2400
  }
2358
2401
  const bambooModules = [
@@ -2653,7 +2696,7 @@ const formatSkipped = (id, skipped) => {
2653
2696
  * with no matching rule.
2654
2697
  */
2655
2698
  const bamboocss = (options = {}) => {
2656
- const { configPath, cwd, reportSkipped = false, reportSummary = true, denseClassNames = true, maxRecipeStates } = options;
2699
+ const { configPath, cwd, reportSkipped = false, reportSummary = true, denseClassNames = true, maxRecipeStates, renameCssAsset = true } = options;
2657
2700
  if (maxRecipeStates !== void 0 && (!Number.isSafeInteger(maxRecipeStates) || maxRecipeStates < 1)) throw new Error("bamboocss: `maxRecipeStates` must be a positive safe integer.");
2658
2701
  /** Totals across the build, for the summary. */
2659
2702
  const totals = {
@@ -2689,7 +2732,7 @@ const bamboocss = (options = {}) => {
2689
2732
  separator: "\n"
2690
2733
  });
2691
2734
  const threw = entries.some((entry) => entry.reason === "compile-failed");
2692
- return /* @__PURE__ */ new Error(`bamboocss: ${entries.length} call(s) could not be compiled.\n\n${detail}\n\n` + (threw ? "`compile-failed` is a module the compiler threw on — see the error logged for it above. Nothing was established about its calls either way.\n\n" : "") + "Bamboo emits no runtime styling fallback or recipe layer. Make the values finite and statically analyzable, move variation into declared recipe variants, or safelist intentional dynamic classes with `staticCss`.");
2735
+ return /* @__PURE__ */ new Error(`bamboocss: ${entries.length} call(s) could not be compiled.\n\n${detail}\n\n` + (threw ? "`compile-failed` is a module the compiler threw on — see the error logged for it above. Nothing was established about its calls either way.\n\n" : "") + (entries.some((entry) => entry.reason === "runtime-binding") ? "`runtime-binding` is a Bamboo value read outside a call the compiler rewrote — most often an inline `cva`/`sva` imported by another module. Its declaration is erased where it is declared, so the import receives `undefined`; a recipe used in more than one file has to be a config recipe under `theme.extend.recipes`. The location given is the reference to change, not the declaration.\n\n" : "") + "Bamboo emits no runtime styling fallback or recipe layer. Make the values finite and statically analyzable, move variation into declared recipe variants, or safelist intentional dynamic classes with `staticCss`.\n\nSet `BAMBOO_DIAGNOSTIC_LIMIT=all` to list every finding rather than the first few.");
2693
2736
  };
2694
2737
  /**
2695
2738
  * Recipe configs read out of modules other than the one being transformed.
@@ -2718,7 +2761,8 @@ const bamboocss = (options = {}) => {
2718
2761
  return [bamboocssCss({
2719
2762
  configPath,
2720
2763
  cwd,
2721
- session: staticSession
2764
+ session: staticSession,
2765
+ renameCssAsset
2722
2766
  }), {
2723
2767
  name: "bamboocss:compiler",
2724
2768
  enforce: "pre",
@@ -2815,7 +2859,12 @@ const bamboocss = (options = {}) => {
2815
2859
  for (const entry of result.skipped) {
2816
2860
  if (entry.reason === "not-imported" || entry.reason === "overlapping") continue;
2817
2861
  if (entry.name === "cx" && entry.reason === "dynamic") continue;
2818
- addSurvivor({
2862
+ addSurvivor(entry.origin ? {
2863
+ file: entry.origin.filePath,
2864
+ line: entry.origin.line,
2865
+ name: entry.name,
2866
+ reason: entry.reason
2867
+ } : {
2819
2868
  file: filePath,
2820
2869
  line: lineAt(code, entry.start),
2821
2870
  name: entry.name,
@@ -2835,7 +2884,7 @@ const bamboocss = (options = {}) => {
2835
2884
  buildEnd() {
2836
2885
  if (survivors.length) throw createSurvivorError(survivors);
2837
2886
  if (typeof this.getModuleInfo === "function") {
2838
- if (!staticSession.cssLoaded) throw new Error(`bamboocss: compiled class values were produced, but ${JSON.stringify(VIRTUAL_CSS_ID)} was not imported. Import it once from the application entry so the compiled rules are emitted.`);
2887
+ if (!staticSession.cssLoaded) throw new Error(`bamboocss: compiled class values were produced, but ${JSON.stringify(VIRTUAL_CSS_ID)} was not imported. Add \`import ${JSON.stringify(VIRTUAL_CSS_ID)}\` once, from a JavaScript or TypeScript module in the application entry graph.\n\nIt has to be a JS import. \`@import\` from a stylesheet does not reach it: the id names a virtual module resolved by this plugin, and Vite resolves CSS \`@import\` before plugin resolution, so it fails as an unresolvable path. A project that ships one preloaded stylesheet imports this from its entry module instead, and lets Vite emit the CSS asset.`);
2839
2888
  const outsideExtraction = [...staticSession.transformedFiles].filter((file) => !staticSession.extractedFiles.has(file));
2840
2889
  if (outsideExtraction.length) throw new Error(`bamboocss: ${outsideExtraction.length} statically compiled module(s) are outside the CSS extraction graph:\n\n${(0, _bamboocss_shared.truncateList)(outsideExtraction.map((file) => ` ${file}`), {
2841
2890
  unit: "file",
package/dist/index.d.cts CHANGED
@@ -45,6 +45,24 @@ interface BambooVitePluginOptions {
45
45
  * build time and memory for the exact compound-variant decision table. @default 65536
46
46
  */
47
47
  maxRecipeStates?: number;
48
+ /**
49
+ * Give the pruned stylesheet a final name derived from its own bytes.
50
+ *
51
+ * Rollup expands `[hash]` before `generateBundle`, so pruning after it can leave two
52
+ * different reachable subsets under one CDN key. Renaming closes that, at the cost of
53
+ * replacing an entry in the output bundle — which not every consumer of the bundle
54
+ * tolerates. Rolldown drops the asset outright (detected automatically, no flag needed),
55
+ * and a framework that relocates assets itself, such as react-router's SSR build, can
56
+ * lose track of the new name.
57
+ *
58
+ * Turning it off keeps the pruned bytes and Vite's own content hash. That hash is computed
59
+ * before pruning, so it stops describing the file exactly; in practice it still changes
60
+ * whenever the extracted CSS does, and only a change that alters *reachability alone*
61
+ * can now reuse a key. Prefer that over an asset your framework cannot find.
62
+ *
63
+ * @default true
64
+ */
65
+ renameCssAsset?: boolean;
48
66
  }
49
67
  /**
50
68
  * Vite integration for Bamboo CSS.
package/dist/index.d.mts CHANGED
@@ -45,6 +45,24 @@ interface BambooVitePluginOptions {
45
45
  * build time and memory for the exact compound-variant decision table. @default 65536
46
46
  */
47
47
  maxRecipeStates?: number;
48
+ /**
49
+ * Give the pruned stylesheet a final name derived from its own bytes.
50
+ *
51
+ * Rollup expands `[hash]` before `generateBundle`, so pruning after it can leave two
52
+ * different reachable subsets under one CDN key. Renaming closes that, at the cost of
53
+ * replacing an entry in the output bundle — which not every consumer of the bundle
54
+ * tolerates. Rolldown drops the asset outright (detected automatically, no flag needed),
55
+ * and a framework that relocates assets itself, such as react-router's SSR build, can
56
+ * lose track of the new name.
57
+ *
58
+ * Turning it off keeps the pruned bytes and Vite's own content hash. That hash is computed
59
+ * before pruning, so it stops describing the file exactly; in practice it still changes
60
+ * whenever the extracted CSS does, and only a change that alters *reachability alone*
61
+ * can now reuse a key. Prefer that over an asset your framework cannot find.
62
+ *
63
+ * @default true
64
+ */
65
+ renameCssAsset?: boolean;
48
66
  }
49
67
  /**
50
68
  * Vite integration for Bamboo CSS.
package/dist/index.mjs CHANGED
@@ -146,7 +146,8 @@ const replaceAssetReferences = (bundle, previous, next, sourcemap) => {
146
146
  * would therefore leave two different reachable subsets under one CDN key. The extra final
147
147
  * hash is not cosmetic: it makes late graph reachability cache-safe.
148
148
  */
149
- const optimizeStaticCssAssets = (bundle, session) => {
149
+ const optimizeStaticCssAssets = (bundle, session, options = {}) => {
150
+ const { rename = true } = options;
150
151
  for (const [bundleName, output] of Object.entries(bundle)) {
151
152
  if (output.type !== "asset") continue;
152
153
  const source = typeof output.source === "string" ? output.source : Buffer.from(output.source).toString();
@@ -154,6 +155,7 @@ const optimizeStaticCssAssets = (bundle, session) => {
154
155
  const optimized = pruneStaticCss(source, session);
155
156
  output.source = optimized;
156
157
  if (optimized === source) continue;
158
+ if (!rename) continue;
157
159
  const nextName = output.fileName.replace(/\.css$/, `.b-${toHash(optimized)}.css`);
158
160
  if (nextName === output.fileName) continue;
159
161
  if (bundle[nextName] && bundle[nextName] !== output) throw new Error(`bamboocss: final CSS asset name collision at ${JSON.stringify(nextName)}.`);
@@ -178,7 +180,7 @@ const optimizeStaticCssAssets = (bundle, session) => {
178
180
  * process just wrote, which is a race on any watch rebuild.
179
181
  */
180
182
  const bamboocssCss = (options) => {
181
- const { configPath, cwd, session } = options;
183
+ const { configPath, cwd, session, renameCssAsset = true } = options;
182
184
  const builder = new Builder();
183
185
  let server;
184
186
  let command = "build";
@@ -262,7 +264,13 @@ const bamboocssCss = (options) => {
262
264
  generateBundle: {
263
265
  order: "post",
264
266
  handler(_, bundle) {
265
- optimizeStaticCssAssets(bundle, session);
267
+ const rolldown = Boolean(this.meta?.rolldownVersion);
268
+ optimizeStaticCssAssets(bundle, session, { rename: renameCssAsset && !rolldown });
269
+ if (!session.transformedFiles.size) return;
270
+ if (!Object.values(bundle).some((output) => {
271
+ if (output.type !== "asset") return false;
272
+ return (typeof output.source === "string" ? output.source : Buffer.from(output.source).toString()).includes("--made-with-bamboo");
273
+ })) throw new Error(`bamboocss: ${session.transformedFiles.size} module(s) were compiled to Bamboo class values, but no emitted asset carries the generated stylesheet. The build would ship unstyled.\n\nThis happens when another plugin, or the bundler itself, drops or replaces the CSS asset after it is emitted. If you are on Rolldown, report this — the rename that used to cause it is already disabled there. Otherwise look for a plugin running in \`generateBundle\` that rewrites CSS assets.`);
266
274
  }
267
275
  }
268
276
  };
@@ -679,9 +687,10 @@ const propertyKey = (nameNode) => {
679
687
  * there is nothing to match — the host here is any import of the generated css module, which
680
688
  * a file defining a recipe necessarily has, since `cva` came from it.
681
689
  */
682
- const ensureRecipeHelperImport = (imported, call, isBambooCssModule, isGeneratedCssModule, isShadowed, newImportModule) => {
690
+ const ensureRecipeHelperImport = (imported, call, isBambooCssModule, isGeneratedCssModule, isShadowed, newImportModule, helperModuleFromSubpath) => {
683
691
  const sourceFile = call.getSourceFile();
684
692
  let host;
693
+ let subpathModule;
685
694
  for (const declaration of sourceFile.getImportDeclarations()) {
686
695
  const mod = declaration.getModuleSpecifierValue();
687
696
  if (declaration.isTypeOnly()) continue;
@@ -694,8 +703,10 @@ const ensureRecipeHelperImport = (imported, call, isBambooCssModule, isGenerated
694
703
  }
695
704
  }
696
705
  if (!host && isGeneratedCssModule(mod) && declaration.getNamedImports().length > 0) host = declaration;
706
+ if (!subpathModule) subpathModule = helperModuleFromSubpath?.(mod);
697
707
  }
698
- if (!host && !newImportModule) return void 0;
708
+ const fallbackModule = newImportModule ?? subpathModule;
709
+ if (!host && !fallbackModule) return void 0;
699
710
  if (declaredAtModuleScope(sourceFile).has(imported)) return void 0;
700
711
  if (isShadowed(call, imported)) return void 0;
701
712
  if (!host) {
@@ -706,7 +717,7 @@ const ensureRecipeHelperImport = (imported, call, isBambooCssModule, isGenerated
706
717
  insert: {
707
718
  pos: anchor.getEnd(),
708
719
  names: [imported],
709
- module: newImportModule
720
+ module: fallbackModule
710
721
  }
711
722
  };
712
723
  }
@@ -1482,6 +1493,32 @@ const foldSource = (options) => {
1482
1493
  const isBambooCssModule = (mod) => matchesModule(mod, cssModules);
1483
1494
  const isGeneratedCssModule = (mod) => matchesModule(mod, [generatedCssModule]);
1484
1495
  /**
1496
+ * Where the compiler's helpers can be imported from, for a file that imports the generated
1497
+ * css module by *subpath* rather than through its barrel.
1498
+ *
1499
+ * `styled-system/css/cva.js` is a real spelling, and it cannot host the helper: that module
1500
+ * exports `cva`, not `cvaMap`. Matching only the barrel meant no host was found and every
1501
+ * runtime recipe selection in the file declined — a failure whose reported reason said
1502
+ * nothing about import spelling, and whose suggested remedies all pointed elsewhere.
1503
+ *
1504
+ * The sibling `cx` module is what actually exports them. The prefix is verified against the
1505
+ * configured output before anything is derived, so an unrelated `foo/css/bar.js` is left
1506
+ * alone, and the caller's extension is preserved rather than guessed at.
1507
+ */
1508
+ const helperModuleFromSubpath = (mod) => {
1509
+ const normalized = mod.replaceAll("\\", "/");
1510
+ const at = normalized.lastIndexOf("/css/");
1511
+ if (at < 0) return void 0;
1512
+ const prefix = normalized.slice(0, at + 5 - 1);
1513
+ if (!isGeneratedCssModule(prefix)) return void 0;
1514
+ const rest = normalized.slice(at + 5);
1515
+ if (!rest || rest.includes("/")) return void 0;
1516
+ const dot = rest.lastIndexOf(".");
1517
+ const extension = dot > 0 ? rest.slice(dot) : "";
1518
+ if (rest === `cx${extension}`) return void 0;
1519
+ return `${prefix}/cx${extension}`;
1520
+ };
1521
+ /**
1485
1522
  * The generated css entry as spelled beside an imported config recipe.
1486
1523
  *
1487
1524
  * A decision table needs only `cvaMap`, but a module importing a config recipe often has
@@ -1792,7 +1829,7 @@ const foldSource = (options) => {
1792
1829
  }
1793
1830
  const lowered = lowerRecipeCall(call, entry, styleCompiler, isInertExpression, resolvedSelection, inlineSlot, maxRecipeStates);
1794
1831
  if (lowered.kind === "dynamic-style") {
1795
- const helper = ensureRecipeHelperImport(RECIPE_MAP_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed, helperModules.get(name));
1832
+ const helper = ensureRecipeHelperImport(RECIPE_MAP_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed, helperModules.get(name), helperModuleFromSubpath);
1796
1833
  if (helper) {
1797
1834
  candidates.push({
1798
1835
  item,
@@ -1819,7 +1856,7 @@ const foldSource = (options) => {
1819
1856
  continue;
1820
1857
  }
1821
1858
  if (lowered.kind === "slots") {
1822
- const helper = lowered.helper ? ensureRecipeHelperImport(lowered.helper, call, isBambooCssModule, isGeneratedCssModule, isShadowed, helperModules.get(name)) : void 0;
1859
+ const helper = lowered.helper ? ensureRecipeHelperImport(lowered.helper, call, isBambooCssModule, isGeneratedCssModule, isShadowed, helperModules.get(name), helperModuleFromSubpath) : void 0;
1823
1860
  if (!lowered.helper || helper) {
1824
1861
  const replacement = lowered.helper && helper && helper.name !== lowered.helper ? lowered.expression.replaceAll(`${lowered.helper}(`, `${helper.name}(`) : lowered.expression;
1825
1862
  candidates.push({
@@ -2272,7 +2309,7 @@ const foldSource = (options) => {
2272
2309
  const end = call.getEnd();
2273
2310
  if (code.slice(start, end) !== call.getText()) continue;
2274
2311
  if (collides([[start, end]])) continue;
2275
- const helper = ensureRecipeHelperImport(SPLIT_PROPS_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed, helperModules.get(target.getText()));
2312
+ const helper = ensureRecipeHelperImport(SPLIT_PROPS_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed, helperModules.get(target.getText()), helperModuleFromSubpath);
2276
2313
  if (!helper) continue;
2277
2314
  const keys = Object.keys(entry.config.variants ?? {});
2278
2315
  magic.overwrite(start, end, `${helper.name}(${args[0].getText()}, ${JSON.stringify(keys)})`);
@@ -2318,11 +2355,17 @@ const foldSource = (options) => {
2318
2355
  if (skipped.filter((item) => SURVIVES_TO_RUNTIME.has(item.reason) && item.end > item.start).some((item) => nameNode.findReferencesAsNodes().some((ref) => ref.getStart() >= item.start && ref.getStart() < item.end))) continue;
2319
2356
  const survivor = nameNode.findReferencesAsNodes().find((ref) => !applied.some(([from, to]) => ref.getStart() >= from && ref.getStart() < to));
2320
2357
  if (!survivor) continue;
2358
+ const survivorFile = survivor.getSourceFile();
2359
+ const foreign = survivorFile !== sourceFile;
2321
2360
  skipped.push({
2322
2361
  name: binding,
2323
2362
  reason: "runtime-binding",
2324
- start: survivor.getStart(),
2325
- end: survivor.getEnd()
2363
+ start: foreign ? nameNode.getStart() : survivor.getStart(),
2364
+ end: foreign ? nameNode.getEnd() : survivor.getEnd(),
2365
+ ...foreign ? { origin: {
2366
+ filePath: survivorFile.getFilePath(),
2367
+ line: survivor.getStartLineNumber()
2368
+ } } : {}
2326
2369
  });
2327
2370
  }
2328
2371
  const bambooModules = [
@@ -2623,7 +2666,7 @@ const formatSkipped = (id, skipped) => {
2623
2666
  * with no matching rule.
2624
2667
  */
2625
2668
  const bamboocss = (options = {}) => {
2626
- const { configPath, cwd, reportSkipped = false, reportSummary = true, denseClassNames = true, maxRecipeStates } = options;
2669
+ const { configPath, cwd, reportSkipped = false, reportSummary = true, denseClassNames = true, maxRecipeStates, renameCssAsset = true } = options;
2627
2670
  if (maxRecipeStates !== void 0 && (!Number.isSafeInteger(maxRecipeStates) || maxRecipeStates < 1)) throw new Error("bamboocss: `maxRecipeStates` must be a positive safe integer.");
2628
2671
  /** Totals across the build, for the summary. */
2629
2672
  const totals = {
@@ -2659,7 +2702,7 @@ const bamboocss = (options = {}) => {
2659
2702
  separator: "\n"
2660
2703
  });
2661
2704
  const threw = entries.some((entry) => entry.reason === "compile-failed");
2662
- return /* @__PURE__ */ new Error(`bamboocss: ${entries.length} call(s) could not be compiled.\n\n${detail}\n\n` + (threw ? "`compile-failed` is a module the compiler threw on — see the error logged for it above. Nothing was established about its calls either way.\n\n" : "") + "Bamboo emits no runtime styling fallback or recipe layer. Make the values finite and statically analyzable, move variation into declared recipe variants, or safelist intentional dynamic classes with `staticCss`.");
2705
+ return /* @__PURE__ */ new Error(`bamboocss: ${entries.length} call(s) could not be compiled.\n\n${detail}\n\n` + (threw ? "`compile-failed` is a module the compiler threw on — see the error logged for it above. Nothing was established about its calls either way.\n\n" : "") + (entries.some((entry) => entry.reason === "runtime-binding") ? "`runtime-binding` is a Bamboo value read outside a call the compiler rewrote — most often an inline `cva`/`sva` imported by another module. Its declaration is erased where it is declared, so the import receives `undefined`; a recipe used in more than one file has to be a config recipe under `theme.extend.recipes`. The location given is the reference to change, not the declaration.\n\n" : "") + "Bamboo emits no runtime styling fallback or recipe layer. Make the values finite and statically analyzable, move variation into declared recipe variants, or safelist intentional dynamic classes with `staticCss`.\n\nSet `BAMBOO_DIAGNOSTIC_LIMIT=all` to list every finding rather than the first few.");
2663
2706
  };
2664
2707
  /**
2665
2708
  * Recipe configs read out of modules other than the one being transformed.
@@ -2688,7 +2731,8 @@ const bamboocss = (options = {}) => {
2688
2731
  return [bamboocssCss({
2689
2732
  configPath,
2690
2733
  cwd,
2691
- session: staticSession
2734
+ session: staticSession,
2735
+ renameCssAsset
2692
2736
  }), {
2693
2737
  name: "bamboocss:compiler",
2694
2738
  enforce: "pre",
@@ -2785,7 +2829,12 @@ const bamboocss = (options = {}) => {
2785
2829
  for (const entry of result.skipped) {
2786
2830
  if (entry.reason === "not-imported" || entry.reason === "overlapping") continue;
2787
2831
  if (entry.name === "cx" && entry.reason === "dynamic") continue;
2788
- addSurvivor({
2832
+ addSurvivor(entry.origin ? {
2833
+ file: entry.origin.filePath,
2834
+ line: entry.origin.line,
2835
+ name: entry.name,
2836
+ reason: entry.reason
2837
+ } : {
2789
2838
  file: filePath,
2790
2839
  line: lineAt(code, entry.start),
2791
2840
  name: entry.name,
@@ -2805,7 +2854,7 @@ const bamboocss = (options = {}) => {
2805
2854
  buildEnd() {
2806
2855
  if (survivors.length) throw createSurvivorError(survivors);
2807
2856
  if (typeof this.getModuleInfo === "function") {
2808
- if (!staticSession.cssLoaded) throw new Error(`bamboocss: compiled class values were produced, but ${JSON.stringify(VIRTUAL_CSS_ID)} was not imported. Import it once from the application entry so the compiled rules are emitted.`);
2857
+ if (!staticSession.cssLoaded) throw new Error(`bamboocss: compiled class values were produced, but ${JSON.stringify(VIRTUAL_CSS_ID)} was not imported. Add \`import ${JSON.stringify(VIRTUAL_CSS_ID)}\` once, from a JavaScript or TypeScript module in the application entry graph.\n\nIt has to be a JS import. \`@import\` from a stylesheet does not reach it: the id names a virtual module resolved by this plugin, and Vite resolves CSS \`@import\` before plugin resolution, so it fails as an unresolvable path. A project that ships one preloaded stylesheet imports this from its entry module instead, and lets Vite emit the CSS asset.`);
2809
2858
  const outsideExtraction = [...staticSession.transformedFiles].filter((file) => !staticSession.extractedFiles.has(file));
2810
2859
  if (outsideExtraction.length) throw new Error(`bamboocss: ${outsideExtraction.length} statically compiled module(s) are outside the CSS extraction graph:\n\n${truncateList(outsideExtraction.map((file) => ` ${file}`), {
2811
2860
  unit: "file",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamboocss/vite",
3
- "version": "1.35.1",
3
+ "version": "1.35.3",
4
4
  "description": "Vite integration for Bamboo CSS",
5
5
  "homepage": "https://bamboocss.com",
6
6
  "license": "MIT",
@@ -40,18 +40,18 @@
40
40
  "postcss": "8.5.26",
41
41
  "postcss-selector-parser": "7.1.5",
42
42
  "ts-morph": "28.0.0",
43
- "@bamboocss/config": "1.35.1",
44
- "@bamboocss/core": "1.35.1",
45
- "@bamboocss/extractor": "1.35.1",
46
- "@bamboocss/node": "1.35.1",
47
- "@bamboocss/logger": "1.35.1",
48
- "@bamboocss/shared": "1.35.1",
49
- "@bamboocss/types": "1.35.1"
43
+ "@bamboocss/config": "1.35.3",
44
+ "@bamboocss/extractor": "1.35.3",
45
+ "@bamboocss/logger": "1.35.3",
46
+ "@bamboocss/node": "1.35.3",
47
+ "@bamboocss/core": "1.35.3",
48
+ "@bamboocss/shared": "1.35.3",
49
+ "@bamboocss/types": "1.35.3"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@jridgewell/trace-mapping": "^0.3.31",
53
53
  "vite": "7.2.6",
54
- "@bamboocss/fixture": "1.35.1"
54
+ "@bamboocss/fixture": "1.35.3"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "vite": ">=5"