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