@bamboocss/vite 1.46.0 → 1.46.1

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.
Files changed (3) hide show
  1. package/dist/index.cjs +142 -38
  2. package/dist/index.mjs +142 -38
  3. package/package.json +9 -9
package/dist/index.cjs CHANGED
@@ -208,6 +208,23 @@ const bamboocssCss = (options) => {
208
208
  * stylesheet twice over.
209
209
  */
210
210
  let pending;
211
+ /**
212
+ * Which change the current `pending` was generated for, and the validated sheet it produced.
213
+ *
214
+ * Every environment loads the virtual stylesheet — a react-router dev server loads it once
215
+ * for the client graph and once for SSR — and each load used to run a complete extraction
216
+ * and optimization pass to produce byte-identical CSS. The sheet is a function of the source
217
+ * files alone, and the watcher below is the single point every event that can reach it
218
+ * passes through — Vite's own propagation only arrives via the watch edges `load` registers,
219
+ * over the same extracted files the watcher checks. A monotonic counter bumped there is
220
+ * therefore enough to know whether a build already reflects the world a load is asking about.
221
+ *
222
+ * Dev only. A production build has no dev watcher to advance the counter, so serving the
223
+ * memo there would hand `vite build --watch` a stale sheet; builds regenerate per load.
224
+ */
225
+ let changeGeneration = 0;
226
+ let pendingGeneration = -1;
227
+ let servedCss;
211
228
  const build = async () => {
212
229
  const builder = await ensureBuilder();
213
230
  await builder.setup({
@@ -245,6 +262,8 @@ const bamboocssCss = (options) => {
245
262
  return css;
246
263
  };
247
264
  const generate = () => {
265
+ if (command === "serve" && pending && pendingGeneration === changeGeneration) return pending;
266
+ pendingGeneration = changeGeneration;
248
267
  pending = Promise.resolve(pending).catch(() => void 0).then(build);
249
268
  return pending;
250
269
  };
@@ -393,6 +412,11 @@ const bamboocssCss = (options) => {
393
412
  const query = queryOf(id);
394
413
  if (id.slice(0, id.length - query.length) !== RESOLVED_ID) return null;
395
414
  session.cssLoaded = true;
415
+ const generationAtStart = changeGeneration;
416
+ if (command === "serve" && servedCss?.generation === generationAtStart) {
417
+ if (this.addWatchFile) for (const file of extractedSourceFiles()) this.addWatchFile(file);
418
+ return servedCss.css;
419
+ }
396
420
  let css;
397
421
  try {
398
422
  const validateDevCss = command === "serve" ? (await loadCssOutput()).pruneStaticCss : void 0;
@@ -403,6 +427,10 @@ const bamboocssCss = (options) => {
403
427
  } catch (error) {
404
428
  throw asError(error, `failed to generate ${VIRTUAL_CSS_ID}`);
405
429
  }
430
+ if (command === "serve" && generationAtStart === changeGeneration) servedCss = {
431
+ generation: generationAtStart,
432
+ css
433
+ };
406
434
  if (this.addWatchFile) for (const file of extractedSourceFiles()) this.addWatchFile(file);
407
435
  return css;
408
436
  },
@@ -427,6 +455,7 @@ const bamboocssCss = (options) => {
427
455
  if (!ctx) return;
428
456
  const absoluteFile = ctx.runtime.path.abs(ctx.config.cwd, file);
429
457
  if (!session.extractedFiles.has(absoluteFile)) return;
458
+ changeGeneration++;
430
459
  prebuilt = void 0;
431
460
  const mod = server?.moduleGraph.getModuleById(RESOLVED_ID);
432
461
  if (!mod) return;
@@ -694,6 +723,43 @@ const bamboocss = (options = {}) => {
694
723
  return /* @__PURE__ */ new Error(`bamboocss: cached transform metadata for ${JSON.stringify(id)} in the ${JSON.stringify(environment)} environment ${problem}.\n\nBamboo cannot safely rebuild from this entry because cached JavaScript may still name CSS classes whose rules would be dropped. Restart Vite to invalidate its in-memory transform cache. If this persists, clear Vite's cache directory and rebuild.`);
695
724
  };
696
725
  const transformStateByEnvironment = /* @__PURE__ */ new Map();
726
+ /**
727
+ * One fold per file content per change event, shared across environments and hooks.
728
+ *
729
+ * A single edit folds the same bytes repeatedly: `hotUpdate` provisionally re-folds every
730
+ * dependent once per environment to decide what to invalidate, then `transform` folds the
731
+ * edited module for the client graph, again for SSR, and once more for each update a
732
+ * framework re-drives — react-router's server-change trigger calls `reloadModule` per pass.
733
+ * All of them read the same shared ts-morph project under the same config, so the result is
734
+ * a function of the bytes alone and the repeats were pure cost: on the app this was measured
735
+ * on, four transforms of a 46 kB route module per edit, ~10 ms each.
736
+ *
737
+ * `watchChange` clears it, which is the exact validity window: entries are correct until a
738
+ * file event changes what a fold could resolve, and `watchChange` is the one hook Vite calls
739
+ * for every such event before any update work begins. The per-environment resolution closure
740
+ * is deliberately not memoized — `withResolutionClosure` is recomputed per consumer against
741
+ * that environment's own recorded dependencies.
742
+ *
743
+ * Keyed by path *and* content digest, not path alone, because one physical file is served
744
+ * as more than one module shape in the same event — react-router clips a route module down
745
+ * to its route exports for the client graph while SSR gets the full file — and a last-write
746
+ * key would make the two shapes evict each other on every pass.
747
+ *
748
+ * Dev only, like the verdict memo it sits beside: a build transforms each module once per
749
+ * environment with no bracketing events, and holding every module's fold for the length of a
750
+ * build is memory a one-shot pass has no reason to spend.
751
+ */
752
+ const foldMemoByContent = /* @__PURE__ */ new Map();
753
+ const foldMemoKey = (filePath, inputDigest) => `${filePath}\0${inputDigest}`;
754
+ /**
755
+ * The Project resolution walk `withResolutionClosure` runs, memoized per change event.
756
+ *
757
+ * The walk is the dominant per-dependent cost once the fold itself is memoized — it runs
758
+ * once per dependent per environment with identical inputs, since both environments record
759
+ * the same fold dependencies for byte-identical source. Same bracketing as the fold memo:
760
+ * `watchChange` clears it, so no entry outlives the project state it was computed against.
761
+ */
762
+ const resolutionClosureMemo = /* @__PURE__ */ new Map();
697
763
  /** The immutable generation currently occupying each configured output on disk. */
698
764
  const liveOutputSlotsByEnvironment = /* @__PURE__ */ new Map();
699
765
  /** Graphs which passed `buildEnd`, but whose output has not succeeded yet. */
@@ -1231,23 +1297,40 @@ const bamboocss = (options = {}) => {
1231
1297
  if (!signature || !ctx || !foldSourceImpl || !runtimeCss || !styleCompiler) return false;
1232
1298
  try {
1233
1299
  const code = (0, node_fs.readFileSync)(signature.path, "utf8");
1234
- if (digest(code) !== signature.input) return false;
1235
- const sourceFile = ctx.project.addSourceFile(signature.path, code);
1236
- const parserResult = ctx.project.parseSourceFile(signature.path);
1237
- if (!parserResult) return false;
1238
- const result = withResolutionClosure(signature.path, foldSourceImpl({
1239
- ctx,
1240
- code,
1241
- parserResult,
1242
- filePath: signature.path,
1243
- runtimeCss,
1244
- styleCompiler,
1245
- maxRecipeStates,
1246
- parseModule: (path) => ctx?.project.parseSourceFile(path),
1247
- recipeConfigCache: state.recipeConfigCache,
1248
- reportSurvivors: false,
1249
- sourceFile
1250
- }), parserResult.getDependencies(), state.dependenciesByModule.get(dependent));
1300
+ const inputDigest = digest(code);
1301
+ if (inputDigest !== signature.input) return false;
1302
+ let raw;
1303
+ let parserDependencies;
1304
+ const memoKey = foldMemoKey(signature.path, inputDigest);
1305
+ const memoized = foldMemoByContent.get(memoKey);
1306
+ if (memoized) {
1307
+ raw = memoized.result;
1308
+ parserDependencies = memoized.parserDependencies;
1309
+ } else {
1310
+ const sourceFile = ctx.project.addSourceFile(signature.path, code);
1311
+ const parserResult = ctx.project.parseSourceFile(signature.path);
1312
+ if (!parserResult) return false;
1313
+ raw = foldSourceImpl({
1314
+ ctx,
1315
+ code,
1316
+ parserResult,
1317
+ filePath: signature.path,
1318
+ runtimeCss,
1319
+ styleCompiler,
1320
+ maxRecipeStates,
1321
+ parseModule: (path) => ctx?.project.parseSourceFile(path),
1322
+ recipeConfigCache: state.recipeConfigCache,
1323
+ reportSurvivors: false,
1324
+ sourceFile
1325
+ });
1326
+ parserDependencies = parserResult.getDependencies();
1327
+ foldMemoByContent.set(memoKey, {
1328
+ result: raw,
1329
+ parserDependencies,
1330
+ reportedSurvivors: false
1331
+ });
1332
+ }
1333
+ const result = withResolutionClosure(signature.path, raw, parserDependencies, state.dependenciesByModule.get(dependent));
1251
1334
  const unchanged = digest(result.code) === signature.output;
1252
1335
  /**
1253
1336
  * Edges re-recorded exactly on the way to suppressing a module. A changed provisional
@@ -1397,7 +1480,14 @@ const bamboocss = (options = {}) => {
1397
1480
  const targets = new Set(dependencies);
1398
1481
  for (const dependency of previousDependencies ?? []) targets.add(dependency);
1399
1482
  if (!targets.size) return result;
1400
- for (const dependency of ctx.project.getDependencies(filePath, [...targets])) if (!isGeneratedOutput(dependency, ctx)) dependencies.add(dependency);
1483
+ const targetList = [...targets];
1484
+ const closureKey = command === "serve" ? `${filePath}\0${targetList.slice().sort().join("|")}` : void 0;
1485
+ let reachable = closureKey ? resolutionClosureMemo.get(closureKey) : void 0;
1486
+ if (!reachable) {
1487
+ reachable = ctx.project.getDependencies(filePath, targetList);
1488
+ if (closureKey) resolutionClosureMemo.set(closureKey, reachable);
1489
+ }
1490
+ for (const dependency of reachable) if (!isGeneratedOutput(dependency, ctx)) dependencies.add(dependency);
1401
1491
  const expanded = [...dependencies];
1402
1492
  if (expanded.length === result.dependencies.length && expanded.every((dependency, index) => dependency === result.dependencies[index])) return result;
1403
1493
  return {
@@ -1581,6 +1671,8 @@ const bamboocss = (options = {}) => {
1581
1671
  * the parser still holds the file.
1582
1672
  */
1583
1673
  watchChange(id, change) {
1674
+ foldMemoByContent.clear();
1675
+ resolutionClosureMemo.clear();
1584
1676
  for (const state of transformStateByEnvironment.values()) {
1585
1677
  state.unchangedFolds.clear();
1586
1678
  state.changedRun = 0;
@@ -1650,27 +1742,39 @@ const bamboocss = (options = {}) => {
1650
1742
  const previousDependencies = previousSignature && previousSignature.input === (inputDigest ??= digest(code)) ? state.dependenciesByModule.get(id) : void 0;
1651
1743
  let result;
1652
1744
  try {
1653
- const sourceFile = ctx.project.addSourceFile(filePath, code);
1654
- const parserResult = ctx.project.parseSourceFile(filePath);
1655
- if (!parserResult) {
1656
- state.transformArtifactsByModule.delete(id);
1657
- recordFoldDependencies(state, id, filePath, []);
1658
- state.foldSignatures.delete(id);
1659
- return null;
1745
+ const memoKey = command === "serve" ? foldMemoKey(filePath, inputDigest ??= digest(code)) : void 0;
1746
+ const memoized = memoKey ? foldMemoByContent.get(memoKey) : void 0;
1747
+ if (memoized?.reportedSurvivors) result = withResolutionClosure(filePath, memoized.result, memoized.parserDependencies, previousDependencies);
1748
+ else {
1749
+ const sourceFile = ctx.project.addSourceFile(filePath, code);
1750
+ const parserResult = ctx.project.parseSourceFile(filePath);
1751
+ if (!parserResult) {
1752
+ state.transformArtifactsByModule.delete(id);
1753
+ recordFoldDependencies(state, id, filePath, []);
1754
+ state.foldSignatures.delete(id);
1755
+ return null;
1756
+ }
1757
+ const folded = foldSourceImpl({
1758
+ ctx,
1759
+ code,
1760
+ parserResult,
1761
+ filePath,
1762
+ runtimeCss,
1763
+ styleCompiler,
1764
+ maxRecipeStates,
1765
+ parseModule: (path) => ctx?.project.parseSourceFile(path),
1766
+ recipeConfigCache: state.recipeConfigCache,
1767
+ reportSurvivors: true,
1768
+ sourceFile
1769
+ });
1770
+ const parserDependencies = parserResult.getDependencies();
1771
+ if (memoKey) foldMemoByContent.set(memoKey, {
1772
+ result: folded,
1773
+ parserDependencies,
1774
+ reportedSurvivors: true
1775
+ });
1776
+ result = withResolutionClosure(filePath, folded, parserDependencies, previousDependencies);
1660
1777
  }
1661
- result = withResolutionClosure(filePath, foldSourceImpl({
1662
- ctx,
1663
- code,
1664
- parserResult,
1665
- filePath,
1666
- runtimeCss,
1667
- styleCompiler,
1668
- maxRecipeStates,
1669
- parseModule: (path) => ctx?.project.parseSourceFile(path),
1670
- recipeConfigCache: state.recipeConfigCache,
1671
- reportSurvivors: true,
1672
- sourceFile
1673
- }), parserResult.getDependencies(), previousDependencies);
1674
1778
  } catch (error) {
1675
1779
  _bamboocss_logger.logger.caughtError("vite:transform", `Failed to compile ${filePath}`, error);
1676
1780
  const previousDependencies = [...state.dependenciesByModule.get(id) ?? []];
package/dist/index.mjs CHANGED
@@ -203,6 +203,23 @@ const bamboocssCss = (options) => {
203
203
  * stylesheet twice over.
204
204
  */
205
205
  let pending;
206
+ /**
207
+ * Which change the current `pending` was generated for, and the validated sheet it produced.
208
+ *
209
+ * Every environment loads the virtual stylesheet — a react-router dev server loads it once
210
+ * for the client graph and once for SSR — and each load used to run a complete extraction
211
+ * and optimization pass to produce byte-identical CSS. The sheet is a function of the source
212
+ * files alone, and the watcher below is the single point every event that can reach it
213
+ * passes through — Vite's own propagation only arrives via the watch edges `load` registers,
214
+ * over the same extracted files the watcher checks. A monotonic counter bumped there is
215
+ * therefore enough to know whether a build already reflects the world a load is asking about.
216
+ *
217
+ * Dev only. A production build has no dev watcher to advance the counter, so serving the
218
+ * memo there would hand `vite build --watch` a stale sheet; builds regenerate per load.
219
+ */
220
+ let changeGeneration = 0;
221
+ let pendingGeneration = -1;
222
+ let servedCss;
206
223
  const build = async () => {
207
224
  const builder = await ensureBuilder();
208
225
  await builder.setup({
@@ -240,6 +257,8 @@ const bamboocssCss = (options) => {
240
257
  return css;
241
258
  };
242
259
  const generate = () => {
260
+ if (command === "serve" && pending && pendingGeneration === changeGeneration) return pending;
261
+ pendingGeneration = changeGeneration;
243
262
  pending = Promise.resolve(pending).catch(() => void 0).then(build);
244
263
  return pending;
245
264
  };
@@ -388,6 +407,11 @@ const bamboocssCss = (options) => {
388
407
  const query = queryOf(id);
389
408
  if (id.slice(0, id.length - query.length) !== RESOLVED_ID) return null;
390
409
  session.cssLoaded = true;
410
+ const generationAtStart = changeGeneration;
411
+ if (command === "serve" && servedCss?.generation === generationAtStart) {
412
+ if (this.addWatchFile) for (const file of extractedSourceFiles()) this.addWatchFile(file);
413
+ return servedCss.css;
414
+ }
391
415
  let css;
392
416
  try {
393
417
  const validateDevCss = command === "serve" ? (await loadCssOutput()).pruneStaticCss : void 0;
@@ -398,6 +422,10 @@ const bamboocssCss = (options) => {
398
422
  } catch (error) {
399
423
  throw asError(error, `failed to generate ${VIRTUAL_CSS_ID}`);
400
424
  }
425
+ if (command === "serve" && generationAtStart === changeGeneration) servedCss = {
426
+ generation: generationAtStart,
427
+ css
428
+ };
401
429
  if (this.addWatchFile) for (const file of extractedSourceFiles()) this.addWatchFile(file);
402
430
  return css;
403
431
  },
@@ -422,6 +450,7 @@ const bamboocssCss = (options) => {
422
450
  if (!ctx) return;
423
451
  const absoluteFile = ctx.runtime.path.abs(ctx.config.cwd, file);
424
452
  if (!session.extractedFiles.has(absoluteFile)) return;
453
+ changeGeneration++;
425
454
  prebuilt = void 0;
426
455
  const mod = server?.moduleGraph.getModuleById(RESOLVED_ID);
427
456
  if (!mod) return;
@@ -689,6 +718,43 @@ const bamboocss = (options = {}) => {
689
718
  return /* @__PURE__ */ new Error(`bamboocss: cached transform metadata for ${JSON.stringify(id)} in the ${JSON.stringify(environment)} environment ${problem}.\n\nBamboo cannot safely rebuild from this entry because cached JavaScript may still name CSS classes whose rules would be dropped. Restart Vite to invalidate its in-memory transform cache. If this persists, clear Vite's cache directory and rebuild.`);
690
719
  };
691
720
  const transformStateByEnvironment = /* @__PURE__ */ new Map();
721
+ /**
722
+ * One fold per file content per change event, shared across environments and hooks.
723
+ *
724
+ * A single edit folds the same bytes repeatedly: `hotUpdate` provisionally re-folds every
725
+ * dependent once per environment to decide what to invalidate, then `transform` folds the
726
+ * edited module for the client graph, again for SSR, and once more for each update a
727
+ * framework re-drives — react-router's server-change trigger calls `reloadModule` per pass.
728
+ * All of them read the same shared ts-morph project under the same config, so the result is
729
+ * a function of the bytes alone and the repeats were pure cost: on the app this was measured
730
+ * on, four transforms of a 46 kB route module per edit, ~10 ms each.
731
+ *
732
+ * `watchChange` clears it, which is the exact validity window: entries are correct until a
733
+ * file event changes what a fold could resolve, and `watchChange` is the one hook Vite calls
734
+ * for every such event before any update work begins. The per-environment resolution closure
735
+ * is deliberately not memoized — `withResolutionClosure` is recomputed per consumer against
736
+ * that environment's own recorded dependencies.
737
+ *
738
+ * Keyed by path *and* content digest, not path alone, because one physical file is served
739
+ * as more than one module shape in the same event — react-router clips a route module down
740
+ * to its route exports for the client graph while SSR gets the full file — and a last-write
741
+ * key would make the two shapes evict each other on every pass.
742
+ *
743
+ * Dev only, like the verdict memo it sits beside: a build transforms each module once per
744
+ * environment with no bracketing events, and holding every module's fold for the length of a
745
+ * build is memory a one-shot pass has no reason to spend.
746
+ */
747
+ const foldMemoByContent = /* @__PURE__ */ new Map();
748
+ const foldMemoKey = (filePath, inputDigest) => `${filePath}\0${inputDigest}`;
749
+ /**
750
+ * The Project resolution walk `withResolutionClosure` runs, memoized per change event.
751
+ *
752
+ * The walk is the dominant per-dependent cost once the fold itself is memoized — it runs
753
+ * once per dependent per environment with identical inputs, since both environments record
754
+ * the same fold dependencies for byte-identical source. Same bracketing as the fold memo:
755
+ * `watchChange` clears it, so no entry outlives the project state it was computed against.
756
+ */
757
+ const resolutionClosureMemo = /* @__PURE__ */ new Map();
692
758
  /** The immutable generation currently occupying each configured output on disk. */
693
759
  const liveOutputSlotsByEnvironment = /* @__PURE__ */ new Map();
694
760
  /** Graphs which passed `buildEnd`, but whose output has not succeeded yet. */
@@ -1226,23 +1292,40 @@ const bamboocss = (options = {}) => {
1226
1292
  if (!signature || !ctx || !foldSourceImpl || !runtimeCss || !styleCompiler) return false;
1227
1293
  try {
1228
1294
  const code = readFileSync(signature.path, "utf8");
1229
- if (digest(code) !== signature.input) return false;
1230
- const sourceFile = ctx.project.addSourceFile(signature.path, code);
1231
- const parserResult = ctx.project.parseSourceFile(signature.path);
1232
- if (!parserResult) return false;
1233
- const result = withResolutionClosure(signature.path, foldSourceImpl({
1234
- ctx,
1235
- code,
1236
- parserResult,
1237
- filePath: signature.path,
1238
- runtimeCss,
1239
- styleCompiler,
1240
- maxRecipeStates,
1241
- parseModule: (path) => ctx?.project.parseSourceFile(path),
1242
- recipeConfigCache: state.recipeConfigCache,
1243
- reportSurvivors: false,
1244
- sourceFile
1245
- }), parserResult.getDependencies(), state.dependenciesByModule.get(dependent));
1295
+ const inputDigest = digest(code);
1296
+ if (inputDigest !== signature.input) return false;
1297
+ let raw;
1298
+ let parserDependencies;
1299
+ const memoKey = foldMemoKey(signature.path, inputDigest);
1300
+ const memoized = foldMemoByContent.get(memoKey);
1301
+ if (memoized) {
1302
+ raw = memoized.result;
1303
+ parserDependencies = memoized.parserDependencies;
1304
+ } else {
1305
+ const sourceFile = ctx.project.addSourceFile(signature.path, code);
1306
+ const parserResult = ctx.project.parseSourceFile(signature.path);
1307
+ if (!parserResult) return false;
1308
+ raw = foldSourceImpl({
1309
+ ctx,
1310
+ code,
1311
+ parserResult,
1312
+ filePath: signature.path,
1313
+ runtimeCss,
1314
+ styleCompiler,
1315
+ maxRecipeStates,
1316
+ parseModule: (path) => ctx?.project.parseSourceFile(path),
1317
+ recipeConfigCache: state.recipeConfigCache,
1318
+ reportSurvivors: false,
1319
+ sourceFile
1320
+ });
1321
+ parserDependencies = parserResult.getDependencies();
1322
+ foldMemoByContent.set(memoKey, {
1323
+ result: raw,
1324
+ parserDependencies,
1325
+ reportedSurvivors: false
1326
+ });
1327
+ }
1328
+ const result = withResolutionClosure(signature.path, raw, parserDependencies, state.dependenciesByModule.get(dependent));
1246
1329
  const unchanged = digest(result.code) === signature.output;
1247
1330
  /**
1248
1331
  * Edges re-recorded exactly on the way to suppressing a module. A changed provisional
@@ -1392,7 +1475,14 @@ const bamboocss = (options = {}) => {
1392
1475
  const targets = new Set(dependencies);
1393
1476
  for (const dependency of previousDependencies ?? []) targets.add(dependency);
1394
1477
  if (!targets.size) return result;
1395
- for (const dependency of ctx.project.getDependencies(filePath, [...targets])) if (!isGeneratedOutput(dependency, ctx)) dependencies.add(dependency);
1478
+ const targetList = [...targets];
1479
+ const closureKey = command === "serve" ? `${filePath}\0${targetList.slice().sort().join("|")}` : void 0;
1480
+ let reachable = closureKey ? resolutionClosureMemo.get(closureKey) : void 0;
1481
+ if (!reachable) {
1482
+ reachable = ctx.project.getDependencies(filePath, targetList);
1483
+ if (closureKey) resolutionClosureMemo.set(closureKey, reachable);
1484
+ }
1485
+ for (const dependency of reachable) if (!isGeneratedOutput(dependency, ctx)) dependencies.add(dependency);
1396
1486
  const expanded = [...dependencies];
1397
1487
  if (expanded.length === result.dependencies.length && expanded.every((dependency, index) => dependency === result.dependencies[index])) return result;
1398
1488
  return {
@@ -1576,6 +1666,8 @@ const bamboocss = (options = {}) => {
1576
1666
  * the parser still holds the file.
1577
1667
  */
1578
1668
  watchChange(id, change) {
1669
+ foldMemoByContent.clear();
1670
+ resolutionClosureMemo.clear();
1579
1671
  for (const state of transformStateByEnvironment.values()) {
1580
1672
  state.unchangedFolds.clear();
1581
1673
  state.changedRun = 0;
@@ -1645,27 +1737,39 @@ const bamboocss = (options = {}) => {
1645
1737
  const previousDependencies = previousSignature && previousSignature.input === (inputDigest ??= digest(code)) ? state.dependenciesByModule.get(id) : void 0;
1646
1738
  let result;
1647
1739
  try {
1648
- const sourceFile = ctx.project.addSourceFile(filePath, code);
1649
- const parserResult = ctx.project.parseSourceFile(filePath);
1650
- if (!parserResult) {
1651
- state.transformArtifactsByModule.delete(id);
1652
- recordFoldDependencies(state, id, filePath, []);
1653
- state.foldSignatures.delete(id);
1654
- return null;
1740
+ const memoKey = command === "serve" ? foldMemoKey(filePath, inputDigest ??= digest(code)) : void 0;
1741
+ const memoized = memoKey ? foldMemoByContent.get(memoKey) : void 0;
1742
+ if (memoized?.reportedSurvivors) result = withResolutionClosure(filePath, memoized.result, memoized.parserDependencies, previousDependencies);
1743
+ else {
1744
+ const sourceFile = ctx.project.addSourceFile(filePath, code);
1745
+ const parserResult = ctx.project.parseSourceFile(filePath);
1746
+ if (!parserResult) {
1747
+ state.transformArtifactsByModule.delete(id);
1748
+ recordFoldDependencies(state, id, filePath, []);
1749
+ state.foldSignatures.delete(id);
1750
+ return null;
1751
+ }
1752
+ const folded = foldSourceImpl({
1753
+ ctx,
1754
+ code,
1755
+ parserResult,
1756
+ filePath,
1757
+ runtimeCss,
1758
+ styleCompiler,
1759
+ maxRecipeStates,
1760
+ parseModule: (path) => ctx?.project.parseSourceFile(path),
1761
+ recipeConfigCache: state.recipeConfigCache,
1762
+ reportSurvivors: true,
1763
+ sourceFile
1764
+ });
1765
+ const parserDependencies = parserResult.getDependencies();
1766
+ if (memoKey) foldMemoByContent.set(memoKey, {
1767
+ result: folded,
1768
+ parserDependencies,
1769
+ reportedSurvivors: true
1770
+ });
1771
+ result = withResolutionClosure(filePath, folded, parserDependencies, previousDependencies);
1655
1772
  }
1656
- result = withResolutionClosure(filePath, foldSourceImpl({
1657
- ctx,
1658
- code,
1659
- parserResult,
1660
- filePath,
1661
- runtimeCss,
1662
- styleCompiler,
1663
- maxRecipeStates,
1664
- parseModule: (path) => ctx?.project.parseSourceFile(path),
1665
- recipeConfigCache: state.recipeConfigCache,
1666
- reportSurvivors: true,
1667
- sourceFile
1668
- }), parserResult.getDependencies(), previousDependencies);
1669
1773
  } catch (error) {
1670
1774
  logger.caughtError("vite:transform", `Failed to compile ${filePath}`, error);
1671
1775
  const previousDependencies = [...state.dependenciesByModule.get(id) ?? []];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamboocss/vite",
3
- "version": "1.46.0",
3
+ "version": "1.46.1",
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.0",
46
- "@bamboocss/core": "1.46.0",
47
- "@bamboocss/extractor": "1.46.0",
48
- "@bamboocss/logger": "1.46.0",
49
- "@bamboocss/node": "1.46.0",
50
- "@bamboocss/shared": "1.46.0",
51
- "@bamboocss/types": "1.46.0"
45
+ "@bamboocss/config": "1.46.1",
46
+ "@bamboocss/core": "1.46.1",
47
+ "@bamboocss/node": "1.46.1",
48
+ "@bamboocss/shared": "1.46.1",
49
+ "@bamboocss/extractor": "1.46.1",
50
+ "@bamboocss/types": "1.46.1",
51
+ "@bamboocss/logger": "1.46.1"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@jridgewell/trace-mapping": "^0.3.31",
55
55
  "vite": "7.2.6",
56
- "@bamboocss/fixture": "1.46.0"
56
+ "@bamboocss/fixture": "1.46.1"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "vite": ">=5"