@bamboocss/extractor 1.15.0 → 1.16.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 +80 -44
- package/dist/index.d.cts +28 -16
- package/dist/index.d.mts +28 -16
- package/dist/index.mjs +80 -44
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -116,6 +116,29 @@ const recipeProps = [
|
|
|
116
116
|
var BoxNodeMap = class extends BoxNodeType {
|
|
117
117
|
value;
|
|
118
118
|
spreadConditions;
|
|
119
|
+
/**
|
|
120
|
+
* Spreads the extractor walked structurally, paired with what it walked them into.
|
|
121
|
+
*
|
|
122
|
+
* The map records what a spread *contributed*, so a spread that flattened and one that
|
|
123
|
+
* was silently skipped look identical once folded in — both simply add keys, or fail to.
|
|
124
|
+
* That ambiguity is why a consumer rewriting source would otherwise have to decline every
|
|
125
|
+
* spread rather than only the ones it cannot account for.
|
|
126
|
+
*
|
|
127
|
+
* The *walked* ones are listed rather than the skipped ones deliberately: a consumer asks
|
|
128
|
+
* "may I trust this spread", and a list of failures answers that only while it is
|
|
129
|
+
* exhaustive. A list of successes is safe to be incomplete — the worst an omission costs
|
|
130
|
+
* is a fold that does not happen.
|
|
131
|
+
*
|
|
132
|
+
* `box` is the map the spread flattened, and being here is **not** a promise that every
|
|
133
|
+
* one of its keys survived — the extractor omits what it cannot evaluate, at any depth. It
|
|
134
|
+
* is the handle a consumer needs to go and check for itself, which it cannot do from the
|
|
135
|
+
* flattened result. `node` is the spread's own expression, so the pair can be matched
|
|
136
|
+
* against the source being inspected.
|
|
137
|
+
*
|
|
138
|
+
* Deliberately kept off `value`, and therefore invisible to `unbox` — this describes the
|
|
139
|
+
* extraction, not the styles, and nothing that generates CSS should see it.
|
|
140
|
+
*/
|
|
141
|
+
resolvedSpreads;
|
|
119
142
|
constructor(definition) {
|
|
120
143
|
super(definition);
|
|
121
144
|
this.value = definition.value;
|
|
@@ -440,6 +463,8 @@ const getObjectLiteralExpressionPropPairs = (expression, expressionStack, ctx, m
|
|
|
440
463
|
if (properties.length === 0) return box.emptyObject(expression, expressionStack);
|
|
441
464
|
const extractedPropValues = [];
|
|
442
465
|
const spreadConditions = [];
|
|
466
|
+
/** Spreads the extractor walked structurally — see `BoxNodeMap.resolvedSpreads`. */
|
|
467
|
+
const resolvedSpreads = [];
|
|
443
468
|
properties.forEach((property) => {
|
|
444
469
|
const stack = [...expressionStack];
|
|
445
470
|
stack.push(property);
|
|
@@ -492,6 +517,10 @@ const getObjectLiteralExpressionPropPairs = (expression, expressionStack, ctx, m
|
|
|
492
517
|
maybeObject.value.forEach((nested, propName) => {
|
|
493
518
|
extractedPropValues.push([propName, nested]);
|
|
494
519
|
});
|
|
520
|
+
resolvedSpreads.push({
|
|
521
|
+
node: initializer,
|
|
522
|
+
box: maybeObject
|
|
523
|
+
});
|
|
495
524
|
return;
|
|
496
525
|
}
|
|
497
526
|
if (box.isConditional(maybeObject)) spreadConditions.push(maybeObject);
|
|
@@ -503,6 +532,7 @@ const getObjectLiteralExpressionPropPairs = (expression, expressionStack, ctx, m
|
|
|
503
532
|
orderedMapValue.set(propName, value);
|
|
504
533
|
});
|
|
505
534
|
const map = box.map(orderedMapValue, expression, expressionStack);
|
|
535
|
+
if (resolvedSpreads.length > 0) map.resolvedSpreads = resolvedSpreads;
|
|
506
536
|
if (spreadConditions.length > 0) map.spreadConditions = spreadConditions;
|
|
507
537
|
return map;
|
|
508
538
|
};
|
|
@@ -631,6 +661,7 @@ function maybeBoxNode(node, stack, ctx, matchProp) {
|
|
|
631
661
|
if (isPlusSyntax(operatorKind)) {
|
|
632
662
|
const value = tryComputingPlusTokenBinaryExpressionToString(node, stack, ctx) ?? safeEvaluateNode(node, stack, ctx);
|
|
633
663
|
if (!value) return;
|
|
664
|
+
if (typeof value === "string" && value.includes("undefined")) return;
|
|
634
665
|
return cache(box.from(value, node, stack));
|
|
635
666
|
}
|
|
636
667
|
if (isLogicalSyntax(operatorKind)) return cache(maybeResolveConditionalExpression({
|
|
@@ -1348,6 +1379,34 @@ const runtimeProfiles = [
|
|
|
1348
1379
|
getPropNodes: getArgsAt(1)
|
|
1349
1380
|
}
|
|
1350
1381
|
];
|
|
1382
|
+
/**
|
|
1383
|
+
* The declaration `identifier` binds to, found by walking its enclosing scopes outward.
|
|
1384
|
+
*
|
|
1385
|
+
* Deliberately not `identifier.getDefinitions()`. That is a language-service query, and
|
|
1386
|
+
* the first one forces `synchronizeHostData` -> `createProgram`, which resolves, parses
|
|
1387
|
+
* and binds the whole transitive `.d.ts` closure of the project — in a 5-file sandbox
|
|
1388
|
+
* that is 161 files and 5.1MB, most of it `node_modules`, and it grows with the
|
|
1389
|
+
* dependency graph rather than with the user's source. The extractor is built to avoid
|
|
1390
|
+
* exactly that: `createTsProject` sets `skipAddingFilesFromTsConfig`,
|
|
1391
|
+
* `skipFileDependencyResolution` and `skipLoadingLibFiles`, and `getModuleSpecifierSourceFile`
|
|
1392
|
+
* carries the same note. Inside a bundler the program is built alongside the module graph
|
|
1393
|
+
* in one heap, so the cost lands as a slow build and then an OOM.
|
|
1394
|
+
*
|
|
1395
|
+
* A lexical walk is enough because this only ever runs for a callee that is declared in
|
|
1396
|
+
* this file: `resolveCallee` matches against the import map first, and `collectImports`
|
|
1397
|
+
* covers every import form. Innermost scope wins, which is how the binding resolves
|
|
1398
|
+
* anyway, and a scope can hold only one declaration of a given name.
|
|
1399
|
+
*/
|
|
1400
|
+
const findLocalDeclaration = (identifier) => {
|
|
1401
|
+
const name = identifier.getText();
|
|
1402
|
+
for (let scope = identifier.getParent(); scope; scope = scope.getParent()) {
|
|
1403
|
+
if (!ts_morph.Node.isStatemented(scope)) continue;
|
|
1404
|
+
const variable = scope.getVariableDeclaration(name);
|
|
1405
|
+
if (variable) return variable;
|
|
1406
|
+
const fn = scope.getFunction(name);
|
|
1407
|
+
if (fn) return fn;
|
|
1408
|
+
}
|
|
1409
|
+
};
|
|
1351
1410
|
const createCompiledJsxContext = (sourceFile) => {
|
|
1352
1411
|
const imports = collectImports(sourceFile);
|
|
1353
1412
|
const normalizeCallee = (node) => {
|
|
@@ -1374,30 +1433,26 @@ const createCompiledJsxContext = (sourceFile) => {
|
|
|
1374
1433
|
if (ts_morph.Node.isConditionalExpression(expression)) return resolveLocalAlias(expression.getWhenTrue()) ?? resolveLocalAlias(expression.getWhenFalse());
|
|
1375
1434
|
if (ts_morph.Node.isBinaryExpression(expression) && expression.getOperatorToken().getKind() === ts_morph.SyntaxKind.CommaToken) return resolveLocalAlias(expression.getRight());
|
|
1376
1435
|
};
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
return resolved;
|
|
1389
|
-
}
|
|
1390
|
-
if (ts_morph.Node.isVariableDeclaration(declaration)) {
|
|
1391
|
-
const directImport = resolveBundledHelperImport(declaration.getName(), declaration.getInitializer());
|
|
1392
|
-
const aliasImport = directImport ? {
|
|
1393
|
-
mod: directImport.mod,
|
|
1394
|
-
importedName: directImport.importedName
|
|
1395
|
-
} : resolveLocalAlias(declaration.getInitializer());
|
|
1396
|
-
if (!aliasImport) continue;
|
|
1397
|
-
localDefinitionCache.set(name, aliasImport);
|
|
1398
|
-
return aliasImport;
|
|
1399
|
-
}
|
|
1436
|
+
const declaration = findLocalDeclaration(identifier);
|
|
1437
|
+
if (!declaration) return;
|
|
1438
|
+
if (ts_morph.Node.isFunctionDeclaration(declaration)) {
|
|
1439
|
+
const imported = resolveBundledHelperImport(declaration.getName() ?? name, declaration);
|
|
1440
|
+
if (!imported) return;
|
|
1441
|
+
const resolved = {
|
|
1442
|
+
mod: imported.mod,
|
|
1443
|
+
importedName: imported.importedName
|
|
1444
|
+
};
|
|
1445
|
+
localDefinitionCache.set(name, resolved);
|
|
1446
|
+
return resolved;
|
|
1400
1447
|
}
|
|
1448
|
+
const directImport = resolveBundledHelperImport(declaration.getName(), declaration.getInitializer());
|
|
1449
|
+
const aliasImport = directImport ? {
|
|
1450
|
+
mod: directImport.mod,
|
|
1451
|
+
importedName: directImport.importedName
|
|
1452
|
+
} : resolveLocalAlias(declaration.getInitializer());
|
|
1453
|
+
if (!aliasImport) return;
|
|
1454
|
+
localDefinitionCache.set(name, aliasImport);
|
|
1455
|
+
return aliasImport;
|
|
1401
1456
|
};
|
|
1402
1457
|
const resolveCallee = (node) => {
|
|
1403
1458
|
const expression = normalizeCallee(node);
|
|
@@ -1492,7 +1547,7 @@ const objectLikeToMap = (maybeObject, node) => {
|
|
|
1492
1547
|
const isImportOrExport = (node) => ts_morph.Node.isImportDeclaration(node) || ts_morph.Node.isExportDeclaration(node);
|
|
1493
1548
|
const isJsxElement = (node) => ts_morph.Node.isJsxOpeningElement(node) || ts_morph.Node.isJsxSelfClosingElement(node);
|
|
1494
1549
|
const extract = ({ ast, ...ctx }) => {
|
|
1495
|
-
const { components, functions
|
|
1550
|
+
const { components, functions } = ctx;
|
|
1496
1551
|
const compiledJsx = createCompiledJsxContext(ast);
|
|
1497
1552
|
/** contains all the extracted nodes from this ast parsing */
|
|
1498
1553
|
const byName = /* @__PURE__ */ new Map();
|
|
@@ -1662,6 +1717,7 @@ const extract = ({ ast, ...ctx }) => {
|
|
|
1662
1717
|
});
|
|
1663
1718
|
const boxMap = box.map(mapValue, node, boxNode.getStack());
|
|
1664
1719
|
if (box.isMap(boxNode) && boxNode.spreadConditions?.length) boxMap.spreadConditions = boxNode.spreadConditions;
|
|
1720
|
+
if (box.isMap(boxNode) && boxNode.resolvedSpreads?.length) boxMap.resolvedSpreads = boxNode.resolvedSpreads;
|
|
1665
1721
|
return boxMap;
|
|
1666
1722
|
}
|
|
1667
1723
|
return boxNode;
|
|
@@ -1673,26 +1729,6 @@ const extract = ({ ast, ...ctx }) => {
|
|
|
1673
1729
|
};
|
|
1674
1730
|
fnResultMap.queryList.push(query);
|
|
1675
1731
|
}
|
|
1676
|
-
if (taggedTemplates && ts_morph.Node.isTaggedTemplateExpression(node)) {
|
|
1677
|
-
const tag = node.getTag();
|
|
1678
|
-
const fnName = ts_morph.Node.isCallExpression(tag) ? tag.getExpression().getText() : tag.getText();
|
|
1679
|
-
if (!taggedTemplates.matchTaggedTemplate({
|
|
1680
|
-
taggedTemplateNode: node,
|
|
1681
|
-
fnName
|
|
1682
|
-
})) return;
|
|
1683
|
-
if (!byName.has(fnName)) byName.set(fnName, {
|
|
1684
|
-
kind: "function",
|
|
1685
|
-
nodesByProp: /* @__PURE__ */ new Map(),
|
|
1686
|
-
queryList: []
|
|
1687
|
-
});
|
|
1688
|
-
const fnResultMap = byName.get(fnName);
|
|
1689
|
-
const query = {
|
|
1690
|
-
kind: "tagged-template",
|
|
1691
|
-
name: fnName,
|
|
1692
|
-
box: maybeBoxNode(node, [], ctx)
|
|
1693
|
-
};
|
|
1694
|
-
fnResultMap.queryList.push(query);
|
|
1695
|
-
}
|
|
1696
1732
|
});
|
|
1697
1733
|
componentByNode.forEach((parentRef, componentNode) => {
|
|
1698
1734
|
const component = componentByNode.get(componentNode);
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BindingElement, CallExpression, EnumDeclaration, Expression, FunctionDeclaration, GetAccessorDeclaration, Identifier, JsxAttribute, JsxOpeningElement, JsxSelfClosingElement, JsxSpreadAttribute, Node, ParameterDeclaration, PropertyAssignment, ShorthandPropertyAssignment, SourceFile,
|
|
1
|
+
import { BindingElement, CallExpression, EnumDeclaration, Expression, FunctionDeclaration, GetAccessorDeclaration, Identifier, JsxAttribute, JsxOpeningElement, JsxSelfClosingElement, JsxSpreadAttribute, Node, ParameterDeclaration, PropertyAssignment, ShorthandPropertyAssignment, SourceFile, VariableDeclaration } from "ts-morph";
|
|
2
2
|
import { EvaluateOptions as EvaluateOptions$1 } from "ts-evaluator";
|
|
3
3
|
|
|
4
4
|
//#region src/box-factory.d.ts
|
|
@@ -79,6 +79,32 @@ declare class BoxNodeLiteral extends BoxNodeType$1<LiteralType> {
|
|
|
79
79
|
declare class BoxNodeMap extends BoxNodeType$1<MapType> {
|
|
80
80
|
value: MapType['value'];
|
|
81
81
|
spreadConditions?: BoxNodeConditional[];
|
|
82
|
+
/**
|
|
83
|
+
* Spreads the extractor walked structurally, paired with what it walked them into.
|
|
84
|
+
*
|
|
85
|
+
* The map records what a spread *contributed*, so a spread that flattened and one that
|
|
86
|
+
* was silently skipped look identical once folded in — both simply add keys, or fail to.
|
|
87
|
+
* That ambiguity is why a consumer rewriting source would otherwise have to decline every
|
|
88
|
+
* spread rather than only the ones it cannot account for.
|
|
89
|
+
*
|
|
90
|
+
* The *walked* ones are listed rather than the skipped ones deliberately: a consumer asks
|
|
91
|
+
* "may I trust this spread", and a list of failures answers that only while it is
|
|
92
|
+
* exhaustive. A list of successes is safe to be incomplete — the worst an omission costs
|
|
93
|
+
* is a fold that does not happen.
|
|
94
|
+
*
|
|
95
|
+
* `box` is the map the spread flattened, and being here is **not** a promise that every
|
|
96
|
+
* one of its keys survived — the extractor omits what it cannot evaluate, at any depth. It
|
|
97
|
+
* is the handle a consumer needs to go and check for itself, which it cannot do from the
|
|
98
|
+
* flattened result. `node` is the spread's own expression, so the pair can be matched
|
|
99
|
+
* against the source being inspected.
|
|
100
|
+
*
|
|
101
|
+
* Deliberately kept off `value`, and therefore invisible to `unbox` — this describes the
|
|
102
|
+
* extraction, not the styles, and nothing that generates CSS should see it.
|
|
103
|
+
*/
|
|
104
|
+
resolvedSpreads?: Array<{
|
|
105
|
+
node: Node;
|
|
106
|
+
box: BoxNodeMap;
|
|
107
|
+
}>;
|
|
82
108
|
constructor(definition: MapType);
|
|
83
109
|
isRecipe: () => boolean;
|
|
84
110
|
}
|
|
@@ -111,16 +137,10 @@ interface ExtractedFunctionInstance {
|
|
|
111
137
|
fromNode: () => CallExpression;
|
|
112
138
|
box: BoxNodeArray;
|
|
113
139
|
}
|
|
114
|
-
interface ExtractedTaggedTemplateInstance {
|
|
115
|
-
name: string;
|
|
116
|
-
kind: 'tagged-template';
|
|
117
|
-
fromNode: () => TaggedTemplateExpression;
|
|
118
|
-
box: BoxNodeLiteral;
|
|
119
|
-
}
|
|
120
140
|
interface ExtractedFunctionResult {
|
|
121
141
|
kind: 'function';
|
|
122
142
|
nodesByProp: Map<string, BoxNode[]>;
|
|
123
|
-
queryList:
|
|
143
|
+
queryList: ExtractedFunctionInstance[];
|
|
124
144
|
}
|
|
125
145
|
interface ExtractedComponentInstance {
|
|
126
146
|
name: string;
|
|
@@ -164,11 +184,6 @@ interface ComponentMatchers {
|
|
|
164
184
|
matchTag: (element: MatchTagArgs) => boolean;
|
|
165
185
|
matchProp: (prop: Pick<MatchTagArgs, 'tagName' | 'tagNode'> & MatchPropArgs) => boolean;
|
|
166
186
|
}
|
|
167
|
-
interface MatchTaggedTemplateArgs {
|
|
168
|
-
fnName: string;
|
|
169
|
-
taggedTemplateNode: TaggedTemplateExpression;
|
|
170
|
-
}
|
|
171
|
-
type MatchTaggedTemplate = (tag: MatchTaggedTemplateArgs) => boolean;
|
|
172
187
|
interface BoxContext {
|
|
173
188
|
getEvaluateOptions?: (node: Expression, stack: Node[]) => Omit<EvaluateOptions, 'node' | 'policy'> | void;
|
|
174
189
|
canEval?: (node: Expression, stack: Node[]) => boolean;
|
|
@@ -190,9 +205,6 @@ type ExtractOptions = BoxContext & {
|
|
|
190
205
|
ast: SourceFile;
|
|
191
206
|
components?: ComponentMatchers;
|
|
192
207
|
functions?: FunctionMatchers;
|
|
193
|
-
taggedTemplates?: {
|
|
194
|
-
matchTaggedTemplate: MatchTaggedTemplate;
|
|
195
|
-
};
|
|
196
208
|
};
|
|
197
209
|
//#endregion
|
|
198
210
|
//#region src/to-box-node.d.ts
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BindingElement, CallExpression, EnumDeclaration, Expression, FunctionDeclaration, GetAccessorDeclaration, Identifier, JsxAttribute, JsxOpeningElement, JsxSelfClosingElement, JsxSpreadAttribute, Node, ParameterDeclaration, PropertyAssignment, ShorthandPropertyAssignment, SourceFile,
|
|
1
|
+
import { BindingElement, CallExpression, EnumDeclaration, Expression, FunctionDeclaration, GetAccessorDeclaration, Identifier, JsxAttribute, JsxOpeningElement, JsxSelfClosingElement, JsxSpreadAttribute, Node, ParameterDeclaration, PropertyAssignment, ShorthandPropertyAssignment, SourceFile, VariableDeclaration } from "ts-morph";
|
|
2
2
|
import { EvaluateOptions as EvaluateOptions$1 } from "ts-evaluator";
|
|
3
3
|
|
|
4
4
|
//#region src/box-factory.d.ts
|
|
@@ -79,6 +79,32 @@ declare class BoxNodeLiteral extends BoxNodeType$1<LiteralType> {
|
|
|
79
79
|
declare class BoxNodeMap extends BoxNodeType$1<MapType> {
|
|
80
80
|
value: MapType['value'];
|
|
81
81
|
spreadConditions?: BoxNodeConditional[];
|
|
82
|
+
/**
|
|
83
|
+
* Spreads the extractor walked structurally, paired with what it walked them into.
|
|
84
|
+
*
|
|
85
|
+
* The map records what a spread *contributed*, so a spread that flattened and one that
|
|
86
|
+
* was silently skipped look identical once folded in — both simply add keys, or fail to.
|
|
87
|
+
* That ambiguity is why a consumer rewriting source would otherwise have to decline every
|
|
88
|
+
* spread rather than only the ones it cannot account for.
|
|
89
|
+
*
|
|
90
|
+
* The *walked* ones are listed rather than the skipped ones deliberately: a consumer asks
|
|
91
|
+
* "may I trust this spread", and a list of failures answers that only while it is
|
|
92
|
+
* exhaustive. A list of successes is safe to be incomplete — the worst an omission costs
|
|
93
|
+
* is a fold that does not happen.
|
|
94
|
+
*
|
|
95
|
+
* `box` is the map the spread flattened, and being here is **not** a promise that every
|
|
96
|
+
* one of its keys survived — the extractor omits what it cannot evaluate, at any depth. It
|
|
97
|
+
* is the handle a consumer needs to go and check for itself, which it cannot do from the
|
|
98
|
+
* flattened result. `node` is the spread's own expression, so the pair can be matched
|
|
99
|
+
* against the source being inspected.
|
|
100
|
+
*
|
|
101
|
+
* Deliberately kept off `value`, and therefore invisible to `unbox` — this describes the
|
|
102
|
+
* extraction, not the styles, and nothing that generates CSS should see it.
|
|
103
|
+
*/
|
|
104
|
+
resolvedSpreads?: Array<{
|
|
105
|
+
node: Node;
|
|
106
|
+
box: BoxNodeMap;
|
|
107
|
+
}>;
|
|
82
108
|
constructor(definition: MapType);
|
|
83
109
|
isRecipe: () => boolean;
|
|
84
110
|
}
|
|
@@ -111,16 +137,10 @@ interface ExtractedFunctionInstance {
|
|
|
111
137
|
fromNode: () => CallExpression;
|
|
112
138
|
box: BoxNodeArray;
|
|
113
139
|
}
|
|
114
|
-
interface ExtractedTaggedTemplateInstance {
|
|
115
|
-
name: string;
|
|
116
|
-
kind: 'tagged-template';
|
|
117
|
-
fromNode: () => TaggedTemplateExpression;
|
|
118
|
-
box: BoxNodeLiteral;
|
|
119
|
-
}
|
|
120
140
|
interface ExtractedFunctionResult {
|
|
121
141
|
kind: 'function';
|
|
122
142
|
nodesByProp: Map<string, BoxNode[]>;
|
|
123
|
-
queryList:
|
|
143
|
+
queryList: ExtractedFunctionInstance[];
|
|
124
144
|
}
|
|
125
145
|
interface ExtractedComponentInstance {
|
|
126
146
|
name: string;
|
|
@@ -164,11 +184,6 @@ interface ComponentMatchers {
|
|
|
164
184
|
matchTag: (element: MatchTagArgs) => boolean;
|
|
165
185
|
matchProp: (prop: Pick<MatchTagArgs, 'tagName' | 'tagNode'> & MatchPropArgs) => boolean;
|
|
166
186
|
}
|
|
167
|
-
interface MatchTaggedTemplateArgs {
|
|
168
|
-
fnName: string;
|
|
169
|
-
taggedTemplateNode: TaggedTemplateExpression;
|
|
170
|
-
}
|
|
171
|
-
type MatchTaggedTemplate = (tag: MatchTaggedTemplateArgs) => boolean;
|
|
172
187
|
interface BoxContext {
|
|
173
188
|
getEvaluateOptions?: (node: Expression, stack: Node[]) => Omit<EvaluateOptions, 'node' | 'policy'> | void;
|
|
174
189
|
canEval?: (node: Expression, stack: Node[]) => boolean;
|
|
@@ -190,9 +205,6 @@ type ExtractOptions = BoxContext & {
|
|
|
190
205
|
ast: SourceFile;
|
|
191
206
|
components?: ComponentMatchers;
|
|
192
207
|
functions?: FunctionMatchers;
|
|
193
|
-
taggedTemplates?: {
|
|
194
|
-
matchTaggedTemplate: MatchTaggedTemplate;
|
|
195
|
-
};
|
|
196
208
|
};
|
|
197
209
|
//#endregion
|
|
198
210
|
//#region src/to-box-node.d.ts
|
package/dist/index.mjs
CHANGED
|
@@ -115,6 +115,29 @@ const recipeProps = [
|
|
|
115
115
|
var BoxNodeMap = class extends BoxNodeType {
|
|
116
116
|
value;
|
|
117
117
|
spreadConditions;
|
|
118
|
+
/**
|
|
119
|
+
* Spreads the extractor walked structurally, paired with what it walked them into.
|
|
120
|
+
*
|
|
121
|
+
* The map records what a spread *contributed*, so a spread that flattened and one that
|
|
122
|
+
* was silently skipped look identical once folded in — both simply add keys, or fail to.
|
|
123
|
+
* That ambiguity is why a consumer rewriting source would otherwise have to decline every
|
|
124
|
+
* spread rather than only the ones it cannot account for.
|
|
125
|
+
*
|
|
126
|
+
* The *walked* ones are listed rather than the skipped ones deliberately: a consumer asks
|
|
127
|
+
* "may I trust this spread", and a list of failures answers that only while it is
|
|
128
|
+
* exhaustive. A list of successes is safe to be incomplete — the worst an omission costs
|
|
129
|
+
* is a fold that does not happen.
|
|
130
|
+
*
|
|
131
|
+
* `box` is the map the spread flattened, and being here is **not** a promise that every
|
|
132
|
+
* one of its keys survived — the extractor omits what it cannot evaluate, at any depth. It
|
|
133
|
+
* is the handle a consumer needs to go and check for itself, which it cannot do from the
|
|
134
|
+
* flattened result. `node` is the spread's own expression, so the pair can be matched
|
|
135
|
+
* against the source being inspected.
|
|
136
|
+
*
|
|
137
|
+
* Deliberately kept off `value`, and therefore invisible to `unbox` — this describes the
|
|
138
|
+
* extraction, not the styles, and nothing that generates CSS should see it.
|
|
139
|
+
*/
|
|
140
|
+
resolvedSpreads;
|
|
118
141
|
constructor(definition) {
|
|
119
142
|
super(definition);
|
|
120
143
|
this.value = definition.value;
|
|
@@ -439,6 +462,8 @@ const getObjectLiteralExpressionPropPairs = (expression, expressionStack, ctx, m
|
|
|
439
462
|
if (properties.length === 0) return box.emptyObject(expression, expressionStack);
|
|
440
463
|
const extractedPropValues = [];
|
|
441
464
|
const spreadConditions = [];
|
|
465
|
+
/** Spreads the extractor walked structurally — see `BoxNodeMap.resolvedSpreads`. */
|
|
466
|
+
const resolvedSpreads = [];
|
|
442
467
|
properties.forEach((property) => {
|
|
443
468
|
const stack = [...expressionStack];
|
|
444
469
|
stack.push(property);
|
|
@@ -491,6 +516,10 @@ const getObjectLiteralExpressionPropPairs = (expression, expressionStack, ctx, m
|
|
|
491
516
|
maybeObject.value.forEach((nested, propName) => {
|
|
492
517
|
extractedPropValues.push([propName, nested]);
|
|
493
518
|
});
|
|
519
|
+
resolvedSpreads.push({
|
|
520
|
+
node: initializer,
|
|
521
|
+
box: maybeObject
|
|
522
|
+
});
|
|
494
523
|
return;
|
|
495
524
|
}
|
|
496
525
|
if (box.isConditional(maybeObject)) spreadConditions.push(maybeObject);
|
|
@@ -502,6 +531,7 @@ const getObjectLiteralExpressionPropPairs = (expression, expressionStack, ctx, m
|
|
|
502
531
|
orderedMapValue.set(propName, value);
|
|
503
532
|
});
|
|
504
533
|
const map = box.map(orderedMapValue, expression, expressionStack);
|
|
534
|
+
if (resolvedSpreads.length > 0) map.resolvedSpreads = resolvedSpreads;
|
|
505
535
|
if (spreadConditions.length > 0) map.spreadConditions = spreadConditions;
|
|
506
536
|
return map;
|
|
507
537
|
};
|
|
@@ -630,6 +660,7 @@ function maybeBoxNode(node, stack, ctx, matchProp) {
|
|
|
630
660
|
if (isPlusSyntax(operatorKind)) {
|
|
631
661
|
const value = tryComputingPlusTokenBinaryExpressionToString(node, stack, ctx) ?? safeEvaluateNode(node, stack, ctx);
|
|
632
662
|
if (!value) return;
|
|
663
|
+
if (typeof value === "string" && value.includes("undefined")) return;
|
|
633
664
|
return cache(box.from(value, node, stack));
|
|
634
665
|
}
|
|
635
666
|
if (isLogicalSyntax(operatorKind)) return cache(maybeResolveConditionalExpression({
|
|
@@ -1347,6 +1378,34 @@ const runtimeProfiles = [
|
|
|
1347
1378
|
getPropNodes: getArgsAt(1)
|
|
1348
1379
|
}
|
|
1349
1380
|
];
|
|
1381
|
+
/**
|
|
1382
|
+
* The declaration `identifier` binds to, found by walking its enclosing scopes outward.
|
|
1383
|
+
*
|
|
1384
|
+
* Deliberately not `identifier.getDefinitions()`. That is a language-service query, and
|
|
1385
|
+
* the first one forces `synchronizeHostData` -> `createProgram`, which resolves, parses
|
|
1386
|
+
* and binds the whole transitive `.d.ts` closure of the project — in a 5-file sandbox
|
|
1387
|
+
* that is 161 files and 5.1MB, most of it `node_modules`, and it grows with the
|
|
1388
|
+
* dependency graph rather than with the user's source. The extractor is built to avoid
|
|
1389
|
+
* exactly that: `createTsProject` sets `skipAddingFilesFromTsConfig`,
|
|
1390
|
+
* `skipFileDependencyResolution` and `skipLoadingLibFiles`, and `getModuleSpecifierSourceFile`
|
|
1391
|
+
* carries the same note. Inside a bundler the program is built alongside the module graph
|
|
1392
|
+
* in one heap, so the cost lands as a slow build and then an OOM.
|
|
1393
|
+
*
|
|
1394
|
+
* A lexical walk is enough because this only ever runs for a callee that is declared in
|
|
1395
|
+
* this file: `resolveCallee` matches against the import map first, and `collectImports`
|
|
1396
|
+
* covers every import form. Innermost scope wins, which is how the binding resolves
|
|
1397
|
+
* anyway, and a scope can hold only one declaration of a given name.
|
|
1398
|
+
*/
|
|
1399
|
+
const findLocalDeclaration = (identifier) => {
|
|
1400
|
+
const name = identifier.getText();
|
|
1401
|
+
for (let scope = identifier.getParent(); scope; scope = scope.getParent()) {
|
|
1402
|
+
if (!Node.isStatemented(scope)) continue;
|
|
1403
|
+
const variable = scope.getVariableDeclaration(name);
|
|
1404
|
+
if (variable) return variable;
|
|
1405
|
+
const fn = scope.getFunction(name);
|
|
1406
|
+
if (fn) return fn;
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1350
1409
|
const createCompiledJsxContext = (sourceFile) => {
|
|
1351
1410
|
const imports = collectImports(sourceFile);
|
|
1352
1411
|
const normalizeCallee = (node) => {
|
|
@@ -1373,30 +1432,26 @@ const createCompiledJsxContext = (sourceFile) => {
|
|
|
1373
1432
|
if (Node.isConditionalExpression(expression)) return resolveLocalAlias(expression.getWhenTrue()) ?? resolveLocalAlias(expression.getWhenFalse());
|
|
1374
1433
|
if (Node.isBinaryExpression(expression) && expression.getOperatorToken().getKind() === SyntaxKind.CommaToken) return resolveLocalAlias(expression.getRight());
|
|
1375
1434
|
};
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
return resolved;
|
|
1388
|
-
}
|
|
1389
|
-
if (Node.isVariableDeclaration(declaration)) {
|
|
1390
|
-
const directImport = resolveBundledHelperImport(declaration.getName(), declaration.getInitializer());
|
|
1391
|
-
const aliasImport = directImport ? {
|
|
1392
|
-
mod: directImport.mod,
|
|
1393
|
-
importedName: directImport.importedName
|
|
1394
|
-
} : resolveLocalAlias(declaration.getInitializer());
|
|
1395
|
-
if (!aliasImport) continue;
|
|
1396
|
-
localDefinitionCache.set(name, aliasImport);
|
|
1397
|
-
return aliasImport;
|
|
1398
|
-
}
|
|
1435
|
+
const declaration = findLocalDeclaration(identifier);
|
|
1436
|
+
if (!declaration) return;
|
|
1437
|
+
if (Node.isFunctionDeclaration(declaration)) {
|
|
1438
|
+
const imported = resolveBundledHelperImport(declaration.getName() ?? name, declaration);
|
|
1439
|
+
if (!imported) return;
|
|
1440
|
+
const resolved = {
|
|
1441
|
+
mod: imported.mod,
|
|
1442
|
+
importedName: imported.importedName
|
|
1443
|
+
};
|
|
1444
|
+
localDefinitionCache.set(name, resolved);
|
|
1445
|
+
return resolved;
|
|
1399
1446
|
}
|
|
1447
|
+
const directImport = resolveBundledHelperImport(declaration.getName(), declaration.getInitializer());
|
|
1448
|
+
const aliasImport = directImport ? {
|
|
1449
|
+
mod: directImport.mod,
|
|
1450
|
+
importedName: directImport.importedName
|
|
1451
|
+
} : resolveLocalAlias(declaration.getInitializer());
|
|
1452
|
+
if (!aliasImport) return;
|
|
1453
|
+
localDefinitionCache.set(name, aliasImport);
|
|
1454
|
+
return aliasImport;
|
|
1400
1455
|
};
|
|
1401
1456
|
const resolveCallee = (node) => {
|
|
1402
1457
|
const expression = normalizeCallee(node);
|
|
@@ -1491,7 +1546,7 @@ const objectLikeToMap = (maybeObject, node) => {
|
|
|
1491
1546
|
const isImportOrExport = (node) => Node.isImportDeclaration(node) || Node.isExportDeclaration(node);
|
|
1492
1547
|
const isJsxElement = (node) => Node.isJsxOpeningElement(node) || Node.isJsxSelfClosingElement(node);
|
|
1493
1548
|
const extract = ({ ast, ...ctx }) => {
|
|
1494
|
-
const { components, functions
|
|
1549
|
+
const { components, functions } = ctx;
|
|
1495
1550
|
const compiledJsx = createCompiledJsxContext(ast);
|
|
1496
1551
|
/** contains all the extracted nodes from this ast parsing */
|
|
1497
1552
|
const byName = /* @__PURE__ */ new Map();
|
|
@@ -1661,6 +1716,7 @@ const extract = ({ ast, ...ctx }) => {
|
|
|
1661
1716
|
});
|
|
1662
1717
|
const boxMap = box.map(mapValue, node, boxNode.getStack());
|
|
1663
1718
|
if (box.isMap(boxNode) && boxNode.spreadConditions?.length) boxMap.spreadConditions = boxNode.spreadConditions;
|
|
1719
|
+
if (box.isMap(boxNode) && boxNode.resolvedSpreads?.length) boxMap.resolvedSpreads = boxNode.resolvedSpreads;
|
|
1664
1720
|
return boxMap;
|
|
1665
1721
|
}
|
|
1666
1722
|
return boxNode;
|
|
@@ -1672,26 +1728,6 @@ const extract = ({ ast, ...ctx }) => {
|
|
|
1672
1728
|
};
|
|
1673
1729
|
fnResultMap.queryList.push(query);
|
|
1674
1730
|
}
|
|
1675
|
-
if (taggedTemplates && Node.isTaggedTemplateExpression(node)) {
|
|
1676
|
-
const tag = node.getTag();
|
|
1677
|
-
const fnName = Node.isCallExpression(tag) ? tag.getExpression().getText() : tag.getText();
|
|
1678
|
-
if (!taggedTemplates.matchTaggedTemplate({
|
|
1679
|
-
taggedTemplateNode: node,
|
|
1680
|
-
fnName
|
|
1681
|
-
})) return;
|
|
1682
|
-
if (!byName.has(fnName)) byName.set(fnName, {
|
|
1683
|
-
kind: "function",
|
|
1684
|
-
nodesByProp: /* @__PURE__ */ new Map(),
|
|
1685
|
-
queryList: []
|
|
1686
|
-
});
|
|
1687
|
-
const fnResultMap = byName.get(fnName);
|
|
1688
|
-
const query = {
|
|
1689
|
-
kind: "tagged-template",
|
|
1690
|
-
name: fnName,
|
|
1691
|
-
box: maybeBoxNode(node, [], ctx)
|
|
1692
|
-
};
|
|
1693
|
-
fnResultMap.queryList.push(query);
|
|
1694
|
-
}
|
|
1695
1731
|
});
|
|
1696
1732
|
componentByNode.forEach((parentRef, componentNode) => {
|
|
1697
1733
|
const component = componentByNode.get(componentNode);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bamboocss/extractor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.1",
|
|
4
4
|
"description": "The css extractor for css bamboo",
|
|
5
5
|
"homepage": "https://bamboocss.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"ts-evaluator": "1.2.0",
|
|
37
37
|
"ts-morph": "28.0.0",
|
|
38
|
-
"@bamboocss/shared": "1.
|
|
38
|
+
"@bamboocss/shared": "1.16.1"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "tsdown src/index.ts --format=cjs,esm --shims --dts",
|