@bamboocss/vite 1.46.3 → 1.47.0

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.
@@ -2378,7 +2378,7 @@ const foldSource = (options) => {
2378
2378
  const definition = entry?.box?.getNode?.();
2379
2379
  const nameNode = definition?.getSourceFile() === sourceFile ? definition?.getFirstAncestorByKind(ts_morph.SyntaxKind.VariableDeclaration)?.getNameNode() : importSpecifierFor(sourceFile, binding);
2380
2380
  if (!nameNode || !ts_morph.Node.isIdentifier(nameNode)) continue;
2381
- const references = localReferencesTo(identifiersByName(), binding, nameNode);
2381
+ const references = localReferencesTo(identifiersByName(), binding, nameNode).filter((reference) => !reference.getFirstAncestorByKind(ts_morph.SyntaxKind.TypeQuery));
2382
2382
  if (skipped.filter((item) => SURVIVES_TO_RUNTIME.has(item.reason) && item.end > item.start).some((item) => references.some((ref) => ref.getStart() >= item.start && ref.getStart() < item.end))) continue;
2383
2383
  const survivor = references.find((ref) => !applied.some(([from, to]) => ref.getStart() >= from && ref.getStart() < to));
2384
2384
  if (!survivor) continue;
@@ -2376,7 +2376,7 @@ const foldSource = (options) => {
2376
2376
  const definition = entry?.box?.getNode?.();
2377
2377
  const nameNode = definition?.getSourceFile() === sourceFile ? definition?.getFirstAncestorByKind(SyntaxKind.VariableDeclaration)?.getNameNode() : importSpecifierFor(sourceFile, binding);
2378
2378
  if (!nameNode || !Node.isIdentifier(nameNode)) continue;
2379
- const references = localReferencesTo(identifiersByName(), binding, nameNode);
2379
+ const references = localReferencesTo(identifiersByName(), binding, nameNode).filter((reference) => !reference.getFirstAncestorByKind(SyntaxKind.TypeQuery));
2380
2380
  if (skipped.filter((item) => SURVIVES_TO_RUNTIME.has(item.reason) && item.end > item.start).some((item) => references.some((ref) => ref.getStart() >= item.start && ref.getStart() < item.end))) continue;
2381
2381
  const survivor = references.find((ref) => !applied.some(([from, to]) => ref.getStart() >= from && ref.getStart() < to));
2382
2382
  if (!survivor) continue;
package/dist/index.cjs CHANGED
@@ -236,7 +236,6 @@ const bamboocssCss = (options) => {
236
236
  await builder.emit();
237
237
  builder.extract();
238
238
  await new Promise((settle) => setImmediate(settle));
239
- if (builder.context?.config.polyfill) throw new Error("bamboocss: the cascade-layer polyfill is incompatible with compiled atomic styles. The polyfill removes the utility-layer boundary required for safe atom reachability and renaming.");
240
239
  if (builder.context) {
241
240
  session.utilityLayer = builder.context.config.layers?.utilities ?? "utilities";
242
241
  session.extractedFiles.clear();
@@ -547,6 +546,15 @@ const bamboocssCss = (options) => {
547
546
  //#endregion
548
547
  //#region src/plugin.ts
549
548
  const DEFAULT_EXTENSIONS = /\.(?:[cm]?[jt]sx?)$/;
549
+ const SFC_EXTENSIONS = /\.(?:vue|svelte|astro)$/i;
550
+ /**
551
+ * Framework script submodules. Vue spells `lang.ts` as a bare query key; Svelte uses
552
+ * `lang=ts`; both set `type=script`. The compiler must see that JS, not the wrapping SFC —
553
+ * folding the SFC uses parser:before offsets that do not match the file Vite emits.
554
+ */
555
+ const SFC_SCRIPT_QUERY = /[?&](?:type=script(?:&|$)|lang\.tsx?(?:&|$)|lang=tsx?(?:&|$)|lang\.jsx?(?:&|$))/i;
556
+ const SFC_JSX_QUERY = /[?&](?:lang\.tsx|lang=tsx|lang\.jsx|lang=jsx)(?:&|$)/i;
557
+ const SFC_SCRIPT_TAG = /<script[\s>/]/i;
550
558
  const NODE_MODULES = /node_modules/;
551
559
  const TRANSFORM_META_KEY = "bamboocss:transform";
552
560
  const TRANSFORM_ARTIFACT_VERSION = 3;
@@ -594,7 +602,30 @@ const shouldTransform = (id) => {
594
602
  const [filePath] = id.split("?");
595
603
  if (!filePath) return false;
596
604
  if (NODE_MODULES.test(filePath)) return false;
597
- return DEFAULT_EXTENSIONS.test(filePath);
605
+ return DEFAULT_EXTENSIONS.test(filePath) || SFC_EXTENSIONS.test(filePath);
606
+ };
607
+ /**
608
+ * Path ts-morph should parse for this transform.
609
+ *
610
+ * A `.vue` / `.svelte` / `.astro` id is either a raw SFC (skip — offsets would not match), a
611
+ * `type=script` submodule, or the framework's compiled JS stored under the SFC path. The last
612
+ * two are JavaScript or TypeScript: parsing them as the SFC would run `parser:before` and fold
613
+ * the wrong bytes. A sibling `.ts`/`.tsx` path preserves JSX parsing and skips those hooks.
614
+ *
615
+ * Returns `null` when the module is still a raw SFC and must be left to the framework plugin.
616
+ * Astro frontmatter is `---`, not `<script>`, so a tag check alone would parse the template.
617
+ */
618
+ const compilerParsePath = (id, code) => {
619
+ const [filePath, query = ""] = id.split("?");
620
+ if (!filePath) return null;
621
+ if (!SFC_EXTENSIONS.test(filePath)) return filePath;
622
+ const normalizedQuery = `?${query}`;
623
+ if (SFC_SCRIPT_QUERY.test(normalizedQuery)) return `${filePath}.__bamboo__.${SFC_JSX_QUERY.test(normalizedQuery) ? "tsx" : "ts"}`;
624
+ const trimmed = code.trimStart();
625
+ if (/\.astro$/i.test(filePath) && (trimmed.startsWith("---") || trimmed.startsWith("<"))) return null;
626
+ if (SFC_SCRIPT_TAG.test(code) || /<(?:template|style)[\s>/]/i.test(code)) return null;
627
+ if (trimmed.startsWith("<")) return null;
628
+ return `${filePath}.__bamboo__.ts`;
598
629
  };
599
630
  /**
600
631
  * Is this file part of the generated `styled-system` rather than the user's source?
@@ -638,15 +669,12 @@ const formatSkipped = (id, skipped) => {
638
669
  /**
639
670
  * Vite integration for Bamboo CSS.
640
671
  *
641
- * Two plugins, because they do unrelated jobs on different schedules. The first emits the
642
- * stylesheet as a virtual module and runs in dev and build alike that is the integration,
643
- * and nothing styles without it. The second compiles every Bamboo source call in both dev
644
- * and build; there is no runtime styling fallback.
645
- *
646
- * The compiler runs with `enforce: 'pre'` so it sees module source as close as possible to what
647
- * the CSS extractor reads off disk. A plugin that rewrites style calls before bamboo
648
- * sees them would otherwise make the two disagree, and a folded class could end up
649
- * with no matching rule.
672
+ * Three plugins. The first emits the stylesheet as a virtual module. The second compiles
673
+ * JavaScript and TypeScript with `enforce: 'pre'` so it sees source close to what the CSS
674
+ * extractor reads off disk. The third compiles Vue, Svelte and Astro with `enforce: 'post'`
675
+ * so it folds the framework's compiled JavaScript — a `pre` hook that skipped the raw SFC
676
+ * would never run again on the same id. Script submodules (`type=script`) are SFC paths and
677
+ * therefore fold in the post plugin, after the framework has extracted them.
650
678
  */
651
679
  const bamboocss = (options = {}) => {
652
680
  const { configPath, cwd, reportSkipped = false, reportSummary = true, maxRecipeStates, pruneCss = true } = options;
@@ -794,6 +822,7 @@ const bamboocss = (options = {}) => {
794
822
  dependenciesByModule: /* @__PURE__ */ new Map(),
795
823
  filesByModule: /* @__PURE__ */ new Map(),
796
824
  foldSignatures: /* @__PURE__ */ new Map(),
825
+ foldInputsByModule: /* @__PURE__ */ new Map(),
797
826
  recipeConfigCache: /* @__PURE__ */ new Map(),
798
827
  transformedModulesThisRun: /* @__PURE__ */ new Set(),
799
828
  unchangedFolds: /* @__PURE__ */ new Map(),
@@ -807,6 +836,7 @@ const bamboocss = (options = {}) => {
807
836
  dependenciesByModule: new Map([...state.dependenciesByModule].map(([moduleId, dependencies]) => [moduleId, new Set(dependencies)])),
808
837
  filesByModule: new Map(state.filesByModule),
809
838
  foldSignatures: new Map(state.foldSignatures),
839
+ foldInputsByModule: new Map(state.foldInputsByModule),
810
840
  recipeConfigCache: new Map(state.recipeConfigCache),
811
841
  transformedModulesThisRun: new Set(state.transformedModulesThisRun),
812
842
  unchangedFolds: new Map(state.unchangedFolds),
@@ -1223,7 +1253,10 @@ const bamboocss = (options = {}) => {
1223
1253
  state.transformArtifactsByModule.set(moduleId, artifact);
1224
1254
  recordFoldDependencies(state, moduleId, file, artifact.dependencies);
1225
1255
  if (artifact.signature) state.foldSignatures.set(moduleId, artifact.signature);
1226
- else state.foldSignatures.delete(moduleId);
1256
+ else {
1257
+ state.foldSignatures.delete(moduleId);
1258
+ state.foldInputsByModule.delete(moduleId);
1259
+ }
1227
1260
  };
1228
1261
  /**
1229
1262
  * Replay transform metadata for modules Rollup reused from its cache.
@@ -1302,7 +1335,9 @@ const bamboocss = (options = {}) => {
1302
1335
  const signature = state.foldSignatures.get(dependent);
1303
1336
  if (!signature || !ctx || !foldSourceImpl || !runtimeCss || !styleCompiler) return false;
1304
1337
  try {
1305
- const code = (0, node_fs.readFileSync)(signature.path, "utf8");
1338
+ const retained = state.foldInputsByModule.get(dependent);
1339
+ const code = retained?.input === signature.input ? retained.code : (0, node_fs.readFileSync)(signature.path, "utf8");
1340
+ const parsePath = retained?.input === signature.input ? retained.parsePath : signature.path;
1306
1341
  const inputDigest = digest(code);
1307
1342
  if (inputDigest !== signature.input) return false;
1308
1343
  /**
@@ -1328,20 +1363,20 @@ const bamboocss = (options = {}) => {
1328
1363
  }
1329
1364
  let raw;
1330
1365
  let parserDependencies;
1331
- const memoKey = foldMemoKey(signature.path, inputDigest);
1366
+ const memoKey = foldMemoKey(parsePath, inputDigest);
1332
1367
  const memoized = foldMemoByContent.get(memoKey);
1333
1368
  if (memoized) {
1334
1369
  raw = memoized.result;
1335
1370
  parserDependencies = memoized.parserDependencies;
1336
1371
  } else {
1337
- const sourceFile = ctx.project.addSourceFile(signature.path, code);
1338
- const parserResult = ctx.project.parseSourceFile(signature.path);
1372
+ const sourceFile = ctx.project.addSourceFile(parsePath, code);
1373
+ const parserResult = ctx.project.parseSourceFile(parsePath);
1339
1374
  if (!parserResult) return false;
1340
1375
  raw = foldSourceImpl({
1341
1376
  ctx,
1342
1377
  code,
1343
1378
  parserResult,
1344
- filePath: signature.path,
1379
+ filePath: parsePath,
1345
1380
  runtimeCss,
1346
1381
  styleCompiler,
1347
1382
  maxRecipeStates,
@@ -1358,7 +1393,7 @@ const bamboocss = (options = {}) => {
1358
1393
  reportedSurvivors: false
1359
1394
  });
1360
1395
  }
1361
- const result = withResolutionClosure(signature.path, raw, parserDependencies, state.dependenciesByModule.get(dependent));
1396
+ const result = withResolutionClosure(parsePath, raw, parserDependencies, state.dependenciesByModule.get(dependent));
1362
1397
  const unchanged = digest(result.code) === signature.output;
1363
1398
  /**
1364
1399
  * Edges re-recorded exactly on the way to suppressing a module. A changed provisional
@@ -1713,6 +1748,7 @@ const bamboocss = (options = {}) => {
1713
1748
  const [filePath] = id.split("?");
1714
1749
  if (!filePath) return;
1715
1750
  for (const state of transformStateByEnvironment.values()) state.recipeConfigCache.clear();
1751
+ if (SFC_EXTENSIONS.test(filePath)) return;
1716
1752
  if (change.event === "delete") {
1717
1753
  ctx.project.removeSourceFile(filePath);
1718
1754
  const deleted = normalizeFsPath(filePath);
@@ -1720,6 +1756,7 @@ const bamboocss = (options = {}) => {
1720
1756
  if (normalizeFsPath(moduleFile) !== deleted) continue;
1721
1757
  recordFoldDependencies(state, moduleId, moduleFile, []);
1722
1758
  state.foldSignatures.delete(moduleId);
1759
+ state.foldInputsByModule.delete(moduleId);
1723
1760
  state.transformArtifactsByModule.delete(moduleId);
1724
1761
  state.filesByModule.delete(moduleId);
1725
1762
  }
@@ -1818,136 +1855,7 @@ const bamboocss = (options = {}) => {
1818
1855
  return foldDependentModules(environmentState(this), file, modules, legacy.moduleGraph);
1819
1856
  },
1820
1857
  async transform(code, id) {
1821
- if (!shouldTransform(id)) return null;
1822
- try {
1823
- await ensureCompilerState();
1824
- } catch (error) {
1825
- throw asError(error, "failed to initialize the bamboo compiler");
1826
- }
1827
- if (!ctx || !foldSourceImpl || !runtimeCss || !styleCompiler) return null;
1828
- const [filePath] = id.split("?");
1829
- if (isGeneratedOutput(filePath, ctx)) return null;
1830
- const state = environmentState(this);
1831
- state.transformedModulesThisRun.add(id);
1832
- let inputDigest;
1833
- const previousSignature = state.foldSignatures.get(id);
1834
- const previousDependencies = previousSignature && previousSignature.input === (inputDigest ??= digest(code)) ? state.dependenciesByModule.get(id) : void 0;
1835
- let result;
1836
- try {
1837
- const memoKey = command === "serve" ? foldMemoKey(filePath, inputDigest ??= digest(code)) : void 0;
1838
- const memoized = memoKey ? foldMemoByContent.get(memoKey) : void 0;
1839
- let valueReads = [];
1840
- if (memoized?.reportedSurvivors) {
1841
- valueReads = memoized.valueReads;
1842
- result = withResolutionClosure(filePath, memoized.result, memoized.parserDependencies, previousDependencies);
1843
- } else {
1844
- const sourceFile = ctx.project.addSourceFile(filePath, code);
1845
- const parserResult = ctx.project.parseSourceFile(filePath);
1846
- if (!parserResult) {
1847
- state.transformArtifactsByModule.delete(id);
1848
- recordFoldDependencies(state, id, filePath, []);
1849
- state.foldSignatures.delete(id);
1850
- return null;
1851
- }
1852
- const folded = foldSourceImpl({
1853
- ctx,
1854
- code,
1855
- parserResult,
1856
- filePath,
1857
- runtimeCss,
1858
- styleCompiler,
1859
- maxRecipeStates,
1860
- parseModule: (path) => ctx?.project.parseSourceFile(path),
1861
- recipeConfigCache: state.recipeConfigCache,
1862
- reportSurvivors: true,
1863
- sourceFile
1864
- });
1865
- const parserDependencies = parserResult.getDependencies();
1866
- valueReads = parserResult.getExportReads?.() ?? [];
1867
- if (memoKey) foldMemoByContent.set(memoKey, {
1868
- result: folded,
1869
- parserDependencies,
1870
- valueReads,
1871
- reportedSurvivors: true
1872
- });
1873
- result = withResolutionClosure(filePath, folded, parserDependencies, previousDependencies);
1874
- }
1875
- state.exportReadsByModule.set(id, [...valueReads.map((read) => ({
1876
- kind: "value",
1877
- ...read
1878
- })), ...result.exportReads]);
1879
- } catch (error) {
1880
- _bamboocss_logger.logger.caughtError("vite:transform", `Failed to compile ${filePath}`, error);
1881
- const previousDependencies = [...state.dependenciesByModule.get(id) ?? []];
1882
- applyTransformArtifact(state, sealTransformArtifact(environmentName(this), {
1883
- version: TRANSFORM_ARTIFACT_VERSION,
1884
- moduleId: id,
1885
- file: filePath,
1886
- folded: 0,
1887
- skipped: [["compile-failed", 1]],
1888
- survivors: [{
1889
- line: 1,
1890
- name: "compiler",
1891
- reason: "compile-failed"
1892
- }],
1893
- transformedFile: false,
1894
- classNames: [],
1895
- dependencies: previousDependencies
1896
- }), id, environmentName(this));
1897
- state.foldSignatures.delete(id);
1898
- if (command === "serve") throw asError(error, `failed to compile ${filePath}`);
1899
- return null;
1900
- }
1901
- const skippedHere = /* @__PURE__ */ new Map();
1902
- for (const entry of result.skipped) skippedHere.set(entry.reason, (skippedHere.get(entry.reason) ?? 0) + 1);
1903
- const survivorsHere = [];
1904
- for (const entry of result.skipped) {
1905
- if (entry.reason === "not-imported" || entry.reason === "overlapping") continue;
1906
- if (entry.name === "cx" && entry.reason === "dynamic") continue;
1907
- survivorsHere.push({
1908
- line: lineAt(code, entry.start),
1909
- name: entry.name,
1910
- reason: entry.reason
1911
- });
1912
- }
1913
- const artifact = sealTransformArtifact(environmentName(this), {
1914
- version: TRANSFORM_ARTIFACT_VERSION,
1915
- moduleId: id,
1916
- file: filePath,
1917
- folded: result.folded.length,
1918
- skipped: [...skippedHere],
1919
- survivors: survivorsHere,
1920
- transformedFile: result.folded.some((entry) => entry.kind === "class" || entry.kind === "slots"),
1921
- classNames: [...new Set(result.folded.flatMap((entry) => entry.classNames))],
1922
- dependencies: [...result.dependencies],
1923
- ...result.dependencies.length ? { signature: {
1924
- input: inputDigest ??= digest(code),
1925
- output: digest(result.code),
1926
- path: filePath
1927
- } } : {}
1928
- });
1929
- applyTransformArtifact(state, artifact, id, environmentName(this));
1930
- if (reportSkipped && result.skipped.length) _bamboocss_logger.logger.info("vite:transform", formatSkipped(filePath, result.skipped));
1931
- for (const dependency of result.dependencies) this.addWatchFile?.(dependency);
1932
- if (command === "serve" && artifact.survivors.length) {
1933
- state.foldSignatures.delete(id);
1934
- throw createSurvivorError(artifact.survivors.map((survivor) => ({
1935
- file: filePath,
1936
- ...survivor
1937
- })));
1938
- }
1939
- const meta = { [TRANSFORM_META_KEY]: artifact };
1940
- if (!result.folded.length) return typeof this.getModuleInfo === "function" ? {
1941
- code,
1942
- map: null,
1943
- meta
1944
- } : null;
1945
- _bamboocss_logger.logger.debug("vite:transform", `Compiled ${result.folded.length} call(s) in ${filePath}`);
1946
- return {
1947
- code: result.code,
1948
- map: result.map,
1949
- meta
1950
- };
1858
+ return compileModule.call(this, code, id, false);
1951
1859
  },
1952
1860
  buildEnd(buildError) {
1953
1861
  const environment = environmentName(this);
@@ -1995,6 +1903,160 @@ const bamboocss = (options = {}) => {
1995
1903
  if (state) rollbackEnvironmentGeneration(environment, state);
1996
1904
  }
1997
1905
  };
1906
+ const compilerSfc = {
1907
+ name: "bamboocss:compiler-sfc",
1908
+ enforce: "post",
1909
+ sharedDuringBuild: true,
1910
+ async transform(code, id) {
1911
+ return compileModule.call(this, code, id, true);
1912
+ }
1913
+ };
1914
+ async function compileModule(code, id, sfcOnly) {
1915
+ if (!shouldTransform(id)) return null;
1916
+ const [pathForFilter] = id.split("?");
1917
+ if (!pathForFilter) return null;
1918
+ if (SFC_EXTENSIONS.test(pathForFilter) !== sfcOnly) return null;
1919
+ try {
1920
+ await ensureCompilerState();
1921
+ } catch (error) {
1922
+ throw asError(error, "failed to initialize the bamboo compiler");
1923
+ }
1924
+ if (!ctx || !foldSourceImpl || !runtimeCss || !styleCompiler) return null;
1925
+ const [filePath] = id.split("?");
1926
+ if (isGeneratedOutput(filePath, ctx)) return null;
1927
+ const parsePath = compilerParsePath(id, code);
1928
+ if (parsePath === null) return null;
1929
+ const state = environmentState(this);
1930
+ state.transformedModulesThisRun.add(id);
1931
+ let inputDigest;
1932
+ const previousSignature = state.foldSignatures.get(id);
1933
+ const previousDependencies = previousSignature && previousSignature.input === (inputDigest ??= digest(code)) ? state.dependenciesByModule.get(id) : void 0;
1934
+ let result;
1935
+ try {
1936
+ const memoKey = command === "serve" ? foldMemoKey(parsePath, inputDigest ??= digest(code)) : void 0;
1937
+ const memoized = memoKey ? foldMemoByContent.get(memoKey) : void 0;
1938
+ let valueReads = [];
1939
+ if (memoized?.reportedSurvivors) {
1940
+ valueReads = memoized.valueReads;
1941
+ result = withResolutionClosure(parsePath, memoized.result, memoized.parserDependencies, previousDependencies);
1942
+ } else {
1943
+ const sourceFile = ctx.project.addSourceFile(parsePath, code);
1944
+ const parserResult = ctx.project.parseSourceFile(parsePath);
1945
+ if (!parserResult) {
1946
+ state.transformArtifactsByModule.delete(id);
1947
+ recordFoldDependencies(state, id, filePath, []);
1948
+ state.foldSignatures.delete(id);
1949
+ state.foldInputsByModule.delete(id);
1950
+ return null;
1951
+ }
1952
+ const folded = foldSourceImpl({
1953
+ ctx,
1954
+ code,
1955
+ parserResult,
1956
+ filePath: parsePath,
1957
+ runtimeCss,
1958
+ styleCompiler,
1959
+ maxRecipeStates,
1960
+ parseModule: (path) => ctx?.project.parseSourceFile(path),
1961
+ recipeConfigCache: state.recipeConfigCache,
1962
+ reportSurvivors: true,
1963
+ sourceFile
1964
+ });
1965
+ const parserDependencies = parserResult.getDependencies();
1966
+ valueReads = parserResult.getExportReads?.() ?? [];
1967
+ if (memoKey) foldMemoByContent.set(memoKey, {
1968
+ result: folded,
1969
+ parserDependencies,
1970
+ valueReads,
1971
+ reportedSurvivors: true
1972
+ });
1973
+ result = withResolutionClosure(parsePath, folded, parserDependencies, previousDependencies);
1974
+ }
1975
+ state.exportReadsByModule.set(id, [...valueReads.map((read) => ({
1976
+ kind: "value",
1977
+ ...read
1978
+ })), ...result.exportReads]);
1979
+ } catch (error) {
1980
+ _bamboocss_logger.logger.caughtError("vite:transform", `Failed to compile ${filePath}`, error);
1981
+ const previousDependencies = [...state.dependenciesByModule.get(id) ?? []];
1982
+ applyTransformArtifact(state, sealTransformArtifact(environmentName(this), {
1983
+ version: TRANSFORM_ARTIFACT_VERSION,
1984
+ moduleId: id,
1985
+ file: filePath,
1986
+ folded: 0,
1987
+ skipped: [["compile-failed", 1]],
1988
+ survivors: [{
1989
+ line: 1,
1990
+ name: "compiler",
1991
+ reason: "compile-failed"
1992
+ }],
1993
+ transformedFile: false,
1994
+ classNames: [],
1995
+ dependencies: previousDependencies
1996
+ }), id, environmentName(this));
1997
+ state.foldSignatures.delete(id);
1998
+ state.foldInputsByModule.delete(id);
1999
+ if (command === "serve") throw asError(error, `failed to compile ${filePath}`);
2000
+ return null;
2001
+ }
2002
+ const skippedHere = /* @__PURE__ */ new Map();
2003
+ for (const entry of result.skipped) skippedHere.set(entry.reason, (skippedHere.get(entry.reason) ?? 0) + 1);
2004
+ const survivorsHere = [];
2005
+ for (const entry of result.skipped) {
2006
+ if (entry.reason === "not-imported" || entry.reason === "overlapping") continue;
2007
+ if (entry.name === "cx" && entry.reason === "dynamic") continue;
2008
+ survivorsHere.push({
2009
+ line: lineAt(code, entry.start),
2010
+ name: entry.name,
2011
+ reason: entry.reason
2012
+ });
2013
+ }
2014
+ const artifact = sealTransformArtifact(environmentName(this), {
2015
+ version: TRANSFORM_ARTIFACT_VERSION,
2016
+ moduleId: id,
2017
+ file: filePath,
2018
+ folded: result.folded.length,
2019
+ skipped: [...skippedHere],
2020
+ survivors: survivorsHere,
2021
+ transformedFile: result.folded.some((entry) => entry.kind === "class" || entry.kind === "slots"),
2022
+ classNames: [...new Set(result.folded.flatMap((entry) => entry.classNames))],
2023
+ dependencies: [...result.dependencies],
2024
+ ...result.dependencies.length ? { signature: {
2025
+ input: inputDigest ??= digest(code),
2026
+ output: digest(result.code),
2027
+ path: filePath
2028
+ } } : {}
2029
+ });
2030
+ applyTransformArtifact(state, artifact, id, environmentName(this));
2031
+ if (artifact.signature && (command === "serve" || parsePath !== filePath)) state.foldInputsByModule.set(id, {
2032
+ code,
2033
+ input: artifact.signature.input,
2034
+ parsePath
2035
+ });
2036
+ else state.foldInputsByModule.delete(id);
2037
+ if (reportSkipped && result.skipped.length) _bamboocss_logger.logger.info("vite:transform", formatSkipped(filePath, result.skipped));
2038
+ for (const dependency of result.dependencies) this.addWatchFile?.(dependency);
2039
+ if (command === "serve" && artifact.survivors.length) {
2040
+ state.foldSignatures.delete(id);
2041
+ state.foldInputsByModule.delete(id);
2042
+ throw createSurvivorError(artifact.survivors.map((survivor) => ({
2043
+ file: filePath,
2044
+ ...survivor
2045
+ })));
2046
+ }
2047
+ const meta = { [TRANSFORM_META_KEY]: artifact };
2048
+ if (!result.folded.length) return typeof this.getModuleInfo === "function" ? {
2049
+ code,
2050
+ map: null,
2051
+ meta
2052
+ } : null;
2053
+ _bamboocss_logger.logger.debug("vite:transform", `Compiled ${result.folded.length} call(s) in ${filePath}`);
2054
+ return {
2055
+ code: result.code,
2056
+ map: result.map,
2057
+ meta
2058
+ };
2059
+ }
1998
2060
  const outputWriteObserver = {
1999
2061
  name: "bamboocss:output-write-observer",
2000
2062
  enforce: "pre",
@@ -2027,12 +2089,16 @@ const bamboocss = (options = {}) => {
2027
2089
  }
2028
2090
  }
2029
2091
  };
2030
- return [bamboocssCss({
2031
- configPath,
2032
- cwd,
2033
- session: staticSession,
2034
- pruneCss
2035
- }), compiler];
2092
+ return [
2093
+ bamboocssCss({
2094
+ configPath,
2095
+ cwd,
2096
+ session: staticSession,
2097
+ pruneCss
2098
+ }),
2099
+ compiler,
2100
+ compilerSfc
2101
+ ];
2036
2102
  };
2037
2103
  //#endregion
2038
2104
  exports.VIRTUAL_CSS_ID = VIRTUAL_CSS_ID;
package/dist/index.d.cts CHANGED
@@ -67,15 +67,12 @@ interface BambooVitePluginOptions {
67
67
  /**
68
68
  * Vite integration for Bamboo CSS.
69
69
  *
70
- * Two plugins, because they do unrelated jobs on different schedules. The first emits the
71
- * stylesheet as a virtual module and runs in dev and build alike that is the integration,
72
- * and nothing styles without it. The second compiles every Bamboo source call in both dev
73
- * and build; there is no runtime styling fallback.
74
- *
75
- * The compiler runs with `enforce: 'pre'` so it sees module source as close as possible to what
76
- * the CSS extractor reads off disk. A plugin that rewrites style calls before bamboo
77
- * sees them would otherwise make the two disagree, and a folded class could end up
78
- * with no matching rule.
70
+ * Three plugins. The first emits the stylesheet as a virtual module. The second compiles
71
+ * JavaScript and TypeScript with `enforce: 'pre'` so it sees source close to what the CSS
72
+ * extractor reads off disk. The third compiles Vue, Svelte and Astro with `enforce: 'post'`
73
+ * so it folds the framework's compiled JavaScript — a `pre` hook that skipped the raw SFC
74
+ * would never run again on the same id. Script submodules (`type=script`) are SFC paths and
75
+ * therefore fold in the post plugin, after the framework has extracted them.
79
76
  */
80
77
  declare const bamboocss: (options?: BambooVitePluginOptions) => Plugin[];
81
78
  //#endregion
package/dist/index.d.mts CHANGED
@@ -67,15 +67,12 @@ interface BambooVitePluginOptions {
67
67
  /**
68
68
  * Vite integration for Bamboo CSS.
69
69
  *
70
- * Two plugins, because they do unrelated jobs on different schedules. The first emits the
71
- * stylesheet as a virtual module and runs in dev and build alike that is the integration,
72
- * and nothing styles without it. The second compiles every Bamboo source call in both dev
73
- * and build; there is no runtime styling fallback.
74
- *
75
- * The compiler runs with `enforce: 'pre'` so it sees module source as close as possible to what
76
- * the CSS extractor reads off disk. A plugin that rewrites style calls before bamboo
77
- * sees them would otherwise make the two disagree, and a folded class could end up
78
- * with no matching rule.
70
+ * Three plugins. The first emits the stylesheet as a virtual module. The second compiles
71
+ * JavaScript and TypeScript with `enforce: 'pre'` so it sees source close to what the CSS
72
+ * extractor reads off disk. The third compiles Vue, Svelte and Astro with `enforce: 'post'`
73
+ * so it folds the framework's compiled JavaScript — a `pre` hook that skipped the raw SFC
74
+ * would never run again on the same id. Script submodules (`type=script`) are SFC paths and
75
+ * therefore fold in the post plugin, after the framework has extracted them.
79
76
  */
80
77
  declare const bamboocss: (options?: BambooVitePluginOptions) => Plugin[];
81
78
  //#endregion
package/dist/index.mjs CHANGED
@@ -231,7 +231,6 @@ const bamboocssCss = (options) => {
231
231
  await builder.emit();
232
232
  builder.extract();
233
233
  await new Promise((settle) => setImmediate(settle));
234
- if (builder.context?.config.polyfill) throw new Error("bamboocss: the cascade-layer polyfill is incompatible with compiled atomic styles. The polyfill removes the utility-layer boundary required for safe atom reachability and renaming.");
235
234
  if (builder.context) {
236
235
  session.utilityLayer = builder.context.config.layers?.utilities ?? "utilities";
237
236
  session.extractedFiles.clear();
@@ -542,6 +541,15 @@ const bamboocssCss = (options) => {
542
541
  //#endregion
543
542
  //#region src/plugin.ts
544
543
  const DEFAULT_EXTENSIONS = /\.(?:[cm]?[jt]sx?)$/;
544
+ const SFC_EXTENSIONS = /\.(?:vue|svelte|astro)$/i;
545
+ /**
546
+ * Framework script submodules. Vue spells `lang.ts` as a bare query key; Svelte uses
547
+ * `lang=ts`; both set `type=script`. The compiler must see that JS, not the wrapping SFC —
548
+ * folding the SFC uses parser:before offsets that do not match the file Vite emits.
549
+ */
550
+ const SFC_SCRIPT_QUERY = /[?&](?:type=script(?:&|$)|lang\.tsx?(?:&|$)|lang=tsx?(?:&|$)|lang\.jsx?(?:&|$))/i;
551
+ const SFC_JSX_QUERY = /[?&](?:lang\.tsx|lang=tsx|lang\.jsx|lang=jsx)(?:&|$)/i;
552
+ const SFC_SCRIPT_TAG = /<script[\s>/]/i;
545
553
  const NODE_MODULES = /node_modules/;
546
554
  const TRANSFORM_META_KEY = "bamboocss:transform";
547
555
  const TRANSFORM_ARTIFACT_VERSION = 3;
@@ -589,7 +597,30 @@ const shouldTransform = (id) => {
589
597
  const [filePath] = id.split("?");
590
598
  if (!filePath) return false;
591
599
  if (NODE_MODULES.test(filePath)) return false;
592
- return DEFAULT_EXTENSIONS.test(filePath);
600
+ return DEFAULT_EXTENSIONS.test(filePath) || SFC_EXTENSIONS.test(filePath);
601
+ };
602
+ /**
603
+ * Path ts-morph should parse for this transform.
604
+ *
605
+ * A `.vue` / `.svelte` / `.astro` id is either a raw SFC (skip — offsets would not match), a
606
+ * `type=script` submodule, or the framework's compiled JS stored under the SFC path. The last
607
+ * two are JavaScript or TypeScript: parsing them as the SFC would run `parser:before` and fold
608
+ * the wrong bytes. A sibling `.ts`/`.tsx` path preserves JSX parsing and skips those hooks.
609
+ *
610
+ * Returns `null` when the module is still a raw SFC and must be left to the framework plugin.
611
+ * Astro frontmatter is `---`, not `<script>`, so a tag check alone would parse the template.
612
+ */
613
+ const compilerParsePath = (id, code) => {
614
+ const [filePath, query = ""] = id.split("?");
615
+ if (!filePath) return null;
616
+ if (!SFC_EXTENSIONS.test(filePath)) return filePath;
617
+ const normalizedQuery = `?${query}`;
618
+ if (SFC_SCRIPT_QUERY.test(normalizedQuery)) return `${filePath}.__bamboo__.${SFC_JSX_QUERY.test(normalizedQuery) ? "tsx" : "ts"}`;
619
+ const trimmed = code.trimStart();
620
+ if (/\.astro$/i.test(filePath) && (trimmed.startsWith("---") || trimmed.startsWith("<"))) return null;
621
+ if (SFC_SCRIPT_TAG.test(code) || /<(?:template|style)[\s>/]/i.test(code)) return null;
622
+ if (trimmed.startsWith("<")) return null;
623
+ return `${filePath}.__bamboo__.ts`;
593
624
  };
594
625
  /**
595
626
  * Is this file part of the generated `styled-system` rather than the user's source?
@@ -633,15 +664,12 @@ const formatSkipped = (id, skipped) => {
633
664
  /**
634
665
  * Vite integration for Bamboo CSS.
635
666
  *
636
- * Two plugins, because they do unrelated jobs on different schedules. The first emits the
637
- * stylesheet as a virtual module and runs in dev and build alike that is the integration,
638
- * and nothing styles without it. The second compiles every Bamboo source call in both dev
639
- * and build; there is no runtime styling fallback.
640
- *
641
- * The compiler runs with `enforce: 'pre'` so it sees module source as close as possible to what
642
- * the CSS extractor reads off disk. A plugin that rewrites style calls before bamboo
643
- * sees them would otherwise make the two disagree, and a folded class could end up
644
- * with no matching rule.
667
+ * Three plugins. The first emits the stylesheet as a virtual module. The second compiles
668
+ * JavaScript and TypeScript with `enforce: 'pre'` so it sees source close to what the CSS
669
+ * extractor reads off disk. The third compiles Vue, Svelte and Astro with `enforce: 'post'`
670
+ * so it folds the framework's compiled JavaScript — a `pre` hook that skipped the raw SFC
671
+ * would never run again on the same id. Script submodules (`type=script`) are SFC paths and
672
+ * therefore fold in the post plugin, after the framework has extracted them.
645
673
  */
646
674
  const bamboocss = (options = {}) => {
647
675
  const { configPath, cwd, reportSkipped = false, reportSummary = true, maxRecipeStates, pruneCss = true } = options;
@@ -789,6 +817,7 @@ const bamboocss = (options = {}) => {
789
817
  dependenciesByModule: /* @__PURE__ */ new Map(),
790
818
  filesByModule: /* @__PURE__ */ new Map(),
791
819
  foldSignatures: /* @__PURE__ */ new Map(),
820
+ foldInputsByModule: /* @__PURE__ */ new Map(),
792
821
  recipeConfigCache: /* @__PURE__ */ new Map(),
793
822
  transformedModulesThisRun: /* @__PURE__ */ new Set(),
794
823
  unchangedFolds: /* @__PURE__ */ new Map(),
@@ -802,6 +831,7 @@ const bamboocss = (options = {}) => {
802
831
  dependenciesByModule: new Map([...state.dependenciesByModule].map(([moduleId, dependencies]) => [moduleId, new Set(dependencies)])),
803
832
  filesByModule: new Map(state.filesByModule),
804
833
  foldSignatures: new Map(state.foldSignatures),
834
+ foldInputsByModule: new Map(state.foldInputsByModule),
805
835
  recipeConfigCache: new Map(state.recipeConfigCache),
806
836
  transformedModulesThisRun: new Set(state.transformedModulesThisRun),
807
837
  unchangedFolds: new Map(state.unchangedFolds),
@@ -1218,7 +1248,10 @@ const bamboocss = (options = {}) => {
1218
1248
  state.transformArtifactsByModule.set(moduleId, artifact);
1219
1249
  recordFoldDependencies(state, moduleId, file, artifact.dependencies);
1220
1250
  if (artifact.signature) state.foldSignatures.set(moduleId, artifact.signature);
1221
- else state.foldSignatures.delete(moduleId);
1251
+ else {
1252
+ state.foldSignatures.delete(moduleId);
1253
+ state.foldInputsByModule.delete(moduleId);
1254
+ }
1222
1255
  };
1223
1256
  /**
1224
1257
  * Replay transform metadata for modules Rollup reused from its cache.
@@ -1297,7 +1330,9 @@ const bamboocss = (options = {}) => {
1297
1330
  const signature = state.foldSignatures.get(dependent);
1298
1331
  if (!signature || !ctx || !foldSourceImpl || !runtimeCss || !styleCompiler) return false;
1299
1332
  try {
1300
- const code = readFileSync(signature.path, "utf8");
1333
+ const retained = state.foldInputsByModule.get(dependent);
1334
+ const code = retained?.input === signature.input ? retained.code : readFileSync(signature.path, "utf8");
1335
+ const parsePath = retained?.input === signature.input ? retained.parsePath : signature.path;
1301
1336
  const inputDigest = digest(code);
1302
1337
  if (inputDigest !== signature.input) return false;
1303
1338
  /**
@@ -1323,20 +1358,20 @@ const bamboocss = (options = {}) => {
1323
1358
  }
1324
1359
  let raw;
1325
1360
  let parserDependencies;
1326
- const memoKey = foldMemoKey(signature.path, inputDigest);
1361
+ const memoKey = foldMemoKey(parsePath, inputDigest);
1327
1362
  const memoized = foldMemoByContent.get(memoKey);
1328
1363
  if (memoized) {
1329
1364
  raw = memoized.result;
1330
1365
  parserDependencies = memoized.parserDependencies;
1331
1366
  } else {
1332
- const sourceFile = ctx.project.addSourceFile(signature.path, code);
1333
- const parserResult = ctx.project.parseSourceFile(signature.path);
1367
+ const sourceFile = ctx.project.addSourceFile(parsePath, code);
1368
+ const parserResult = ctx.project.parseSourceFile(parsePath);
1334
1369
  if (!parserResult) return false;
1335
1370
  raw = foldSourceImpl({
1336
1371
  ctx,
1337
1372
  code,
1338
1373
  parserResult,
1339
- filePath: signature.path,
1374
+ filePath: parsePath,
1340
1375
  runtimeCss,
1341
1376
  styleCompiler,
1342
1377
  maxRecipeStates,
@@ -1353,7 +1388,7 @@ const bamboocss = (options = {}) => {
1353
1388
  reportedSurvivors: false
1354
1389
  });
1355
1390
  }
1356
- const result = withResolutionClosure(signature.path, raw, parserDependencies, state.dependenciesByModule.get(dependent));
1391
+ const result = withResolutionClosure(parsePath, raw, parserDependencies, state.dependenciesByModule.get(dependent));
1357
1392
  const unchanged = digest(result.code) === signature.output;
1358
1393
  /**
1359
1394
  * Edges re-recorded exactly on the way to suppressing a module. A changed provisional
@@ -1708,6 +1743,7 @@ const bamboocss = (options = {}) => {
1708
1743
  const [filePath] = id.split("?");
1709
1744
  if (!filePath) return;
1710
1745
  for (const state of transformStateByEnvironment.values()) state.recipeConfigCache.clear();
1746
+ if (SFC_EXTENSIONS.test(filePath)) return;
1711
1747
  if (change.event === "delete") {
1712
1748
  ctx.project.removeSourceFile(filePath);
1713
1749
  const deleted = normalizeFsPath(filePath);
@@ -1715,6 +1751,7 @@ const bamboocss = (options = {}) => {
1715
1751
  if (normalizeFsPath(moduleFile) !== deleted) continue;
1716
1752
  recordFoldDependencies(state, moduleId, moduleFile, []);
1717
1753
  state.foldSignatures.delete(moduleId);
1754
+ state.foldInputsByModule.delete(moduleId);
1718
1755
  state.transformArtifactsByModule.delete(moduleId);
1719
1756
  state.filesByModule.delete(moduleId);
1720
1757
  }
@@ -1813,136 +1850,7 @@ const bamboocss = (options = {}) => {
1813
1850
  return foldDependentModules(environmentState(this), file, modules, legacy.moduleGraph);
1814
1851
  },
1815
1852
  async transform(code, id) {
1816
- if (!shouldTransform(id)) return null;
1817
- try {
1818
- await ensureCompilerState();
1819
- } catch (error) {
1820
- throw asError(error, "failed to initialize the bamboo compiler");
1821
- }
1822
- if (!ctx || !foldSourceImpl || !runtimeCss || !styleCompiler) return null;
1823
- const [filePath] = id.split("?");
1824
- if (isGeneratedOutput(filePath, ctx)) return null;
1825
- const state = environmentState(this);
1826
- state.transformedModulesThisRun.add(id);
1827
- let inputDigest;
1828
- const previousSignature = state.foldSignatures.get(id);
1829
- const previousDependencies = previousSignature && previousSignature.input === (inputDigest ??= digest(code)) ? state.dependenciesByModule.get(id) : void 0;
1830
- let result;
1831
- try {
1832
- const memoKey = command === "serve" ? foldMemoKey(filePath, inputDigest ??= digest(code)) : void 0;
1833
- const memoized = memoKey ? foldMemoByContent.get(memoKey) : void 0;
1834
- let valueReads = [];
1835
- if (memoized?.reportedSurvivors) {
1836
- valueReads = memoized.valueReads;
1837
- result = withResolutionClosure(filePath, memoized.result, memoized.parserDependencies, previousDependencies);
1838
- } else {
1839
- const sourceFile = ctx.project.addSourceFile(filePath, code);
1840
- const parserResult = ctx.project.parseSourceFile(filePath);
1841
- if (!parserResult) {
1842
- state.transformArtifactsByModule.delete(id);
1843
- recordFoldDependencies(state, id, filePath, []);
1844
- state.foldSignatures.delete(id);
1845
- return null;
1846
- }
1847
- const folded = foldSourceImpl({
1848
- ctx,
1849
- code,
1850
- parserResult,
1851
- filePath,
1852
- runtimeCss,
1853
- styleCompiler,
1854
- maxRecipeStates,
1855
- parseModule: (path) => ctx?.project.parseSourceFile(path),
1856
- recipeConfigCache: state.recipeConfigCache,
1857
- reportSurvivors: true,
1858
- sourceFile
1859
- });
1860
- const parserDependencies = parserResult.getDependencies();
1861
- valueReads = parserResult.getExportReads?.() ?? [];
1862
- if (memoKey) foldMemoByContent.set(memoKey, {
1863
- result: folded,
1864
- parserDependencies,
1865
- valueReads,
1866
- reportedSurvivors: true
1867
- });
1868
- result = withResolutionClosure(filePath, folded, parserDependencies, previousDependencies);
1869
- }
1870
- state.exportReadsByModule.set(id, [...valueReads.map((read) => ({
1871
- kind: "value",
1872
- ...read
1873
- })), ...result.exportReads]);
1874
- } catch (error) {
1875
- logger.caughtError("vite:transform", `Failed to compile ${filePath}`, error);
1876
- const previousDependencies = [...state.dependenciesByModule.get(id) ?? []];
1877
- applyTransformArtifact(state, sealTransformArtifact(environmentName(this), {
1878
- version: TRANSFORM_ARTIFACT_VERSION,
1879
- moduleId: id,
1880
- file: filePath,
1881
- folded: 0,
1882
- skipped: [["compile-failed", 1]],
1883
- survivors: [{
1884
- line: 1,
1885
- name: "compiler",
1886
- reason: "compile-failed"
1887
- }],
1888
- transformedFile: false,
1889
- classNames: [],
1890
- dependencies: previousDependencies
1891
- }), id, environmentName(this));
1892
- state.foldSignatures.delete(id);
1893
- if (command === "serve") throw asError(error, `failed to compile ${filePath}`);
1894
- return null;
1895
- }
1896
- const skippedHere = /* @__PURE__ */ new Map();
1897
- for (const entry of result.skipped) skippedHere.set(entry.reason, (skippedHere.get(entry.reason) ?? 0) + 1);
1898
- const survivorsHere = [];
1899
- for (const entry of result.skipped) {
1900
- if (entry.reason === "not-imported" || entry.reason === "overlapping") continue;
1901
- if (entry.name === "cx" && entry.reason === "dynamic") continue;
1902
- survivorsHere.push({
1903
- line: lineAt(code, entry.start),
1904
- name: entry.name,
1905
- reason: entry.reason
1906
- });
1907
- }
1908
- const artifact = sealTransformArtifact(environmentName(this), {
1909
- version: TRANSFORM_ARTIFACT_VERSION,
1910
- moduleId: id,
1911
- file: filePath,
1912
- folded: result.folded.length,
1913
- skipped: [...skippedHere],
1914
- survivors: survivorsHere,
1915
- transformedFile: result.folded.some((entry) => entry.kind === "class" || entry.kind === "slots"),
1916
- classNames: [...new Set(result.folded.flatMap((entry) => entry.classNames))],
1917
- dependencies: [...result.dependencies],
1918
- ...result.dependencies.length ? { signature: {
1919
- input: inputDigest ??= digest(code),
1920
- output: digest(result.code),
1921
- path: filePath
1922
- } } : {}
1923
- });
1924
- applyTransformArtifact(state, artifact, id, environmentName(this));
1925
- if (reportSkipped && result.skipped.length) logger.info("vite:transform", formatSkipped(filePath, result.skipped));
1926
- for (const dependency of result.dependencies) this.addWatchFile?.(dependency);
1927
- if (command === "serve" && artifact.survivors.length) {
1928
- state.foldSignatures.delete(id);
1929
- throw createSurvivorError(artifact.survivors.map((survivor) => ({
1930
- file: filePath,
1931
- ...survivor
1932
- })));
1933
- }
1934
- const meta = { [TRANSFORM_META_KEY]: artifact };
1935
- if (!result.folded.length) return typeof this.getModuleInfo === "function" ? {
1936
- code,
1937
- map: null,
1938
- meta
1939
- } : null;
1940
- logger.debug("vite:transform", `Compiled ${result.folded.length} call(s) in ${filePath}`);
1941
- return {
1942
- code: result.code,
1943
- map: result.map,
1944
- meta
1945
- };
1853
+ return compileModule.call(this, code, id, false);
1946
1854
  },
1947
1855
  buildEnd(buildError) {
1948
1856
  const environment = environmentName(this);
@@ -1990,6 +1898,160 @@ const bamboocss = (options = {}) => {
1990
1898
  if (state) rollbackEnvironmentGeneration(environment, state);
1991
1899
  }
1992
1900
  };
1901
+ const compilerSfc = {
1902
+ name: "bamboocss:compiler-sfc",
1903
+ enforce: "post",
1904
+ sharedDuringBuild: true,
1905
+ async transform(code, id) {
1906
+ return compileModule.call(this, code, id, true);
1907
+ }
1908
+ };
1909
+ async function compileModule(code, id, sfcOnly) {
1910
+ if (!shouldTransform(id)) return null;
1911
+ const [pathForFilter] = id.split("?");
1912
+ if (!pathForFilter) return null;
1913
+ if (SFC_EXTENSIONS.test(pathForFilter) !== sfcOnly) return null;
1914
+ try {
1915
+ await ensureCompilerState();
1916
+ } catch (error) {
1917
+ throw asError(error, "failed to initialize the bamboo compiler");
1918
+ }
1919
+ if (!ctx || !foldSourceImpl || !runtimeCss || !styleCompiler) return null;
1920
+ const [filePath] = id.split("?");
1921
+ if (isGeneratedOutput(filePath, ctx)) return null;
1922
+ const parsePath = compilerParsePath(id, code);
1923
+ if (parsePath === null) return null;
1924
+ const state = environmentState(this);
1925
+ state.transformedModulesThisRun.add(id);
1926
+ let inputDigest;
1927
+ const previousSignature = state.foldSignatures.get(id);
1928
+ const previousDependencies = previousSignature && previousSignature.input === (inputDigest ??= digest(code)) ? state.dependenciesByModule.get(id) : void 0;
1929
+ let result;
1930
+ try {
1931
+ const memoKey = command === "serve" ? foldMemoKey(parsePath, inputDigest ??= digest(code)) : void 0;
1932
+ const memoized = memoKey ? foldMemoByContent.get(memoKey) : void 0;
1933
+ let valueReads = [];
1934
+ if (memoized?.reportedSurvivors) {
1935
+ valueReads = memoized.valueReads;
1936
+ result = withResolutionClosure(parsePath, memoized.result, memoized.parserDependencies, previousDependencies);
1937
+ } else {
1938
+ const sourceFile = ctx.project.addSourceFile(parsePath, code);
1939
+ const parserResult = ctx.project.parseSourceFile(parsePath);
1940
+ if (!parserResult) {
1941
+ state.transformArtifactsByModule.delete(id);
1942
+ recordFoldDependencies(state, id, filePath, []);
1943
+ state.foldSignatures.delete(id);
1944
+ state.foldInputsByModule.delete(id);
1945
+ return null;
1946
+ }
1947
+ const folded = foldSourceImpl({
1948
+ ctx,
1949
+ code,
1950
+ parserResult,
1951
+ filePath: parsePath,
1952
+ runtimeCss,
1953
+ styleCompiler,
1954
+ maxRecipeStates,
1955
+ parseModule: (path) => ctx?.project.parseSourceFile(path),
1956
+ recipeConfigCache: state.recipeConfigCache,
1957
+ reportSurvivors: true,
1958
+ sourceFile
1959
+ });
1960
+ const parserDependencies = parserResult.getDependencies();
1961
+ valueReads = parserResult.getExportReads?.() ?? [];
1962
+ if (memoKey) foldMemoByContent.set(memoKey, {
1963
+ result: folded,
1964
+ parserDependencies,
1965
+ valueReads,
1966
+ reportedSurvivors: true
1967
+ });
1968
+ result = withResolutionClosure(parsePath, folded, parserDependencies, previousDependencies);
1969
+ }
1970
+ state.exportReadsByModule.set(id, [...valueReads.map((read) => ({
1971
+ kind: "value",
1972
+ ...read
1973
+ })), ...result.exportReads]);
1974
+ } catch (error) {
1975
+ logger.caughtError("vite:transform", `Failed to compile ${filePath}`, error);
1976
+ const previousDependencies = [...state.dependenciesByModule.get(id) ?? []];
1977
+ applyTransformArtifact(state, sealTransformArtifact(environmentName(this), {
1978
+ version: TRANSFORM_ARTIFACT_VERSION,
1979
+ moduleId: id,
1980
+ file: filePath,
1981
+ folded: 0,
1982
+ skipped: [["compile-failed", 1]],
1983
+ survivors: [{
1984
+ line: 1,
1985
+ name: "compiler",
1986
+ reason: "compile-failed"
1987
+ }],
1988
+ transformedFile: false,
1989
+ classNames: [],
1990
+ dependencies: previousDependencies
1991
+ }), id, environmentName(this));
1992
+ state.foldSignatures.delete(id);
1993
+ state.foldInputsByModule.delete(id);
1994
+ if (command === "serve") throw asError(error, `failed to compile ${filePath}`);
1995
+ return null;
1996
+ }
1997
+ const skippedHere = /* @__PURE__ */ new Map();
1998
+ for (const entry of result.skipped) skippedHere.set(entry.reason, (skippedHere.get(entry.reason) ?? 0) + 1);
1999
+ const survivorsHere = [];
2000
+ for (const entry of result.skipped) {
2001
+ if (entry.reason === "not-imported" || entry.reason === "overlapping") continue;
2002
+ if (entry.name === "cx" && entry.reason === "dynamic") continue;
2003
+ survivorsHere.push({
2004
+ line: lineAt(code, entry.start),
2005
+ name: entry.name,
2006
+ reason: entry.reason
2007
+ });
2008
+ }
2009
+ const artifact = sealTransformArtifact(environmentName(this), {
2010
+ version: TRANSFORM_ARTIFACT_VERSION,
2011
+ moduleId: id,
2012
+ file: filePath,
2013
+ folded: result.folded.length,
2014
+ skipped: [...skippedHere],
2015
+ survivors: survivorsHere,
2016
+ transformedFile: result.folded.some((entry) => entry.kind === "class" || entry.kind === "slots"),
2017
+ classNames: [...new Set(result.folded.flatMap((entry) => entry.classNames))],
2018
+ dependencies: [...result.dependencies],
2019
+ ...result.dependencies.length ? { signature: {
2020
+ input: inputDigest ??= digest(code),
2021
+ output: digest(result.code),
2022
+ path: filePath
2023
+ } } : {}
2024
+ });
2025
+ applyTransformArtifact(state, artifact, id, environmentName(this));
2026
+ if (artifact.signature && (command === "serve" || parsePath !== filePath)) state.foldInputsByModule.set(id, {
2027
+ code,
2028
+ input: artifact.signature.input,
2029
+ parsePath
2030
+ });
2031
+ else state.foldInputsByModule.delete(id);
2032
+ if (reportSkipped && result.skipped.length) logger.info("vite:transform", formatSkipped(filePath, result.skipped));
2033
+ for (const dependency of result.dependencies) this.addWatchFile?.(dependency);
2034
+ if (command === "serve" && artifact.survivors.length) {
2035
+ state.foldSignatures.delete(id);
2036
+ state.foldInputsByModule.delete(id);
2037
+ throw createSurvivorError(artifact.survivors.map((survivor) => ({
2038
+ file: filePath,
2039
+ ...survivor
2040
+ })));
2041
+ }
2042
+ const meta = { [TRANSFORM_META_KEY]: artifact };
2043
+ if (!result.folded.length) return typeof this.getModuleInfo === "function" ? {
2044
+ code,
2045
+ map: null,
2046
+ meta
2047
+ } : null;
2048
+ logger.debug("vite:transform", `Compiled ${result.folded.length} call(s) in ${filePath}`);
2049
+ return {
2050
+ code: result.code,
2051
+ map: result.map,
2052
+ meta
2053
+ };
2054
+ }
1993
2055
  const outputWriteObserver = {
1994
2056
  name: "bamboocss:output-write-observer",
1995
2057
  enforce: "pre",
@@ -2022,12 +2084,16 @@ const bamboocss = (options = {}) => {
2022
2084
  }
2023
2085
  }
2024
2086
  };
2025
- return [bamboocssCss({
2026
- configPath,
2027
- cwd,
2028
- session: staticSession,
2029
- pruneCss
2030
- }), compiler];
2087
+ return [
2088
+ bamboocssCss({
2089
+ configPath,
2090
+ cwd,
2091
+ session: staticSession,
2092
+ pruneCss
2093
+ }),
2094
+ compiler,
2095
+ compilerSfc
2096
+ ];
2031
2097
  };
2032
2098
  //#endregion
2033
2099
  export { VIRTUAL_CSS_ID, bamboocss, bamboocss as default };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamboocss/vite",
3
- "version": "1.46.3",
3
+ "version": "1.47.0",
4
4
  "description": "Vite integration for Bamboo CSS",
5
5
  "homepage": "https://bamboocss.com",
6
6
  "license": "MIT",
@@ -42,18 +42,18 @@
42
42
  "postcss": "8.5.26",
43
43
  "postcss-selector-parser": "7.1.5",
44
44
  "ts-morph": "28.0.0",
45
- "@bamboocss/config": "1.46.3",
46
- "@bamboocss/core": "1.46.3",
47
- "@bamboocss/extractor": "1.46.3",
48
- "@bamboocss/logger": "1.46.3",
49
- "@bamboocss/node": "1.46.3",
50
- "@bamboocss/shared": "1.46.3",
51
- "@bamboocss/types": "1.46.3"
45
+ "@bamboocss/core": "1.47.0",
46
+ "@bamboocss/config": "1.47.0",
47
+ "@bamboocss/extractor": "1.47.0",
48
+ "@bamboocss/logger": "1.47.0",
49
+ "@bamboocss/node": "1.47.0",
50
+ "@bamboocss/shared": "1.47.0",
51
+ "@bamboocss/types": "1.47.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@jridgewell/trace-mapping": "^0.3.31",
55
55
  "vite": "7.2.6",
56
- "@bamboocss/fixture": "1.46.3"
56
+ "@bamboocss/fixture": "1.47.0"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "vite": ">=5"