@bamboocss/vite 1.37.12 → 1.37.13
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 +122 -39
- package/dist/index.mjs +122 -39
- package/package.json +9 -9
package/dist/index.cjs
CHANGED
|
@@ -94,6 +94,7 @@ const pruneStaticCss = (css, session, { prune = true } = {}) => {
|
|
|
94
94
|
if (!className || !prunable.has(className) || used.has(className)) return;
|
|
95
95
|
candidate.remove();
|
|
96
96
|
removedAny = true;
|
|
97
|
+
session.prunedClasses.add(className);
|
|
97
98
|
});
|
|
98
99
|
}).processSync(rule.selector);
|
|
99
100
|
} catch {
|
|
@@ -154,6 +155,56 @@ const pruneStaticCss = (css, session, { prune = true } = {}) => {
|
|
|
154
155
|
return root.toString();
|
|
155
156
|
};
|
|
156
157
|
//#endregion
|
|
158
|
+
//#region src/static-session.ts
|
|
159
|
+
const createStaticCompilationSession = () => {
|
|
160
|
+
const session = {
|
|
161
|
+
utilityLayer: "utilities",
|
|
162
|
+
sourcemap: false,
|
|
163
|
+
cssLoaded: false,
|
|
164
|
+
transformedFiles: /* @__PURE__ */ new Set(),
|
|
165
|
+
extractedFiles: /* @__PURE__ */ new Set(),
|
|
166
|
+
prunableClasses: /* @__PURE__ */ new Set(),
|
|
167
|
+
viewTransitionClasses: /* @__PURE__ */ new Set(),
|
|
168
|
+
usedClasses: /* @__PURE__ */ new Set(),
|
|
169
|
+
expectedEnvironments: void 0,
|
|
170
|
+
startedEnvironments: /* @__PURE__ */ new Set(),
|
|
171
|
+
prunedClasses: /* @__PURE__ */ new Set(),
|
|
172
|
+
markClassUsed(className) {
|
|
173
|
+
for (const token of className.split(" ")) {
|
|
174
|
+
if (!token) continue;
|
|
175
|
+
session.usedClasses.add(token.includes("\\") ? token : (0, _bamboocss_shared.esc)(token));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
return session;
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* Environments this run intends to build that have not been compiled yet.
|
|
183
|
+
*
|
|
184
|
+
* Empty means everything the run will contribute has been contributed, which is the condition
|
|
185
|
+
* every whole-run judgement here waits for: pruning the stylesheet against reachability, and
|
|
186
|
+
* the two guards that ask whether the compiled modules and the extraction graph agree. Each of
|
|
187
|
+
* those is false about a build in progress and true only about a finished one.
|
|
188
|
+
*
|
|
189
|
+
* Empty is also the answer for a single-environment build, where nothing announced an
|
|
190
|
+
* environment list because there is only ever one — so that path is unchanged.
|
|
191
|
+
*
|
|
192
|
+
* An environment a run declares and then never builds leaves this permanently non-empty, and
|
|
193
|
+
* those judgements are skipped for the run. Every one of them errs towards shipping more CSS
|
|
194
|
+
* or asserting less, so that is the safe direction to be wrong in.
|
|
195
|
+
*/
|
|
196
|
+
const remainingEnvironments = (session) => [...session.expectedEnvironments ?? []].filter((name) => !session.startedEnvironments.has(name));
|
|
197
|
+
const resetStaticCompilationSession = (session) => {
|
|
198
|
+
session.cssLoaded = false;
|
|
199
|
+
session.transformedFiles.clear();
|
|
200
|
+
session.extractedFiles.clear();
|
|
201
|
+
session.prunableClasses.clear();
|
|
202
|
+
session.viewTransitionClasses.clear();
|
|
203
|
+
session.usedClasses.clear();
|
|
204
|
+
session.startedEnvironments.clear();
|
|
205
|
+
session.prunedClasses.clear();
|
|
206
|
+
};
|
|
207
|
+
//#endregion
|
|
157
208
|
//#region src/css.ts
|
|
158
209
|
/**
|
|
159
210
|
* What a project imports to get the stylesheet.
|
|
@@ -262,11 +313,15 @@ const carriesGeneratedCss = (output) => output.type === "asset" && output.fileNa
|
|
|
262
313
|
* hash is not cosmetic: it makes late graph reachability cache-safe.
|
|
263
314
|
*/
|
|
264
315
|
const optimizeStaticCssAssets = (bundle, session, options = {}) => {
|
|
265
|
-
const { rename = true } = options;
|
|
316
|
+
const { rename = true, prune = true, sourcemap = session.sourcemap } = options;
|
|
317
|
+
/** Assets in this bundle that carry the generated stylesheet, pruned or not. */
|
|
318
|
+
let sheets = 0;
|
|
266
319
|
for (const output of Object.values(bundle)) {
|
|
267
320
|
if (!carriesGeneratedCss(output)) continue;
|
|
268
321
|
const source = typeof output.source === "string" ? output.source : Buffer.from(output.source).toString();
|
|
269
322
|
if (!source.includes("--made-with-bamboo")) continue;
|
|
323
|
+
sheets++;
|
|
324
|
+
if (!prune) continue;
|
|
270
325
|
const optimized = pruneStaticCss(source, session);
|
|
271
326
|
output.source = optimized;
|
|
272
327
|
if (optimized === source) continue;
|
|
@@ -279,8 +334,9 @@ const optimizeStaticCssAssets = (bundle, session, options = {}) => {
|
|
|
279
334
|
if (bundle[nextName] && bundle[nextName] !== output) throw new Error(`bamboocss: final CSS asset name collision at ${JSON.stringify(nextName)}.`);
|
|
280
335
|
const previous = output.fileName;
|
|
281
336
|
output.fileName = nextName;
|
|
282
|
-
replaceAssetReferences(bundle, previous, nextName,
|
|
337
|
+
replaceAssetReferences(bundle, previous, nextName, sourcemap);
|
|
283
338
|
}
|
|
339
|
+
return { sheets };
|
|
284
340
|
};
|
|
285
341
|
/**
|
|
286
342
|
* Serve bamboo's stylesheet as a virtual module, in dev and in build.
|
|
@@ -356,9 +412,32 @@ const bamboocssCss = (options) => {
|
|
|
356
412
|
};
|
|
357
413
|
return {
|
|
358
414
|
name: "bamboocss:css",
|
|
415
|
+
/**
|
|
416
|
+
* One instance for every environment of a build, rather than one per environment.
|
|
417
|
+
*
|
|
418
|
+
* Vite re-reads the config file once per environment, so a project that lists this plugin
|
|
419
|
+
* in `vite.config.ts` — every project — got a *fresh* instance per environment, each with
|
|
420
|
+
* its own compilation session, context and ts-morph project. Nothing an environment
|
|
421
|
+
* established could then be seen by the next one, which is the premise the reachability
|
|
422
|
+
* accounting below is built on, and it also meant the whole config load and extraction
|
|
423
|
+
* happened once per environment.
|
|
424
|
+
*/
|
|
425
|
+
sharedDuringBuild: true,
|
|
359
426
|
configResolved(config) {
|
|
360
427
|
command = config.command;
|
|
361
428
|
session.sourcemap = config.build.sourcemap;
|
|
429
|
+
if (config.builder && config.environments) session.expectedEnvironments = new Set(Object.keys(config.environments));
|
|
430
|
+
},
|
|
431
|
+
/**
|
|
432
|
+
* The definitive environment list, for a run that reaches `builder.buildApp()` without
|
|
433
|
+
* configuring `builder` — the shape `vite build` itself takes, where exactly one
|
|
434
|
+
* environment is set up and pruning is therefore safe.
|
|
435
|
+
*/
|
|
436
|
+
buildApp: {
|
|
437
|
+
order: "pre",
|
|
438
|
+
async handler(builder) {
|
|
439
|
+
session.expectedEnvironments = new Set(Object.keys(builder.environments));
|
|
440
|
+
}
|
|
362
441
|
},
|
|
363
442
|
resolveId(id) {
|
|
364
443
|
const query = queryOf(id);
|
|
@@ -398,8 +477,34 @@ const bamboocssCss = (options) => {
|
|
|
398
477
|
generateBundle: {
|
|
399
478
|
order: "post",
|
|
400
479
|
handler(_, bundle) {
|
|
401
|
-
|
|
402
|
-
|
|
480
|
+
const environment = this.environment;
|
|
481
|
+
/**
|
|
482
|
+
* Every environment of this run has already had its modules compiled.
|
|
483
|
+
*
|
|
484
|
+
* Reachability is what pruning removes rules against, and it is only complete once
|
|
485
|
+
* nothing is left to contribute to it. The stylesheet is emitted and finalized by the
|
|
486
|
+
* environment that *imports* it, which in an SSR app is the client — and the client
|
|
487
|
+
* builds first, before the server environment has transformed a single module. Pruning
|
|
488
|
+
* there deletes every rule for a class only the server graph reaches, and the pages
|
|
489
|
+
* link the pruned copy: one project lost 39% of its atoms that way, presenting as
|
|
490
|
+
* rarely-used classes such as `md:{display:inline-block}` silently not applying.
|
|
491
|
+
*
|
|
492
|
+
* So the full extracted stylesheet ships instead, exactly as `renameCssAsset: false`
|
|
493
|
+
* already does. Being the last environment is not the common case — frameworks build
|
|
494
|
+
* the client first — but it is the only one where the answer is knowable, and a
|
|
495
|
+
* framework that builds its server bundle first does get pruned output.
|
|
496
|
+
*/
|
|
497
|
+
const pending = remainingEnvironments(session);
|
|
498
|
+
const { sheets } = optimizeStaticCssAssets(bundle, session, {
|
|
499
|
+
rename: renameCssAsset,
|
|
500
|
+
prune: pending.length === 0,
|
|
501
|
+
sourcemap: environment?.config?.build?.sourcemap
|
|
502
|
+
});
|
|
503
|
+
if (sheets && pending.length) _bamboocss_logger.logger.info("vite", `Reachability pruning skipped: the stylesheet is emitted by the ${JSON.stringify(environment?.name ?? "default")} environment, and ${(0, _bamboocss_shared.truncateList)(pending, {
|
|
504
|
+
unit: "environment",
|
|
505
|
+
separator: ", "
|
|
506
|
+
})} ${pending.length === 1 ? "has" : "have"} not been compiled in this run. The full extracted stylesheet ships — nothing is missing from it.`);
|
|
507
|
+
if (!servedEnvironments.has(environment?.name ?? "default")) return;
|
|
403
508
|
if (!session.transformedFiles.size) return;
|
|
404
509
|
if (!Object.values(bundle).some((output) => {
|
|
405
510
|
if (!carriesGeneratedCss(output)) return false;
|
|
@@ -2784,35 +2889,6 @@ const createStaticStyleSetCompiler = (ctx, runtimeCss, allocateClassString = (cl
|
|
|
2784
2889
|
};
|
|
2785
2890
|
};
|
|
2786
2891
|
//#endregion
|
|
2787
|
-
//#region src/static-session.ts
|
|
2788
|
-
const createStaticCompilationSession = () => {
|
|
2789
|
-
const session = {
|
|
2790
|
-
utilityLayer: "utilities",
|
|
2791
|
-
sourcemap: false,
|
|
2792
|
-
cssLoaded: false,
|
|
2793
|
-
transformedFiles: /* @__PURE__ */ new Set(),
|
|
2794
|
-
extractedFiles: /* @__PURE__ */ new Set(),
|
|
2795
|
-
prunableClasses: /* @__PURE__ */ new Set(),
|
|
2796
|
-
viewTransitionClasses: /* @__PURE__ */ new Set(),
|
|
2797
|
-
usedClasses: /* @__PURE__ */ new Set(),
|
|
2798
|
-
markClassUsed(className) {
|
|
2799
|
-
for (const token of className.split(" ")) {
|
|
2800
|
-
if (!token) continue;
|
|
2801
|
-
session.usedClasses.add(token.includes("\\") ? token : (0, _bamboocss_shared.esc)(token));
|
|
2802
|
-
}
|
|
2803
|
-
}
|
|
2804
|
-
};
|
|
2805
|
-
return session;
|
|
2806
|
-
};
|
|
2807
|
-
const resetStaticCompilationSession = (session) => {
|
|
2808
|
-
session.cssLoaded = false;
|
|
2809
|
-
session.transformedFiles.clear();
|
|
2810
|
-
session.extractedFiles.clear();
|
|
2811
|
-
session.prunableClasses.clear();
|
|
2812
|
-
session.viewTransitionClasses.clear();
|
|
2813
|
-
session.usedClasses.clear();
|
|
2814
|
-
};
|
|
2815
|
-
//#endregion
|
|
2816
2892
|
//#region src/plugin.ts
|
|
2817
2893
|
const DEFAULT_EXTENSIONS = /\.(?:[cm]?[jt]sx?)$/;
|
|
2818
2894
|
const NODE_MODULES = /node_modules/;
|
|
@@ -2866,8 +2942,6 @@ const formatSkipped = (id, skipped) => {
|
|
|
2866
2942
|
const bamboocss = (options = {}) => {
|
|
2867
2943
|
const { configPath, cwd, reportSkipped = false, reportSummary = true, maxRecipeStates, renameCssAsset = true } = options;
|
|
2868
2944
|
if (maxRecipeStates !== void 0 && (!Number.isSafeInteger(maxRecipeStates) || maxRecipeStates < 1)) throw new Error("bamboocss: `maxRecipeStates` must be a positive safe integer.");
|
|
2869
|
-
/** Environments whose `buildStart` has run in the build currently in progress. */
|
|
2870
|
-
const seenEnvironments = /* @__PURE__ */ new Set();
|
|
2871
2945
|
/** Totals across the build, for the summary. */
|
|
2872
2946
|
const totals = {
|
|
2873
2947
|
folded: 0,
|
|
@@ -2941,13 +3015,14 @@ const bamboocss = (options = {}) => {
|
|
|
2941
3015
|
}), {
|
|
2942
3016
|
name: "bamboocss:compiler",
|
|
2943
3017
|
enforce: "pre",
|
|
3018
|
+
/** See the same declaration on the css plugin: one instance per build, not per environment. */
|
|
3019
|
+
sharedDuringBuild: true,
|
|
2944
3020
|
configResolved(config) {
|
|
2945
3021
|
command = config.command;
|
|
2946
3022
|
},
|
|
2947
3023
|
async buildStart() {
|
|
2948
3024
|
const environment = this.environment?.name ?? "default";
|
|
2949
|
-
if (
|
|
2950
|
-
seenEnvironments.clear();
|
|
3025
|
+
if (staticSession.startedEnvironments.has(environment)) {
|
|
2951
3026
|
totals.folded = 0;
|
|
2952
3027
|
totals.files = 0;
|
|
2953
3028
|
totals.filesWithFolds = 0;
|
|
@@ -2956,7 +3031,7 @@ const bamboocss = (options = {}) => {
|
|
|
2956
3031
|
recipeConfigCache.clear();
|
|
2957
3032
|
resetStaticCompilationSession(staticSession);
|
|
2958
3033
|
}
|
|
2959
|
-
|
|
3034
|
+
staticSession.startedEnvironments.add(environment);
|
|
2960
3035
|
try {
|
|
2961
3036
|
await ensureContext();
|
|
2962
3037
|
} catch (error) {
|
|
@@ -3067,7 +3142,15 @@ const bamboocss = (options = {}) => {
|
|
|
3067
3142
|
buildEnd() {
|
|
3068
3143
|
const survivors = allSurvivors();
|
|
3069
3144
|
if (survivors.length) throw createSurvivorError(survivors);
|
|
3070
|
-
|
|
3145
|
+
const lost = [...staticSession.usedClasses].filter((className) => staticSession.prunedClasses.has(bare(className)));
|
|
3146
|
+
if (lost.length) {
|
|
3147
|
+
const environment = this.environment?.name ?? "default";
|
|
3148
|
+
throw new Error(`bamboocss: ${lost.length} class(es) compiled in the ${JSON.stringify(environment)} environment were already pruned out of a stylesheet emitted by an earlier one. Elements carrying them would render unstyled.\n\n${(0, _bamboocss_shared.truncateList)(lost.map((className) => ` ${className}`), {
|
|
3149
|
+
unit: "class",
|
|
3150
|
+
separator: "\n"
|
|
3151
|
+
})}\n\nThe stylesheet is finalized by the environment that imports it, so pruning it is only safe once every environment has been compiled. This build did not say how many there would be: it called \`builder.build(environment)\` directly. Run it through \`vite build\`, call \`builder.buildApp()\`, or set \`builder: {}\` in the Vite config so the environments are known before the first one builds. \`bamboocss({ renameCssAsset: false })\` also turns pruning off entirely.`);
|
|
3152
|
+
}
|
|
3153
|
+
if (typeof this.getModuleInfo === "function" && !remainingEnvironments(staticSession).length) {
|
|
3071
3154
|
if (!staticSession.cssLoaded) throw new Error(`bamboocss: compiled class values were produced, but ${JSON.stringify(VIRTUAL_CSS_ID)} was not imported. Add \`import ${JSON.stringify(VIRTUAL_CSS_ID)}\` once, from a JavaScript or TypeScript module in the application entry graph.\n\nIt has to be a JS import. \`@import\` from a stylesheet does not reach it: the id names a virtual module resolved by this plugin, and Vite resolves CSS \`@import\` before plugin resolution, so it fails as an unresolvable path. A project that ships one preloaded stylesheet imports this from its entry module instead, and lets Vite emit the CSS asset.`);
|
|
3072
3155
|
const outsideExtraction = [...staticSession.transformedFiles].filter((file) => !staticSession.extractedFiles.has(file));
|
|
3073
3156
|
if (outsideExtraction.length) throw new Error(`bamboocss: ${outsideExtraction.length} statically compiled module(s) are outside the CSS extraction graph:\n\n${(0, _bamboocss_shared.truncateList)(outsideExtraction.map((file) => ` ${file}`), {
|
package/dist/index.mjs
CHANGED
|
@@ -64,6 +64,7 @@ const pruneStaticCss = (css, session, { prune = true } = {}) => {
|
|
|
64
64
|
if (!className || !prunable.has(className) || used.has(className)) return;
|
|
65
65
|
candidate.remove();
|
|
66
66
|
removedAny = true;
|
|
67
|
+
session.prunedClasses.add(className);
|
|
67
68
|
});
|
|
68
69
|
}).processSync(rule.selector);
|
|
69
70
|
} catch {
|
|
@@ -124,6 +125,56 @@ const pruneStaticCss = (css, session, { prune = true } = {}) => {
|
|
|
124
125
|
return root.toString();
|
|
125
126
|
};
|
|
126
127
|
//#endregion
|
|
128
|
+
//#region src/static-session.ts
|
|
129
|
+
const createStaticCompilationSession = () => {
|
|
130
|
+
const session = {
|
|
131
|
+
utilityLayer: "utilities",
|
|
132
|
+
sourcemap: false,
|
|
133
|
+
cssLoaded: false,
|
|
134
|
+
transformedFiles: /* @__PURE__ */ new Set(),
|
|
135
|
+
extractedFiles: /* @__PURE__ */ new Set(),
|
|
136
|
+
prunableClasses: /* @__PURE__ */ new Set(),
|
|
137
|
+
viewTransitionClasses: /* @__PURE__ */ new Set(),
|
|
138
|
+
usedClasses: /* @__PURE__ */ new Set(),
|
|
139
|
+
expectedEnvironments: void 0,
|
|
140
|
+
startedEnvironments: /* @__PURE__ */ new Set(),
|
|
141
|
+
prunedClasses: /* @__PURE__ */ new Set(),
|
|
142
|
+
markClassUsed(className) {
|
|
143
|
+
for (const token of className.split(" ")) {
|
|
144
|
+
if (!token) continue;
|
|
145
|
+
session.usedClasses.add(token.includes("\\") ? token : esc(token));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
return session;
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Environments this run intends to build that have not been compiled yet.
|
|
153
|
+
*
|
|
154
|
+
* Empty means everything the run will contribute has been contributed, which is the condition
|
|
155
|
+
* every whole-run judgement here waits for: pruning the stylesheet against reachability, and
|
|
156
|
+
* the two guards that ask whether the compiled modules and the extraction graph agree. Each of
|
|
157
|
+
* those is false about a build in progress and true only about a finished one.
|
|
158
|
+
*
|
|
159
|
+
* Empty is also the answer for a single-environment build, where nothing announced an
|
|
160
|
+
* environment list because there is only ever one — so that path is unchanged.
|
|
161
|
+
*
|
|
162
|
+
* An environment a run declares and then never builds leaves this permanently non-empty, and
|
|
163
|
+
* those judgements are skipped for the run. Every one of them errs towards shipping more CSS
|
|
164
|
+
* or asserting less, so that is the safe direction to be wrong in.
|
|
165
|
+
*/
|
|
166
|
+
const remainingEnvironments = (session) => [...session.expectedEnvironments ?? []].filter((name) => !session.startedEnvironments.has(name));
|
|
167
|
+
const resetStaticCompilationSession = (session) => {
|
|
168
|
+
session.cssLoaded = false;
|
|
169
|
+
session.transformedFiles.clear();
|
|
170
|
+
session.extractedFiles.clear();
|
|
171
|
+
session.prunableClasses.clear();
|
|
172
|
+
session.viewTransitionClasses.clear();
|
|
173
|
+
session.usedClasses.clear();
|
|
174
|
+
session.startedEnvironments.clear();
|
|
175
|
+
session.prunedClasses.clear();
|
|
176
|
+
};
|
|
177
|
+
//#endregion
|
|
127
178
|
//#region src/css.ts
|
|
128
179
|
/**
|
|
129
180
|
* What a project imports to get the stylesheet.
|
|
@@ -232,11 +283,15 @@ const carriesGeneratedCss = (output) => output.type === "asset" && output.fileNa
|
|
|
232
283
|
* hash is not cosmetic: it makes late graph reachability cache-safe.
|
|
233
284
|
*/
|
|
234
285
|
const optimizeStaticCssAssets = (bundle, session, options = {}) => {
|
|
235
|
-
const { rename = true } = options;
|
|
286
|
+
const { rename = true, prune = true, sourcemap = session.sourcemap } = options;
|
|
287
|
+
/** Assets in this bundle that carry the generated stylesheet, pruned or not. */
|
|
288
|
+
let sheets = 0;
|
|
236
289
|
for (const output of Object.values(bundle)) {
|
|
237
290
|
if (!carriesGeneratedCss(output)) continue;
|
|
238
291
|
const source = typeof output.source === "string" ? output.source : Buffer.from(output.source).toString();
|
|
239
292
|
if (!source.includes("--made-with-bamboo")) continue;
|
|
293
|
+
sheets++;
|
|
294
|
+
if (!prune) continue;
|
|
240
295
|
const optimized = pruneStaticCss(source, session);
|
|
241
296
|
output.source = optimized;
|
|
242
297
|
if (optimized === source) continue;
|
|
@@ -249,8 +304,9 @@ const optimizeStaticCssAssets = (bundle, session, options = {}) => {
|
|
|
249
304
|
if (bundle[nextName] && bundle[nextName] !== output) throw new Error(`bamboocss: final CSS asset name collision at ${JSON.stringify(nextName)}.`);
|
|
250
305
|
const previous = output.fileName;
|
|
251
306
|
output.fileName = nextName;
|
|
252
|
-
replaceAssetReferences(bundle, previous, nextName,
|
|
307
|
+
replaceAssetReferences(bundle, previous, nextName, sourcemap);
|
|
253
308
|
}
|
|
309
|
+
return { sheets };
|
|
254
310
|
};
|
|
255
311
|
/**
|
|
256
312
|
* Serve bamboo's stylesheet as a virtual module, in dev and in build.
|
|
@@ -326,9 +382,32 @@ const bamboocssCss = (options) => {
|
|
|
326
382
|
};
|
|
327
383
|
return {
|
|
328
384
|
name: "bamboocss:css",
|
|
385
|
+
/**
|
|
386
|
+
* One instance for every environment of a build, rather than one per environment.
|
|
387
|
+
*
|
|
388
|
+
* Vite re-reads the config file once per environment, so a project that lists this plugin
|
|
389
|
+
* in `vite.config.ts` — every project — got a *fresh* instance per environment, each with
|
|
390
|
+
* its own compilation session, context and ts-morph project. Nothing an environment
|
|
391
|
+
* established could then be seen by the next one, which is the premise the reachability
|
|
392
|
+
* accounting below is built on, and it also meant the whole config load and extraction
|
|
393
|
+
* happened once per environment.
|
|
394
|
+
*/
|
|
395
|
+
sharedDuringBuild: true,
|
|
329
396
|
configResolved(config) {
|
|
330
397
|
command = config.command;
|
|
331
398
|
session.sourcemap = config.build.sourcemap;
|
|
399
|
+
if (config.builder && config.environments) session.expectedEnvironments = new Set(Object.keys(config.environments));
|
|
400
|
+
},
|
|
401
|
+
/**
|
|
402
|
+
* The definitive environment list, for a run that reaches `builder.buildApp()` without
|
|
403
|
+
* configuring `builder` — the shape `vite build` itself takes, where exactly one
|
|
404
|
+
* environment is set up and pruning is therefore safe.
|
|
405
|
+
*/
|
|
406
|
+
buildApp: {
|
|
407
|
+
order: "pre",
|
|
408
|
+
async handler(builder) {
|
|
409
|
+
session.expectedEnvironments = new Set(Object.keys(builder.environments));
|
|
410
|
+
}
|
|
332
411
|
},
|
|
333
412
|
resolveId(id) {
|
|
334
413
|
const query = queryOf(id);
|
|
@@ -368,8 +447,34 @@ const bamboocssCss = (options) => {
|
|
|
368
447
|
generateBundle: {
|
|
369
448
|
order: "post",
|
|
370
449
|
handler(_, bundle) {
|
|
371
|
-
|
|
372
|
-
|
|
450
|
+
const environment = this.environment;
|
|
451
|
+
/**
|
|
452
|
+
* Every environment of this run has already had its modules compiled.
|
|
453
|
+
*
|
|
454
|
+
* Reachability is what pruning removes rules against, and it is only complete once
|
|
455
|
+
* nothing is left to contribute to it. The stylesheet is emitted and finalized by the
|
|
456
|
+
* environment that *imports* it, which in an SSR app is the client — and the client
|
|
457
|
+
* builds first, before the server environment has transformed a single module. Pruning
|
|
458
|
+
* there deletes every rule for a class only the server graph reaches, and the pages
|
|
459
|
+
* link the pruned copy: one project lost 39% of its atoms that way, presenting as
|
|
460
|
+
* rarely-used classes such as `md:{display:inline-block}` silently not applying.
|
|
461
|
+
*
|
|
462
|
+
* So the full extracted stylesheet ships instead, exactly as `renameCssAsset: false`
|
|
463
|
+
* already does. Being the last environment is not the common case — frameworks build
|
|
464
|
+
* the client first — but it is the only one where the answer is knowable, and a
|
|
465
|
+
* framework that builds its server bundle first does get pruned output.
|
|
466
|
+
*/
|
|
467
|
+
const pending = remainingEnvironments(session);
|
|
468
|
+
const { sheets } = optimizeStaticCssAssets(bundle, session, {
|
|
469
|
+
rename: renameCssAsset,
|
|
470
|
+
prune: pending.length === 0,
|
|
471
|
+
sourcemap: environment?.config?.build?.sourcemap
|
|
472
|
+
});
|
|
473
|
+
if (sheets && pending.length) logger.info("vite", `Reachability pruning skipped: the stylesheet is emitted by the ${JSON.stringify(environment?.name ?? "default")} environment, and ${truncateList(pending, {
|
|
474
|
+
unit: "environment",
|
|
475
|
+
separator: ", "
|
|
476
|
+
})} ${pending.length === 1 ? "has" : "have"} not been compiled in this run. The full extracted stylesheet ships — nothing is missing from it.`);
|
|
477
|
+
if (!servedEnvironments.has(environment?.name ?? "default")) return;
|
|
373
478
|
if (!session.transformedFiles.size) return;
|
|
374
479
|
if (!Object.values(bundle).some((output) => {
|
|
375
480
|
if (!carriesGeneratedCss(output)) return false;
|
|
@@ -2754,35 +2859,6 @@ const createStaticStyleSetCompiler = (ctx, runtimeCss, allocateClassString = (cl
|
|
|
2754
2859
|
};
|
|
2755
2860
|
};
|
|
2756
2861
|
//#endregion
|
|
2757
|
-
//#region src/static-session.ts
|
|
2758
|
-
const createStaticCompilationSession = () => {
|
|
2759
|
-
const session = {
|
|
2760
|
-
utilityLayer: "utilities",
|
|
2761
|
-
sourcemap: false,
|
|
2762
|
-
cssLoaded: false,
|
|
2763
|
-
transformedFiles: /* @__PURE__ */ new Set(),
|
|
2764
|
-
extractedFiles: /* @__PURE__ */ new Set(),
|
|
2765
|
-
prunableClasses: /* @__PURE__ */ new Set(),
|
|
2766
|
-
viewTransitionClasses: /* @__PURE__ */ new Set(),
|
|
2767
|
-
usedClasses: /* @__PURE__ */ new Set(),
|
|
2768
|
-
markClassUsed(className) {
|
|
2769
|
-
for (const token of className.split(" ")) {
|
|
2770
|
-
if (!token) continue;
|
|
2771
|
-
session.usedClasses.add(token.includes("\\") ? token : esc(token));
|
|
2772
|
-
}
|
|
2773
|
-
}
|
|
2774
|
-
};
|
|
2775
|
-
return session;
|
|
2776
|
-
};
|
|
2777
|
-
const resetStaticCompilationSession = (session) => {
|
|
2778
|
-
session.cssLoaded = false;
|
|
2779
|
-
session.transformedFiles.clear();
|
|
2780
|
-
session.extractedFiles.clear();
|
|
2781
|
-
session.prunableClasses.clear();
|
|
2782
|
-
session.viewTransitionClasses.clear();
|
|
2783
|
-
session.usedClasses.clear();
|
|
2784
|
-
};
|
|
2785
|
-
//#endregion
|
|
2786
2862
|
//#region src/plugin.ts
|
|
2787
2863
|
const DEFAULT_EXTENSIONS = /\.(?:[cm]?[jt]sx?)$/;
|
|
2788
2864
|
const NODE_MODULES = /node_modules/;
|
|
@@ -2836,8 +2912,6 @@ const formatSkipped = (id, skipped) => {
|
|
|
2836
2912
|
const bamboocss = (options = {}) => {
|
|
2837
2913
|
const { configPath, cwd, reportSkipped = false, reportSummary = true, maxRecipeStates, renameCssAsset = true } = options;
|
|
2838
2914
|
if (maxRecipeStates !== void 0 && (!Number.isSafeInteger(maxRecipeStates) || maxRecipeStates < 1)) throw new Error("bamboocss: `maxRecipeStates` must be a positive safe integer.");
|
|
2839
|
-
/** Environments whose `buildStart` has run in the build currently in progress. */
|
|
2840
|
-
const seenEnvironments = /* @__PURE__ */ new Set();
|
|
2841
2915
|
/** Totals across the build, for the summary. */
|
|
2842
2916
|
const totals = {
|
|
2843
2917
|
folded: 0,
|
|
@@ -2911,13 +2985,14 @@ const bamboocss = (options = {}) => {
|
|
|
2911
2985
|
}), {
|
|
2912
2986
|
name: "bamboocss:compiler",
|
|
2913
2987
|
enforce: "pre",
|
|
2988
|
+
/** See the same declaration on the css plugin: one instance per build, not per environment. */
|
|
2989
|
+
sharedDuringBuild: true,
|
|
2914
2990
|
configResolved(config) {
|
|
2915
2991
|
command = config.command;
|
|
2916
2992
|
},
|
|
2917
2993
|
async buildStart() {
|
|
2918
2994
|
const environment = this.environment?.name ?? "default";
|
|
2919
|
-
if (
|
|
2920
|
-
seenEnvironments.clear();
|
|
2995
|
+
if (staticSession.startedEnvironments.has(environment)) {
|
|
2921
2996
|
totals.folded = 0;
|
|
2922
2997
|
totals.files = 0;
|
|
2923
2998
|
totals.filesWithFolds = 0;
|
|
@@ -2926,7 +3001,7 @@ const bamboocss = (options = {}) => {
|
|
|
2926
3001
|
recipeConfigCache.clear();
|
|
2927
3002
|
resetStaticCompilationSession(staticSession);
|
|
2928
3003
|
}
|
|
2929
|
-
|
|
3004
|
+
staticSession.startedEnvironments.add(environment);
|
|
2930
3005
|
try {
|
|
2931
3006
|
await ensureContext();
|
|
2932
3007
|
} catch (error) {
|
|
@@ -3037,7 +3112,15 @@ const bamboocss = (options = {}) => {
|
|
|
3037
3112
|
buildEnd() {
|
|
3038
3113
|
const survivors = allSurvivors();
|
|
3039
3114
|
if (survivors.length) throw createSurvivorError(survivors);
|
|
3040
|
-
|
|
3115
|
+
const lost = [...staticSession.usedClasses].filter((className) => staticSession.prunedClasses.has(bare(className)));
|
|
3116
|
+
if (lost.length) {
|
|
3117
|
+
const environment = this.environment?.name ?? "default";
|
|
3118
|
+
throw new Error(`bamboocss: ${lost.length} class(es) compiled in the ${JSON.stringify(environment)} environment were already pruned out of a stylesheet emitted by an earlier one. Elements carrying them would render unstyled.\n\n${truncateList(lost.map((className) => ` ${className}`), {
|
|
3119
|
+
unit: "class",
|
|
3120
|
+
separator: "\n"
|
|
3121
|
+
})}\n\nThe stylesheet is finalized by the environment that imports it, so pruning it is only safe once every environment has been compiled. This build did not say how many there would be: it called \`builder.build(environment)\` directly. Run it through \`vite build\`, call \`builder.buildApp()\`, or set \`builder: {}\` in the Vite config so the environments are known before the first one builds. \`bamboocss({ renameCssAsset: false })\` also turns pruning off entirely.`);
|
|
3122
|
+
}
|
|
3123
|
+
if (typeof this.getModuleInfo === "function" && !remainingEnvironments(staticSession).length) {
|
|
3041
3124
|
if (!staticSession.cssLoaded) throw new Error(`bamboocss: compiled class values were produced, but ${JSON.stringify(VIRTUAL_CSS_ID)} was not imported. Add \`import ${JSON.stringify(VIRTUAL_CSS_ID)}\` once, from a JavaScript or TypeScript module in the application entry graph.\n\nIt has to be a JS import. \`@import\` from a stylesheet does not reach it: the id names a virtual module resolved by this plugin, and Vite resolves CSS \`@import\` before plugin resolution, so it fails as an unresolvable path. A project that ships one preloaded stylesheet imports this from its entry module instead, and lets Vite emit the CSS asset.`);
|
|
3042
3125
|
const outsideExtraction = [...staticSession.transformedFiles].filter((file) => !staticSession.extractedFiles.has(file));
|
|
3043
3126
|
if (outsideExtraction.length) throw new Error(`bamboocss: ${outsideExtraction.length} statically compiled module(s) are outside the CSS extraction graph:\n\n${truncateList(outsideExtraction.map((file) => ` ${file}`), {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bamboocss/vite",
|
|
3
|
-
"version": "1.37.
|
|
3
|
+
"version": "1.37.13",
|
|
4
4
|
"description": "Vite integration for Bamboo CSS",
|
|
5
5
|
"homepage": "https://bamboocss.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,18 +40,18 @@
|
|
|
40
40
|
"postcss": "8.5.26",
|
|
41
41
|
"postcss-selector-parser": "7.1.5",
|
|
42
42
|
"ts-morph": "28.0.0",
|
|
43
|
-
"@bamboocss/config": "1.37.
|
|
44
|
-
"@bamboocss/core": "1.37.
|
|
45
|
-
"@bamboocss/extractor": "1.37.
|
|
46
|
-
"@bamboocss/logger": "1.37.
|
|
47
|
-
"@bamboocss/node": "1.37.
|
|
48
|
-
"@bamboocss/
|
|
49
|
-
"@bamboocss/
|
|
43
|
+
"@bamboocss/config": "1.37.13",
|
|
44
|
+
"@bamboocss/core": "1.37.13",
|
|
45
|
+
"@bamboocss/extractor": "1.37.13",
|
|
46
|
+
"@bamboocss/logger": "1.37.13",
|
|
47
|
+
"@bamboocss/node": "1.37.13",
|
|
48
|
+
"@bamboocss/types": "1.37.13",
|
|
49
|
+
"@bamboocss/shared": "1.37.13"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@jridgewell/trace-mapping": "^0.3.31",
|
|
53
53
|
"vite": "7.2.6",
|
|
54
|
-
"@bamboocss/fixture": "1.37.
|
|
54
|
+
"@bamboocss/fixture": "1.37.13"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"vite": ">=5"
|