@bamboocss/extractor 1.16.0 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -310,6 +310,38 @@ const box = {
310
310
  const TsEvalError = Symbol("EvalError");
311
311
  const cacheMap$2 = /* @__PURE__ */ new WeakMap();
312
312
  /**
313
+ * Whether a call reaches a function declared outside this project.
314
+ *
315
+ * Passing a type checker lets the evaluator resolve an identifier to its declaration in
316
+ * another module, which is what makes a call to an imported helper resolvable at all — a
317
+ * style helper in a neighbouring file used to come back unresolvable, and for a recipe that
318
+ * is not a partial loss but a different config, a different hash, and an element with no
319
+ * styles.
320
+ *
321
+ * It also means evaluating whatever it resolves to, so the project boundary is where that
322
+ * stops. A dependency's code is not ours to run at build time, however pure it looks, and
323
+ * declining leaves exactly the behaviour that shipped before the checker was passed.
324
+ */
325
+ const resolvesWithinProject = (node) => {
326
+ if (!ts_morph.Node.isCallExpression(node)) return false;
327
+ const symbol = node.getExpression().getSymbol();
328
+ if (!symbol) return false;
329
+ const declarations = (symbol.getAliasedSymbol() ?? symbol).getDeclarations();
330
+ if (!declarations.length) return false;
331
+ return !declarations.some((declaration) => declaration.getSourceFile().isInNodeModules());
332
+ };
333
+ /** One per project. `getTypeChecker()` is cheap, but this runs per evaluated call. */
334
+ const typeCheckers = /* @__PURE__ */ new WeakMap();
335
+ const typeCheckerFor = (node) => {
336
+ const project = node.getProject();
337
+ let checker = typeCheckers.get(project);
338
+ if (!checker) {
339
+ checker = project.getTypeChecker().compilerObject;
340
+ typeCheckers.set(project, checker);
341
+ }
342
+ return checker;
343
+ };
344
+ /**
313
345
  * Evaluates a node with strict policies restrictions
314
346
  * @see https://github.com/wessberg/ts-evaluator#setting-up-policies
315
347
  */
@@ -318,6 +350,7 @@ const evaluateNode = (node, stack, ctx) => {
318
350
  if (ctx.canEval && !ctx.canEval?.(node, stack)) return;
319
351
  if (cacheMap$2.has(node)) return cacheMap$2.get(node);
320
352
  const result = (0, ts_evaluator.evaluate)({
353
+ ...resolvesWithinProject(node) ? { typeChecker: typeCheckerFor(node) } : {},
321
354
  policy: {
322
355
  deterministic: true,
323
356
  network: false,
@@ -1379,6 +1412,34 @@ const runtimeProfiles = [
1379
1412
  getPropNodes: getArgsAt(1)
1380
1413
  }
1381
1414
  ];
1415
+ /**
1416
+ * The declaration `identifier` binds to, found by walking its enclosing scopes outward.
1417
+ *
1418
+ * Deliberately not `identifier.getDefinitions()`. That is a language-service query, and
1419
+ * the first one forces `synchronizeHostData` -> `createProgram`, which resolves, parses
1420
+ * and binds the whole transitive `.d.ts` closure of the project — in a 5-file sandbox
1421
+ * that is 161 files and 5.1MB, most of it `node_modules`, and it grows with the
1422
+ * dependency graph rather than with the user's source. The extractor is built to avoid
1423
+ * exactly that: `createTsProject` sets `skipAddingFilesFromTsConfig`,
1424
+ * `skipFileDependencyResolution` and `skipLoadingLibFiles`, and `getModuleSpecifierSourceFile`
1425
+ * carries the same note. Inside a bundler the program is built alongside the module graph
1426
+ * in one heap, so the cost lands as a slow build and then an OOM.
1427
+ *
1428
+ * A lexical walk is enough because this only ever runs for a callee that is declared in
1429
+ * this file: `resolveCallee` matches against the import map first, and `collectImports`
1430
+ * covers every import form. Innermost scope wins, which is how the binding resolves
1431
+ * anyway, and a scope can hold only one declaration of a given name.
1432
+ */
1433
+ const findLocalDeclaration = (identifier) => {
1434
+ const name = identifier.getText();
1435
+ for (let scope = identifier.getParent(); scope; scope = scope.getParent()) {
1436
+ if (!ts_morph.Node.isStatemented(scope)) continue;
1437
+ const variable = scope.getVariableDeclaration(name);
1438
+ if (variable) return variable;
1439
+ const fn = scope.getFunction(name);
1440
+ if (fn) return fn;
1441
+ }
1442
+ };
1382
1443
  const createCompiledJsxContext = (sourceFile) => {
1383
1444
  const imports = collectImports(sourceFile);
1384
1445
  const normalizeCallee = (node) => {
@@ -1405,30 +1466,26 @@ const createCompiledJsxContext = (sourceFile) => {
1405
1466
  if (ts_morph.Node.isConditionalExpression(expression)) return resolveLocalAlias(expression.getWhenTrue()) ?? resolveLocalAlias(expression.getWhenFalse());
1406
1467
  if (ts_morph.Node.isBinaryExpression(expression) && expression.getOperatorToken().getKind() === ts_morph.SyntaxKind.CommaToken) return resolveLocalAlias(expression.getRight());
1407
1468
  };
1408
- for (const definition of identifier.getDefinitions()) {
1409
- const declaration = definition.getDeclarationNode();
1410
- if (!declaration) continue;
1411
- if (ts_morph.Node.isFunctionDeclaration(declaration)) {
1412
- const imported = resolveBundledHelperImport(declaration.getName() ?? name, declaration);
1413
- if (!imported) continue;
1414
- const resolved = {
1415
- mod: imported.mod,
1416
- importedName: imported.importedName
1417
- };
1418
- localDefinitionCache.set(name, resolved);
1419
- return resolved;
1420
- }
1421
- if (ts_morph.Node.isVariableDeclaration(declaration)) {
1422
- const directImport = resolveBundledHelperImport(declaration.getName(), declaration.getInitializer());
1423
- const aliasImport = directImport ? {
1424
- mod: directImport.mod,
1425
- importedName: directImport.importedName
1426
- } : resolveLocalAlias(declaration.getInitializer());
1427
- if (!aliasImport) continue;
1428
- localDefinitionCache.set(name, aliasImport);
1429
- return aliasImport;
1430
- }
1469
+ const declaration = findLocalDeclaration(identifier);
1470
+ if (!declaration) return;
1471
+ if (ts_morph.Node.isFunctionDeclaration(declaration)) {
1472
+ const imported = resolveBundledHelperImport(declaration.getName() ?? name, declaration);
1473
+ if (!imported) return;
1474
+ const resolved = {
1475
+ mod: imported.mod,
1476
+ importedName: imported.importedName
1477
+ };
1478
+ localDefinitionCache.set(name, resolved);
1479
+ return resolved;
1431
1480
  }
1481
+ const directImport = resolveBundledHelperImport(declaration.getName(), declaration.getInitializer());
1482
+ const aliasImport = directImport ? {
1483
+ mod: directImport.mod,
1484
+ importedName: directImport.importedName
1485
+ } : resolveLocalAlias(declaration.getInitializer());
1486
+ if (!aliasImport) return;
1487
+ localDefinitionCache.set(name, aliasImport);
1488
+ return aliasImport;
1432
1489
  };
1433
1490
  const resolveCallee = (node) => {
1434
1491
  const expression = normalizeCallee(node);
@@ -1878,3 +1935,4 @@ exports.isBoxNode = isBoxNode;
1878
1935
  exports.maybeBoxNode = maybeBoxNode;
1879
1936
  exports.maybeIdentifierValue = maybeIdentifierValue;
1880
1937
  exports.unbox = unbox;
1938
+ exports.unwrapExpression = unwrapExpression;
package/dist/index.d.cts CHANGED
@@ -302,6 +302,9 @@ declare const maybeIdentifierValue: (identifier: Identifier, _stack: Node[], ctx
302
302
  type MatchProp = (prop: MatchFnPropArgs | MatchPropArgs) => boolean;
303
303
  declare const extractJsxSpreadAttributeValues: (node: JsxSpreadAttribute, ctx: BoxContext, matchProp: MatchProp) => MaybeBoxNodeReturn;
304
304
  //#endregion
305
+ //#region src/utils.d.ts
306
+ declare const unwrapExpression: (node: Node) => Node;
307
+ //#endregion
305
308
  //#region src/unbox.d.ts
306
309
  type BoxNodeType = BoxNode | BoxNode[] | undefined;
307
310
  type CacheMap = WeakMap<BoxNode, Unboxed>;
@@ -321,4 +324,4 @@ interface Unboxed {
321
324
  }
322
325
  declare const unbox: (node: BoxNodeType, ctx?: Pick<UnboxContext, "cache">) => Unboxed;
323
326
  //#endregion
324
- export { type BoxContext, type BoxNode, BoxNodeArray, BoxNodeConditional, BoxNodeEmptyInitializer, BoxNodeLiteral, BoxNodeMap, BoxNodeObject, BoxNodeUnresolvable, type EvaluateOptions, type ExtractOptions, type ExtractResultByName, type ExtractResultItem, type ExtractedComponentInstance, type ExtractedComponentResult, type ExtractedFunctionInstance, type ExtractedFunctionResult, type NodeRange, type PrimitiveType, type Unboxed, box, clearBoxNodeCache, extract, extractCallExpressionArguments, extractJsxAttribute, extractJsxElementProps, extractJsxSpreadAttributeValues, findIdentifierValueDeclaration, isBoxNode, maybeBoxNode, maybeIdentifierValue, unbox };
327
+ export { type BoxContext, type BoxNode, BoxNodeArray, BoxNodeConditional, BoxNodeEmptyInitializer, BoxNodeLiteral, BoxNodeMap, BoxNodeObject, BoxNodeUnresolvable, type EvaluateOptions, type ExtractOptions, type ExtractResultByName, type ExtractResultItem, type ExtractedComponentInstance, type ExtractedComponentResult, type ExtractedFunctionInstance, type ExtractedFunctionResult, type NodeRange, type PrimitiveType, type Unboxed, box, clearBoxNodeCache, extract, extractCallExpressionArguments, extractJsxAttribute, extractJsxElementProps, extractJsxSpreadAttributeValues, findIdentifierValueDeclaration, isBoxNode, maybeBoxNode, maybeIdentifierValue, unbox, unwrapExpression };
package/dist/index.d.mts CHANGED
@@ -302,6 +302,9 @@ declare const maybeIdentifierValue: (identifier: Identifier, _stack: Node[], ctx
302
302
  type MatchProp = (prop: MatchFnPropArgs | MatchPropArgs) => boolean;
303
303
  declare const extractJsxSpreadAttributeValues: (node: JsxSpreadAttribute, ctx: BoxContext, matchProp: MatchProp) => MaybeBoxNodeReturn;
304
304
  //#endregion
305
+ //#region src/utils.d.ts
306
+ declare const unwrapExpression: (node: Node) => Node;
307
+ //#endregion
305
308
  //#region src/unbox.d.ts
306
309
  type BoxNodeType = BoxNode | BoxNode[] | undefined;
307
310
  type CacheMap = WeakMap<BoxNode, Unboxed>;
@@ -321,4 +324,4 @@ interface Unboxed {
321
324
  }
322
325
  declare const unbox: (node: BoxNodeType, ctx?: Pick<UnboxContext, "cache">) => Unboxed;
323
326
  //#endregion
324
- export { type BoxContext, type BoxNode, BoxNodeArray, BoxNodeConditional, BoxNodeEmptyInitializer, BoxNodeLiteral, BoxNodeMap, BoxNodeObject, BoxNodeUnresolvable, type EvaluateOptions, type ExtractOptions, type ExtractResultByName, type ExtractResultItem, type ExtractedComponentInstance, type ExtractedComponentResult, type ExtractedFunctionInstance, type ExtractedFunctionResult, type NodeRange, type PrimitiveType, type Unboxed, box, clearBoxNodeCache, extract, extractCallExpressionArguments, extractJsxAttribute, extractJsxElementProps, extractJsxSpreadAttributeValues, findIdentifierValueDeclaration, isBoxNode, maybeBoxNode, maybeIdentifierValue, unbox };
327
+ export { type BoxContext, type BoxNode, BoxNodeArray, BoxNodeConditional, BoxNodeEmptyInitializer, BoxNodeLiteral, BoxNodeMap, BoxNodeObject, BoxNodeUnresolvable, type EvaluateOptions, type ExtractOptions, type ExtractResultByName, type ExtractResultItem, type ExtractedComponentInstance, type ExtractedComponentResult, type ExtractedFunctionInstance, type ExtractedFunctionResult, type NodeRange, type PrimitiveType, type Unboxed, box, clearBoxNodeCache, extract, extractCallExpressionArguments, extractJsxAttribute, extractJsxElementProps, extractJsxSpreadAttributeValues, findIdentifierValueDeclaration, isBoxNode, maybeBoxNode, maybeIdentifierValue, unbox, unwrapExpression };
package/dist/index.mjs CHANGED
@@ -309,6 +309,38 @@ const box = {
309
309
  const TsEvalError = Symbol("EvalError");
310
310
  const cacheMap$2 = /* @__PURE__ */ new WeakMap();
311
311
  /**
312
+ * Whether a call reaches a function declared outside this project.
313
+ *
314
+ * Passing a type checker lets the evaluator resolve an identifier to its declaration in
315
+ * another module, which is what makes a call to an imported helper resolvable at all — a
316
+ * style helper in a neighbouring file used to come back unresolvable, and for a recipe that
317
+ * is not a partial loss but a different config, a different hash, and an element with no
318
+ * styles.
319
+ *
320
+ * It also means evaluating whatever it resolves to, so the project boundary is where that
321
+ * stops. A dependency's code is not ours to run at build time, however pure it looks, and
322
+ * declining leaves exactly the behaviour that shipped before the checker was passed.
323
+ */
324
+ const resolvesWithinProject = (node) => {
325
+ if (!Node.isCallExpression(node)) return false;
326
+ const symbol = node.getExpression().getSymbol();
327
+ if (!symbol) return false;
328
+ const declarations = (symbol.getAliasedSymbol() ?? symbol).getDeclarations();
329
+ if (!declarations.length) return false;
330
+ return !declarations.some((declaration) => declaration.getSourceFile().isInNodeModules());
331
+ };
332
+ /** One per project. `getTypeChecker()` is cheap, but this runs per evaluated call. */
333
+ const typeCheckers = /* @__PURE__ */ new WeakMap();
334
+ const typeCheckerFor = (node) => {
335
+ const project = node.getProject();
336
+ let checker = typeCheckers.get(project);
337
+ if (!checker) {
338
+ checker = project.getTypeChecker().compilerObject;
339
+ typeCheckers.set(project, checker);
340
+ }
341
+ return checker;
342
+ };
343
+ /**
312
344
  * Evaluates a node with strict policies restrictions
313
345
  * @see https://github.com/wessberg/ts-evaluator#setting-up-policies
314
346
  */
@@ -317,6 +349,7 @@ const evaluateNode = (node, stack, ctx) => {
317
349
  if (ctx.canEval && !ctx.canEval?.(node, stack)) return;
318
350
  if (cacheMap$2.has(node)) return cacheMap$2.get(node);
319
351
  const result = evaluate({
352
+ ...resolvesWithinProject(node) ? { typeChecker: typeCheckerFor(node) } : {},
320
353
  policy: {
321
354
  deterministic: true,
322
355
  network: false,
@@ -1378,6 +1411,34 @@ const runtimeProfiles = [
1378
1411
  getPropNodes: getArgsAt(1)
1379
1412
  }
1380
1413
  ];
1414
+ /**
1415
+ * The declaration `identifier` binds to, found by walking its enclosing scopes outward.
1416
+ *
1417
+ * Deliberately not `identifier.getDefinitions()`. That is a language-service query, and
1418
+ * the first one forces `synchronizeHostData` -> `createProgram`, which resolves, parses
1419
+ * and binds the whole transitive `.d.ts` closure of the project — in a 5-file sandbox
1420
+ * that is 161 files and 5.1MB, most of it `node_modules`, and it grows with the
1421
+ * dependency graph rather than with the user's source. The extractor is built to avoid
1422
+ * exactly that: `createTsProject` sets `skipAddingFilesFromTsConfig`,
1423
+ * `skipFileDependencyResolution` and `skipLoadingLibFiles`, and `getModuleSpecifierSourceFile`
1424
+ * carries the same note. Inside a bundler the program is built alongside the module graph
1425
+ * in one heap, so the cost lands as a slow build and then an OOM.
1426
+ *
1427
+ * A lexical walk is enough because this only ever runs for a callee that is declared in
1428
+ * this file: `resolveCallee` matches against the import map first, and `collectImports`
1429
+ * covers every import form. Innermost scope wins, which is how the binding resolves
1430
+ * anyway, and a scope can hold only one declaration of a given name.
1431
+ */
1432
+ const findLocalDeclaration = (identifier) => {
1433
+ const name = identifier.getText();
1434
+ for (let scope = identifier.getParent(); scope; scope = scope.getParent()) {
1435
+ if (!Node.isStatemented(scope)) continue;
1436
+ const variable = scope.getVariableDeclaration(name);
1437
+ if (variable) return variable;
1438
+ const fn = scope.getFunction(name);
1439
+ if (fn) return fn;
1440
+ }
1441
+ };
1381
1442
  const createCompiledJsxContext = (sourceFile) => {
1382
1443
  const imports = collectImports(sourceFile);
1383
1444
  const normalizeCallee = (node) => {
@@ -1404,30 +1465,26 @@ const createCompiledJsxContext = (sourceFile) => {
1404
1465
  if (Node.isConditionalExpression(expression)) return resolveLocalAlias(expression.getWhenTrue()) ?? resolveLocalAlias(expression.getWhenFalse());
1405
1466
  if (Node.isBinaryExpression(expression) && expression.getOperatorToken().getKind() === SyntaxKind.CommaToken) return resolveLocalAlias(expression.getRight());
1406
1467
  };
1407
- for (const definition of identifier.getDefinitions()) {
1408
- const declaration = definition.getDeclarationNode();
1409
- if (!declaration) continue;
1410
- if (Node.isFunctionDeclaration(declaration)) {
1411
- const imported = resolveBundledHelperImport(declaration.getName() ?? name, declaration);
1412
- if (!imported) continue;
1413
- const resolved = {
1414
- mod: imported.mod,
1415
- importedName: imported.importedName
1416
- };
1417
- localDefinitionCache.set(name, resolved);
1418
- return resolved;
1419
- }
1420
- if (Node.isVariableDeclaration(declaration)) {
1421
- const directImport = resolveBundledHelperImport(declaration.getName(), declaration.getInitializer());
1422
- const aliasImport = directImport ? {
1423
- mod: directImport.mod,
1424
- importedName: directImport.importedName
1425
- } : resolveLocalAlias(declaration.getInitializer());
1426
- if (!aliasImport) continue;
1427
- localDefinitionCache.set(name, aliasImport);
1428
- return aliasImport;
1429
- }
1468
+ const declaration = findLocalDeclaration(identifier);
1469
+ if (!declaration) return;
1470
+ if (Node.isFunctionDeclaration(declaration)) {
1471
+ const imported = resolveBundledHelperImport(declaration.getName() ?? name, declaration);
1472
+ if (!imported) return;
1473
+ const resolved = {
1474
+ mod: imported.mod,
1475
+ importedName: imported.importedName
1476
+ };
1477
+ localDefinitionCache.set(name, resolved);
1478
+ return resolved;
1430
1479
  }
1480
+ const directImport = resolveBundledHelperImport(declaration.getName(), declaration.getInitializer());
1481
+ const aliasImport = directImport ? {
1482
+ mod: directImport.mod,
1483
+ importedName: directImport.importedName
1484
+ } : resolveLocalAlias(declaration.getInitializer());
1485
+ if (!aliasImport) return;
1486
+ localDefinitionCache.set(name, aliasImport);
1487
+ return aliasImport;
1431
1488
  };
1432
1489
  const resolveCallee = (node) => {
1433
1490
  const expression = normalizeCallee(node);
@@ -1858,4 +1915,4 @@ const unbox = (node, ctx) => {
1858
1915
  return result;
1859
1916
  };
1860
1917
  //#endregion
1861
- export { BoxNodeArray, BoxNodeConditional, BoxNodeEmptyInitializer, BoxNodeLiteral, BoxNodeMap, BoxNodeObject, BoxNodeUnresolvable, box, clearBoxNodeCache, extract, extractCallExpressionArguments, extractJsxAttribute, extractJsxElementProps, extractJsxSpreadAttributeValues, findIdentifierValueDeclaration, isBoxNode, maybeBoxNode, maybeIdentifierValue, unbox };
1918
+ export { BoxNodeArray, BoxNodeConditional, BoxNodeEmptyInitializer, BoxNodeLiteral, BoxNodeMap, BoxNodeObject, BoxNodeUnresolvable, box, clearBoxNodeCache, extract, extractCallExpressionArguments, extractJsxAttribute, extractJsxElementProps, extractJsxSpreadAttributeValues, findIdentifierValueDeclaration, isBoxNode, maybeBoxNode, maybeIdentifierValue, unbox, unwrapExpression };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamboocss/extractor",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
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.16.0"
38
+ "@bamboocss/shared": "1.17.0"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsdown src/index.ts --format=cjs,esm --shims --dts",