@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.cjs
CHANGED
|
@@ -30,10 +30,10 @@ let _bamboocss_config_ts_path = require("@bamboocss/config/ts-path");
|
|
|
30
30
|
let _bamboocss_extractor = require("@bamboocss/extractor");
|
|
31
31
|
let magic_string = require("magic-string");
|
|
32
32
|
magic_string = __toESM(magic_string);
|
|
33
|
+
let node_path = require("node:path");
|
|
33
34
|
let ts_morph = require("ts-morph");
|
|
34
35
|
let _bamboocss_core = require("@bamboocss/core");
|
|
35
36
|
let _bamboocss_shared = require("@bamboocss/shared");
|
|
36
|
-
let node_path = require("node:path");
|
|
37
37
|
//#region src/css.ts
|
|
38
38
|
/**
|
|
39
39
|
* What a project imports to get the stylesheet.
|
|
@@ -850,7 +850,7 @@ const propertyKey = (nameNode) => {
|
|
|
850
850
|
* there is nothing to match — the host here is any import of the generated css module, which
|
|
851
851
|
* a file defining a recipe necessarily has, since `cva` came from it.
|
|
852
852
|
*/
|
|
853
|
-
const ensureRecipeHelperImport = (imported, call, isBambooCssModule, isGeneratedCssModule, isShadowed) => {
|
|
853
|
+
const ensureRecipeHelperImport = (imported, call, isBambooCssModule, isGeneratedCssModule, isShadowed, newImportModule) => {
|
|
854
854
|
const sourceFile = call.getSourceFile();
|
|
855
855
|
let host;
|
|
856
856
|
for (const declaration of sourceFile.getImportDeclarations()) {
|
|
@@ -866,9 +866,21 @@ const ensureRecipeHelperImport = (imported, call, isBambooCssModule, isGenerated
|
|
|
866
866
|
}
|
|
867
867
|
if (!host && isGeneratedCssModule(mod) && declaration.getNamedImports().length > 0) host = declaration;
|
|
868
868
|
}
|
|
869
|
-
if (!host) return void 0;
|
|
869
|
+
if (!host && !newImportModule) return void 0;
|
|
870
870
|
if (declaredAtModuleScope(sourceFile).has(imported)) return void 0;
|
|
871
871
|
if (isShadowed(call, imported)) return void 0;
|
|
872
|
+
if (!host) {
|
|
873
|
+
const anchor = sourceFile.getImportDeclarations().at(-1);
|
|
874
|
+
if (!anchor) return void 0;
|
|
875
|
+
return {
|
|
876
|
+
name: imported,
|
|
877
|
+
insert: {
|
|
878
|
+
pos: anchor.getEnd(),
|
|
879
|
+
names: [imported],
|
|
880
|
+
module: newImportModule
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
}
|
|
872
884
|
const last = host.getNamedImports().at(-1);
|
|
873
885
|
if (!last) return void 0;
|
|
874
886
|
return {
|
|
@@ -928,6 +940,13 @@ const lowerRecipeCall = (call, entry, ctx, isInert, resolvedSelection) => {
|
|
|
928
940
|
/**
|
|
929
941
|
* `input(variantProps)` — a selection the build cannot see inside.
|
|
930
942
|
*
|
|
943
|
+
* Inline recipes only. `cva` resolves a selection with `getRecipeClassNames`, which reads
|
|
944
|
+
* a variant value as a key and so cannot take a conditional — a `{ base, md }` object finds
|
|
945
|
+
* no entry and names no class, exactly as `cvaPick` does. A **config** recipe routes its
|
|
946
|
+
* selection through `createCss`, which *expands* conditions into one class per breakpoint,
|
|
947
|
+
* so a scalar lookup silently drops them. That is why this lowering is not applied to
|
|
948
|
+
* config recipes: for a dynamic axis the build cannot know which kind of value arrives.
|
|
949
|
+
*
|
|
931
950
|
* The classes are still knowable: a recipe emits one per declared variant, so the call is
|
|
932
951
|
* one term per variant reading that binding. This is the shape a wrapper component takes,
|
|
933
952
|
* where the variants are the component's public API and cannot be literals by definition.
|
|
@@ -1111,6 +1130,9 @@ const createRuntimeCss = (ctx) => {
|
|
|
1111
1130
|
* every module in the build — not once per `foldSource`, which would price a whole token
|
|
1112
1131
|
* table into each of the overwhelming majority of modules that call `token()` zero times.
|
|
1113
1132
|
* Keyed weakly so a context that goes out of scope takes its table with it.
|
|
1133
|
+
*
|
|
1134
|
+
* Both halves of the generated entry are stored, because `token()` and `token.var()` read
|
|
1135
|
+
* different ones and building a second table would pay the same per-project cost twice.
|
|
1114
1136
|
*/
|
|
1115
1137
|
const tokenValues = /* @__PURE__ */ new WeakMap();
|
|
1116
1138
|
const tokenValuesFor = (ctx) => {
|
|
@@ -1119,16 +1141,29 @@ const tokenValuesFor = (ctx) => {
|
|
|
1119
1141
|
values = /* @__PURE__ */ new Map();
|
|
1120
1142
|
for (const token of ctx.tokens.allTokens) {
|
|
1121
1143
|
const { varRef, isVirtual, condition } = token.extensions;
|
|
1122
|
-
values.set(token.name,
|
|
1144
|
+
values.set(token.name, {
|
|
1145
|
+
value: isVirtual || condition !== "base" ? varRef : token.value,
|
|
1146
|
+
variable: varRef
|
|
1147
|
+
});
|
|
1123
1148
|
}
|
|
1124
1149
|
tokenValues.set(ctx, values);
|
|
1125
1150
|
return values;
|
|
1126
1151
|
};
|
|
1127
1152
|
const createRuntimeToken = (ctx) => (path) => {
|
|
1128
|
-
const value = tokenValuesFor(ctx).get(path);
|
|
1153
|
+
const value = tokenValuesFor(ctx).get(path)?.value;
|
|
1129
1154
|
return typeof value === "string" ? value : void 0;
|
|
1130
1155
|
};
|
|
1131
1156
|
/**
|
|
1157
|
+
* The generated runtime's `token.var`, rebuilt in-process.
|
|
1158
|
+
*
|
|
1159
|
+
* Mirrors `tokenVar` in `generateTokenJs`, which reads the `variable` half of the same
|
|
1160
|
+
* entry `token()` reads the `value` half of. That half is `varRef` for every token
|
|
1161
|
+
* regardless of condition, so unlike `createRuntimeToken` there is no split to get wrong
|
|
1162
|
+
* and no non-string case to decline: a `var()` reference is a string or the token does not
|
|
1163
|
+
* exist. Which is what makes this the more foldable of the two.
|
|
1164
|
+
*/
|
|
1165
|
+
const createRuntimeTokenVar = (ctx) => (path) => tokenValuesFor(ctx).get(path)?.variable || void 0;
|
|
1166
|
+
/**
|
|
1132
1167
|
* Whether a slot's class is independent of the variant props.
|
|
1133
1168
|
*
|
|
1134
1169
|
* A scoped slot recipe delivers variants through `@scope` rules anchored on an enclosing
|
|
@@ -1175,11 +1210,24 @@ const createRuntimeRecipe = (ctx) => {
|
|
|
1175
1210
|
}
|
|
1176
1211
|
}
|
|
1177
1212
|
});
|
|
1178
|
-
const
|
|
1213
|
+
const declaredValues = config.variants ?? {};
|
|
1214
|
+
/**
|
|
1215
|
+
* The same filter the generated `createRecipe` applies: only a value the config declares
|
|
1216
|
+
* names a class.
|
|
1217
|
+
*
|
|
1218
|
+
* Scalars only — a conditional or responsive value is an object of leaves, and the leaves
|
|
1219
|
+
* are what name classes when `createCss` walks them.
|
|
1220
|
+
*/
|
|
1221
|
+
const onlyDeclared = (styles) => Object.fromEntries(Object.entries(styles).filter(([prop, value]) => {
|
|
1222
|
+
if (prop === className) return true;
|
|
1223
|
+
if (value === null || typeof value === "object") return true;
|
|
1224
|
+
return Object.hasOwn(declaredValues, prop) && Object.hasOwn(declaredValues[prop] ?? {}, String(value));
|
|
1225
|
+
}));
|
|
1226
|
+
const recipeStyles = isConstantSlot ? { [className]: "__ignore__" } : onlyDeclared({
|
|
1179
1227
|
[className]: "__ignore__",
|
|
1180
1228
|
...defaultVariants,
|
|
1181
1229
|
...(0, _bamboocss_shared.compact)(variants)
|
|
1182
|
-
};
|
|
1230
|
+
});
|
|
1183
1231
|
if (compoundVariants.length > 0 && Object.keys(recipeStyles).some((prop) => typeof variants[prop] === "object")) return;
|
|
1184
1232
|
if (isSlotRecipe) {
|
|
1185
1233
|
const evaluated = anchors.length > 0 ? anchors : config.slots;
|
|
@@ -1219,6 +1267,22 @@ const literalsIn = (expression) => [...expression.matchAll(/"((?:[^"\\]|\\.)*)"/
|
|
|
1219
1267
|
*/
|
|
1220
1268
|
const UNFOLDABLE_TYPES = new Set(["cva", "sva"]);
|
|
1221
1269
|
/**
|
|
1270
|
+
* The skip reasons that leave a `css()`-family call in the output.
|
|
1271
|
+
*
|
|
1272
|
+
* `overlapping` is handled by the enclosing fold, and `not-imported` is somebody else's
|
|
1273
|
+
* function of the same name — neither leaves a call of ours. `not-foldable` is a `cva`/`sva`
|
|
1274
|
+
* definition, which keeps the recipe runtime rather than the css engine; see `strict`.
|
|
1275
|
+
*/
|
|
1276
|
+
const SURVIVES_TO_RUNTIME = new Set([
|
|
1277
|
+
"dynamic",
|
|
1278
|
+
"runtime-binding",
|
|
1279
|
+
"raw-call",
|
|
1280
|
+
"unsupported-kind",
|
|
1281
|
+
"no-call-expression",
|
|
1282
|
+
"empty",
|
|
1283
|
+
"unresolved-token"
|
|
1284
|
+
]);
|
|
1285
|
+
/**
|
|
1222
1286
|
* A call of a recipe the file bound itself: `const badge = cva(...)`, then `badge({ tone })`.
|
|
1223
1287
|
*
|
|
1224
1288
|
* Folded when the whole selection resolves, reported under this reason when it does not.
|
|
@@ -1231,6 +1295,65 @@ const UNFOLDABLE_TYPES = new Set(["cva", "sva"]);
|
|
|
1231
1295
|
*/
|
|
1232
1296
|
const RECIPE_CALL_TYPE = "cva-call";
|
|
1233
1297
|
/**
|
|
1298
|
+
* An identifier that actually reads the binding.
|
|
1299
|
+
*
|
|
1300
|
+
* `getDescendantsOfKind(Identifier)` yields every name in the file, and most of them bind or
|
|
1301
|
+
* label rather than read: a JSX tag (`<button/>` against a recipe called `button`), an object
|
|
1302
|
+
* key, a property name, a declaration. Counting those failed builds on modules that had
|
|
1303
|
+
* folded completely — and `button`, `input`, `label`, `select`, `table`, `dialog` and `form`
|
|
1304
|
+
* are all ordinary recipe names as well as intrinsic elements.
|
|
1305
|
+
*
|
|
1306
|
+
* A type position is excluded for a different reason: it is erased, and with it the import.
|
|
1307
|
+
*/
|
|
1308
|
+
const isValueReference = (identifier) => {
|
|
1309
|
+
const parent = identifier.getParent();
|
|
1310
|
+
if (!parent) return false;
|
|
1311
|
+
if (ts_morph.Node.isImportSpecifier(parent) || ts_morph.Node.isExportSpecifier(parent)) return false;
|
|
1312
|
+
if (ts_morph.Node.isImportClause(parent) || ts_morph.Node.isNamespaceImport(parent)) return false;
|
|
1313
|
+
if (ts_morph.Node.isPropertyAccessExpression(parent) && parent.getNameNode() === identifier) return false;
|
|
1314
|
+
if (ts_morph.Node.isQualifiedName(parent) && parent.getRight() === identifier) return false;
|
|
1315
|
+
if (ts_morph.Node.isPropertyAssignment(parent) && parent.getNameNode() === identifier) return false;
|
|
1316
|
+
if (ts_morph.Node.isMethodDeclaration(parent) || ts_morph.Node.isPropertyDeclaration(parent) || ts_morph.Node.isGetAccessorDeclaration(parent) || ts_morph.Node.isSetAccessorDeclaration(parent) || ts_morph.Node.isMethodSignature(parent) || ts_morph.Node.isPropertySignature(parent) || ts_morph.Node.isEnumMember(parent)) {
|
|
1317
|
+
if (parent.getNameNode() === identifier) return false;
|
|
1318
|
+
}
|
|
1319
|
+
if (ts_morph.Node.isLabeledStatement(parent) || ts_morph.Node.isBreakStatement(parent) || ts_morph.Node.isContinueStatement(parent)) return false;
|
|
1320
|
+
if (ts_morph.Node.isJsxOpeningElement(parent) || ts_morph.Node.isJsxSelfClosingElement(parent) || ts_morph.Node.isJsxClosingElement(parent)) {
|
|
1321
|
+
if (parent.getTagNameNode() === identifier) return identifier.getText()[0] === identifier.getText()[0]?.toUpperCase();
|
|
1322
|
+
}
|
|
1323
|
+
if (ts_morph.Node.isJsxAttribute(parent)) return false;
|
|
1324
|
+
if (ts_morph.Node.isBindingElement(parent) && parent.getPropertyNameNode() === identifier) return false;
|
|
1325
|
+
if ((ts_morph.Node.isVariableDeclaration(parent) || ts_morph.Node.isParameterDeclaration(parent) || ts_morph.Node.isBindingElement(parent) || ts_morph.Node.isFunctionDeclaration(parent) || ts_morph.Node.isClassDeclaration(parent)) && parent.getNameNode() === identifier) return false;
|
|
1326
|
+
return !identifier.getFirstAncestor((ancestor) => ts_morph.Node.isTypeNode(ancestor) || ts_morph.Node.isTypeAliasDeclaration(ancestor) || ts_morph.Node.isInterfaceDeclaration(ancestor));
|
|
1327
|
+
};
|
|
1328
|
+
/**
|
|
1329
|
+
* Imports a surviving reference to is not a failure.
|
|
1330
|
+
*
|
|
1331
|
+
* The first four are what the fold itself writes; all live in `cx` and pull no engine, so a
|
|
1332
|
+
* reference to one is the fold having worked.
|
|
1333
|
+
*
|
|
1334
|
+
* `cva` and `sva` are there for the reason `SURVIVES_TO_RUNTIME` omits `not-foldable`: a
|
|
1335
|
+
* recipe *definition* cannot fold to a class string and never could, and what it keeps is the
|
|
1336
|
+
* recipe runtime rather than the css engine — which `strict` accepts. Their unfoldable
|
|
1337
|
+
* invocations are reported separately, as `recipe-call`.
|
|
1338
|
+
*/
|
|
1339
|
+
const PERMITTED_BINDINGS = new Set([
|
|
1340
|
+
"cx",
|
|
1341
|
+
"cva",
|
|
1342
|
+
"sva",
|
|
1343
|
+
RECIPE_PICK_HELPER,
|
|
1344
|
+
SPLIT_PROPS_HELPER,
|
|
1345
|
+
LEAF_HELPER
|
|
1346
|
+
]);
|
|
1347
|
+
/**
|
|
1348
|
+
* The pieces `trim` reduces a module specifier by, hoisted because a regex literal
|
|
1349
|
+
* constructs a new object every time it is evaluated and `trim` runs per specifier per
|
|
1350
|
+
* import declaration per module.
|
|
1351
|
+
*/
|
|
1352
|
+
const LEADING_RELATIVE = /^(?:\.\.?\/)+/;
|
|
1353
|
+
const TRAILING_SLASH = /\/$/;
|
|
1354
|
+
const MODULE_EXTENSION = /\.[mc]?[jt]sx?$/;
|
|
1355
|
+
const TRAILING_INDEX = /\/index$/;
|
|
1356
|
+
/**
|
|
1234
1357
|
* An argument that cannot run anything when it is evaluated.
|
|
1235
1358
|
*
|
|
1236
1359
|
* `token(path, fallback)` evaluates both arguments before the call, so a fold that drops
|
|
@@ -1446,7 +1569,7 @@ const argumentsAccountedFor = (call, boxNode) => {
|
|
|
1446
1569
|
return accountsForSource(args[0], boxNode);
|
|
1447
1570
|
};
|
|
1448
1571
|
const foldSource = (options) => {
|
|
1449
|
-
const { ctx, code, parserResult, partial: partial_ = true, runtimeCss = createRuntimeCss(ctx) } = options;
|
|
1572
|
+
const { ctx, code, parserResult, partial: partial_ = true, runtimeCss = createRuntimeCss(ctx), parseModule, recipeConfigCache, reportSurvivors, sourceFile: ownSourceFile } = options;
|
|
1450
1573
|
/**
|
|
1451
1574
|
* Recover the static half of a call the whole-call path gave up on. Only a
|
|
1452
1575
|
* single-argument `css()` qualifies: a pattern or recipe call takes props rather than a
|
|
@@ -1498,12 +1621,14 @@ const foldSource = (options) => {
|
|
|
1498
1621
|
className: plan.className,
|
|
1499
1622
|
classNames: [plan.className, ...plan.finite.filter((entry) => entry.emitsLiterals).flatMap((entry) => literalsIn(entry.expression))].filter(Boolean),
|
|
1500
1623
|
replacement: `${cx.name}(${parts.join(", ")})`,
|
|
1501
|
-
insert: cx.insert
|
|
1624
|
+
insert: cx.insert,
|
|
1625
|
+
runtimeCallee: runtimePart ? callee : void 0
|
|
1502
1626
|
};
|
|
1503
1627
|
};
|
|
1504
1628
|
const runtimeRecipe = createRuntimeRecipe(ctx);
|
|
1505
1629
|
const isConstantSlot = createConstantSlotCheck(ctx);
|
|
1506
1630
|
const runtimeToken = createRuntimeToken(ctx);
|
|
1631
|
+
const runtimeTokenVar = createRuntimeTokenVar(ctx);
|
|
1507
1632
|
/**
|
|
1508
1633
|
* Does this specifier name a module that exports the css API, exactly?
|
|
1509
1634
|
*
|
|
@@ -1528,7 +1653,25 @@ const foldSource = (options) => {
|
|
|
1528
1653
|
*/
|
|
1529
1654
|
const generatedCssModule = [ctx.imports.outdir, "css"].join("/");
|
|
1530
1655
|
const pathMappings = ctx.conf.tsOptions?.pathMappings;
|
|
1531
|
-
|
|
1656
|
+
/**
|
|
1657
|
+
* The spelling reduced to the module it names.
|
|
1658
|
+
*
|
|
1659
|
+
* The extension and `/index` are stripped because bamboo's own output makes a file
|
|
1660
|
+
* import them: `outExtension: 'js'` under NodeNext resolution is written
|
|
1661
|
+
* `styled-system/css/index.js`, which is neither equal to `styled-system/css` nor a
|
|
1662
|
+
* tail of it. Extraction admitted such a file anyway — `ImportMap.match` is
|
|
1663
|
+
* substring-based — so the call was folded while the *insert* was refused, and the
|
|
1664
|
+
* result was reported as `dynamic`: the same silent downgrade the alias case above
|
|
1665
|
+
* describes, reached through the extension instead.
|
|
1666
|
+
*
|
|
1667
|
+
* This does not weaken the equality the comment above insists on. `styled-system/css/css`
|
|
1668
|
+
* still names neither, because only a trailing `/index` is a module's own directory.
|
|
1669
|
+
*
|
|
1670
|
+
* `.d.ts` is deliberately not stripped. A declaration file exports no runtime binding, so
|
|
1671
|
+
* matching one would authorise inserting an import that resolves to nothing — and a value
|
|
1672
|
+
* import cannot name one anyway, which is what makes leaving it out free.
|
|
1673
|
+
*/
|
|
1674
|
+
const trim = (value) => value.replaceAll("\\", "/").replace(LEADING_RELATIVE, "").replace(TRAILING_SLASH, "").replace(MODULE_EXTENSION, "").replace(TRAILING_INDEX, "");
|
|
1532
1675
|
const matchesModule = (mod, entries) => {
|
|
1533
1676
|
const candidates = [mod];
|
|
1534
1677
|
if (pathMappings) {
|
|
@@ -1545,6 +1688,100 @@ const foldSource = (options) => {
|
|
|
1545
1688
|
};
|
|
1546
1689
|
const isBambooCssModule = (mod) => matchesModule(mod, cssModules);
|
|
1547
1690
|
const isGeneratedCssModule = (mod) => matchesModule(mod, [generatedCssModule]);
|
|
1691
|
+
/**
|
|
1692
|
+
* How *this* module would have to spell the css module, learnt from one that already does.
|
|
1693
|
+
*
|
|
1694
|
+
* A file calling an imported recipe need not import the css module at all, so when the
|
|
1695
|
+
* lowering needs `cvaPick` there is no spelling in the file to copy. The declaring module
|
|
1696
|
+
* necessarily has one — `cva` came from it — and that is the spelling reused here.
|
|
1697
|
+
*
|
|
1698
|
+
* A bare or aliased specifier resolves identically from any file, so it is taken as
|
|
1699
|
+
* written. A relative one is re-based: resolved against the module that wrote it, then
|
|
1700
|
+
* expressed from the module being folded.
|
|
1701
|
+
*/
|
|
1702
|
+
const cssModuleSpecifierFrom = (declaring) => {
|
|
1703
|
+
for (const declaration of declaring.getImportDeclarations()) {
|
|
1704
|
+
if (declaration.isTypeOnly()) continue;
|
|
1705
|
+
const mod = declaration.getModuleSpecifierValue();
|
|
1706
|
+
if (isGeneratedCssModule(mod)) return mod;
|
|
1707
|
+
}
|
|
1708
|
+
};
|
|
1709
|
+
/**
|
|
1710
|
+
* That spelling, said from the module being folded.
|
|
1711
|
+
*
|
|
1712
|
+
* A bare or aliased specifier resolves identically from any file, so it is taken as
|
|
1713
|
+
* written. A relative one is re-based: resolved against the module that wrote it, then
|
|
1714
|
+
* expressed from the module being folded. Pure path arithmetic, so it holds a string
|
|
1715
|
+
* rather than a node — a cached node does not survive the next `addSourceFile`, which
|
|
1716
|
+
* ts-morph implements by forgetting the file's whole tree.
|
|
1717
|
+
*/
|
|
1718
|
+
const rebaseSpecifier = (specifier, declaringPath, consumingPath) => {
|
|
1719
|
+
if (!specifier.startsWith(".")) return specifier;
|
|
1720
|
+
const absolute = (0, node_path.resolve)((0, node_path.dirname)(declaringPath), specifier);
|
|
1721
|
+
const rebased = (0, node_path.relative)((0, node_path.dirname)(consumingPath), absolute).replaceAll("\\", "/");
|
|
1722
|
+
if (!rebased) return void 0;
|
|
1723
|
+
return rebased.startsWith(".") ? rebased : `./${rebased}`;
|
|
1724
|
+
};
|
|
1725
|
+
/**
|
|
1726
|
+
* Configs of one foreign module, parsed once however many of its recipes are called.
|
|
1727
|
+
*
|
|
1728
|
+
* Falls back to a per-call map when the caller supplies none, so the fold stays correct
|
|
1729
|
+
* standalone — only repeated, which is what the shared cache exists to avoid.
|
|
1730
|
+
*/
|
|
1731
|
+
const configsByModule = recipeConfigCache ?? /* @__PURE__ */ new Map();
|
|
1732
|
+
/** The specifier each imported recipe's module used for the css module, when it needs one. */
|
|
1733
|
+
const helperModules = /* @__PURE__ */ new Map();
|
|
1734
|
+
/** Declaring modules a fold read, recorded as paths because their nodes do not persist. */
|
|
1735
|
+
const foreignDependencies = /* @__PURE__ */ new Set();
|
|
1736
|
+
/** Resolutions for this module's own call sites, keyed by the name the call site writes. */
|
|
1737
|
+
const importedRecipes = /* @__PURE__ */ new Map();
|
|
1738
|
+
/**
|
|
1739
|
+
* The config of a recipe this module imports.
|
|
1740
|
+
*
|
|
1741
|
+
* The binding is followed with ts-morph's symbol aliasing rather than by re-reading import
|
|
1742
|
+
* declarations, because that is what already understands the shapes these are reached
|
|
1743
|
+
* through: `export { badge } from './styles'`, `export * from './styles'`, and an alias at
|
|
1744
|
+
* either end. Each hop is an alias symbol, so following them to a non-alias lands on the
|
|
1745
|
+
* declaration wherever it lives.
|
|
1746
|
+
*
|
|
1747
|
+
* The class names do not depend on which module the call is in — `getRecipeIdentity` hashes
|
|
1748
|
+
* the config — so a recipe lowered here produces exactly the string its own module's call
|
|
1749
|
+
* sites produce, and exactly the one the runtime would have.
|
|
1750
|
+
*/
|
|
1751
|
+
const resolveImportedRecipe = (call, name, origin) => {
|
|
1752
|
+
if (importedRecipes.has(name)) return importedRecipes.get(name);
|
|
1753
|
+
const resolve = () => {
|
|
1754
|
+
if (!parseModule) return void 0;
|
|
1755
|
+
const consuming = call.getSourceFile();
|
|
1756
|
+
if (origin.filePath === consuming.getFilePath()) return void 0;
|
|
1757
|
+
let foreign = configsByModule.get(origin.filePath);
|
|
1758
|
+
if (!foreign) {
|
|
1759
|
+
const result = parseModule(origin.filePath);
|
|
1760
|
+
if (!result) return void 0;
|
|
1761
|
+
const collected = collectRecipeConfigs(result);
|
|
1762
|
+
const declaring = [...collected.values()].find((entry) => entry.box)?.box?.getNode?.()?.getSourceFile();
|
|
1763
|
+
const configs = /* @__PURE__ */ new Map();
|
|
1764
|
+
for (const [key, entry] of collected) configs.set(key, entry === AMBIGUOUS ? entry : {
|
|
1765
|
+
config: entry.config,
|
|
1766
|
+
name: entry.name,
|
|
1767
|
+
box: void 0
|
|
1768
|
+
});
|
|
1769
|
+
foreign = {
|
|
1770
|
+
configs,
|
|
1771
|
+
cssSpecifier: declaring ? cssModuleSpecifierFrom(declaring) : void 0
|
|
1772
|
+
};
|
|
1773
|
+
configsByModule.set(origin.filePath, foreign);
|
|
1774
|
+
}
|
|
1775
|
+
const entry = foreign.configs.get(origin.name);
|
|
1776
|
+
if (!entry || entry === AMBIGUOUS) return void 0;
|
|
1777
|
+
foreignDependencies.add(origin.filePath);
|
|
1778
|
+
helperModules.set(name, foreign.cssSpecifier ? rebaseSpecifier(foreign.cssSpecifier, origin.filePath, consuming.getFilePath()) : void 0);
|
|
1779
|
+
return entry;
|
|
1780
|
+
};
|
|
1781
|
+
const resolved = resolve();
|
|
1782
|
+
importedRecipes.set(name, resolved);
|
|
1783
|
+
return resolved;
|
|
1784
|
+
};
|
|
1548
1785
|
const folded = [];
|
|
1549
1786
|
const skipped = [];
|
|
1550
1787
|
const candidates = [];
|
|
@@ -1575,64 +1812,12 @@ const foldSource = (options) => {
|
|
|
1575
1812
|
}
|
|
1576
1813
|
return names;
|
|
1577
1814
|
};
|
|
1578
|
-
/**
|
|
1579
|
-
* Is this binding the variant half of `<recipe>.splitVariantProps(...)`?
|
|
1580
|
-
*
|
|
1581
|
-
* Read off the declaration rather than the type, so it is the same recipe and the same
|
|
1582
|
-
* destructuring position the source actually wrote.
|
|
1583
|
-
*/
|
|
1584
|
-
const isSplitVariantPropsOf = (binding, recipe) => {
|
|
1585
|
-
for (const declaration of binding.getSourceFile().getDescendantsOfKind(ts_morph.SyntaxKind.VariableDeclaration)) {
|
|
1586
|
-
const nameNode = declaration.getNameNode();
|
|
1587
|
-
if (!ts_morph.Node.isArrayBindingPattern(nameNode)) continue;
|
|
1588
|
-
const first = nameNode.getElements()[0];
|
|
1589
|
-
if (!first || !ts_morph.Node.isBindingElement(first)) continue;
|
|
1590
|
-
if (first.getNameNode().getText() !== binding.getText()) continue;
|
|
1591
|
-
const initializer = declaration.getInitializer();
|
|
1592
|
-
if (!initializer || !ts_morph.Node.isCallExpression(initializer)) return false;
|
|
1593
|
-
const callee = initializer.getExpression();
|
|
1594
|
-
if (!ts_morph.Node.isPropertyAccessExpression(callee)) return false;
|
|
1595
|
-
return callee.getName() === "splitVariantProps" && callee.getExpression().getText() === recipe;
|
|
1596
|
-
}
|
|
1597
|
-
return false;
|
|
1598
|
-
};
|
|
1599
|
-
/**
|
|
1600
|
-
* Lower a config recipe call the same way an inline one lowers.
|
|
1601
|
-
*
|
|
1602
|
-
* The config lives in `ctx.recipes` rather than in the module, and the classes are named
|
|
1603
|
-
* from it identically — so this is the same `lowerRecipeCall`, handed the config from a
|
|
1604
|
-
* different place. Slot recipes are excluded by the caller: they resolve to one class per
|
|
1605
|
-
* slot rather than to a string.
|
|
1606
|
-
*/
|
|
1607
|
-
const lowerConfigRecipeCall = (call, name) => {
|
|
1608
|
-
const config = ctx.recipes.getConfig(name);
|
|
1609
|
-
if (!config || config.slots !== void 0) return void 0;
|
|
1610
|
-
const className = config.className;
|
|
1611
|
-
if (!className) return void 0;
|
|
1612
|
-
if (!ts_morph.Node.isCallExpression(call)) return void 0;
|
|
1613
|
-
const argument = call.getArguments()[0];
|
|
1614
|
-
if (!argument || !ts_morph.Node.isIdentifier(argument) || !isSplitVariantPropsOf(argument, name)) return void 0;
|
|
1615
|
-
const lowered = lowerRecipeCall(call, {
|
|
1616
|
-
config,
|
|
1617
|
-
name: className,
|
|
1618
|
-
box: void 0
|
|
1619
|
-
}, ctx, isInertExpression);
|
|
1620
|
-
if (lowered.kind !== "expression") return void 0;
|
|
1621
|
-
const helper = ensureRecipeHelperImport(RECIPE_PICK_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed);
|
|
1622
|
-
if (!helper) return void 0;
|
|
1623
|
-
return {
|
|
1624
|
-
replacement: helper.name === "cvaPick" ? lowered.expression : lowered.expression.replaceAll(`${RECIPE_PICK_HELPER}(`, `${helper.name}(`),
|
|
1625
|
-
className: lowered.staticClasses,
|
|
1626
|
-
classNames: lowered.classNames,
|
|
1627
|
-
insert: helper.insert
|
|
1628
|
-
};
|
|
1629
|
-
};
|
|
1630
1815
|
for (const item of parserResult.toArray()) {
|
|
1631
1816
|
const type = item.type ?? "";
|
|
1632
1817
|
const name = item.name ?? type;
|
|
1633
1818
|
if (!item.box) continue;
|
|
1634
1819
|
const call = findCallExpression(item.box);
|
|
1635
|
-
if (type === "token") {
|
|
1820
|
+
if (type === "token" || type === "tokenVar") {
|
|
1636
1821
|
if (!call) {
|
|
1637
1822
|
skipped.push({
|
|
1638
1823
|
name,
|
|
@@ -1667,7 +1852,18 @@ const foldSource = (options) => {
|
|
|
1667
1852
|
continue;
|
|
1668
1853
|
}
|
|
1669
1854
|
const callee = ts_morph.Node.isCallExpression(call) ? call.getExpression() : void 0;
|
|
1670
|
-
|
|
1855
|
+
const propertyName = ts_morph.Node.isPropertyAccessExpression(callee) ? callee.getNameNode().getText() : void 0;
|
|
1856
|
+
const wantsVar = type === "tokenVar";
|
|
1857
|
+
if (wantsVar !== (propertyName === "var")) {
|
|
1858
|
+
skipped.push({
|
|
1859
|
+
name,
|
|
1860
|
+
reason: "unsupported-kind",
|
|
1861
|
+
start,
|
|
1862
|
+
end
|
|
1863
|
+
});
|
|
1864
|
+
continue;
|
|
1865
|
+
}
|
|
1866
|
+
if (!wantsVar && propertyName !== void 0 && !ctx.imports.matchers.tokens.match(propertyName)) {
|
|
1671
1867
|
skipped.push({
|
|
1672
1868
|
name,
|
|
1673
1869
|
reason: "unsupported-kind",
|
|
@@ -1704,7 +1900,7 @@ const foldSource = (options) => {
|
|
|
1704
1900
|
});
|
|
1705
1901
|
continue;
|
|
1706
1902
|
}
|
|
1707
|
-
const value = runtimeToken(path);
|
|
1903
|
+
const value = wantsVar ? runtimeTokenVar(path) : runtimeToken(path);
|
|
1708
1904
|
if (!value) {
|
|
1709
1905
|
skipped.push({
|
|
1710
1906
|
name,
|
|
@@ -1747,6 +1943,10 @@ const foldSource = (options) => {
|
|
|
1747
1943
|
continue;
|
|
1748
1944
|
}
|
|
1749
1945
|
recipeConfigs ??= collectRecipeConfigs(parserResult);
|
|
1946
|
+
if (!recipeConfigs.has(name) && item.origin) {
|
|
1947
|
+
const imported = resolveImportedRecipe(call, name, item.origin);
|
|
1948
|
+
if (imported) recipeConfigs.set(name, imported);
|
|
1949
|
+
}
|
|
1750
1950
|
const tally = recipeCalls.get(name) ?? {
|
|
1751
1951
|
seen: 0,
|
|
1752
1952
|
lowered: 0
|
|
@@ -1757,7 +1957,7 @@ const foldSource = (options) => {
|
|
|
1757
1957
|
const entry = recipeConfigs.get(name);
|
|
1758
1958
|
const lowered = lowerRecipeCall(call, entry, ctx, isInertExpression, resolvedSelection);
|
|
1759
1959
|
if (lowered.kind === "expression") {
|
|
1760
|
-
const helper = ensureRecipeHelperImport(RECIPE_PICK_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed);
|
|
1960
|
+
const helper = ensureRecipeHelperImport(RECIPE_PICK_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed, helperModules.get(name));
|
|
1761
1961
|
if (helper) {
|
|
1762
1962
|
tally.lowered++;
|
|
1763
1963
|
candidates.push({
|
|
@@ -1871,25 +2071,19 @@ const foldSource = (options) => {
|
|
|
1871
2071
|
if (!(ts_morph.Node.isCallExpression(call) && call.getArguments().length === 0 && item.data.length === 1) && !isStaticBox(item.box) || !hasStyles(item.data) || !argumentsAccountedFor(call, item.box)) {
|
|
1872
2072
|
const partial = partial_ ? tryPartial(item, call, rootName) : void 0;
|
|
1873
2073
|
if (partial) {
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
node: call,
|
|
2074
|
+
if (reportSurvivors && partial.runtimeCallee) skipped.push({
|
|
2075
|
+
name,
|
|
2076
|
+
reason: "runtime-binding",
|
|
1878
2077
|
start,
|
|
1879
|
-
end
|
|
1880
|
-
...partial
|
|
2078
|
+
end
|
|
1881
2079
|
});
|
|
1882
|
-
continue;
|
|
1883
|
-
}
|
|
1884
|
-
const lowered = type === "recipe" && !slot ? lowerConfigRecipeCall(call, name) : void 0;
|
|
1885
|
-
if (lowered) {
|
|
1886
2080
|
candidates.push({
|
|
1887
2081
|
item,
|
|
1888
2082
|
call,
|
|
1889
2083
|
node: call,
|
|
1890
2084
|
start,
|
|
1891
2085
|
end,
|
|
1892
|
-
...
|
|
2086
|
+
...partial
|
|
1893
2087
|
});
|
|
1894
2088
|
continue;
|
|
1895
2089
|
}
|
|
@@ -1910,13 +2104,22 @@ const foldSource = (options) => {
|
|
|
1910
2104
|
slot
|
|
1911
2105
|
});
|
|
1912
2106
|
}
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
2107
|
+
/**
|
|
2108
|
+
* Ranges the rewrite actually replaced. Declared before the early return below, because
|
|
2109
|
+
* that return is now also a reporting point: a module with nothing to fold is exactly the
|
|
2110
|
+
* shape `reportSurvivors` exists to catch.
|
|
2111
|
+
*/
|
|
2112
|
+
const applied = [];
|
|
2113
|
+
if (candidates.length === 0) {
|
|
2114
|
+
if (reportSurvivors) reportRuntimeBindings();
|
|
2115
|
+
return {
|
|
2116
|
+
code,
|
|
2117
|
+
map: null,
|
|
2118
|
+
folded,
|
|
2119
|
+
skipped,
|
|
2120
|
+
dependencies: []
|
|
2121
|
+
};
|
|
2122
|
+
}
|
|
1920
2123
|
const dependencyScan = createDependencyScan(candidates[0].node.getSourceFile());
|
|
1921
2124
|
candidates.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
1922
2125
|
const magic = new magic_string.default(code);
|
|
@@ -1925,10 +2128,9 @@ const foldSource = (options) => {
|
|
|
1925
2128
|
if (!insert) return;
|
|
1926
2129
|
const missing = insert.names.filter((name) => !insertedNames.has(name));
|
|
1927
2130
|
if (!missing.length) return;
|
|
1928
|
-
magic.appendLeft(insert.pos, missing.map((name) => `, ${name}`).join(""));
|
|
2131
|
+
magic.appendLeft(insert.pos, insert.module ? `\nimport { ${missing.join(", ")} } from '${insert.module}'` : missing.map((name) => `, ${name}`).join(""));
|
|
1929
2132
|
for (const name of missing) insertedNames.add(name);
|
|
1930
2133
|
};
|
|
1931
|
-
const applied = [];
|
|
1932
2134
|
const collides = (edits) => edits.some(([start, end]) => applied.some(([from, to]) => start < to && from < end));
|
|
1933
2135
|
for (const candidate of candidates) {
|
|
1934
2136
|
const { item, start, end } = candidate;
|
|
@@ -2020,7 +2222,7 @@ const foldSource = (options) => {
|
|
|
2020
2222
|
});
|
|
2021
2223
|
collectSourceFiles(item.box, dependencyScan);
|
|
2022
2224
|
}
|
|
2023
|
-
const recipeSourceFile =
|
|
2225
|
+
const recipeSourceFile = candidates[0]?.node.getSourceFile();
|
|
2024
2226
|
if (recipeSourceFile) for (const access of recipeSourceFile.getDescendantsOfKind(ts_morph.SyntaxKind.PropertyAccessExpression)) {
|
|
2025
2227
|
if (access.getName() !== "splitVariantProps") continue;
|
|
2026
2228
|
const target = access.getExpression();
|
|
@@ -2042,7 +2244,7 @@ const foldSource = (options) => {
|
|
|
2042
2244
|
const end = call.getEnd();
|
|
2043
2245
|
if (code.slice(start, end) !== call.getText()) continue;
|
|
2044
2246
|
if (collides([[start, end]])) continue;
|
|
2045
|
-
const helper = ensureRecipeHelperImport(SPLIT_PROPS_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed);
|
|
2247
|
+
const helper = ensureRecipeHelperImport(SPLIT_PROPS_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed, helperModules.get(target.getText()));
|
|
2046
2248
|
if (!helper) continue;
|
|
2047
2249
|
const keys = Object.keys(entry.config.variants ?? {});
|
|
2048
2250
|
magic.overwrite(start, end, `${helper.name}(${args[0].getText()}, ${JSON.stringify(keys)})`);
|
|
@@ -2060,6 +2262,104 @@ const foldSource = (options) => {
|
|
|
2060
2262
|
if (code.slice(start, call.getEnd()) !== call.getText()) continue;
|
|
2061
2263
|
magic.appendLeft(start, "/*#__PURE__*/");
|
|
2062
2264
|
}
|
|
2265
|
+
/**
|
|
2266
|
+
* Bindings from a bamboo module still referenced once every rewrite is applied.
|
|
2267
|
+
*
|
|
2268
|
+
* Deliberately not driven by `parserResult`: that is the recogniser, and the point here is
|
|
2269
|
+
* to catch what it did not see. A namespace import called as `s.cva(...)`, a default
|
|
2270
|
+
* import, a specifier that resolved to nothing — each leaves a live reference and no ledger
|
|
2271
|
+
* entry at all, which is how `strict` came to pass a build that still shipped the engine.
|
|
2272
|
+
*
|
|
2273
|
+
* The helpers the fold itself writes are excluded: `cx`, `cvaPick`, `splitProps` and the
|
|
2274
|
+
* leaf helper live in `cx` and pull no engine, so a reference to one is the fold working
|
|
2275
|
+
* rather than failing.
|
|
2276
|
+
*/
|
|
2277
|
+
function reportRuntimeBindings() {
|
|
2278
|
+
const sourceFile = ownSourceFile ?? candidates[0]?.node.getSourceFile();
|
|
2279
|
+
if (!sourceFile) return;
|
|
2280
|
+
const bambooModules = [
|
|
2281
|
+
...cssModules,
|
|
2282
|
+
...ctx.imports.matchers.recipe?.mods ?? [],
|
|
2283
|
+
...ctx.imports.matchers.pattern?.mods ?? [],
|
|
2284
|
+
...ctx.imports.matchers.tokens?.mods ?? []
|
|
2285
|
+
];
|
|
2286
|
+
/** Local name -> what to call it in the report. */
|
|
2287
|
+
const watched = /* @__PURE__ */ new Map();
|
|
2288
|
+
for (const declaration of sourceFile.getImportDeclarations()) {
|
|
2289
|
+
if (declaration.isTypeOnly()) continue;
|
|
2290
|
+
if (!matchesModule(declaration.getModuleSpecifierValue(), bambooModules)) continue;
|
|
2291
|
+
for (const named of declaration.getNamedImports()) {
|
|
2292
|
+
if (named.isTypeOnly()) continue;
|
|
2293
|
+
const imported = named.getNameNode().getText();
|
|
2294
|
+
if (PERMITTED_BINDINGS.has(imported)) continue;
|
|
2295
|
+
watched.set((named.getAliasNode() ?? named.getNameNode()).getText(), imported);
|
|
2296
|
+
}
|
|
2297
|
+
const namespace = declaration.getNamespaceImport();
|
|
2298
|
+
if (namespace) watched.set(namespace.getText(), `${namespace.getText()}.*`);
|
|
2299
|
+
const defaultImport = declaration.getDefaultImport();
|
|
2300
|
+
if (defaultImport) watched.set(defaultImport.getText(), defaultImport.getText());
|
|
2301
|
+
}
|
|
2302
|
+
for (const declaration of sourceFile.getExportDeclarations()) {
|
|
2303
|
+
if (declaration.isTypeOnly()) continue;
|
|
2304
|
+
if (!matchesModule(declaration.getModuleSpecifierValue() ?? "", bambooModules)) continue;
|
|
2305
|
+
if (declaration.isNamespaceExport()) {
|
|
2306
|
+
skipped.push({
|
|
2307
|
+
name: declaration.getNamespaceExport()?.getName() ?? "*",
|
|
2308
|
+
reason: "runtime-binding",
|
|
2309
|
+
start: declaration.getStart(),
|
|
2310
|
+
end: declaration.getEnd()
|
|
2311
|
+
});
|
|
2312
|
+
continue;
|
|
2313
|
+
}
|
|
2314
|
+
for (const named of declaration.getNamedExports()) {
|
|
2315
|
+
if (named.isTypeOnly()) continue;
|
|
2316
|
+
const imported = named.getNameNode().getText();
|
|
2317
|
+
if (PERMITTED_BINDINGS.has(imported)) continue;
|
|
2318
|
+
skipped.push({
|
|
2319
|
+
name: imported,
|
|
2320
|
+
reason: "runtime-binding",
|
|
2321
|
+
start: named.getStart(),
|
|
2322
|
+
end: named.getEnd()
|
|
2323
|
+
});
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
for (const declaration of sourceFile.getExportDeclarations()) {
|
|
2327
|
+
if (declaration.isTypeOnly() || declaration.getModuleSpecifier()) continue;
|
|
2328
|
+
for (const named of declaration.getNamedExports()) {
|
|
2329
|
+
if (named.isTypeOnly()) continue;
|
|
2330
|
+
const imported = watched.get(named.getNameNode().getText());
|
|
2331
|
+
if (imported === void 0) continue;
|
|
2332
|
+
skipped.push({
|
|
2333
|
+
name: imported,
|
|
2334
|
+
reason: "runtime-binding",
|
|
2335
|
+
start: named.getStart(),
|
|
2336
|
+
end: named.getEnd()
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
if (watched.size === 0) return;
|
|
2341
|
+
const declined = skipped.filter((entry) => SURVIVES_TO_RUNTIME.has(entry.reason) && entry.end > entry.start).map((entry) => [entry.start, entry.end]);
|
|
2342
|
+
const reported = /* @__PURE__ */ new Set();
|
|
2343
|
+
for (const identifier of sourceFile.getDescendantsOfKind(ts_morph.SyntaxKind.Identifier)) {
|
|
2344
|
+
const local = identifier.getText();
|
|
2345
|
+
const imported = watched.get(local);
|
|
2346
|
+
if (imported === void 0 || reported.has(local)) continue;
|
|
2347
|
+
const start = identifier.getStart();
|
|
2348
|
+
if (identifier.getFirstAncestorByKind(ts_morph.SyntaxKind.ImportDeclaration)) continue;
|
|
2349
|
+
if (applied.some(([from, to]) => start >= from && start < to)) continue;
|
|
2350
|
+
if (declined.some(([from, to]) => start >= from && start < to)) continue;
|
|
2351
|
+
if (!isValueReference(identifier)) continue;
|
|
2352
|
+
if (isShadowed(identifier, local)) continue;
|
|
2353
|
+
reported.add(local);
|
|
2354
|
+
skipped.push({
|
|
2355
|
+
name: imported,
|
|
2356
|
+
reason: "runtime-binding",
|
|
2357
|
+
start,
|
|
2358
|
+
end: identifier.getEnd()
|
|
2359
|
+
});
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
if (reportSurvivors) reportRuntimeBindings();
|
|
2063
2363
|
if (folded.length === 0) return {
|
|
2064
2364
|
code,
|
|
2065
2365
|
map: null,
|
|
@@ -2076,7 +2376,7 @@ const foldSource = (options) => {
|
|
|
2076
2376
|
}),
|
|
2077
2377
|
folded,
|
|
2078
2378
|
skipped,
|
|
2079
|
-
dependencies:
|
|
2379
|
+
dependencies: [...dependencyScan.results, ...foreignDependencies]
|
|
2080
2380
|
};
|
|
2081
2381
|
};
|
|
2082
2382
|
//#endregion
|
|
@@ -2110,21 +2410,6 @@ const isGeneratedOutput = (filePath, ctx) => {
|
|
|
2110
2410
|
const file = slashed(filePath);
|
|
2111
2411
|
return file === root || file.startsWith(`${root}/`);
|
|
2112
2412
|
};
|
|
2113
|
-
/**
|
|
2114
|
-
* The skip reasons that leave a `css()`-family call in the output.
|
|
2115
|
-
*
|
|
2116
|
-
* `overlapping` is handled by the enclosing fold, and `not-imported` is somebody else's
|
|
2117
|
-
* function of the same name — neither leaves a call of ours. `not-foldable` is a `cva`/`sva`
|
|
2118
|
-
* definition, which keeps the recipe runtime rather than the css engine; see `strict`.
|
|
2119
|
-
*/
|
|
2120
|
-
const SURVIVES_TO_RUNTIME = new Set([
|
|
2121
|
-
"dynamic",
|
|
2122
|
-
"raw-call",
|
|
2123
|
-
"unsupported-kind",
|
|
2124
|
-
"no-call-expression",
|
|
2125
|
-
"empty",
|
|
2126
|
-
"unresolved-token"
|
|
2127
|
-
]);
|
|
2128
2413
|
/** 1-indexed line of a source offset, for an error a user can navigate to. */
|
|
2129
2414
|
const lineAt = (code, offset) => code.slice(0, offset).split("\n").length;
|
|
2130
2415
|
const formatSkipped = (id, skipped) => {
|
|
@@ -2155,6 +2440,13 @@ const bamboocss = (options = {}) => {
|
|
|
2155
2440
|
};
|
|
2156
2441
|
/** Under `strict`, every call that would still reach the runtime. */
|
|
2157
2442
|
const survivors = [];
|
|
2443
|
+
/**
|
|
2444
|
+
* Recipe configs read out of modules other than the one being transformed.
|
|
2445
|
+
*
|
|
2446
|
+
* Per build rather than per module: a recipe declared once and imported by fifty components
|
|
2447
|
+
* would otherwise re-parse its module fifty times, which is the transform path.
|
|
2448
|
+
*/
|
|
2449
|
+
const recipeConfigCache = /* @__PURE__ */ new Map();
|
|
2158
2450
|
let ctx;
|
|
2159
2451
|
let runtimeCss;
|
|
2160
2452
|
let setup;
|
|
@@ -2182,6 +2474,7 @@ const bamboocss = (options = {}) => {
|
|
|
2182
2474
|
totals.filesWithFolds = 0;
|
|
2183
2475
|
totals.skipped.clear();
|
|
2184
2476
|
survivors.length = 0;
|
|
2477
|
+
recipeConfigCache.clear();
|
|
2185
2478
|
await ensureContext();
|
|
2186
2479
|
},
|
|
2187
2480
|
/**
|
|
@@ -2209,6 +2502,7 @@ const bamboocss = (options = {}) => {
|
|
|
2209
2502
|
if (!shouldTransform(id)) return;
|
|
2210
2503
|
const [filePath] = id.split("?");
|
|
2211
2504
|
if (!filePath) return;
|
|
2505
|
+
recipeConfigCache.clear();
|
|
2212
2506
|
if (change.event === "delete") {
|
|
2213
2507
|
ctx.project.removeSourceFile(filePath);
|
|
2214
2508
|
return;
|
|
@@ -2224,16 +2518,20 @@ const bamboocss = (options = {}) => {
|
|
|
2224
2518
|
if (isGeneratedOutput(filePath, ctx)) return null;
|
|
2225
2519
|
let result;
|
|
2226
2520
|
try {
|
|
2227
|
-
ctx.project.addSourceFile(filePath, code);
|
|
2521
|
+
const sourceFile = ctx.project.addSourceFile(filePath, code);
|
|
2228
2522
|
const parserResult = ctx.project.parseSourceFile(filePath);
|
|
2229
|
-
if (!parserResult || parserResult.isEmpty()) return null;
|
|
2523
|
+
if (!parserResult || parserResult.isEmpty() && !strict) return null;
|
|
2230
2524
|
result = foldSource({
|
|
2231
2525
|
ctx,
|
|
2232
2526
|
code,
|
|
2233
2527
|
parserResult,
|
|
2234
2528
|
filePath,
|
|
2235
2529
|
runtimeCss,
|
|
2236
|
-
partial
|
|
2530
|
+
partial,
|
|
2531
|
+
parseModule: (path) => ctx?.project.parseSourceFile(path),
|
|
2532
|
+
recipeConfigCache,
|
|
2533
|
+
reportSurvivors: strict,
|
|
2534
|
+
sourceFile
|
|
2237
2535
|
});
|
|
2238
2536
|
} catch (error) {
|
|
2239
2537
|
_bamboocss_logger.logger.caughtError("vite:transform", `Failed to fold ${filePath}`, error);
|
|
@@ -2277,7 +2575,7 @@ const bamboocss = (options = {}) => {
|
|
|
2277
2575
|
list.push(entry);
|
|
2278
2576
|
byFile.set(entry.file, list);
|
|
2279
2577
|
}
|
|
2280
|
-
const detail = Array.from(byFile.entries()).map(([file, entries]) => [` ${file}`, ...entries.map((e) => ` ${e.line}: ${e.name}() — ${e.reason}`)].join("\n")).join("\n");
|
|
2578
|
+
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");
|
|
2281
2579
|
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.`);
|
|
2282
2580
|
}
|
|
2283
2581
|
if (!transform || !reportSummary) return;
|