@bamboocss/vite 1.26.0 → 1.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +406 -108
- package/dist/index.d.cts +102 -2
- package/dist/index.d.mts +101 -3
- package/dist/index.mjs +406 -108
- package/package.json +9 -9
package/dist/index.mjs
CHANGED
|
@@ -3,10 +3,10 @@ import { logger } from "@bamboocss/logger";
|
|
|
3
3
|
import { resolveTsPathPattern } from "@bamboocss/config/ts-path";
|
|
4
4
|
import { box, maybeBoxNode, unbox } from "@bamboocss/extractor";
|
|
5
5
|
import MagicString from "magic-string";
|
|
6
|
+
import { dirname, relative, resolve } from "node:path";
|
|
6
7
|
import { Node, SyntaxKind, VariableDeclarationKind } from "ts-morph";
|
|
7
8
|
import { Recipes, classFormatter } from "@bamboocss/core";
|
|
8
9
|
import { compact, createCssUncached, createMergeCss, getRecipeClassNames, getRecipeIdentity, getSlotCompoundVariant, memo, withoutSpace } from "@bamboocss/shared";
|
|
9
|
-
import { resolve } from "node:path";
|
|
10
10
|
//#region src/css.ts
|
|
11
11
|
/**
|
|
12
12
|
* What a project imports to get the stylesheet.
|
|
@@ -823,7 +823,7 @@ const propertyKey = (nameNode) => {
|
|
|
823
823
|
* there is nothing to match — the host here is any import of the generated css module, which
|
|
824
824
|
* a file defining a recipe necessarily has, since `cva` came from it.
|
|
825
825
|
*/
|
|
826
|
-
const ensureRecipeHelperImport = (imported, call, isBambooCssModule, isGeneratedCssModule, isShadowed) => {
|
|
826
|
+
const ensureRecipeHelperImport = (imported, call, isBambooCssModule, isGeneratedCssModule, isShadowed, newImportModule) => {
|
|
827
827
|
const sourceFile = call.getSourceFile();
|
|
828
828
|
let host;
|
|
829
829
|
for (const declaration of sourceFile.getImportDeclarations()) {
|
|
@@ -839,9 +839,21 @@ const ensureRecipeHelperImport = (imported, call, isBambooCssModule, isGenerated
|
|
|
839
839
|
}
|
|
840
840
|
if (!host && isGeneratedCssModule(mod) && declaration.getNamedImports().length > 0) host = declaration;
|
|
841
841
|
}
|
|
842
|
-
if (!host) return void 0;
|
|
842
|
+
if (!host && !newImportModule) return void 0;
|
|
843
843
|
if (declaredAtModuleScope(sourceFile).has(imported)) return void 0;
|
|
844
844
|
if (isShadowed(call, imported)) return void 0;
|
|
845
|
+
if (!host) {
|
|
846
|
+
const anchor = sourceFile.getImportDeclarations().at(-1);
|
|
847
|
+
if (!anchor) return void 0;
|
|
848
|
+
return {
|
|
849
|
+
name: imported,
|
|
850
|
+
insert: {
|
|
851
|
+
pos: anchor.getEnd(),
|
|
852
|
+
names: [imported],
|
|
853
|
+
module: newImportModule
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
}
|
|
845
857
|
const last = host.getNamedImports().at(-1);
|
|
846
858
|
if (!last) return void 0;
|
|
847
859
|
return {
|
|
@@ -901,6 +913,13 @@ const lowerRecipeCall = (call, entry, ctx, isInert, resolvedSelection) => {
|
|
|
901
913
|
/**
|
|
902
914
|
* `input(variantProps)` — a selection the build cannot see inside.
|
|
903
915
|
*
|
|
916
|
+
* Inline recipes only. `cva` resolves a selection with `getRecipeClassNames`, which reads
|
|
917
|
+
* a variant value as a key and so cannot take a conditional — a `{ base, md }` object finds
|
|
918
|
+
* no entry and names no class, exactly as `cvaPick` does. A **config** recipe routes its
|
|
919
|
+
* selection through `createCss`, which *expands* conditions into one class per breakpoint,
|
|
920
|
+
* so a scalar lookup silently drops them. That is why this lowering is not applied to
|
|
921
|
+
* config recipes: for a dynamic axis the build cannot know which kind of value arrives.
|
|
922
|
+
*
|
|
904
923
|
* The classes are still knowable: a recipe emits one per declared variant, so the call is
|
|
905
924
|
* one term per variant reading that binding. This is the shape a wrapper component takes,
|
|
906
925
|
* where the variants are the component's public API and cannot be literals by definition.
|
|
@@ -1084,6 +1103,9 @@ const createRuntimeCss = (ctx) => {
|
|
|
1084
1103
|
* every module in the build — not once per `foldSource`, which would price a whole token
|
|
1085
1104
|
* table into each of the overwhelming majority of modules that call `token()` zero times.
|
|
1086
1105
|
* Keyed weakly so a context that goes out of scope takes its table with it.
|
|
1106
|
+
*
|
|
1107
|
+
* Both halves of the generated entry are stored, because `token()` and `token.var()` read
|
|
1108
|
+
* different ones and building a second table would pay the same per-project cost twice.
|
|
1087
1109
|
*/
|
|
1088
1110
|
const tokenValues = /* @__PURE__ */ new WeakMap();
|
|
1089
1111
|
const tokenValuesFor = (ctx) => {
|
|
@@ -1092,16 +1114,29 @@ const tokenValuesFor = (ctx) => {
|
|
|
1092
1114
|
values = /* @__PURE__ */ new Map();
|
|
1093
1115
|
for (const token of ctx.tokens.allTokens) {
|
|
1094
1116
|
const { varRef, isVirtual, condition } = token.extensions;
|
|
1095
|
-
values.set(token.name,
|
|
1117
|
+
values.set(token.name, {
|
|
1118
|
+
value: isVirtual || condition !== "base" ? varRef : token.value,
|
|
1119
|
+
variable: varRef
|
|
1120
|
+
});
|
|
1096
1121
|
}
|
|
1097
1122
|
tokenValues.set(ctx, values);
|
|
1098
1123
|
return values;
|
|
1099
1124
|
};
|
|
1100
1125
|
const createRuntimeToken = (ctx) => (path) => {
|
|
1101
|
-
const value = tokenValuesFor(ctx).get(path);
|
|
1126
|
+
const value = tokenValuesFor(ctx).get(path)?.value;
|
|
1102
1127
|
return typeof value === "string" ? value : void 0;
|
|
1103
1128
|
};
|
|
1104
1129
|
/**
|
|
1130
|
+
* The generated runtime's `token.var`, rebuilt in-process.
|
|
1131
|
+
*
|
|
1132
|
+
* Mirrors `tokenVar` in `generateTokenJs`, which reads the `variable` half of the same
|
|
1133
|
+
* entry `token()` reads the `value` half of. That half is `varRef` for every token
|
|
1134
|
+
* regardless of condition, so unlike `createRuntimeToken` there is no split to get wrong
|
|
1135
|
+
* and no non-string case to decline: a `var()` reference is a string or the token does not
|
|
1136
|
+
* exist. Which is what makes this the more foldable of the two.
|
|
1137
|
+
*/
|
|
1138
|
+
const createRuntimeTokenVar = (ctx) => (path) => tokenValuesFor(ctx).get(path)?.variable || void 0;
|
|
1139
|
+
/**
|
|
1105
1140
|
* Whether a slot's class is independent of the variant props.
|
|
1106
1141
|
*
|
|
1107
1142
|
* A scoped slot recipe delivers variants through `@scope` rules anchored on an enclosing
|
|
@@ -1148,11 +1183,24 @@ const createRuntimeRecipe = (ctx) => {
|
|
|
1148
1183
|
}
|
|
1149
1184
|
}
|
|
1150
1185
|
});
|
|
1151
|
-
const
|
|
1186
|
+
const declaredValues = config.variants ?? {};
|
|
1187
|
+
/**
|
|
1188
|
+
* The same filter the generated `createRecipe` applies: only a value the config declares
|
|
1189
|
+
* names a class.
|
|
1190
|
+
*
|
|
1191
|
+
* Scalars only — a conditional or responsive value is an object of leaves, and the leaves
|
|
1192
|
+
* are what name classes when `createCss` walks them.
|
|
1193
|
+
*/
|
|
1194
|
+
const onlyDeclared = (styles) => Object.fromEntries(Object.entries(styles).filter(([prop, value]) => {
|
|
1195
|
+
if (prop === className) return true;
|
|
1196
|
+
if (value === null || typeof value === "object") return true;
|
|
1197
|
+
return Object.hasOwn(declaredValues, prop) && Object.hasOwn(declaredValues[prop] ?? {}, String(value));
|
|
1198
|
+
}));
|
|
1199
|
+
const recipeStyles = isConstantSlot ? { [className]: "__ignore__" } : onlyDeclared({
|
|
1152
1200
|
[className]: "__ignore__",
|
|
1153
1201
|
...defaultVariants,
|
|
1154
1202
|
...compact(variants)
|
|
1155
|
-
};
|
|
1203
|
+
});
|
|
1156
1204
|
if (compoundVariants.length > 0 && Object.keys(recipeStyles).some((prop) => typeof variants[prop] === "object")) return;
|
|
1157
1205
|
if (isSlotRecipe) {
|
|
1158
1206
|
const evaluated = anchors.length > 0 ? anchors : config.slots;
|
|
@@ -1192,6 +1240,22 @@ const literalsIn = (expression) => [...expression.matchAll(/"((?:[^"\\]|\\.)*)"/
|
|
|
1192
1240
|
*/
|
|
1193
1241
|
const UNFOLDABLE_TYPES = new Set(["cva", "sva"]);
|
|
1194
1242
|
/**
|
|
1243
|
+
* The skip reasons that leave a `css()`-family call in the output.
|
|
1244
|
+
*
|
|
1245
|
+
* `overlapping` is handled by the enclosing fold, and `not-imported` is somebody else's
|
|
1246
|
+
* function of the same name — neither leaves a call of ours. `not-foldable` is a `cva`/`sva`
|
|
1247
|
+
* definition, which keeps the recipe runtime rather than the css engine; see `strict`.
|
|
1248
|
+
*/
|
|
1249
|
+
const SURVIVES_TO_RUNTIME = new Set([
|
|
1250
|
+
"dynamic",
|
|
1251
|
+
"runtime-binding",
|
|
1252
|
+
"raw-call",
|
|
1253
|
+
"unsupported-kind",
|
|
1254
|
+
"no-call-expression",
|
|
1255
|
+
"empty",
|
|
1256
|
+
"unresolved-token"
|
|
1257
|
+
]);
|
|
1258
|
+
/**
|
|
1195
1259
|
* A call of a recipe the file bound itself: `const badge = cva(...)`, then `badge({ tone })`.
|
|
1196
1260
|
*
|
|
1197
1261
|
* Folded when the whole selection resolves, reported under this reason when it does not.
|
|
@@ -1204,6 +1268,65 @@ const UNFOLDABLE_TYPES = new Set(["cva", "sva"]);
|
|
|
1204
1268
|
*/
|
|
1205
1269
|
const RECIPE_CALL_TYPE = "cva-call";
|
|
1206
1270
|
/**
|
|
1271
|
+
* An identifier that actually reads the binding.
|
|
1272
|
+
*
|
|
1273
|
+
* `getDescendantsOfKind(Identifier)` yields every name in the file, and most of them bind or
|
|
1274
|
+
* label rather than read: a JSX tag (`<button/>` against a recipe called `button`), an object
|
|
1275
|
+
* key, a property name, a declaration. Counting those failed builds on modules that had
|
|
1276
|
+
* folded completely — and `button`, `input`, `label`, `select`, `table`, `dialog` and `form`
|
|
1277
|
+
* are all ordinary recipe names as well as intrinsic elements.
|
|
1278
|
+
*
|
|
1279
|
+
* A type position is excluded for a different reason: it is erased, and with it the import.
|
|
1280
|
+
*/
|
|
1281
|
+
const isValueReference = (identifier) => {
|
|
1282
|
+
const parent = identifier.getParent();
|
|
1283
|
+
if (!parent) return false;
|
|
1284
|
+
if (Node.isImportSpecifier(parent) || Node.isExportSpecifier(parent)) return false;
|
|
1285
|
+
if (Node.isImportClause(parent) || Node.isNamespaceImport(parent)) return false;
|
|
1286
|
+
if (Node.isPropertyAccessExpression(parent) && parent.getNameNode() === identifier) return false;
|
|
1287
|
+
if (Node.isQualifiedName(parent) && parent.getRight() === identifier) return false;
|
|
1288
|
+
if (Node.isPropertyAssignment(parent) && parent.getNameNode() === identifier) return false;
|
|
1289
|
+
if (Node.isMethodDeclaration(parent) || Node.isPropertyDeclaration(parent) || Node.isGetAccessorDeclaration(parent) || Node.isSetAccessorDeclaration(parent) || Node.isMethodSignature(parent) || Node.isPropertySignature(parent) || Node.isEnumMember(parent)) {
|
|
1290
|
+
if (parent.getNameNode() === identifier) return false;
|
|
1291
|
+
}
|
|
1292
|
+
if (Node.isLabeledStatement(parent) || Node.isBreakStatement(parent) || Node.isContinueStatement(parent)) return false;
|
|
1293
|
+
if (Node.isJsxOpeningElement(parent) || Node.isJsxSelfClosingElement(parent) || Node.isJsxClosingElement(parent)) {
|
|
1294
|
+
if (parent.getTagNameNode() === identifier) return identifier.getText()[0] === identifier.getText()[0]?.toUpperCase();
|
|
1295
|
+
}
|
|
1296
|
+
if (Node.isJsxAttribute(parent)) return false;
|
|
1297
|
+
if (Node.isBindingElement(parent) && parent.getPropertyNameNode() === identifier) return false;
|
|
1298
|
+
if ((Node.isVariableDeclaration(parent) || Node.isParameterDeclaration(parent) || Node.isBindingElement(parent) || Node.isFunctionDeclaration(parent) || Node.isClassDeclaration(parent)) && parent.getNameNode() === identifier) return false;
|
|
1299
|
+
return !identifier.getFirstAncestor((ancestor) => Node.isTypeNode(ancestor) || Node.isTypeAliasDeclaration(ancestor) || Node.isInterfaceDeclaration(ancestor));
|
|
1300
|
+
};
|
|
1301
|
+
/**
|
|
1302
|
+
* Imports a surviving reference to is not a failure.
|
|
1303
|
+
*
|
|
1304
|
+
* The first four are what the fold itself writes; all live in `cx` and pull no engine, so a
|
|
1305
|
+
* reference to one is the fold having worked.
|
|
1306
|
+
*
|
|
1307
|
+
* `cva` and `sva` are there for the reason `SURVIVES_TO_RUNTIME` omits `not-foldable`: a
|
|
1308
|
+
* recipe *definition* cannot fold to a class string and never could, and what it keeps is the
|
|
1309
|
+
* recipe runtime rather than the css engine — which `strict` accepts. Their unfoldable
|
|
1310
|
+
* invocations are reported separately, as `recipe-call`.
|
|
1311
|
+
*/
|
|
1312
|
+
const PERMITTED_BINDINGS = new Set([
|
|
1313
|
+
"cx",
|
|
1314
|
+
"cva",
|
|
1315
|
+
"sva",
|
|
1316
|
+
RECIPE_PICK_HELPER,
|
|
1317
|
+
SPLIT_PROPS_HELPER,
|
|
1318
|
+
LEAF_HELPER
|
|
1319
|
+
]);
|
|
1320
|
+
/**
|
|
1321
|
+
* The pieces `trim` reduces a module specifier by, hoisted because a regex literal
|
|
1322
|
+
* constructs a new object every time it is evaluated and `trim` runs per specifier per
|
|
1323
|
+
* import declaration per module.
|
|
1324
|
+
*/
|
|
1325
|
+
const LEADING_RELATIVE = /^(?:\.\.?\/)+/;
|
|
1326
|
+
const TRAILING_SLASH = /\/$/;
|
|
1327
|
+
const MODULE_EXTENSION = /\.[mc]?[jt]sx?$/;
|
|
1328
|
+
const TRAILING_INDEX = /\/index$/;
|
|
1329
|
+
/**
|
|
1207
1330
|
* An argument that cannot run anything when it is evaluated.
|
|
1208
1331
|
*
|
|
1209
1332
|
* `token(path, fallback)` evaluates both arguments before the call, so a fold that drops
|
|
@@ -1419,7 +1542,7 @@ const argumentsAccountedFor = (call, boxNode) => {
|
|
|
1419
1542
|
return accountsForSource(args[0], boxNode);
|
|
1420
1543
|
};
|
|
1421
1544
|
const foldSource = (options) => {
|
|
1422
|
-
const { ctx, code, parserResult, partial: partial_ = true, runtimeCss = createRuntimeCss(ctx) } = options;
|
|
1545
|
+
const { ctx, code, parserResult, partial: partial_ = true, runtimeCss = createRuntimeCss(ctx), parseModule, recipeConfigCache, reportSurvivors, sourceFile: ownSourceFile } = options;
|
|
1423
1546
|
/**
|
|
1424
1547
|
* Recover the static half of a call the whole-call path gave up on. Only a
|
|
1425
1548
|
* single-argument `css()` qualifies: a pattern or recipe call takes props rather than a
|
|
@@ -1471,12 +1594,14 @@ const foldSource = (options) => {
|
|
|
1471
1594
|
className: plan.className,
|
|
1472
1595
|
classNames: [plan.className, ...plan.finite.filter((entry) => entry.emitsLiterals).flatMap((entry) => literalsIn(entry.expression))].filter(Boolean),
|
|
1473
1596
|
replacement: `${cx.name}(${parts.join(", ")})`,
|
|
1474
|
-
insert: cx.insert
|
|
1597
|
+
insert: cx.insert,
|
|
1598
|
+
runtimeCallee: runtimePart ? callee : void 0
|
|
1475
1599
|
};
|
|
1476
1600
|
};
|
|
1477
1601
|
const runtimeRecipe = createRuntimeRecipe(ctx);
|
|
1478
1602
|
const isConstantSlot = createConstantSlotCheck(ctx);
|
|
1479
1603
|
const runtimeToken = createRuntimeToken(ctx);
|
|
1604
|
+
const runtimeTokenVar = createRuntimeTokenVar(ctx);
|
|
1480
1605
|
/**
|
|
1481
1606
|
* Does this specifier name a module that exports the css API, exactly?
|
|
1482
1607
|
*
|
|
@@ -1501,7 +1626,25 @@ const foldSource = (options) => {
|
|
|
1501
1626
|
*/
|
|
1502
1627
|
const generatedCssModule = [ctx.imports.outdir, "css"].join("/");
|
|
1503
1628
|
const pathMappings = ctx.conf.tsOptions?.pathMappings;
|
|
1504
|
-
|
|
1629
|
+
/**
|
|
1630
|
+
* The spelling reduced to the module it names.
|
|
1631
|
+
*
|
|
1632
|
+
* The extension and `/index` are stripped because bamboo's own output makes a file
|
|
1633
|
+
* import them: `outExtension: 'js'` under NodeNext resolution is written
|
|
1634
|
+
* `styled-system/css/index.js`, which is neither equal to `styled-system/css` nor a
|
|
1635
|
+
* tail of it. Extraction admitted such a file anyway — `ImportMap.match` is
|
|
1636
|
+
* substring-based — so the call was folded while the *insert* was refused, and the
|
|
1637
|
+
* result was reported as `dynamic`: the same silent downgrade the alias case above
|
|
1638
|
+
* describes, reached through the extension instead.
|
|
1639
|
+
*
|
|
1640
|
+
* This does not weaken the equality the comment above insists on. `styled-system/css/css`
|
|
1641
|
+
* still names neither, because only a trailing `/index` is a module's own directory.
|
|
1642
|
+
*
|
|
1643
|
+
* `.d.ts` is deliberately not stripped. A declaration file exports no runtime binding, so
|
|
1644
|
+
* matching one would authorise inserting an import that resolves to nothing — and a value
|
|
1645
|
+
* import cannot name one anyway, which is what makes leaving it out free.
|
|
1646
|
+
*/
|
|
1647
|
+
const trim = (value) => value.replaceAll("\\", "/").replace(LEADING_RELATIVE, "").replace(TRAILING_SLASH, "").replace(MODULE_EXTENSION, "").replace(TRAILING_INDEX, "");
|
|
1505
1648
|
const matchesModule = (mod, entries) => {
|
|
1506
1649
|
const candidates = [mod];
|
|
1507
1650
|
if (pathMappings) {
|
|
@@ -1518,6 +1661,100 @@ const foldSource = (options) => {
|
|
|
1518
1661
|
};
|
|
1519
1662
|
const isBambooCssModule = (mod) => matchesModule(mod, cssModules);
|
|
1520
1663
|
const isGeneratedCssModule = (mod) => matchesModule(mod, [generatedCssModule]);
|
|
1664
|
+
/**
|
|
1665
|
+
* How *this* module would have to spell the css module, learnt from one that already does.
|
|
1666
|
+
*
|
|
1667
|
+
* A file calling an imported recipe need not import the css module at all, so when the
|
|
1668
|
+
* lowering needs `cvaPick` there is no spelling in the file to copy. The declaring module
|
|
1669
|
+
* necessarily has one — `cva` came from it — and that is the spelling reused here.
|
|
1670
|
+
*
|
|
1671
|
+
* A bare or aliased specifier resolves identically from any file, so it is taken as
|
|
1672
|
+
* written. A relative one is re-based: resolved against the module that wrote it, then
|
|
1673
|
+
* expressed from the module being folded.
|
|
1674
|
+
*/
|
|
1675
|
+
const cssModuleSpecifierFrom = (declaring) => {
|
|
1676
|
+
for (const declaration of declaring.getImportDeclarations()) {
|
|
1677
|
+
if (declaration.isTypeOnly()) continue;
|
|
1678
|
+
const mod = declaration.getModuleSpecifierValue();
|
|
1679
|
+
if (isGeneratedCssModule(mod)) return mod;
|
|
1680
|
+
}
|
|
1681
|
+
};
|
|
1682
|
+
/**
|
|
1683
|
+
* That spelling, said from the module being folded.
|
|
1684
|
+
*
|
|
1685
|
+
* A bare or aliased specifier resolves identically from any file, so it is taken as
|
|
1686
|
+
* written. A relative one is re-based: resolved against the module that wrote it, then
|
|
1687
|
+
* expressed from the module being folded. Pure path arithmetic, so it holds a string
|
|
1688
|
+
* rather than a node — a cached node does not survive the next `addSourceFile`, which
|
|
1689
|
+
* ts-morph implements by forgetting the file's whole tree.
|
|
1690
|
+
*/
|
|
1691
|
+
const rebaseSpecifier = (specifier, declaringPath, consumingPath) => {
|
|
1692
|
+
if (!specifier.startsWith(".")) return specifier;
|
|
1693
|
+
const absolute = resolve(dirname(declaringPath), specifier);
|
|
1694
|
+
const rebased = relative(dirname(consumingPath), absolute).replaceAll("\\", "/");
|
|
1695
|
+
if (!rebased) return void 0;
|
|
1696
|
+
return rebased.startsWith(".") ? rebased : `./${rebased}`;
|
|
1697
|
+
};
|
|
1698
|
+
/**
|
|
1699
|
+
* Configs of one foreign module, parsed once however many of its recipes are called.
|
|
1700
|
+
*
|
|
1701
|
+
* Falls back to a per-call map when the caller supplies none, so the fold stays correct
|
|
1702
|
+
* standalone — only repeated, which is what the shared cache exists to avoid.
|
|
1703
|
+
*/
|
|
1704
|
+
const configsByModule = recipeConfigCache ?? /* @__PURE__ */ new Map();
|
|
1705
|
+
/** The specifier each imported recipe's module used for the css module, when it needs one. */
|
|
1706
|
+
const helperModules = /* @__PURE__ */ new Map();
|
|
1707
|
+
/** Declaring modules a fold read, recorded as paths because their nodes do not persist. */
|
|
1708
|
+
const foreignDependencies = /* @__PURE__ */ new Set();
|
|
1709
|
+
/** Resolutions for this module's own call sites, keyed by the name the call site writes. */
|
|
1710
|
+
const importedRecipes = /* @__PURE__ */ new Map();
|
|
1711
|
+
/**
|
|
1712
|
+
* The config of a recipe this module imports.
|
|
1713
|
+
*
|
|
1714
|
+
* The binding is followed with ts-morph's symbol aliasing rather than by re-reading import
|
|
1715
|
+
* declarations, because that is what already understands the shapes these are reached
|
|
1716
|
+
* through: `export { badge } from './styles'`, `export * from './styles'`, and an alias at
|
|
1717
|
+
* either end. Each hop is an alias symbol, so following them to a non-alias lands on the
|
|
1718
|
+
* declaration wherever it lives.
|
|
1719
|
+
*
|
|
1720
|
+
* The class names do not depend on which module the call is in — `getRecipeIdentity` hashes
|
|
1721
|
+
* the config — so a recipe lowered here produces exactly the string its own module's call
|
|
1722
|
+
* sites produce, and exactly the one the runtime would have.
|
|
1723
|
+
*/
|
|
1724
|
+
const resolveImportedRecipe = (call, name, origin) => {
|
|
1725
|
+
if (importedRecipes.has(name)) return importedRecipes.get(name);
|
|
1726
|
+
const resolve = () => {
|
|
1727
|
+
if (!parseModule) return void 0;
|
|
1728
|
+
const consuming = call.getSourceFile();
|
|
1729
|
+
if (origin.filePath === consuming.getFilePath()) return void 0;
|
|
1730
|
+
let foreign = configsByModule.get(origin.filePath);
|
|
1731
|
+
if (!foreign) {
|
|
1732
|
+
const result = parseModule(origin.filePath);
|
|
1733
|
+
if (!result) return void 0;
|
|
1734
|
+
const collected = collectRecipeConfigs(result);
|
|
1735
|
+
const declaring = [...collected.values()].find((entry) => entry.box)?.box?.getNode?.()?.getSourceFile();
|
|
1736
|
+
const configs = /* @__PURE__ */ new Map();
|
|
1737
|
+
for (const [key, entry] of collected) configs.set(key, entry === AMBIGUOUS ? entry : {
|
|
1738
|
+
config: entry.config,
|
|
1739
|
+
name: entry.name,
|
|
1740
|
+
box: void 0
|
|
1741
|
+
});
|
|
1742
|
+
foreign = {
|
|
1743
|
+
configs,
|
|
1744
|
+
cssSpecifier: declaring ? cssModuleSpecifierFrom(declaring) : void 0
|
|
1745
|
+
};
|
|
1746
|
+
configsByModule.set(origin.filePath, foreign);
|
|
1747
|
+
}
|
|
1748
|
+
const entry = foreign.configs.get(origin.name);
|
|
1749
|
+
if (!entry || entry === AMBIGUOUS) return void 0;
|
|
1750
|
+
foreignDependencies.add(origin.filePath);
|
|
1751
|
+
helperModules.set(name, foreign.cssSpecifier ? rebaseSpecifier(foreign.cssSpecifier, origin.filePath, consuming.getFilePath()) : void 0);
|
|
1752
|
+
return entry;
|
|
1753
|
+
};
|
|
1754
|
+
const resolved = resolve();
|
|
1755
|
+
importedRecipes.set(name, resolved);
|
|
1756
|
+
return resolved;
|
|
1757
|
+
};
|
|
1521
1758
|
const folded = [];
|
|
1522
1759
|
const skipped = [];
|
|
1523
1760
|
const candidates = [];
|
|
@@ -1548,64 +1785,12 @@ const foldSource = (options) => {
|
|
|
1548
1785
|
}
|
|
1549
1786
|
return names;
|
|
1550
1787
|
};
|
|
1551
|
-
/**
|
|
1552
|
-
* Is this binding the variant half of `<recipe>.splitVariantProps(...)`?
|
|
1553
|
-
*
|
|
1554
|
-
* Read off the declaration rather than the type, so it is the same recipe and the same
|
|
1555
|
-
* destructuring position the source actually wrote.
|
|
1556
|
-
*/
|
|
1557
|
-
const isSplitVariantPropsOf = (binding, recipe) => {
|
|
1558
|
-
for (const declaration of binding.getSourceFile().getDescendantsOfKind(SyntaxKind.VariableDeclaration)) {
|
|
1559
|
-
const nameNode = declaration.getNameNode();
|
|
1560
|
-
if (!Node.isArrayBindingPattern(nameNode)) continue;
|
|
1561
|
-
const first = nameNode.getElements()[0];
|
|
1562
|
-
if (!first || !Node.isBindingElement(first)) continue;
|
|
1563
|
-
if (first.getNameNode().getText() !== binding.getText()) continue;
|
|
1564
|
-
const initializer = declaration.getInitializer();
|
|
1565
|
-
if (!initializer || !Node.isCallExpression(initializer)) return false;
|
|
1566
|
-
const callee = initializer.getExpression();
|
|
1567
|
-
if (!Node.isPropertyAccessExpression(callee)) return false;
|
|
1568
|
-
return callee.getName() === "splitVariantProps" && callee.getExpression().getText() === recipe;
|
|
1569
|
-
}
|
|
1570
|
-
return false;
|
|
1571
|
-
};
|
|
1572
|
-
/**
|
|
1573
|
-
* Lower a config recipe call the same way an inline one lowers.
|
|
1574
|
-
*
|
|
1575
|
-
* The config lives in `ctx.recipes` rather than in the module, and the classes are named
|
|
1576
|
-
* from it identically — so this is the same `lowerRecipeCall`, handed the config from a
|
|
1577
|
-
* different place. Slot recipes are excluded by the caller: they resolve to one class per
|
|
1578
|
-
* slot rather than to a string.
|
|
1579
|
-
*/
|
|
1580
|
-
const lowerConfigRecipeCall = (call, name) => {
|
|
1581
|
-
const config = ctx.recipes.getConfig(name);
|
|
1582
|
-
if (!config || config.slots !== void 0) return void 0;
|
|
1583
|
-
const className = config.className;
|
|
1584
|
-
if (!className) return void 0;
|
|
1585
|
-
if (!Node.isCallExpression(call)) return void 0;
|
|
1586
|
-
const argument = call.getArguments()[0];
|
|
1587
|
-
if (!argument || !Node.isIdentifier(argument) || !isSplitVariantPropsOf(argument, name)) return void 0;
|
|
1588
|
-
const lowered = lowerRecipeCall(call, {
|
|
1589
|
-
config,
|
|
1590
|
-
name: className,
|
|
1591
|
-
box: void 0
|
|
1592
|
-
}, ctx, isInertExpression);
|
|
1593
|
-
if (lowered.kind !== "expression") return void 0;
|
|
1594
|
-
const helper = ensureRecipeHelperImport(RECIPE_PICK_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed);
|
|
1595
|
-
if (!helper) return void 0;
|
|
1596
|
-
return {
|
|
1597
|
-
replacement: helper.name === "cvaPick" ? lowered.expression : lowered.expression.replaceAll(`${RECIPE_PICK_HELPER}(`, `${helper.name}(`),
|
|
1598
|
-
className: lowered.staticClasses,
|
|
1599
|
-
classNames: lowered.classNames,
|
|
1600
|
-
insert: helper.insert
|
|
1601
|
-
};
|
|
1602
|
-
};
|
|
1603
1788
|
for (const item of parserResult.toArray()) {
|
|
1604
1789
|
const type = item.type ?? "";
|
|
1605
1790
|
const name = item.name ?? type;
|
|
1606
1791
|
if (!item.box) continue;
|
|
1607
1792
|
const call = findCallExpression(item.box);
|
|
1608
|
-
if (type === "token") {
|
|
1793
|
+
if (type === "token" || type === "tokenVar") {
|
|
1609
1794
|
if (!call) {
|
|
1610
1795
|
skipped.push({
|
|
1611
1796
|
name,
|
|
@@ -1640,7 +1825,18 @@ const foldSource = (options) => {
|
|
|
1640
1825
|
continue;
|
|
1641
1826
|
}
|
|
1642
1827
|
const callee = Node.isCallExpression(call) ? call.getExpression() : void 0;
|
|
1643
|
-
|
|
1828
|
+
const propertyName = Node.isPropertyAccessExpression(callee) ? callee.getNameNode().getText() : void 0;
|
|
1829
|
+
const wantsVar = type === "tokenVar";
|
|
1830
|
+
if (wantsVar !== (propertyName === "var")) {
|
|
1831
|
+
skipped.push({
|
|
1832
|
+
name,
|
|
1833
|
+
reason: "unsupported-kind",
|
|
1834
|
+
start,
|
|
1835
|
+
end
|
|
1836
|
+
});
|
|
1837
|
+
continue;
|
|
1838
|
+
}
|
|
1839
|
+
if (!wantsVar && propertyName !== void 0 && !ctx.imports.matchers.tokens.match(propertyName)) {
|
|
1644
1840
|
skipped.push({
|
|
1645
1841
|
name,
|
|
1646
1842
|
reason: "unsupported-kind",
|
|
@@ -1677,7 +1873,7 @@ const foldSource = (options) => {
|
|
|
1677
1873
|
});
|
|
1678
1874
|
continue;
|
|
1679
1875
|
}
|
|
1680
|
-
const value = runtimeToken(path);
|
|
1876
|
+
const value = wantsVar ? runtimeTokenVar(path) : runtimeToken(path);
|
|
1681
1877
|
if (!value) {
|
|
1682
1878
|
skipped.push({
|
|
1683
1879
|
name,
|
|
@@ -1720,6 +1916,10 @@ const foldSource = (options) => {
|
|
|
1720
1916
|
continue;
|
|
1721
1917
|
}
|
|
1722
1918
|
recipeConfigs ??= collectRecipeConfigs(parserResult);
|
|
1919
|
+
if (!recipeConfigs.has(name) && item.origin) {
|
|
1920
|
+
const imported = resolveImportedRecipe(call, name, item.origin);
|
|
1921
|
+
if (imported) recipeConfigs.set(name, imported);
|
|
1922
|
+
}
|
|
1723
1923
|
const tally = recipeCalls.get(name) ?? {
|
|
1724
1924
|
seen: 0,
|
|
1725
1925
|
lowered: 0
|
|
@@ -1730,7 +1930,7 @@ const foldSource = (options) => {
|
|
|
1730
1930
|
const entry = recipeConfigs.get(name);
|
|
1731
1931
|
const lowered = lowerRecipeCall(call, entry, ctx, isInertExpression, resolvedSelection);
|
|
1732
1932
|
if (lowered.kind === "expression") {
|
|
1733
|
-
const helper = ensureRecipeHelperImport(RECIPE_PICK_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed);
|
|
1933
|
+
const helper = ensureRecipeHelperImport(RECIPE_PICK_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed, helperModules.get(name));
|
|
1734
1934
|
if (helper) {
|
|
1735
1935
|
tally.lowered++;
|
|
1736
1936
|
candidates.push({
|
|
@@ -1844,25 +2044,19 @@ const foldSource = (options) => {
|
|
|
1844
2044
|
if (!(Node.isCallExpression(call) && call.getArguments().length === 0 && item.data.length === 1) && !isStaticBox(item.box) || !hasStyles(item.data) || !argumentsAccountedFor(call, item.box)) {
|
|
1845
2045
|
const partial = partial_ ? tryPartial(item, call, rootName) : void 0;
|
|
1846
2046
|
if (partial) {
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
node: call,
|
|
2047
|
+
if (reportSurvivors && partial.runtimeCallee) skipped.push({
|
|
2048
|
+
name,
|
|
2049
|
+
reason: "runtime-binding",
|
|
1851
2050
|
start,
|
|
1852
|
-
end
|
|
1853
|
-
...partial
|
|
2051
|
+
end
|
|
1854
2052
|
});
|
|
1855
|
-
continue;
|
|
1856
|
-
}
|
|
1857
|
-
const lowered = type === "recipe" && !slot ? lowerConfigRecipeCall(call, name) : void 0;
|
|
1858
|
-
if (lowered) {
|
|
1859
2053
|
candidates.push({
|
|
1860
2054
|
item,
|
|
1861
2055
|
call,
|
|
1862
2056
|
node: call,
|
|
1863
2057
|
start,
|
|
1864
2058
|
end,
|
|
1865
|
-
...
|
|
2059
|
+
...partial
|
|
1866
2060
|
});
|
|
1867
2061
|
continue;
|
|
1868
2062
|
}
|
|
@@ -1883,13 +2077,22 @@ const foldSource = (options) => {
|
|
|
1883
2077
|
slot
|
|
1884
2078
|
});
|
|
1885
2079
|
}
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
2080
|
+
/**
|
|
2081
|
+
* Ranges the rewrite actually replaced. Declared before the early return below, because
|
|
2082
|
+
* that return is now also a reporting point: a module with nothing to fold is exactly the
|
|
2083
|
+
* shape `reportSurvivors` exists to catch.
|
|
2084
|
+
*/
|
|
2085
|
+
const applied = [];
|
|
2086
|
+
if (candidates.length === 0) {
|
|
2087
|
+
if (reportSurvivors) reportRuntimeBindings();
|
|
2088
|
+
return {
|
|
2089
|
+
code,
|
|
2090
|
+
map: null,
|
|
2091
|
+
folded,
|
|
2092
|
+
skipped,
|
|
2093
|
+
dependencies: []
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
1893
2096
|
const dependencyScan = createDependencyScan(candidates[0].node.getSourceFile());
|
|
1894
2097
|
candidates.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
1895
2098
|
const magic = new MagicString(code);
|
|
@@ -1898,10 +2101,9 @@ const foldSource = (options) => {
|
|
|
1898
2101
|
if (!insert) return;
|
|
1899
2102
|
const missing = insert.names.filter((name) => !insertedNames.has(name));
|
|
1900
2103
|
if (!missing.length) return;
|
|
1901
|
-
magic.appendLeft(insert.pos, missing.map((name) => `, ${name}`).join(""));
|
|
2104
|
+
magic.appendLeft(insert.pos, insert.module ? `\nimport { ${missing.join(", ")} } from '${insert.module}'` : missing.map((name) => `, ${name}`).join(""));
|
|
1902
2105
|
for (const name of missing) insertedNames.add(name);
|
|
1903
2106
|
};
|
|
1904
|
-
const applied = [];
|
|
1905
2107
|
const collides = (edits) => edits.some(([start, end]) => applied.some(([from, to]) => start < to && from < end));
|
|
1906
2108
|
for (const candidate of candidates) {
|
|
1907
2109
|
const { item, start, end } = candidate;
|
|
@@ -1993,7 +2195,7 @@ const foldSource = (options) => {
|
|
|
1993
2195
|
});
|
|
1994
2196
|
collectSourceFiles(item.box, dependencyScan);
|
|
1995
2197
|
}
|
|
1996
|
-
const recipeSourceFile =
|
|
2198
|
+
const recipeSourceFile = candidates[0]?.node.getSourceFile();
|
|
1997
2199
|
if (recipeSourceFile) for (const access of recipeSourceFile.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression)) {
|
|
1998
2200
|
if (access.getName() !== "splitVariantProps") continue;
|
|
1999
2201
|
const target = access.getExpression();
|
|
@@ -2015,7 +2217,7 @@ const foldSource = (options) => {
|
|
|
2015
2217
|
const end = call.getEnd();
|
|
2016
2218
|
if (code.slice(start, end) !== call.getText()) continue;
|
|
2017
2219
|
if (collides([[start, end]])) continue;
|
|
2018
|
-
const helper = ensureRecipeHelperImport(SPLIT_PROPS_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed);
|
|
2220
|
+
const helper = ensureRecipeHelperImport(SPLIT_PROPS_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed, helperModules.get(target.getText()));
|
|
2019
2221
|
if (!helper) continue;
|
|
2020
2222
|
const keys = Object.keys(entry.config.variants ?? {});
|
|
2021
2223
|
magic.overwrite(start, end, `${helper.name}(${args[0].getText()}, ${JSON.stringify(keys)})`);
|
|
@@ -2033,6 +2235,104 @@ const foldSource = (options) => {
|
|
|
2033
2235
|
if (code.slice(start, call.getEnd()) !== call.getText()) continue;
|
|
2034
2236
|
magic.appendLeft(start, "/*#__PURE__*/");
|
|
2035
2237
|
}
|
|
2238
|
+
/**
|
|
2239
|
+
* Bindings from a bamboo module still referenced once every rewrite is applied.
|
|
2240
|
+
*
|
|
2241
|
+
* Deliberately not driven by `parserResult`: that is the recogniser, and the point here is
|
|
2242
|
+
* to catch what it did not see. A namespace import called as `s.cva(...)`, a default
|
|
2243
|
+
* import, a specifier that resolved to nothing — each leaves a live reference and no ledger
|
|
2244
|
+
* entry at all, which is how `strict` came to pass a build that still shipped the engine.
|
|
2245
|
+
*
|
|
2246
|
+
* The helpers the fold itself writes are excluded: `cx`, `cvaPick`, `splitProps` and the
|
|
2247
|
+
* leaf helper live in `cx` and pull no engine, so a reference to one is the fold working
|
|
2248
|
+
* rather than failing.
|
|
2249
|
+
*/
|
|
2250
|
+
function reportRuntimeBindings() {
|
|
2251
|
+
const sourceFile = ownSourceFile ?? candidates[0]?.node.getSourceFile();
|
|
2252
|
+
if (!sourceFile) return;
|
|
2253
|
+
const bambooModules = [
|
|
2254
|
+
...cssModules,
|
|
2255
|
+
...ctx.imports.matchers.recipe?.mods ?? [],
|
|
2256
|
+
...ctx.imports.matchers.pattern?.mods ?? [],
|
|
2257
|
+
...ctx.imports.matchers.tokens?.mods ?? []
|
|
2258
|
+
];
|
|
2259
|
+
/** Local name -> what to call it in the report. */
|
|
2260
|
+
const watched = /* @__PURE__ */ new Map();
|
|
2261
|
+
for (const declaration of sourceFile.getImportDeclarations()) {
|
|
2262
|
+
if (declaration.isTypeOnly()) continue;
|
|
2263
|
+
if (!matchesModule(declaration.getModuleSpecifierValue(), bambooModules)) continue;
|
|
2264
|
+
for (const named of declaration.getNamedImports()) {
|
|
2265
|
+
if (named.isTypeOnly()) continue;
|
|
2266
|
+
const imported = named.getNameNode().getText();
|
|
2267
|
+
if (PERMITTED_BINDINGS.has(imported)) continue;
|
|
2268
|
+
watched.set((named.getAliasNode() ?? named.getNameNode()).getText(), imported);
|
|
2269
|
+
}
|
|
2270
|
+
const namespace = declaration.getNamespaceImport();
|
|
2271
|
+
if (namespace) watched.set(namespace.getText(), `${namespace.getText()}.*`);
|
|
2272
|
+
const defaultImport = declaration.getDefaultImport();
|
|
2273
|
+
if (defaultImport) watched.set(defaultImport.getText(), defaultImport.getText());
|
|
2274
|
+
}
|
|
2275
|
+
for (const declaration of sourceFile.getExportDeclarations()) {
|
|
2276
|
+
if (declaration.isTypeOnly()) continue;
|
|
2277
|
+
if (!matchesModule(declaration.getModuleSpecifierValue() ?? "", bambooModules)) continue;
|
|
2278
|
+
if (declaration.isNamespaceExport()) {
|
|
2279
|
+
skipped.push({
|
|
2280
|
+
name: declaration.getNamespaceExport()?.getName() ?? "*",
|
|
2281
|
+
reason: "runtime-binding",
|
|
2282
|
+
start: declaration.getStart(),
|
|
2283
|
+
end: declaration.getEnd()
|
|
2284
|
+
});
|
|
2285
|
+
continue;
|
|
2286
|
+
}
|
|
2287
|
+
for (const named of declaration.getNamedExports()) {
|
|
2288
|
+
if (named.isTypeOnly()) continue;
|
|
2289
|
+
const imported = named.getNameNode().getText();
|
|
2290
|
+
if (PERMITTED_BINDINGS.has(imported)) continue;
|
|
2291
|
+
skipped.push({
|
|
2292
|
+
name: imported,
|
|
2293
|
+
reason: "runtime-binding",
|
|
2294
|
+
start: named.getStart(),
|
|
2295
|
+
end: named.getEnd()
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
for (const declaration of sourceFile.getExportDeclarations()) {
|
|
2300
|
+
if (declaration.isTypeOnly() || declaration.getModuleSpecifier()) continue;
|
|
2301
|
+
for (const named of declaration.getNamedExports()) {
|
|
2302
|
+
if (named.isTypeOnly()) continue;
|
|
2303
|
+
const imported = watched.get(named.getNameNode().getText());
|
|
2304
|
+
if (imported === void 0) continue;
|
|
2305
|
+
skipped.push({
|
|
2306
|
+
name: imported,
|
|
2307
|
+
reason: "runtime-binding",
|
|
2308
|
+
start: named.getStart(),
|
|
2309
|
+
end: named.getEnd()
|
|
2310
|
+
});
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
if (watched.size === 0) return;
|
|
2314
|
+
const declined = skipped.filter((entry) => SURVIVES_TO_RUNTIME.has(entry.reason) && entry.end > entry.start).map((entry) => [entry.start, entry.end]);
|
|
2315
|
+
const reported = /* @__PURE__ */ new Set();
|
|
2316
|
+
for (const identifier of sourceFile.getDescendantsOfKind(SyntaxKind.Identifier)) {
|
|
2317
|
+
const local = identifier.getText();
|
|
2318
|
+
const imported = watched.get(local);
|
|
2319
|
+
if (imported === void 0 || reported.has(local)) continue;
|
|
2320
|
+
const start = identifier.getStart();
|
|
2321
|
+
if (identifier.getFirstAncestorByKind(SyntaxKind.ImportDeclaration)) continue;
|
|
2322
|
+
if (applied.some(([from, to]) => start >= from && start < to)) continue;
|
|
2323
|
+
if (declined.some(([from, to]) => start >= from && start < to)) continue;
|
|
2324
|
+
if (!isValueReference(identifier)) continue;
|
|
2325
|
+
if (isShadowed(identifier, local)) continue;
|
|
2326
|
+
reported.add(local);
|
|
2327
|
+
skipped.push({
|
|
2328
|
+
name: imported,
|
|
2329
|
+
reason: "runtime-binding",
|
|
2330
|
+
start,
|
|
2331
|
+
end: identifier.getEnd()
|
|
2332
|
+
});
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
if (reportSurvivors) reportRuntimeBindings();
|
|
2036
2336
|
if (folded.length === 0) return {
|
|
2037
2337
|
code,
|
|
2038
2338
|
map: null,
|
|
@@ -2049,7 +2349,7 @@ const foldSource = (options) => {
|
|
|
2049
2349
|
}),
|
|
2050
2350
|
folded,
|
|
2051
2351
|
skipped,
|
|
2052
|
-
dependencies:
|
|
2352
|
+
dependencies: [...dependencyScan.results, ...foreignDependencies]
|
|
2053
2353
|
};
|
|
2054
2354
|
};
|
|
2055
2355
|
//#endregion
|
|
@@ -2083,21 +2383,6 @@ const isGeneratedOutput = (filePath, ctx) => {
|
|
|
2083
2383
|
const file = slashed(filePath);
|
|
2084
2384
|
return file === root || file.startsWith(`${root}/`);
|
|
2085
2385
|
};
|
|
2086
|
-
/**
|
|
2087
|
-
* The skip reasons that leave a `css()`-family call in the output.
|
|
2088
|
-
*
|
|
2089
|
-
* `overlapping` is handled by the enclosing fold, and `not-imported` is somebody else's
|
|
2090
|
-
* function of the same name — neither leaves a call of ours. `not-foldable` is a `cva`/`sva`
|
|
2091
|
-
* definition, which keeps the recipe runtime rather than the css engine; see `strict`.
|
|
2092
|
-
*/
|
|
2093
|
-
const SURVIVES_TO_RUNTIME = new Set([
|
|
2094
|
-
"dynamic",
|
|
2095
|
-
"raw-call",
|
|
2096
|
-
"unsupported-kind",
|
|
2097
|
-
"no-call-expression",
|
|
2098
|
-
"empty",
|
|
2099
|
-
"unresolved-token"
|
|
2100
|
-
]);
|
|
2101
2386
|
/** 1-indexed line of a source offset, for an error a user can navigate to. */
|
|
2102
2387
|
const lineAt = (code, offset) => code.slice(0, offset).split("\n").length;
|
|
2103
2388
|
const formatSkipped = (id, skipped) => {
|
|
@@ -2128,6 +2413,13 @@ const bamboocss = (options = {}) => {
|
|
|
2128
2413
|
};
|
|
2129
2414
|
/** Under `strict`, every call that would still reach the runtime. */
|
|
2130
2415
|
const survivors = [];
|
|
2416
|
+
/**
|
|
2417
|
+
* Recipe configs read out of modules other than the one being transformed.
|
|
2418
|
+
*
|
|
2419
|
+
* Per build rather than per module: a recipe declared once and imported by fifty components
|
|
2420
|
+
* would otherwise re-parse its module fifty times, which is the transform path.
|
|
2421
|
+
*/
|
|
2422
|
+
const recipeConfigCache = /* @__PURE__ */ new Map();
|
|
2131
2423
|
let ctx;
|
|
2132
2424
|
let runtimeCss;
|
|
2133
2425
|
let setup;
|
|
@@ -2155,6 +2447,7 @@ const bamboocss = (options = {}) => {
|
|
|
2155
2447
|
totals.filesWithFolds = 0;
|
|
2156
2448
|
totals.skipped.clear();
|
|
2157
2449
|
survivors.length = 0;
|
|
2450
|
+
recipeConfigCache.clear();
|
|
2158
2451
|
await ensureContext();
|
|
2159
2452
|
},
|
|
2160
2453
|
/**
|
|
@@ -2182,6 +2475,7 @@ const bamboocss = (options = {}) => {
|
|
|
2182
2475
|
if (!shouldTransform(id)) return;
|
|
2183
2476
|
const [filePath] = id.split("?");
|
|
2184
2477
|
if (!filePath) return;
|
|
2478
|
+
recipeConfigCache.clear();
|
|
2185
2479
|
if (change.event === "delete") {
|
|
2186
2480
|
ctx.project.removeSourceFile(filePath);
|
|
2187
2481
|
return;
|
|
@@ -2197,16 +2491,20 @@ const bamboocss = (options = {}) => {
|
|
|
2197
2491
|
if (isGeneratedOutput(filePath, ctx)) return null;
|
|
2198
2492
|
let result;
|
|
2199
2493
|
try {
|
|
2200
|
-
ctx.project.addSourceFile(filePath, code);
|
|
2494
|
+
const sourceFile = ctx.project.addSourceFile(filePath, code);
|
|
2201
2495
|
const parserResult = ctx.project.parseSourceFile(filePath);
|
|
2202
|
-
if (!parserResult || parserResult.isEmpty()) return null;
|
|
2496
|
+
if (!parserResult || parserResult.isEmpty() && !strict) return null;
|
|
2203
2497
|
result = foldSource({
|
|
2204
2498
|
ctx,
|
|
2205
2499
|
code,
|
|
2206
2500
|
parserResult,
|
|
2207
2501
|
filePath,
|
|
2208
2502
|
runtimeCss,
|
|
2209
|
-
partial
|
|
2503
|
+
partial,
|
|
2504
|
+
parseModule: (path) => ctx?.project.parseSourceFile(path),
|
|
2505
|
+
recipeConfigCache,
|
|
2506
|
+
reportSurvivors: strict,
|
|
2507
|
+
sourceFile
|
|
2210
2508
|
});
|
|
2211
2509
|
} catch (error) {
|
|
2212
2510
|
logger.caughtError("vite:transform", `Failed to fold ${filePath}`, error);
|
|
@@ -2250,7 +2548,7 @@ const bamboocss = (options = {}) => {
|
|
|
2250
2548
|
list.push(entry);
|
|
2251
2549
|
byFile.set(entry.file, list);
|
|
2252
2550
|
}
|
|
2253
|
-
const detail = Array.from(byFile.entries()).map(([file, entries]) => [` ${file}`, ...entries.map((e) => ` ${e.line}: ${e.name}() — ${e.reason}`)].join("\n")).join("\n");
|
|
2551
|
+
const detail = Array.from(byFile.entries()).map(([file, entries]) => [` ${file}`, ...entries.map((e) => ` ${e.line}: ${e.name}${e.reason === "runtime-binding" ? "" : "()"} — ${e.reason}`)].join("\n")).join("\n");
|
|
2254
2552
|
throw new Error(`bamboocss: ${survivors.length} call(s) could not be folded, and \`strict\` is on.\n\n${detail}\n\nEach one keeps \`styled-system/css\` in the bundle, so the engine cannot be dropped however many other calls folded. Make the values static, move the variation into a \`cva\` variant, or generate them with \`staticCss\` — or set \`strict: false\` to accept the runtime.`);
|
|
2255
2553
|
}
|
|
2256
2554
|
if (!transform || !reportSummary) return;
|