@bamboocss/extractor 1.46.1 → 1.46.2

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
@@ -311,6 +311,37 @@ const EMPTY_DEPENDENCIES = [];
311
311
  /** One synchronous extraction context owns one nested capture stack. */
312
312
  const trackers = /* @__PURE__ */ new WeakMap();
313
313
  /**
314
+ * Per-path staleness, so an edit invalidates exactly the entries that read the edited file.
315
+ *
316
+ * The caches these entries live in are `WeakMap`s keyed on AST nodes — deliberately, so a
317
+ * retired tree releases its memory — which means they cannot be swept. What they *can* do is
318
+ * refuse a hit: every entry already records the module paths its computation read, because
319
+ * that same record is what the watch system replays into fold dependencies. Stamping entries
320
+ * with a creation generation and bumping a per-path generation on each edit turns the
321
+ * recorded read-set into a validity check, evaluated lazily at lookup.
322
+ *
323
+ * The trust base is unchanged. If a cross-file read were missing from `dependencies`, the
324
+ * fold's watch edges would already be missing it, and editing that file would fail to
325
+ * re-transform its consumers today — the recorded read-set is load-bearing for invalidation
326
+ * breadth before it is for cache validity.
327
+ *
328
+ * An entry's *own* file needs no record: reloading or overwriting a file re-parses it, which
329
+ * retires every node previously taken from it, and a retired node can never be a lookup key
330
+ * again. Only cross-file reads can go stale while the key survives, and those are exactly
331
+ * what the capture records.
332
+ *
333
+ * File-tree changes are not handled here at all: a created or deleted file can change what a
334
+ * specifier *resolves to* without any recorded path's content moving, so those events keep
335
+ * clearing the caches outright, exactly as before.
336
+ */
337
+ let dependencyGeneration = 1;
338
+ const invalidatedAt = /* @__PURE__ */ new Map();
339
+ /** Mark one edited file's content stale for every cache entry that recorded reading it. */
340
+ const invalidateDependencyPath = (filePath) => {
341
+ dependencyGeneration++;
342
+ invalidatedAt.set(filePath.replaceAll("\\", "/"), dependencyGeneration);
343
+ };
344
+ /**
314
345
  * Attribute one resolved local module to every cache computation currently enclosing it.
315
346
  *
316
347
  * Paths, rather than SourceFile objects, are the replay payload. A source replacement may
@@ -341,6 +372,7 @@ const beginDependencyCapture = (ctx) => {
341
372
  return {
342
373
  dependencies: frame.dependencies ? [...frame.dependencies].sort() : EMPTY_DEPENDENCIES,
343
374
  resolveModule: ctx.resolveModule,
375
+ generation: dependencyGeneration,
344
376
  value
345
377
  };
346
378
  },
@@ -358,6 +390,10 @@ const beginDependencyCapture = (ctx) => {
358
390
  */
359
391
  const replayDependencyCache = (entry, ctx) => {
360
392
  if (entry.resolveModule !== ctx.resolveModule) return { hit: false };
393
+ for (const dependency of entry.dependencies) {
394
+ const at = invalidatedAt.get(dependency);
395
+ if (at !== void 0 && at > entry.generation) return { hit: false };
396
+ }
361
397
  for (const dependency of entry.dependencies) recordModuleDependency(ctx, dependency);
362
398
  return {
363
399
  hit: true,
@@ -1248,6 +1284,7 @@ const maybeDefinitionValue = (def, stack, ctx) => {
1248
1284
  }
1249
1285
  };
1250
1286
  const getExportedVarDeclarationWithName = (varName, sourceFile, stack, ctx, visited = /* @__PURE__ */ new Set()) => {
1287
+ ctx.recordExportRead?.(sourceFile.getFilePath(), varName);
1251
1288
  const key = `${sourceFile.getFilePath()}:${varName}`;
1252
1289
  if (visited.has(key)) return;
1253
1290
  visited.add(key);
@@ -2211,6 +2248,8 @@ exports.extractJsxAttribute = extractJsxAttribute;
2211
2248
  exports.extractJsxElementProps = extractJsxElementProps;
2212
2249
  exports.extractJsxSpreadAttributeValues = extractJsxSpreadAttributeValues;
2213
2250
  exports.findIdentifierValueDeclaration = findIdentifierValueDeclaration;
2251
+ exports.getExportedVarDeclarationWithName = getExportedVarDeclarationWithName;
2252
+ exports.invalidateDependencyPath = invalidateDependencyPath;
2214
2253
  exports.isBoxNode = isBoxNode;
2215
2254
  exports.maybeBoxNode = maybeBoxNode;
2216
2255
  exports.maybeIdentifierValue = maybeIdentifierValue;
package/dist/index.d.cts CHANGED
@@ -196,6 +196,8 @@ interface BoxContext {
196
196
  resolveModule?: ResolveModule;
197
197
  /** @internal Receives local source paths crossed by this exact extraction context. */
198
198
  recordDependency?: (filePath: string) => void;
199
+ /** @internal Receives every `(module, exportedName)` a cross-file value resolution visited. */
200
+ recordExportRead?: (filePath: string, exportedName: string) => void;
199
201
  getEvaluateOptions?: (node: Expression, stack: Node[]) => Omit<EvaluateOptions, 'node' | 'policy'> | void;
200
202
  canEval?: (node: Expression, stack: Node[]) => boolean;
201
203
  tokens?: {
@@ -307,6 +309,20 @@ declare const extractJsxElementProps: (node: JsxOpeningElement | JsxSelfClosingE
307
309
  declare const clearBoxNodeCache: () => void;
308
310
  type MaybeBoxNodeReturn = BoxNode | undefined;
309
311
  declare function maybeBoxNode(node: Node, stack: Node[], ctx: BoxContext, matchProp?: (prop: MatchFnPropArgs) => boolean): MaybeBoxNodeReturn;
312
+ declare const getExportedVarDeclarationWithName: (varName: string, sourceFile: SourceFile, stack: Node[], ctx: BoxContext,
313
+ /**
314
+ * The `(file, name)` pairs already searched, so a cycle of re-exporting files
315
+ * terminates. Re-exports can form one — including a barrel that re-exports
316
+ * itself — and a name that never resolves inside the cycle would otherwise walk
317
+ * it forever. `export *` is the worst case, since it forwards every name.
318
+ *
319
+ * Keyed on the pair, not the file: a renaming re-export changes the name being
320
+ * searched from one hop to the next, so the same file can legitimately be visited
321
+ * for two different names. Keying on the file alone would truncate the second
322
+ * search and lose a value that is genuinely reachable.
323
+ */
324
+
325
+ visited?: Set<string>) => VariableDeclaration | undefined;
310
326
  declare const maybeIdentifierValue: (identifier: Identifier, _stack: Node[], ctx: BoxContext) => BoxNode;
311
327
  //#endregion
312
328
  //#region src/jsx-spread-attribute.d.ts
@@ -316,6 +332,10 @@ declare const extractJsxSpreadAttributeValues: (node: JsxSpreadAttribute, ctx: B
316
332
  //#region src/utils.d.ts
317
333
  declare const unwrapExpression: (node: Node) => Node;
318
334
  //#endregion
335
+ //#region src/dependency-cache.d.ts
336
+ /** Mark one edited file's content stale for every cache entry that recorded reading it. */
337
+ declare const invalidateDependencyPath: (filePath: string) => void;
338
+ //#endregion
319
339
  //#region src/unbox.d.ts
320
340
  type BoxNodeType = BoxNode | BoxNode[] | undefined;
321
341
  type CacheMap = WeakMap<BoxNode, Unboxed>;
@@ -335,4 +355,4 @@ interface Unboxed {
335
355
  }
336
356
  declare const unbox: (node: BoxNodeType, ctx?: Pick<UnboxContext, "cache">) => Unboxed;
337
357
  //#endregion
338
- 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 ResolveModule, type Unboxed, box, clearBoxNodeCache, extract, extractCallExpressionArguments, extractJsxAttribute, extractJsxElementProps, extractJsxSpreadAttributeValues, findIdentifierValueDeclaration, isBoxNode, maybeBoxNode, maybeIdentifierValue, unbox, unwrapExpression };
358
+ 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 ResolveModule, type Unboxed, box, clearBoxNodeCache, extract, extractCallExpressionArguments, extractJsxAttribute, extractJsxElementProps, extractJsxSpreadAttributeValues, findIdentifierValueDeclaration, getExportedVarDeclarationWithName, invalidateDependencyPath, isBoxNode, maybeBoxNode, maybeIdentifierValue, unbox, unwrapExpression };
package/dist/index.d.mts CHANGED
@@ -196,6 +196,8 @@ interface BoxContext {
196
196
  resolveModule?: ResolveModule;
197
197
  /** @internal Receives local source paths crossed by this exact extraction context. */
198
198
  recordDependency?: (filePath: string) => void;
199
+ /** @internal Receives every `(module, exportedName)` a cross-file value resolution visited. */
200
+ recordExportRead?: (filePath: string, exportedName: string) => void;
199
201
  getEvaluateOptions?: (node: Expression, stack: Node[]) => Omit<EvaluateOptions, 'node' | 'policy'> | void;
200
202
  canEval?: (node: Expression, stack: Node[]) => boolean;
201
203
  tokens?: {
@@ -307,6 +309,20 @@ declare const extractJsxElementProps: (node: JsxOpeningElement | JsxSelfClosingE
307
309
  declare const clearBoxNodeCache: () => void;
308
310
  type MaybeBoxNodeReturn = BoxNode | undefined;
309
311
  declare function maybeBoxNode(node: Node, stack: Node[], ctx: BoxContext, matchProp?: (prop: MatchFnPropArgs) => boolean): MaybeBoxNodeReturn;
312
+ declare const getExportedVarDeclarationWithName: (varName: string, sourceFile: SourceFile, stack: Node[], ctx: BoxContext,
313
+ /**
314
+ * The `(file, name)` pairs already searched, so a cycle of re-exporting files
315
+ * terminates. Re-exports can form one — including a barrel that re-exports
316
+ * itself — and a name that never resolves inside the cycle would otherwise walk
317
+ * it forever. `export *` is the worst case, since it forwards every name.
318
+ *
319
+ * Keyed on the pair, not the file: a renaming re-export changes the name being
320
+ * searched from one hop to the next, so the same file can legitimately be visited
321
+ * for two different names. Keying on the file alone would truncate the second
322
+ * search and lose a value that is genuinely reachable.
323
+ */
324
+
325
+ visited?: Set<string>) => VariableDeclaration | undefined;
310
326
  declare const maybeIdentifierValue: (identifier: Identifier, _stack: Node[], ctx: BoxContext) => BoxNode;
311
327
  //#endregion
312
328
  //#region src/jsx-spread-attribute.d.ts
@@ -316,6 +332,10 @@ declare const extractJsxSpreadAttributeValues: (node: JsxSpreadAttribute, ctx: B
316
332
  //#region src/utils.d.ts
317
333
  declare const unwrapExpression: (node: Node) => Node;
318
334
  //#endregion
335
+ //#region src/dependency-cache.d.ts
336
+ /** Mark one edited file's content stale for every cache entry that recorded reading it. */
337
+ declare const invalidateDependencyPath: (filePath: string) => void;
338
+ //#endregion
319
339
  //#region src/unbox.d.ts
320
340
  type BoxNodeType = BoxNode | BoxNode[] | undefined;
321
341
  type CacheMap = WeakMap<BoxNode, Unboxed>;
@@ -335,4 +355,4 @@ interface Unboxed {
335
355
  }
336
356
  declare const unbox: (node: BoxNodeType, ctx?: Pick<UnboxContext, "cache">) => Unboxed;
337
357
  //#endregion
338
- 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 ResolveModule, type Unboxed, box, clearBoxNodeCache, extract, extractCallExpressionArguments, extractJsxAttribute, extractJsxElementProps, extractJsxSpreadAttributeValues, findIdentifierValueDeclaration, isBoxNode, maybeBoxNode, maybeIdentifierValue, unbox, unwrapExpression };
358
+ 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 ResolveModule, type Unboxed, box, clearBoxNodeCache, extract, extractCallExpressionArguments, extractJsxAttribute, extractJsxElementProps, extractJsxSpreadAttributeValues, findIdentifierValueDeclaration, getExportedVarDeclarationWithName, invalidateDependencyPath, isBoxNode, maybeBoxNode, maybeIdentifierValue, unbox, unwrapExpression };
package/dist/index.mjs CHANGED
@@ -310,6 +310,37 @@ const EMPTY_DEPENDENCIES = [];
310
310
  /** One synchronous extraction context owns one nested capture stack. */
311
311
  const trackers = /* @__PURE__ */ new WeakMap();
312
312
  /**
313
+ * Per-path staleness, so an edit invalidates exactly the entries that read the edited file.
314
+ *
315
+ * The caches these entries live in are `WeakMap`s keyed on AST nodes — deliberately, so a
316
+ * retired tree releases its memory — which means they cannot be swept. What they *can* do is
317
+ * refuse a hit: every entry already records the module paths its computation read, because
318
+ * that same record is what the watch system replays into fold dependencies. Stamping entries
319
+ * with a creation generation and bumping a per-path generation on each edit turns the
320
+ * recorded read-set into a validity check, evaluated lazily at lookup.
321
+ *
322
+ * The trust base is unchanged. If a cross-file read were missing from `dependencies`, the
323
+ * fold's watch edges would already be missing it, and editing that file would fail to
324
+ * re-transform its consumers today — the recorded read-set is load-bearing for invalidation
325
+ * breadth before it is for cache validity.
326
+ *
327
+ * An entry's *own* file needs no record: reloading or overwriting a file re-parses it, which
328
+ * retires every node previously taken from it, and a retired node can never be a lookup key
329
+ * again. Only cross-file reads can go stale while the key survives, and those are exactly
330
+ * what the capture records.
331
+ *
332
+ * File-tree changes are not handled here at all: a created or deleted file can change what a
333
+ * specifier *resolves to* without any recorded path's content moving, so those events keep
334
+ * clearing the caches outright, exactly as before.
335
+ */
336
+ let dependencyGeneration = 1;
337
+ const invalidatedAt = /* @__PURE__ */ new Map();
338
+ /** Mark one edited file's content stale for every cache entry that recorded reading it. */
339
+ const invalidateDependencyPath = (filePath) => {
340
+ dependencyGeneration++;
341
+ invalidatedAt.set(filePath.replaceAll("\\", "/"), dependencyGeneration);
342
+ };
343
+ /**
313
344
  * Attribute one resolved local module to every cache computation currently enclosing it.
314
345
  *
315
346
  * Paths, rather than SourceFile objects, are the replay payload. A source replacement may
@@ -340,6 +371,7 @@ const beginDependencyCapture = (ctx) => {
340
371
  return {
341
372
  dependencies: frame.dependencies ? [...frame.dependencies].sort() : EMPTY_DEPENDENCIES,
342
373
  resolveModule: ctx.resolveModule,
374
+ generation: dependencyGeneration,
343
375
  value
344
376
  };
345
377
  },
@@ -357,6 +389,10 @@ const beginDependencyCapture = (ctx) => {
357
389
  */
358
390
  const replayDependencyCache = (entry, ctx) => {
359
391
  if (entry.resolveModule !== ctx.resolveModule) return { hit: false };
392
+ for (const dependency of entry.dependencies) {
393
+ const at = invalidatedAt.get(dependency);
394
+ if (at !== void 0 && at > entry.generation) return { hit: false };
395
+ }
360
396
  for (const dependency of entry.dependencies) recordModuleDependency(ctx, dependency);
361
397
  return {
362
398
  hit: true,
@@ -1247,6 +1283,7 @@ const maybeDefinitionValue = (def, stack, ctx) => {
1247
1283
  }
1248
1284
  };
1249
1285
  const getExportedVarDeclarationWithName = (varName, sourceFile, stack, ctx, visited = /* @__PURE__ */ new Set()) => {
1286
+ ctx.recordExportRead?.(sourceFile.getFilePath(), varName);
1250
1287
  const key = `${sourceFile.getFilePath()}:${varName}`;
1251
1288
  if (visited.has(key)) return;
1252
1289
  visited.add(key);
@@ -2195,4 +2232,4 @@ const unbox = (node, ctx) => {
2195
2232
  return result;
2196
2233
  };
2197
2234
  //#endregion
2198
- export { BoxNodeArray, BoxNodeConditional, BoxNodeEmptyInitializer, BoxNodeLiteral, BoxNodeMap, BoxNodeObject, BoxNodeUnresolvable, box, clearBoxNodeCache, extract, extractCallExpressionArguments, extractJsxAttribute, extractJsxElementProps, extractJsxSpreadAttributeValues, findIdentifierValueDeclaration, isBoxNode, maybeBoxNode, maybeIdentifierValue, unbox, unwrapExpression };
2235
+ export { BoxNodeArray, BoxNodeConditional, BoxNodeEmptyInitializer, BoxNodeLiteral, BoxNodeMap, BoxNodeObject, BoxNodeUnresolvable, box, clearBoxNodeCache, extract, extractCallExpressionArguments, extractJsxAttribute, extractJsxElementProps, extractJsxSpreadAttributeValues, findIdentifierValueDeclaration, getExportedVarDeclarationWithName, invalidateDependencyPath, isBoxNode, maybeBoxNode, maybeIdentifierValue, unbox, unwrapExpression };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamboocss/extractor",
3
- "version": "1.46.1",
3
+ "version": "1.46.2",
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": "2.0.0",
37
37
  "ts-morph": "28.0.0",
38
- "@bamboocss/shared": "1.46.1"
38
+ "@bamboocss/shared": "1.46.2"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsdown src/index.ts --format=cjs,esm --shims --dts",