@bamboocss/vite 1.46.3 → 1.48.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.
package/dist/index.cjs CHANGED
@@ -48,24 +48,121 @@ const loadCssOutputModule = createLazyCssOutputModule();
48
48
  const createLazyFoldModule = (loadFold = () => Promise.resolve().then(() => require("./fold-module.cjs"))) => createRetryableLazy(loadFold);
49
49
  /** One process-wide fold-module load shared by every plugin instance and Vite environment. */
50
50
  const loadFoldModule = createLazyFoldModule();
51
- /** One Builder per CSS plugin instance, created only when a hook first needs it. */
51
+ /**
52
+ * The one Builder a run compiles against, created only when a hook first needs it.
53
+ *
54
+ * Per host rather than per plugin instance: the compiler and the stylesheet share it now.
55
+ * @see `createCompilationHost`
56
+ */
52
57
  const createLazyBuilder = (loadNode = loadNodeModule) => createRetryableLazy(async () => {
53
58
  const { Builder } = await loadNode();
54
59
  return new Builder();
55
60
  });
56
- /** Build and publish one complete fold state; no caller can observe partial initialization. */
57
- const createLazyCompilerState = (loadContext, loadFold) => createRetryableLazy(async () => {
58
- const [context, fold] = await Promise.all([loadContext(), loadFold()]);
59
- const runtimeCss = fold.createRuntimeCss(context);
60
- const styleCompiler = fold.createStaticStyleSetCompiler(context, runtimeCss);
61
+ //#endregion
62
+ //#region src/compilation-host.ts
63
+ const createCompilationHost = (options = {}) => {
64
+ const { configPath, cwd } = options;
65
+ const loadBuilder = options.loadBuilder ?? createLazyBuilder();
66
+ let command = "build";
67
+ let builder;
68
+ let generation;
69
+ let nextGenerationId = 0;
70
+ /**
71
+ * The setup covering the pass currently open.
72
+ *
73
+ * A cold start reaches this twice — the compiler's `pre` `buildStart`, then the CSS
74
+ * plugin's — for one instant in which nothing can have changed on disk. Sharing one attempt
75
+ * across both is what keeps a project from loading and evaluating its config twice per
76
+ * build. Cleared once a stylesheet pass consumes it, and on any source mutation, so no
77
+ * later pass can be answered by a setup taken before an edit.
78
+ */
79
+ let openSetup;
80
+ let openSetupStale = false;
81
+ let cssPass;
82
+ const settled = async (attempt) => {
83
+ try {
84
+ await attempt;
85
+ } catch {}
86
+ };
87
+ const publish = () => {
88
+ const context = builder.getContextOrThrow();
89
+ if (generation?.context !== context) generation = {
90
+ id: ++nextGenerationId,
91
+ context,
92
+ encoder: context.encoder.clone()
93
+ };
94
+ return generation;
95
+ };
96
+ const runSetup = async () => {
97
+ builder ??= await loadBuilder();
98
+ await builder.setup({
99
+ configPath,
100
+ cwd,
101
+ dev: command === "serve"
102
+ });
103
+ return publish();
104
+ };
105
+ /**
106
+ * The setup covering the pass currently open, started at most once.
107
+ *
108
+ * Started through a resolved promise, so a synchronous throw becomes the same
109
+ * rejected-attempt contract a failed module load has and a later hook can retry it. A
110
+ * source mutation observed while one is in flight does not cancel it — two overlapping
111
+ * `Builder.setup` calls would interleave their change detection — it queues a fresh one
112
+ * behind it.
113
+ */
114
+ const setupOnce = () => {
115
+ const previous = openSetup;
116
+ if (previous && !openSetupStale) return previous;
117
+ openSetupStale = false;
118
+ const attempt = previous ? settled(previous).then(runSetup) : Promise.resolve().then(runSetup);
119
+ openSetup = attempt;
120
+ attempt.catch(() => {
121
+ if (openSetup === attempt) openSetup = void 0;
122
+ });
123
+ return attempt;
124
+ };
61
125
  return {
62
- context,
63
- foldSource: fold.foldSource,
64
- verifyExportReads: fold.verifyExportReads,
65
- runtimeCss,
66
- styleCompiler
126
+ setCommand(next) {
127
+ command = next;
128
+ },
129
+ current: () => generation,
130
+ async ensureGeneration() {
131
+ if (cssPass) await settled(cssPass);
132
+ if (generation) return Promise.resolve(generation);
133
+ return setupOnce();
134
+ },
135
+ isCssPassActive: () => cssPass !== void 0,
136
+ async runCssPass(run) {
137
+ while (cssPass) await settled(cssPass);
138
+ let release;
139
+ cssPass = new Promise((resolve) => {
140
+ release = resolve;
141
+ });
142
+ try {
143
+ const passGeneration = await setupOnce();
144
+ return await run(builder, passGeneration);
145
+ } finally {
146
+ openSetup = void 0;
147
+ openSetupStale = false;
148
+ cssPass = void 0;
149
+ release();
150
+ }
151
+ },
152
+ async runCompilerWork(run) {
153
+ while (cssPass) await settled(cssPass);
154
+ return run();
155
+ },
156
+ reloadSource(filePath) {
157
+ openSetupStale = true;
158
+ builder?.reloadSource(filePath);
159
+ },
160
+ removeSource(filePath) {
161
+ openSetupStale = true;
162
+ builder?.removeSource(filePath);
163
+ }
67
164
  };
68
- });
165
+ };
69
166
  //#endregion
70
167
  //#region src/static-session.ts
71
168
  const createStaticCompilationSession = () => {
@@ -184,14 +281,11 @@ const asError = (error, context) => error instanceof Error ? error : new Error(`
184
281
  * process just wrote, which is a race on any watch rebuild.
185
282
  */
186
283
  const bamboocssCss = (options) => {
187
- const { configPath, cwd, loadCssOutput = loadCssOutputModule, session, pruneCss = true } = options;
284
+ const { configPath, cwd, loadCssOutput = loadCssOutputModule, session, host = createCompilationHost({
285
+ configPath,
286
+ cwd
287
+ }), pruneCss = true } = options;
188
288
  let builder;
189
- const loadBuilder = createLazyBuilder();
190
- const ensureBuilder = async () => {
191
- const loaded = await loadBuilder();
192
- builder = loaded;
193
- return loaded;
194
- };
195
289
  let server;
196
290
  let command = "build";
197
291
  /** The run's own `build` options, for a bundler with no per-environment config. */
@@ -226,35 +320,36 @@ const bamboocssCss = (options) => {
226
320
  let changeGeneration = 0;
227
321
  let pendingGeneration = -1;
228
322
  let servedCss;
229
- const build = async () => {
230
- const builder = await ensureBuilder();
231
- await builder.setup({
232
- configPath,
233
- cwd,
234
- dev: command === "serve"
235
- });
236
- await builder.emit();
237
- builder.extract();
323
+ /**
324
+ * Held by the host for its whole length, rather than only around each mutation.
325
+ *
326
+ * Extraction fills the encoder this sheet is emitted from and `toCss` reads it back, with a
327
+ * deliberate macrotask between them. The compiler shares the AST both halves run against,
328
+ * so a transform folding a module in that window would re-prepare a source the extraction
329
+ * pass has already read and `toCss` has not finished reporting on. The host makes compiler
330
+ * work wait instead; a fold is a few milliseconds and this is the one place correctness
331
+ * depends on it.
332
+ */
333
+ const build = () => host.runCssPass(async (activeBuilder) => {
334
+ builder = activeBuilder;
335
+ await activeBuilder.emit();
336
+ activeBuilder.extract();
238
337
  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
- if (builder.context) {
241
- session.utilityLayer = builder.context.config.layers?.utilities ?? "utilities";
338
+ if (activeBuilder.context) {
339
+ session.utilityLayer = activeBuilder.context.config.layers?.utilities ?? "utilities";
242
340
  session.extractedFiles.clear();
243
341
  for (const file of extractedSourceFiles()) session.extractedFiles.add(file);
244
342
  }
245
343
  let graphAtomHashes;
246
- if (builder.context) {
247
- builder.context.encoder.atomizeObservedRecipes();
248
- graphAtomHashes = new Set(builder.context.encoder.atomic);
344
+ if (activeBuilder.context) {
345
+ activeBuilder.context.encoder.atomizeObservedRecipes();
346
+ graphAtomHashes = new Set(activeBuilder.context.encoder.atomic);
249
347
  }
250
- const css = builder.toCss({
251
- layerParams: true,
252
- includeRecipes: false
253
- });
348
+ const css = activeBuilder.toCss({ layerParams: true });
254
349
  session.prunableClasses.clear();
255
350
  session.viewTransitionClasses.clear();
256
- if (graphAtomHashes && builder.context) {
257
- const decoder = builder.context.decoder.collect(builder.context.encoder);
351
+ if (graphAtomHashes && activeBuilder.context) {
352
+ const decoder = activeBuilder.context.decoder.collect(activeBuilder.context.encoder);
258
353
  for (const atom of decoder.atomic) if (graphAtomHashes.has(atom.hash)) session.prunableClasses.add(atom.className);
259
354
  for (const transition of decoder.view_transitions) {
260
355
  session.viewTransitionClasses.add(transition.className);
@@ -262,7 +357,7 @@ const bamboocssCss = (options) => {
262
357
  }
263
358
  }
264
359
  return css;
265
- };
360
+ });
266
361
  const generate = () => {
267
362
  if (command === "serve" && pending && pendingGeneration === changeGeneration) return pending;
268
363
  pendingGeneration = changeGeneration;
@@ -342,6 +437,7 @@ const bamboocssCss = (options) => {
342
437
  sharedDuringBuild: true,
343
438
  async configResolved(config) {
344
439
  command = config.command;
440
+ host.setCommand(config.command);
345
441
  session.sourcemap = config.build.sourcemap;
346
442
  ssrBuildOptions = {
347
443
  ssr: config.build.ssr,
@@ -355,11 +451,12 @@ const bamboocssCss = (options) => {
355
451
  * edited all afternoon. Nothing watched it: `watch` is the CLI's own watcher, and a
356
452
  * project running `vite dev` never reaches it.
357
453
  *
358
- * A restart rather than re-emitting the stylesheet, because this plugin and the compiler
359
- * hold *separate* contexts and only this one reloads its config. A token *value* edit
360
- * came out right on the next source change, and an edit that changes what compiles
361
- * adding a token, a condition, a utility left the compiler naming classes from the old
362
- * config against a sheet emitted from the new one. Half-updated is worse than stale.
454
+ * A restart rather than re-emitting the stylesheet. The two plugins share one context now,
455
+ * and the compiler re-derives everything it holds when `Builder.setup` replaces it so the
456
+ * half-updated state this used to prevent, with the compiler naming classes from the old
457
+ * config against a sheet emitted from the new one, can no longer happen. What a restart
458
+ * still buys is the rest of the server: a changed `outdir`, a preset that adds an entry
459
+ * point, and every module Vite has already transformed against the previous config.
363
460
  *
364
461
  * Through Vite's own list rather than a watcher of ours. Vite adds these paths to the
365
462
  * files it watches, which is what reaches a config *outside* `root` — a monorepo with one
@@ -547,6 +644,15 @@ const bamboocssCss = (options) => {
547
644
  //#endregion
548
645
  //#region src/plugin.ts
549
646
  const DEFAULT_EXTENSIONS = /\.(?:[cm]?[jt]sx?)$/;
647
+ const SFC_EXTENSIONS = /\.(?:vue|svelte|astro)$/i;
648
+ /**
649
+ * Framework script submodules. Vue spells `lang.ts` as a bare query key; Svelte uses
650
+ * `lang=ts`; both set `type=script`. The compiler must see that JS, not the wrapping SFC —
651
+ * folding the SFC uses parser:before offsets that do not match the file Vite emits.
652
+ */
653
+ const SFC_SCRIPT_QUERY = /[?&](?:type=script(?:&|$)|lang\.tsx?(?:&|$)|lang=tsx?(?:&|$)|lang\.jsx?(?:&|$))/i;
654
+ const SFC_JSX_QUERY = /[?&](?:lang\.tsx|lang=tsx|lang\.jsx|lang=jsx)(?:&|$)/i;
655
+ const SFC_SCRIPT_TAG = /<script[\s>/]/i;
550
656
  const NODE_MODULES = /node_modules/;
551
657
  const TRANSFORM_META_KEY = "bamboocss:transform";
552
658
  const TRANSFORM_ARTIFACT_VERSION = 3;
@@ -594,9 +700,48 @@ const shouldTransform = (id) => {
594
700
  const [filePath] = id.split("?");
595
701
  if (!filePath) return false;
596
702
  if (NODE_MODULES.test(filePath)) return false;
597
- return DEFAULT_EXTENSIONS.test(filePath);
703
+ return DEFAULT_EXTENSIONS.test(filePath) || SFC_EXTENSIONS.test(filePath);
704
+ };
705
+ /**
706
+ * Path ts-morph should parse for this transform.
707
+ *
708
+ * A `.vue` / `.svelte` / `.astro` id is either a raw SFC (skip — offsets would not match), a
709
+ * `type=script` submodule, or the framework's compiled JS stored under the SFC path. The last
710
+ * two are JavaScript or TypeScript: parsing them as the SFC would run `parser:before` and fold
711
+ * the wrong bytes. A sibling `.ts`/`.tsx` path preserves JSX parsing and skips those hooks.
712
+ *
713
+ * Returns `null` when the module is still a raw SFC and must be left to the framework plugin.
714
+ * Astro frontmatter is `---`, not `<script>`, so a tag check alone would parse the template.
715
+ */
716
+ const compilerParsePath = (id, code) => {
717
+ const [filePath, query = ""] = id.split("?");
718
+ if (!filePath) return null;
719
+ if (!SFC_EXTENSIONS.test(filePath)) return filePath;
720
+ const normalizedQuery = `?${query}`;
721
+ if (SFC_SCRIPT_QUERY.test(normalizedQuery)) return `${filePath}.__bamboo__.${SFC_JSX_QUERY.test(normalizedQuery) ? "tsx" : "ts"}`;
722
+ const trimmed = code.trimStart();
723
+ if (/\.astro$/i.test(filePath) && (trimmed.startsWith("---") || trimmed.startsWith("<"))) return null;
724
+ if (SFC_SCRIPT_TAG.test(code) || /<(?:template|style)[\s>/]/i.test(code)) return null;
725
+ if (trimmed.startsWith("<")) return null;
726
+ return `${filePath}.__bamboo__.ts`;
598
727
  };
599
728
  /**
729
+ * Where to park a transform's text when it is not what the shared Project holds for the file.
730
+ *
731
+ * The compiler folds the bundler's view of a module — after every `enforce: 'pre'` plugin
732
+ * before it, and after Vite's own load. The stylesheet pass reads the same file off disk
733
+ * through the same ts-morph Project. When the two texts differ and the compiler writes its
734
+ * own under the file's path, that transform silently becomes the canonical source for the
735
+ * next extraction pass: the CSS would then be generated from a bundler artifact rather than
736
+ * from the checkout. Under a sibling path both readings exist and neither overwrites the
737
+ * other, which is the same reason `compilerParsePath` already does this for SFC submodules.
738
+ *
739
+ * The extension carries JSX-ness across, since it is what ts-morph keys its script kind on:
740
+ * anything but an unambiguously non-JSX `.ts`/`.mts`/`.cts` is parsed as `.tsx`, so a `<div>`
741
+ * in a `.js` file still parses and a `<T>value` assertion in a `.ts` file still means a cast.
742
+ */
743
+ const auxiliaryParsePath = (filePath) => `${filePath}.__bamboo__.${/\.[cm]?ts$/i.test(filePath) ? "ts" : "tsx"}`;
744
+ /**
600
745
  * Is this file part of the generated `styled-system` rather than the user's source?
601
746
  *
602
747
  * Resolved to a path and compared as a prefix, rather than by looking for the outdir's
@@ -638,15 +783,12 @@ const formatSkipped = (id, skipped) => {
638
783
  /**
639
784
  * Vite integration for Bamboo CSS.
640
785
  *
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.
786
+ * Three plugins. The first emits the stylesheet as a virtual module. The second compiles
787
+ * JavaScript and TypeScript with `enforce: 'pre'` so it sees source close to what the CSS
788
+ * extractor reads off disk. The third compiles Vue, Svelte and Astro with `enforce: 'post'`
789
+ * so it folds the framework's compiled JavaScript — a `pre` hook that skipped the raw SFC
790
+ * would never run again on the same id. Script submodules (`type=script`) are SFC paths and
791
+ * therefore fold in the post plugin, after the framework has extracted them.
650
792
  */
651
793
  const bamboocss = (options = {}) => {
652
794
  const { configPath, cwd, reportSkipped = false, reportSummary = true, maxRecipeStates, pruneCss = true } = options;
@@ -654,6 +796,17 @@ const bamboocss = (options = {}) => {
654
796
  if (maxRecipeStates !== void 0 && (!Number.isSafeInteger(maxRecipeStates) || maxRecipeStates < 1)) throw new Error("bamboocss: `maxRecipeStates` must be a positive safe integer.");
655
797
  if ("renameCssAsset" in options) throw new Error("bamboocss: `renameCssAsset` has been replaced by `pruneCss`. Use `pruneCss: false` for what `renameCssAsset: false` did — it always disabled the pruning as well, since pruned bytes under the unpruned sheet's name is what lets a CDN serve a stale stylesheet. The new name says which of the two it is really about.");
656
798
  const staticSession = createStaticCompilationSession();
799
+ /**
800
+ * One Builder, one resolved config, one context and one ts-morph project for the run.
801
+ *
802
+ * Created here rather than by either plugin because both need it and neither may own it:
803
+ * the compiler used to load a second config of its own, which is why a token edit could
804
+ * leave it naming classes from the old one against a sheet emitted from the new one.
805
+ */
806
+ const host = createCompilationHost({
807
+ configPath,
808
+ cwd
809
+ });
657
810
  const transformArtifactIntegrityKey = (0, node_crypto.randomBytes)(32);
658
811
  const serializeTransformArtifact = (environment, artifact) => JSON.stringify([
659
812
  TRANSFORM_META_KEY,
@@ -794,6 +947,7 @@ const bamboocss = (options = {}) => {
794
947
  dependenciesByModule: /* @__PURE__ */ new Map(),
795
948
  filesByModule: /* @__PURE__ */ new Map(),
796
949
  foldSignatures: /* @__PURE__ */ new Map(),
950
+ foldInputsByModule: /* @__PURE__ */ new Map(),
797
951
  recipeConfigCache: /* @__PURE__ */ new Map(),
798
952
  transformedModulesThisRun: /* @__PURE__ */ new Set(),
799
953
  unchangedFolds: /* @__PURE__ */ new Map(),
@@ -807,6 +961,7 @@ const bamboocss = (options = {}) => {
807
961
  dependenciesByModule: new Map([...state.dependenciesByModule].map(([moduleId, dependencies]) => [moduleId, new Set(dependencies)])),
808
962
  filesByModule: new Map(state.filesByModule),
809
963
  foldSignatures: new Map(state.foldSignatures),
964
+ foldInputsByModule: new Map(state.foldInputsByModule),
810
965
  recipeConfigCache: new Map(state.recipeConfigCache),
811
966
  transformedModulesThisRun: new Set(state.transformedModulesThisRun),
812
967
  unchangedFolds: new Map(state.unchangedFolds),
@@ -1223,7 +1378,10 @@ const bamboocss = (options = {}) => {
1223
1378
  state.transformArtifactsByModule.set(moduleId, artifact);
1224
1379
  recordFoldDependencies(state, moduleId, file, artifact.dependencies);
1225
1380
  if (artifact.signature) state.foldSignatures.set(moduleId, artifact.signature);
1226
- else state.foldSignatures.delete(moduleId);
1381
+ else {
1382
+ state.foldSignatures.delete(moduleId);
1383
+ state.foldInputsByModule.delete(moduleId);
1384
+ }
1227
1385
  };
1228
1386
  /**
1229
1387
  * Replay transform metadata for modules Rollup reused from its cache.
@@ -1293,6 +1451,8 @@ const bamboocss = (options = {}) => {
1293
1451
  const foldOutputUnchanged = (state, dependent, changedFile) => {
1294
1452
  const memoized = state.unchangedFolds.get(dependent);
1295
1453
  if (memoized !== void 0) return memoized;
1454
+ if (host.isCssPassActive()) return false;
1455
+ if (!compilerStateIsCurrent()) return false;
1296
1456
  const unchanged = state.changedRun < CHANGED_RUN_LIMIT && refoldMatchesSignature(state, dependent, changedFile);
1297
1457
  state.changedRun = unchanged ? 0 : state.changedRun + 1;
1298
1458
  state.unchangedFolds.set(dependent, unchanged);
@@ -1302,7 +1462,10 @@ const bamboocss = (options = {}) => {
1302
1462
  const signature = state.foldSignatures.get(dependent);
1303
1463
  if (!signature || !ctx || !foldSourceImpl || !runtimeCss || !styleCompiler) return false;
1304
1464
  try {
1305
- const code = (0, node_fs.readFileSync)(signature.path, "utf8");
1465
+ const retained = state.foldInputsByModule.get(dependent);
1466
+ const code = retained?.input === signature.input ? retained.code : (0, node_fs.readFileSync)(signature.path, "utf8");
1467
+ const requestedParsePath = retained?.input === signature.input ? retained.parsePath : signature.path;
1468
+ const parsePath = compilerSourcePath(signature.path, requestedParsePath, code);
1306
1469
  const inputDigest = digest(code);
1307
1470
  if (inputDigest !== signature.input) return false;
1308
1471
  /**
@@ -1319,7 +1482,7 @@ const bamboocss = (options = {}) => {
1319
1482
  */
1320
1483
  const reads = state.exportReadsByModule.get(dependent);
1321
1484
  if (reads?.length && verifyExportReadsImpl) {
1322
- const { verdict, crossings } = verifyExportReadsImpl(ctx, (path) => ctx?.project.parseSourceFile(path), reads, normalizeFsPath(changedFile), verifyDigestMemo);
1485
+ const { verdict, crossings } = verifyExportReadsImpl(ctx, parseForCompiler, reads, normalizeFsPath(changedFile), verifyDigestMemo);
1323
1486
  if (verdict === "unchanged") {
1324
1487
  recordFoldDependencies(state, dependent, signature.path, [...state.dependenciesByModule.get(dependent) ?? [], ...crossings]);
1325
1488
  return true;
@@ -1328,24 +1491,25 @@ const bamboocss = (options = {}) => {
1328
1491
  }
1329
1492
  let raw;
1330
1493
  let parserDependencies;
1331
- const memoKey = foldMemoKey(signature.path, inputDigest);
1494
+ const memoKey = foldMemoKey(parsePath, inputDigest);
1332
1495
  const memoized = foldMemoByContent.get(memoKey);
1333
1496
  if (memoized) {
1334
1497
  raw = memoized.result;
1335
1498
  parserDependencies = memoized.parserDependencies;
1336
1499
  } else {
1337
- const sourceFile = ctx.project.addSourceFile(signature.path, code);
1338
- const parserResult = ctx.project.parseSourceFile(signature.path);
1500
+ const sourceFile = addCompilerSource(signature.path, parsePath, code);
1501
+ if (!sourceFile) return false;
1502
+ const parserResult = parseForCompiler(parsePath, requestedParsePath === signature.path ? signature.path : parsePath);
1339
1503
  if (!parserResult) return false;
1340
1504
  raw = foldSourceImpl({
1341
1505
  ctx,
1342
1506
  code,
1343
1507
  parserResult,
1344
- filePath: signature.path,
1508
+ filePath: parsePath,
1345
1509
  runtimeCss,
1346
1510
  styleCompiler,
1347
1511
  maxRecipeStates,
1348
- parseModule: (path) => ctx?.project.parseSourceFile(path),
1512
+ parseModule: parseForCompiler,
1349
1513
  recipeConfigCache: state.recipeConfigCache,
1350
1514
  reportSurvivors: false,
1351
1515
  sourceFile
@@ -1358,7 +1522,7 @@ const bamboocss = (options = {}) => {
1358
1522
  reportedSurvivors: false
1359
1523
  });
1360
1524
  }
1361
- const result = withResolutionClosure(signature.path, raw, parserDependencies, state.dependenciesByModule.get(dependent));
1525
+ const result = withResolutionClosure(parsePath, raw, parserDependencies, state.dependenciesByModule.get(dependent));
1362
1526
  const unchanged = digest(result.code) === signature.output;
1363
1527
  /**
1364
1528
  * Edges re-recorded exactly on the way to suppressing a module. A changed provisional
@@ -1485,6 +1649,8 @@ const bamboocss = (options = {}) => {
1485
1649
  return [...modules, ...added];
1486
1650
  };
1487
1651
  let ctx;
1652
+ /** The compiler's private parse sink for `ctx`. @see `CompilationGeneration.encoder` */
1653
+ let parseEncoder;
1488
1654
  let foldSourceImpl;
1489
1655
  let verifyExportReadsImpl;
1490
1656
  let runtimeCss;
@@ -1524,25 +1690,97 @@ const bamboocss = (options = {}) => {
1524
1690
  dependencies: expanded
1525
1691
  };
1526
1692
  };
1527
- const loadContext = createRetryableLazy(async () => {
1528
- const { loadConfigAndCreateContext } = await loadNodeModule();
1529
- return loadConfigAndCreateContext({
1530
- configPath,
1531
- cwd,
1532
- dev: command === "serve"
1533
- });
1534
- });
1535
- const loadCompilerState = createLazyCompilerState(loadContext, loadFoldModule);
1693
+ /** Which context the published derivations below were built from. */
1694
+ let derivedGeneration = -1;
1695
+ /** Compiler-only sibling ASTs retained for each physical module. */
1696
+ const auxiliarySourcesByFile = /* @__PURE__ */ new Map();
1697
+ /**
1698
+ * Whether the compiler state below still describes the context the host is on.
1699
+ *
1700
+ * Only `ensureCompilerState` re-derives, and only an awaited hook may call it — so the two
1701
+ * synchronous entry points, the speculative prefold and the unchanged-dependent check, can
1702
+ * be reached after a stylesheet pass has published a config reload they have not seen. Both
1703
+ * decline rather than fold against a runtime `css` from the previous config.
1704
+ */
1705
+ const compilerStateIsCurrent = () => {
1706
+ const current = host.current();
1707
+ return current !== void 0 && current.id === derivedGeneration;
1708
+ };
1536
1709
  const ensureContext = async () => {
1537
- ctx = await loadContext();
1710
+ ctx = (await host.ensureGeneration()).context;
1538
1711
  };
1712
+ /**
1713
+ * Load the fold chunk and derive everything that depends on the resolved context.
1714
+ *
1715
+ * Keyed on context *identity* rather than derived once. `Builder.setup` replaces its context
1716
+ * on a config reload, and the runtime `css`, the style-set compiler and the parse sink are
1717
+ * all closures over the previous one — a stale `runtimeCss` names classes from the old
1718
+ * config while the stylesheet is emitted from the new one, and nothing downstream can see
1719
+ * the difference. Re-derivation is cheap; both factories are a handful of bound methods.
1720
+ *
1721
+ * Published as a set, and only once every part of the attempt has succeeded, so a failed
1722
+ * chunk load leaves no half-compiler visible to HMR.
1723
+ */
1539
1724
  const ensureCompilerState = async () => {
1540
- const loaded = await loadCompilerState();
1541
- ctx = loaded.context;
1542
- foldSourceImpl = loaded.foldSource;
1543
- verifyExportReadsImpl = loaded.verifyExportReads;
1544
- runtimeCss = loaded.runtimeCss;
1545
- styleCompiler = loaded.styleCompiler;
1725
+ const [initialGeneration, fold] = await Promise.all([host.ensureGeneration(), loadFoldModule()]);
1726
+ const currentGeneration = await host.ensureGeneration();
1727
+ const generation = currentGeneration.id === initialGeneration.id ? initialGeneration : currentGeneration;
1728
+ if (derivedGeneration === generation.id && foldSourceImpl) {
1729
+ ctx = generation.context;
1730
+ return;
1731
+ }
1732
+ const derivedRuntimeCss = fold.createRuntimeCss(generation.context);
1733
+ const derivedStyleCompiler = fold.createStaticStyleSetCompiler(generation.context, derivedRuntimeCss);
1734
+ ctx = generation.context;
1735
+ parseEncoder = generation.encoder;
1736
+ foldSourceImpl = fold.foldSource;
1737
+ verifyExportReadsImpl = fold.verifyExportReads;
1738
+ runtimeCss = derivedRuntimeCss;
1739
+ styleCompiler = derivedStyleCompiler;
1740
+ derivedGeneration = generation.id;
1741
+ auxiliarySourcesByFile.clear();
1742
+ };
1743
+ /**
1744
+ * Parse a module for the compiler, never for the stylesheet.
1745
+ *
1746
+ * Every compiler parse goes through here so the private encoder cannot be forgotten at one
1747
+ * call site. Forgetting it at any of them puts that module's reading into the encoder the
1748
+ * sheet is emitted from, under a `parse` owner nothing retracts.
1749
+ */
1750
+ const parseForCompiler = (filePath, hookFilePath = filePath) => ctx?.project.parseSourceFile(filePath, parseEncoder, { hookFilePath });
1751
+ /**
1752
+ * Where the compiler may hold `code` for `filePath` without displacing the checkout.
1753
+ *
1754
+ * The file's own path exactly when the shared Project already holds these bytes — then
1755
+ * `addSourceFile` is a lookup and there is nothing to displace. @see `auxiliaryParsePath`
1756
+ */
1757
+ const compilerSourcePath = (filePath, requested, code) => {
1758
+ if (requested !== filePath) return requested;
1759
+ return ctx?.project.getSourceFile(filePath)?.getFullText() === code ? filePath : auxiliaryParsePath(filePath);
1760
+ };
1761
+ /** Add one compiler-owned source without letting it displace or outlive its physical file. */
1762
+ const addCompilerSource = (filePath, parsePath, code) => {
1763
+ if (!ctx) return;
1764
+ const auxiliary = parsePath !== filePath;
1765
+ const sourceFile = ctx.project.addSourceFile(parsePath, code, { auxiliary });
1766
+ if (auxiliary) {
1767
+ const physical = normalizeFsPath(filePath);
1768
+ const paths = auxiliarySourcesByFile.get(physical) ?? /* @__PURE__ */ new Set();
1769
+ paths.add(parsePath);
1770
+ auxiliarySourcesByFile.set(physical, paths);
1771
+ }
1772
+ return sourceFile;
1773
+ };
1774
+ /** Release compiler encoder owners and sibling ASTs when their physical module disappears. */
1775
+ const releaseCompilerSources = (filePath) => {
1776
+ if (!ctx) return;
1777
+ parseEncoder?.releaseFile(filePath);
1778
+ const physical = normalizeFsPath(filePath);
1779
+ for (const auxiliary of auxiliarySourcesByFile.get(physical) ?? []) {
1780
+ parseEncoder?.releaseFile(auxiliary);
1781
+ ctx.project.removeSourceFile(auxiliary);
1782
+ }
1783
+ auxiliarySourcesByFile.delete(physical);
1546
1784
  };
1547
1785
  const outputFinalizerTag = (value) => {
1548
1786
  if (!value || typeof value !== "object") return void 0;
@@ -1659,6 +1897,7 @@ const bamboocss = (options = {}) => {
1659
1897
  },
1660
1898
  configResolved(config) {
1661
1899
  command = config.command;
1900
+ host.setCommand(config.command);
1662
1901
  defaultEmitAssets = config.build?.emitAssets ?? (!config.build?.ssr || config.build?.ssrEmitAssets === true);
1663
1902
  const plugins = config.plugins;
1664
1903
  if (plugins) {
@@ -1714,18 +1953,21 @@ const bamboocss = (options = {}) => {
1714
1953
  if (!filePath) return;
1715
1954
  for (const state of transformStateByEnvironment.values()) state.recipeConfigCache.clear();
1716
1955
  if (change.event === "delete") {
1717
- ctx.project.removeSourceFile(filePath);
1956
+ host.removeSource(filePath);
1957
+ releaseCompilerSources(filePath);
1718
1958
  const deleted = normalizeFsPath(filePath);
1719
1959
  for (const state of transformStateByEnvironment.values()) for (const [moduleId, moduleFile] of [...state.filesByModule]) {
1720
1960
  if (normalizeFsPath(moduleFile) !== deleted) continue;
1721
1961
  recordFoldDependencies(state, moduleId, moduleFile, []);
1722
1962
  state.foldSignatures.delete(moduleId);
1963
+ state.foldInputsByModule.delete(moduleId);
1723
1964
  state.transformArtifactsByModule.delete(moduleId);
1724
1965
  state.filesByModule.delete(moduleId);
1725
1966
  }
1726
1967
  return;
1727
1968
  }
1728
- ctx.project.reloadSourceFile(filePath);
1969
+ if (SFC_EXTENSIONS.test(filePath)) return;
1970
+ host.reloadSource(filePath);
1729
1971
  /**
1730
1972
  * Fold the edited file before the browser asks for it.
1731
1973
  *
@@ -1744,12 +1986,14 @@ const bamboocss = (options = {}) => {
1744
1986
  */
1745
1987
  if (command === "serve") setImmediate(() => {
1746
1988
  if (!ctx || !foldSourceImpl || !runtimeCss || !styleCompiler) return;
1989
+ if (host.isCssPassActive() || !compilerStateIsCurrent()) return;
1747
1990
  try {
1748
1991
  const code = (0, node_fs.readFileSync)(filePath, "utf8");
1749
1992
  const memoKey = foldMemoKey(filePath, digest(code));
1750
1993
  if (foldMemoByContent.has(memoKey)) return;
1751
- const sourceFile = ctx.project.addSourceFile(filePath, code);
1752
- const parserResult = ctx.project.parseSourceFile(filePath);
1994
+ const sourceFile = addCompilerSource(filePath, filePath, code);
1995
+ if (!sourceFile) return;
1996
+ const parserResult = parseForCompiler(filePath);
1753
1997
  if (!parserResult) return;
1754
1998
  const folded = foldSourceImpl({
1755
1999
  ctx,
@@ -1759,7 +2003,7 @@ const bamboocss = (options = {}) => {
1759
2003
  runtimeCss,
1760
2004
  styleCompiler,
1761
2005
  maxRecipeStates,
1762
- parseModule: (path) => ctx?.project.parseSourceFile(path),
2006
+ parseModule: parseForCompiler,
1763
2007
  recipeConfigCache: transformStateByEnvironment.get("client")?.recipeConfigCache ?? /* @__PURE__ */ new Map(),
1764
2008
  reportSurvivors: true,
1765
2009
  sourceFile
@@ -1818,136 +2062,7 @@ const bamboocss = (options = {}) => {
1818
2062
  return foldDependentModules(environmentState(this), file, modules, legacy.moduleGraph);
1819
2063
  },
1820
2064
  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
- };
2065
+ return compileModule.call(this, code, id, false);
1951
2066
  },
1952
2067
  buildEnd(buildError) {
1953
2068
  const environment = environmentName(this);
@@ -1995,6 +2110,176 @@ const bamboocss = (options = {}) => {
1995
2110
  if (state) rollbackEnvironmentGeneration(environment, state);
1996
2111
  }
1997
2112
  };
2113
+ const compilerSfc = {
2114
+ name: "bamboocss:compiler-sfc",
2115
+ enforce: "post",
2116
+ sharedDuringBuild: true,
2117
+ async transform(code, id) {
2118
+ return compileModule.call(this, code, id, true);
2119
+ }
2120
+ };
2121
+ async function compileModule(code, id, sfcOnly) {
2122
+ if (!shouldTransform(id)) return null;
2123
+ const [pathForFilter] = id.split("?");
2124
+ if (!pathForFilter) return null;
2125
+ if (SFC_EXTENSIONS.test(pathForFilter) !== sfcOnly) return null;
2126
+ try {
2127
+ await ensureCompilerState();
2128
+ } catch (error) {
2129
+ throw asError(error, "failed to initialize the bamboo compiler");
2130
+ }
2131
+ if (!ctx || !foldSourceImpl || !runtimeCss || !styleCompiler) return null;
2132
+ const [filePath] = id.split("?");
2133
+ if (isGeneratedOutput(filePath, ctx)) return null;
2134
+ const requestedParsePath = compilerParsePath(id, code);
2135
+ if (requestedParsePath === null) return null;
2136
+ const state = environmentState(this);
2137
+ state.transformedModulesThisRun.add(id);
2138
+ let inputDigest;
2139
+ const previousSignature = state.foldSignatures.get(id);
2140
+ const previousDependencies = previousSignature && previousSignature.input === (inputDigest ??= digest(code)) ? state.dependenciesByModule.get(id) : void 0;
2141
+ let result;
2142
+ try {
2143
+ /**
2144
+ * One serialized region, holding every read and every mutation of the shared AST.
2145
+ *
2146
+ * Synchronous throughout, which is what makes waiting for the stylesheet pass once at
2147
+ * the top sufficient: nothing can open a pass between the wait and the work, because
2148
+ * nothing else runs. The fold is CPU-bound anyway, so there is no await to give up.
2149
+ */
2150
+ const compiled = await host.runCompilerWork(() => {
2151
+ if (!ctx || !foldSourceImpl || !runtimeCss || !styleCompiler) return null;
2152
+ const path = compilerSourcePath(filePath, requestedParsePath, code);
2153
+ const memoKey = command === "serve" ? foldMemoKey(path, inputDigest ??= digest(code)) : void 0;
2154
+ const memoized = memoKey ? foldMemoByContent.get(memoKey) : void 0;
2155
+ if (memoized?.reportedSurvivors) return {
2156
+ valueReads: memoized.valueReads,
2157
+ result: withResolutionClosure(path, memoized.result, memoized.parserDependencies, previousDependencies)
2158
+ };
2159
+ const sourceFile = addCompilerSource(filePath, path, code);
2160
+ if (!sourceFile) return null;
2161
+ const parserResult = parseForCompiler(path, requestedParsePath === filePath ? filePath : path);
2162
+ if (!parserResult) return { unparsed: true };
2163
+ const folded = foldSourceImpl({
2164
+ ctx,
2165
+ code,
2166
+ parserResult,
2167
+ filePath: path,
2168
+ runtimeCss,
2169
+ styleCompiler,
2170
+ maxRecipeStates,
2171
+ parseModule: parseForCompiler,
2172
+ recipeConfigCache: state.recipeConfigCache,
2173
+ reportSurvivors: true,
2174
+ sourceFile
2175
+ });
2176
+ const parserDependencies = parserResult.getDependencies();
2177
+ const valueReads = parserResult.getExportReads?.() ?? [];
2178
+ if (memoKey) foldMemoByContent.set(memoKey, {
2179
+ result: folded,
2180
+ parserDependencies,
2181
+ valueReads,
2182
+ reportedSurvivors: true
2183
+ });
2184
+ return {
2185
+ valueReads,
2186
+ result: withResolutionClosure(path, folded, parserDependencies, previousDependencies)
2187
+ };
2188
+ });
2189
+ if (!compiled) return null;
2190
+ if ("unparsed" in compiled) {
2191
+ state.transformArtifactsByModule.delete(id);
2192
+ recordFoldDependencies(state, id, filePath, []);
2193
+ state.foldSignatures.delete(id);
2194
+ state.foldInputsByModule.delete(id);
2195
+ return null;
2196
+ }
2197
+ result = compiled.result;
2198
+ state.exportReadsByModule.set(id, [...compiled.valueReads.map((read) => ({
2199
+ kind: "value",
2200
+ ...read
2201
+ })), ...result.exportReads]);
2202
+ } catch (error) {
2203
+ _bamboocss_logger.logger.caughtError("vite:transform", `Failed to compile ${filePath}`, error);
2204
+ const previousDependencies = [...state.dependenciesByModule.get(id) ?? []];
2205
+ applyTransformArtifact(state, sealTransformArtifact(environmentName(this), {
2206
+ version: TRANSFORM_ARTIFACT_VERSION,
2207
+ moduleId: id,
2208
+ file: filePath,
2209
+ folded: 0,
2210
+ skipped: [["compile-failed", 1]],
2211
+ survivors: [{
2212
+ line: 1,
2213
+ name: "compiler",
2214
+ reason: "compile-failed"
2215
+ }],
2216
+ transformedFile: false,
2217
+ classNames: [],
2218
+ dependencies: previousDependencies
2219
+ }), id, environmentName(this));
2220
+ state.foldSignatures.delete(id);
2221
+ state.foldInputsByModule.delete(id);
2222
+ if (command === "serve") throw asError(error, `failed to compile ${filePath}`);
2223
+ return null;
2224
+ }
2225
+ const skippedHere = /* @__PURE__ */ new Map();
2226
+ for (const entry of result.skipped) skippedHere.set(entry.reason, (skippedHere.get(entry.reason) ?? 0) + 1);
2227
+ const survivorsHere = [];
2228
+ for (const entry of result.skipped) {
2229
+ if (entry.reason === "not-imported" || entry.reason === "overlapping") continue;
2230
+ if (entry.name === "cx" && entry.reason === "dynamic") continue;
2231
+ survivorsHere.push({
2232
+ line: lineAt(code, entry.start),
2233
+ name: entry.name,
2234
+ reason: entry.reason
2235
+ });
2236
+ }
2237
+ const artifact = sealTransformArtifact(environmentName(this), {
2238
+ version: TRANSFORM_ARTIFACT_VERSION,
2239
+ moduleId: id,
2240
+ file: filePath,
2241
+ folded: result.folded.length,
2242
+ skipped: [...skippedHere],
2243
+ survivors: survivorsHere,
2244
+ transformedFile: result.folded.some((entry) => entry.kind === "class" || entry.kind === "slots"),
2245
+ classNames: [...new Set(result.folded.flatMap((entry) => entry.classNames))],
2246
+ dependencies: [...result.dependencies],
2247
+ ...result.dependencies.length ? { signature: {
2248
+ input: inputDigest ??= digest(code),
2249
+ output: digest(result.code),
2250
+ path: filePath
2251
+ } } : {}
2252
+ });
2253
+ applyTransformArtifact(state, artifact, id, environmentName(this));
2254
+ if (artifact.signature && (command === "serve" || requestedParsePath !== filePath)) state.foldInputsByModule.set(id, {
2255
+ code,
2256
+ input: artifact.signature.input,
2257
+ parsePath: requestedParsePath
2258
+ });
2259
+ else state.foldInputsByModule.delete(id);
2260
+ if (reportSkipped && result.skipped.length) _bamboocss_logger.logger.info("vite:transform", formatSkipped(filePath, result.skipped));
2261
+ for (const dependency of result.dependencies) this.addWatchFile?.(dependency);
2262
+ if (command === "serve" && artifact.survivors.length) {
2263
+ state.foldSignatures.delete(id);
2264
+ state.foldInputsByModule.delete(id);
2265
+ throw createSurvivorError(artifact.survivors.map((survivor) => ({
2266
+ file: filePath,
2267
+ ...survivor
2268
+ })));
2269
+ }
2270
+ const meta = { [TRANSFORM_META_KEY]: artifact };
2271
+ if (!result.folded.length) return typeof this.getModuleInfo === "function" ? {
2272
+ code,
2273
+ map: null,
2274
+ meta
2275
+ } : null;
2276
+ _bamboocss_logger.logger.debug("vite:transform", `Compiled ${result.folded.length} call(s) in ${filePath}`);
2277
+ return {
2278
+ code: result.code,
2279
+ map: result.map,
2280
+ meta
2281
+ };
2282
+ }
1998
2283
  const outputWriteObserver = {
1999
2284
  name: "bamboocss:output-write-observer",
2000
2285
  enforce: "pre",
@@ -2027,12 +2312,17 @@ const bamboocss = (options = {}) => {
2027
2312
  }
2028
2313
  }
2029
2314
  };
2030
- return [bamboocssCss({
2031
- configPath,
2032
- cwd,
2033
- session: staticSession,
2034
- pruneCss
2035
- }), compiler];
2315
+ return [
2316
+ bamboocssCss({
2317
+ configPath,
2318
+ cwd,
2319
+ host,
2320
+ session: staticSession,
2321
+ pruneCss
2322
+ }),
2323
+ compiler,
2324
+ compilerSfc
2325
+ ];
2036
2326
  };
2037
2327
  //#endregion
2038
2328
  exports.VIRTUAL_CSS_ID = VIRTUAL_CSS_ID;