@yahoo/uds 3.162.0 → 3.162.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/cli/commands/purge.cjs +8 -1
- package/dist/cli/commands/purge.helpers.cjs +7 -0
- package/dist/cli/commands/purge.helpers.d.cts +3 -0
- package/dist/cli/commands/purge.helpers.d.ts +3 -0
- package/dist/cli/commands/purge.helpers.js +7 -0
- package/dist/cli/commands/purge.js +8 -1
- package/dist/components/client/Menu/Menu.ItemCheckbox.d.cts +1 -1
- package/dist/components/client/Menu/Menu.ItemCheckbox.d.ts +1 -1
- package/dist/components/client/Popover/UDSPopoverConfigProvider.d.cts +1 -1
- package/dist/components/client/Popover/UDSPopoverConfigProvider.d.ts +1 -1
- package/dist/components/client/Switch/Switch.cjs +5 -2
- package/dist/components/client/Switch/Switch.js +5 -2
- package/dist/components/client/Switch/switchStyles.cjs +2 -2
- package/dist/components/client/Switch/switchStyles.js +2 -2
- package/dist/css/dist/commands/css.cjs +2 -0
- package/dist/css/dist/commands/css.helpers.cjs +2 -0
- package/dist/css/dist/commands/css.helpers.js +2 -0
- package/dist/css/dist/commands/css.js +2 -0
- package/dist/css/dist/css/nodeUtils.cjs +28 -11
- package/dist/css/dist/css/nodeUtils.js +29 -12
- package/dist/css/dist/css/runner.cjs +8 -3
- package/dist/css/dist/css/runner.js +8 -3
- package/dist/css/dist/css/theme.d.cts +16 -0
- package/dist/css/dist/css/theme.d.ts +16 -0
- package/dist/css/dist/css/workerPool.cjs +2 -1
- package/dist/css/dist/css/workerPool.js +2 -1
- package/dist/css/dist/purger/optimized/ast/expressions.cjs +104 -11
- package/dist/css/dist/purger/optimized/ast/expressions.js +104 -11
- package/dist/css/dist/purger/optimized/ast/jsx.cjs +74 -0
- package/dist/css/dist/purger/optimized/ast/jsx.js +73 -0
- package/dist/css/dist/purger/optimized/ast/props.cjs +47 -0
- package/dist/css/dist/purger/optimized/ast/props.js +46 -0
- package/dist/css/dist/purger/optimized/ast/spread.cjs +87 -0
- package/dist/css/dist/purger/optimized/ast/spread.js +86 -0
- package/dist/css/dist/purger/optimized/purge.cjs +14 -8
- package/dist/css/dist/purger/optimized/purge.js +15 -9
- package/dist/css/dist/purger/optimized/purgeFromCode.cjs +176 -18
- package/dist/css/dist/purger/optimized/purgeFromCode.js +176 -19
- package/dist/css/dist/purger/optimized/utils/files.cjs +22 -2
- package/dist/css/dist/purger/optimized/utils/files.js +21 -3
- package/dist/hooks/useRtl.cjs +4 -8
- package/dist/hooks/useRtl.js +4 -8
- package/dist/styles/styler.d.cts +25 -25
- package/dist/styles/styler.d.ts +25 -25
- package/dist/uds/generated/componentData.cjs +2555 -2553
- package/dist/uds/generated/componentData.js +2555 -2553
- package/dist/uds/generated/tailwindPurge.cjs +1 -1
- package/dist/uds/generated/tailwindPurge.js +1 -1
- package/generated/componentData.json +3340 -3338
- package/generated/tailwindPurge.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/*! © 2026 Yahoo, Inc. UDS v0.0.0-development */
|
|
2
|
+
require("../../../../../_virtual/_rolldown/runtime.cjs");
|
|
3
|
+
const require_jsx = require("./jsx.cjs");
|
|
4
|
+
const require_props = require("./props.cjs");
|
|
5
|
+
let ts_morph = require("ts-morph");
|
|
6
|
+
//#region ../css/dist/purger/optimized/ast/spread.mjs
|
|
7
|
+
/*! © 2026 Yahoo, Inc. UDS CSS v0.0.0-development */
|
|
8
|
+
/**
|
|
9
|
+
* Find all usages of a component across the entire project.
|
|
10
|
+
* This is the key function for spread resolution - when we encounter
|
|
11
|
+
* {...props} on a UDS component inside a wrapper, we trace back to
|
|
12
|
+
* find all places where the wrapper is used and extract actual values.
|
|
13
|
+
*
|
|
14
|
+
* @param componentName Name of the component to find usages of
|
|
15
|
+
* @param project The ts-morph project
|
|
16
|
+
* @param cache Optional cache to avoid re-scanning
|
|
17
|
+
* @returns Array of component usages with their props
|
|
18
|
+
*/
|
|
19
|
+
const findComponentUsages = (componentName, project, cache) => {
|
|
20
|
+
if (cache?.has(componentName)) return cache.get(componentName);
|
|
21
|
+
const usages = [];
|
|
22
|
+
project.getSourceFiles().forEach((sourceFile) => {
|
|
23
|
+
if (!fileReferencesComponent(sourceFile, componentName)) return;
|
|
24
|
+
[...sourceFile.getDescendantsOfKind(ts_morph.SyntaxKind.JsxSelfClosingElement), ...sourceFile.getDescendantsOfKind(ts_morph.SyntaxKind.JsxOpeningElement)].filter((el) => el.getTagNameNode().getText() === componentName).forEach((element) => {
|
|
25
|
+
const props = require_props.extractExplicitProps(element);
|
|
26
|
+
usages.push({
|
|
27
|
+
componentName,
|
|
28
|
+
filePath: sourceFile.getFilePath(),
|
|
29
|
+
line: element.getStartLineNumber(),
|
|
30
|
+
props
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
if (cache) cache.set(componentName, usages);
|
|
35
|
+
return usages;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Check if a source file references a component (by import or definition)
|
|
39
|
+
*/
|
|
40
|
+
const fileReferencesComponent = (sourceFile, componentName) => {
|
|
41
|
+
if (sourceFile.getImportDeclarations().some((imp) => imp.getNamedImports().some((named) => named.getName() === componentName))) return true;
|
|
42
|
+
if (sourceFile.getVariableDeclaration(componentName)) return true;
|
|
43
|
+
if (sourceFile.getFunction(componentName)) return true;
|
|
44
|
+
return false;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Resolve spread props by tracing component usage.
|
|
48
|
+
*
|
|
49
|
+
* When we encounter {...props} on a UDS component inside a wrapper:
|
|
50
|
+
* 1. Find the parent component that contains this spread
|
|
51
|
+
* 2. Search ALL files for usages of that parent component
|
|
52
|
+
* 3. Extract actual prop values passed at each usage site
|
|
53
|
+
* 4. Return ONLY those values - never fall back to type properties
|
|
54
|
+
*
|
|
55
|
+
* @param element The JSX element with the spread
|
|
56
|
+
* @param spreadIdentifier The identifier being spread (e.g., 'rest' from {...rest})
|
|
57
|
+
* @param context The purge context for caching
|
|
58
|
+
* @returns Map of prop names to their resolved values, or undefined if couldn't trace
|
|
59
|
+
*/
|
|
60
|
+
const resolveSpreadFromUsages = (element, spreadIdentifier, context) => {
|
|
61
|
+
const parentInfo = require_jsx.getParentComponentInfo(element);
|
|
62
|
+
if (!parentInfo) return;
|
|
63
|
+
if (!(parentInfo.spreadRestIdentifier === spreadIdentifier || parentInfo.paramIdentifier === spreadIdentifier)) return;
|
|
64
|
+
const usages = findComponentUsages(parentInfo.componentName, context.project, context.componentUsageCache);
|
|
65
|
+
if (usages.length === 0) return;
|
|
66
|
+
context.stats.spreadsTraced++;
|
|
67
|
+
const resolvedProps = /* @__PURE__ */ new Map();
|
|
68
|
+
usages.forEach((usage) => {
|
|
69
|
+
usage.props.forEach((prop) => {
|
|
70
|
+
if (prop.fromSpread) return;
|
|
71
|
+
const existing = resolvedProps.get(prop.name) ?? [];
|
|
72
|
+
prop.values.forEach((value) => {
|
|
73
|
+
if (!existing.includes(value)) existing.push(value);
|
|
74
|
+
});
|
|
75
|
+
resolvedProps.set(prop.name, existing);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
const cacheKey = `${element.getSourceFile().getFilePath()}:${element.getStartLineNumber()}:${spreadIdentifier}`;
|
|
79
|
+
context.spreadCache.set(cacheKey, Array.from(resolvedProps).map(([name, values]) => ({
|
|
80
|
+
name,
|
|
81
|
+
values,
|
|
82
|
+
fromSpread: true
|
|
83
|
+
})));
|
|
84
|
+
return resolvedProps;
|
|
85
|
+
};
|
|
86
|
+
//#endregion
|
|
87
|
+
exports.resolveSpreadFromUsages = resolveSpreadFromUsages;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/*! © 2026 Yahoo, Inc. UDS v0.0.0-development */
|
|
2
|
+
import { getParentComponentInfo } from "./jsx.js";
|
|
3
|
+
import { extractExplicitProps } from "./props.js";
|
|
4
|
+
import { SyntaxKind } from "ts-morph";
|
|
5
|
+
//#region ../css/dist/purger/optimized/ast/spread.mjs
|
|
6
|
+
/*! © 2026 Yahoo, Inc. UDS CSS v0.0.0-development */
|
|
7
|
+
/**
|
|
8
|
+
* Find all usages of a component across the entire project.
|
|
9
|
+
* This is the key function for spread resolution - when we encounter
|
|
10
|
+
* {...props} on a UDS component inside a wrapper, we trace back to
|
|
11
|
+
* find all places where the wrapper is used and extract actual values.
|
|
12
|
+
*
|
|
13
|
+
* @param componentName Name of the component to find usages of
|
|
14
|
+
* @param project The ts-morph project
|
|
15
|
+
* @param cache Optional cache to avoid re-scanning
|
|
16
|
+
* @returns Array of component usages with their props
|
|
17
|
+
*/
|
|
18
|
+
const findComponentUsages = (componentName, project, cache) => {
|
|
19
|
+
if (cache?.has(componentName)) return cache.get(componentName);
|
|
20
|
+
const usages = [];
|
|
21
|
+
project.getSourceFiles().forEach((sourceFile) => {
|
|
22
|
+
if (!fileReferencesComponent(sourceFile, componentName)) return;
|
|
23
|
+
[...sourceFile.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement), ...sourceFile.getDescendantsOfKind(SyntaxKind.JsxOpeningElement)].filter((el) => el.getTagNameNode().getText() === componentName).forEach((element) => {
|
|
24
|
+
const props = extractExplicitProps(element);
|
|
25
|
+
usages.push({
|
|
26
|
+
componentName,
|
|
27
|
+
filePath: sourceFile.getFilePath(),
|
|
28
|
+
line: element.getStartLineNumber(),
|
|
29
|
+
props
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
if (cache) cache.set(componentName, usages);
|
|
34
|
+
return usages;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Check if a source file references a component (by import or definition)
|
|
38
|
+
*/
|
|
39
|
+
const fileReferencesComponent = (sourceFile, componentName) => {
|
|
40
|
+
if (sourceFile.getImportDeclarations().some((imp) => imp.getNamedImports().some((named) => named.getName() === componentName))) return true;
|
|
41
|
+
if (sourceFile.getVariableDeclaration(componentName)) return true;
|
|
42
|
+
if (sourceFile.getFunction(componentName)) return true;
|
|
43
|
+
return false;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Resolve spread props by tracing component usage.
|
|
47
|
+
*
|
|
48
|
+
* When we encounter {...props} on a UDS component inside a wrapper:
|
|
49
|
+
* 1. Find the parent component that contains this spread
|
|
50
|
+
* 2. Search ALL files for usages of that parent component
|
|
51
|
+
* 3. Extract actual prop values passed at each usage site
|
|
52
|
+
* 4. Return ONLY those values - never fall back to type properties
|
|
53
|
+
*
|
|
54
|
+
* @param element The JSX element with the spread
|
|
55
|
+
* @param spreadIdentifier The identifier being spread (e.g., 'rest' from {...rest})
|
|
56
|
+
* @param context The purge context for caching
|
|
57
|
+
* @returns Map of prop names to their resolved values, or undefined if couldn't trace
|
|
58
|
+
*/
|
|
59
|
+
const resolveSpreadFromUsages = (element, spreadIdentifier, context) => {
|
|
60
|
+
const parentInfo = getParentComponentInfo(element);
|
|
61
|
+
if (!parentInfo) return;
|
|
62
|
+
if (!(parentInfo.spreadRestIdentifier === spreadIdentifier || parentInfo.paramIdentifier === spreadIdentifier)) return;
|
|
63
|
+
const usages = findComponentUsages(parentInfo.componentName, context.project, context.componentUsageCache);
|
|
64
|
+
if (usages.length === 0) return;
|
|
65
|
+
context.stats.spreadsTraced++;
|
|
66
|
+
const resolvedProps = /* @__PURE__ */ new Map();
|
|
67
|
+
usages.forEach((usage) => {
|
|
68
|
+
usage.props.forEach((prop) => {
|
|
69
|
+
if (prop.fromSpread) return;
|
|
70
|
+
const existing = resolvedProps.get(prop.name) ?? [];
|
|
71
|
+
prop.values.forEach((value) => {
|
|
72
|
+
if (!existing.includes(value)) existing.push(value);
|
|
73
|
+
});
|
|
74
|
+
resolvedProps.set(prop.name, existing);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
const cacheKey = `${element.getSourceFile().getFilePath()}:${element.getStartLineNumber()}:${spreadIdentifier}`;
|
|
78
|
+
context.spreadCache.set(cacheKey, Array.from(resolvedProps).map(([name, values]) => ({
|
|
79
|
+
name,
|
|
80
|
+
values,
|
|
81
|
+
fromSpread: true
|
|
82
|
+
})));
|
|
83
|
+
return resolvedProps;
|
|
84
|
+
};
|
|
85
|
+
//#endregion
|
|
86
|
+
export { resolveSpreadFromUsages };
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
const require_runtime = require("../../../../_virtual/_rolldown/runtime.cjs");
|
|
3
3
|
const require_purgeFromCode = require("./purgeFromCode.cjs");
|
|
4
4
|
const require_entryPoints = require("../../../../tailwind-internal/dist/utils/entryPoints.cjs");
|
|
5
|
-
const require_safelist = require("./utils/safelist.cjs");
|
|
6
5
|
const require_files = require("./utils/files.cjs");
|
|
6
|
+
const require_safelist = require("./utils/safelist.cjs");
|
|
7
7
|
let node_path = require("node:path");
|
|
8
8
|
node_path = require_runtime.__toESM(node_path, 1);
|
|
9
9
|
let node_fs = require("node:fs");
|
|
@@ -24,11 +24,11 @@ const clearWarnings = () => {
|
|
|
24
24
|
* between CLI and playground.
|
|
25
25
|
*/
|
|
26
26
|
const purgeOptimized = async (options) => {
|
|
27
|
-
const { entry = require_entryPoints.DEFAULT_ENTRY, output = "dist/safelist.ts", colorModes = ["dark"], variants, autoVariants, componentData } = options;
|
|
27
|
+
const { entry = require_entryPoints.DEFAULT_ENTRY, output = "dist/safelist.ts", colorModes = ["dark"], ignoreDevFiles = true, ignore = [], includeStories = false, variants, autoVariants, componentData } = options;
|
|
28
28
|
const startTime = performance.now();
|
|
29
29
|
clearWarnings();
|
|
30
30
|
const workspaceDir = process.env.PWD ?? process.cwd();
|
|
31
|
-
const files = await require_files.scanForFiles(entry);
|
|
31
|
+
const files = await require_files.scanForFiles(entry, ignoreDevFiles && !includeStories, ignoreDevFiles && includeStories ? [...ignore, ...require_files.TEST_FILE_IGNORE_GLOBS] : ignore);
|
|
32
32
|
const allClasses = [];
|
|
33
33
|
const allImports = /* @__PURE__ */ new Set();
|
|
34
34
|
const allComponents = /* @__PURE__ */ new Set();
|
|
@@ -37,26 +37,32 @@ const purgeOptimized = async (options) => {
|
|
|
37
37
|
timeMs: 0,
|
|
38
38
|
classesGenerated: 0,
|
|
39
39
|
spreadsTraced: 0,
|
|
40
|
+
spreadsUnresolved: 0,
|
|
40
41
|
expressionsResolved: 0
|
|
41
42
|
};
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
const fileContents = files.map((filePath) => node_fs.default.readFileSync(filePath, "utf-8"));
|
|
44
|
+
const results = await Promise.all(fileContents.map(async (code) => {
|
|
45
|
+
return require_purgeFromCode.purgeFromCodeOptimized(code, {
|
|
44
46
|
colorModes,
|
|
45
47
|
variants,
|
|
46
48
|
autoVariants,
|
|
47
|
-
componentData
|
|
49
|
+
componentData,
|
|
50
|
+
deferWrapperSpreadResolution: true
|
|
48
51
|
});
|
|
49
|
-
}))
|
|
52
|
+
}));
|
|
53
|
+
results.forEach((result) => {
|
|
50
54
|
allClasses.push(...result.safelist);
|
|
51
55
|
result.imports.forEach((imp) => allImports.add(imp));
|
|
52
56
|
result.components.forEach((comp) => allComponents.add(comp));
|
|
53
57
|
stats.spreadsTraced += result.stats.spreadsTraced;
|
|
58
|
+
stats.spreadsUnresolved += result.stats.spreadsUnresolved;
|
|
54
59
|
stats.expressionsResolved += result.stats.expressionsResolved;
|
|
55
60
|
});
|
|
61
|
+
allClasses.push(...require_purgeFromCode.resolveWrapperSpreadSafelist(results.flatMap((result) => result.unresolvedWrapperSpreads), fileContents, variants));
|
|
56
62
|
allClasses.push(...require_safelist.getThemeAndScaleClasses(colorModes));
|
|
57
63
|
allClasses.push(...require_safelist.getInternalSafelistClasses());
|
|
58
64
|
const finalSafelist = require_safelist.deduplicateSafelist(allClasses).sort();
|
|
59
|
-
await require_safelist.saveSafelistToFile(finalSafelist, node_path.default.join(workspaceDir, output));
|
|
65
|
+
await require_safelist.saveSafelistToFile(finalSafelist, node_path.default.isAbsolute(output) ? output : node_path.default.join(workspaceDir, output));
|
|
60
66
|
stats.timeMs = Math.round(performance.now() - startTime);
|
|
61
67
|
stats.classesGenerated = finalSafelist.length;
|
|
62
68
|
return {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/*! © 2026 Yahoo, Inc. UDS v0.0.0-development */
|
|
2
|
-
import { purgeFromCodeOptimized } from "./purgeFromCode.js";
|
|
2
|
+
import { purgeFromCodeOptimized, resolveWrapperSpreadSafelist } from "./purgeFromCode.js";
|
|
3
3
|
import { DEFAULT_ENTRY } from "../../../../tailwind-internal/dist/utils/entryPoints.js";
|
|
4
|
+
import { TEST_FILE_IGNORE_GLOBS, scanForFiles } from "./utils/files.js";
|
|
4
5
|
import { deduplicateSafelist, getInternalSafelistClasses, getThemeAndScaleClasses, saveSafelistToFile } from "./utils/safelist.js";
|
|
5
|
-
import { scanForFiles } from "./utils/files.js";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import fs from "node:fs";
|
|
8
8
|
//#region ../css/dist/purger/optimized/purge.mjs
|
|
@@ -21,11 +21,11 @@ const clearWarnings = () => {
|
|
|
21
21
|
* between CLI and playground.
|
|
22
22
|
*/
|
|
23
23
|
const purgeOptimized = async (options) => {
|
|
24
|
-
const { entry = DEFAULT_ENTRY, output = "dist/safelist.ts", colorModes = ["dark"], variants, autoVariants, componentData } = options;
|
|
24
|
+
const { entry = DEFAULT_ENTRY, output = "dist/safelist.ts", colorModes = ["dark"], ignoreDevFiles = true, ignore = [], includeStories = false, variants, autoVariants, componentData } = options;
|
|
25
25
|
const startTime = performance.now();
|
|
26
26
|
clearWarnings();
|
|
27
27
|
const workspaceDir = process.env.PWD ?? process.cwd();
|
|
28
|
-
const files = await scanForFiles(entry);
|
|
28
|
+
const files = await scanForFiles(entry, ignoreDevFiles && !includeStories, ignoreDevFiles && includeStories ? [...ignore, ...TEST_FILE_IGNORE_GLOBS] : ignore);
|
|
29
29
|
const allClasses = [];
|
|
30
30
|
const allImports = /* @__PURE__ */ new Set();
|
|
31
31
|
const allComponents = /* @__PURE__ */ new Set();
|
|
@@ -34,26 +34,32 @@ const purgeOptimized = async (options) => {
|
|
|
34
34
|
timeMs: 0,
|
|
35
35
|
classesGenerated: 0,
|
|
36
36
|
spreadsTraced: 0,
|
|
37
|
+
spreadsUnresolved: 0,
|
|
37
38
|
expressionsResolved: 0
|
|
38
39
|
};
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
const fileContents = files.map((filePath) => fs.readFileSync(filePath, "utf-8"));
|
|
41
|
+
const results = await Promise.all(fileContents.map(async (code) => {
|
|
42
|
+
return purgeFromCodeOptimized(code, {
|
|
41
43
|
colorModes,
|
|
42
44
|
variants,
|
|
43
45
|
autoVariants,
|
|
44
|
-
componentData
|
|
46
|
+
componentData,
|
|
47
|
+
deferWrapperSpreadResolution: true
|
|
45
48
|
});
|
|
46
|
-
}))
|
|
49
|
+
}));
|
|
50
|
+
results.forEach((result) => {
|
|
47
51
|
allClasses.push(...result.safelist);
|
|
48
52
|
result.imports.forEach((imp) => allImports.add(imp));
|
|
49
53
|
result.components.forEach((comp) => allComponents.add(comp));
|
|
50
54
|
stats.spreadsTraced += result.stats.spreadsTraced;
|
|
55
|
+
stats.spreadsUnresolved += result.stats.spreadsUnresolved;
|
|
51
56
|
stats.expressionsResolved += result.stats.expressionsResolved;
|
|
52
57
|
});
|
|
58
|
+
allClasses.push(...resolveWrapperSpreadSafelist(results.flatMap((result) => result.unresolvedWrapperSpreads), fileContents, variants));
|
|
53
59
|
allClasses.push(...getThemeAndScaleClasses(colorModes));
|
|
54
60
|
allClasses.push(...getInternalSafelistClasses());
|
|
55
61
|
const finalSafelist = deduplicateSafelist(allClasses).sort();
|
|
56
|
-
await saveSafelistToFile(finalSafelist, path.join(workspaceDir, output));
|
|
62
|
+
await saveSafelistToFile(finalSafelist, path.isAbsolute(output) ? output : path.join(workspaceDir, output));
|
|
57
63
|
stats.timeMs = Math.round(performance.now() - startTime);
|
|
58
64
|
stats.classesGenerated = finalSafelist.length;
|
|
59
65
|
return {
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
require("../../../../_virtual/_rolldown/runtime.cjs");
|
|
3
3
|
const require_perf = require("../../css/perf.cjs");
|
|
4
4
|
const require_expressions = require("./ast/expressions.cjs");
|
|
5
|
+
const require_jsx = require("./ast/jsx.cjs");
|
|
6
|
+
const require_spread = require("./ast/spread.cjs");
|
|
5
7
|
let ts_morph = require("ts-morph");
|
|
6
8
|
//#region ../css/dist/purger/optimized/purgeFromCode.mjs
|
|
7
9
|
/*! © 2026 Yahoo, Inc. UDS CSS v0.0.0-development */
|
|
@@ -71,10 +73,10 @@ const normalizeComponentName = (name) => {
|
|
|
71
73
|
return name.replace(/\W/g, "").toLowerCase();
|
|
72
74
|
};
|
|
73
75
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
+
* All autoVariants group keys belonging to a component (by normalized name
|
|
77
|
+
* prefix, e.g. Button -> buttonVariantRoot, buttonSizeIcon, ...).
|
|
76
78
|
*/
|
|
77
|
-
const
|
|
79
|
+
const getComponentAutoVariantKeys = (componentName) => {
|
|
78
80
|
if (!autoVariantsCache) return [];
|
|
79
81
|
const segments = componentName.match(/[A-Z][a-z0-9]*/g) ?? [];
|
|
80
82
|
const candidates = new Set([normalizeComponentName(componentName)]);
|
|
@@ -82,12 +84,19 @@ const getAutoVariantKeysForProp = (componentName, propName) => {
|
|
|
82
84
|
const partial = segments.slice(0, i).join("");
|
|
83
85
|
candidates.add(normalizeComponentName(partial));
|
|
84
86
|
});
|
|
85
|
-
const propLower = propName.toLowerCase();
|
|
86
87
|
return Object.keys(autoVariantsCache).filter((key) => {
|
|
87
88
|
const keyLower = key.toLowerCase();
|
|
88
|
-
return Array.from(candidates).some((candidate) => keyLower.startsWith(candidate)
|
|
89
|
+
return Array.from(candidates).some((candidate) => keyLower.startsWith(candidate));
|
|
89
90
|
});
|
|
90
91
|
};
|
|
92
|
+
/**
|
|
93
|
+
* Fallback: infer prop -> variant key mapping from autoVariants when the
|
|
94
|
+
* generated component data misses it (e.g., helpers like buildAvatarRootClasses).
|
|
95
|
+
*/
|
|
96
|
+
const getAutoVariantKeysForProp = (componentName, propName) => {
|
|
97
|
+
const propLower = propName.toLowerCase();
|
|
98
|
+
return getComponentAutoVariantKeys(componentName).filter((key) => key.toLowerCase().includes(propLower));
|
|
99
|
+
};
|
|
91
100
|
const getVariantKeysForProp = (componentInfo, propName) => {
|
|
92
101
|
const keys = new Set(componentInfo.propToVariantKeys?.[propName] ?? []);
|
|
93
102
|
getAutoVariantKeysForProp(componentInfo.name, propName).forEach((inferred) => keys.add(inferred));
|
|
@@ -169,19 +178,30 @@ const purgeFromCodeOptimized = async (code, options) => {
|
|
|
169
178
|
timeMs: 0,
|
|
170
179
|
classesGenerated: 0,
|
|
171
180
|
spreadsTraced: 0,
|
|
181
|
+
spreadsUnresolved: 0,
|
|
172
182
|
expressionsResolved: 0
|
|
173
|
-
}
|
|
183
|
+
},
|
|
184
|
+
unresolvedWrapperSpreads: []
|
|
174
185
|
};
|
|
175
186
|
const projectCreateStart = performance.now();
|
|
176
|
-
const
|
|
187
|
+
const project = new ts_morph.Project({ useInMemoryFileSystem: true });
|
|
188
|
+
const sourceFile = project.createSourceFile(options.filePath ?? "input.tsx", code, { overwrite: true });
|
|
177
189
|
const projectCreateMs = performance.now() - projectCreateStart;
|
|
178
190
|
const stats = {
|
|
179
191
|
filesScanned: 1,
|
|
180
192
|
timeMs: 0,
|
|
181
193
|
classesGenerated: 0,
|
|
182
194
|
spreadsTraced: 0,
|
|
195
|
+
spreadsUnresolved: 0,
|
|
183
196
|
expressionsResolved: 0
|
|
184
197
|
};
|
|
198
|
+
const purgeContext = {
|
|
199
|
+
project,
|
|
200
|
+
componentUsageCache: /* @__PURE__ */ new Map(),
|
|
201
|
+
spreadCache: /* @__PURE__ */ new Map(),
|
|
202
|
+
udsImportingFiles: /* @__PURE__ */ new Set(),
|
|
203
|
+
stats
|
|
204
|
+
};
|
|
185
205
|
const importParseStart = performance.now();
|
|
186
206
|
const imports = [];
|
|
187
207
|
sourceFile.getImportDeclarations().forEach((importDecl) => {
|
|
@@ -192,6 +212,8 @@ const purgeFromCodeOptimized = async (code, options) => {
|
|
|
192
212
|
const importParseMs = performance.now() - importParseStart;
|
|
193
213
|
const componentProps = /* @__PURE__ */ new Map();
|
|
194
214
|
const componentUnresolvedSpreadProps = /* @__PURE__ */ new Map();
|
|
215
|
+
const componentsWithUnknownSpreads = /* @__PURE__ */ new Set();
|
|
216
|
+
const unresolvedWrapperSpreads = [];
|
|
195
217
|
const referencedComponents = /* @__PURE__ */ new Set();
|
|
196
218
|
const getStylesExtractStart = performance.now();
|
|
197
219
|
const getStylesProps = extractGetStylesCalls(sourceFile, stats, variants);
|
|
@@ -204,7 +226,8 @@ const purgeFromCodeOptimized = async (code, options) => {
|
|
|
204
226
|
referencedComponents.add(componentName);
|
|
205
227
|
const propsMap = componentProps.get(componentName) ?? /* @__PURE__ */ new Map();
|
|
206
228
|
const unresolvedSpreadProps = componentUnresolvedSpreadProps.get(componentName) ?? /* @__PURE__ */ new Set();
|
|
207
|
-
extractPropsFromReference(reference, stats, sourceFile
|
|
229
|
+
const { props, unknownSpreadWrappers } = extractPropsFromReference(reference, stats, sourceFile, purgeContext);
|
|
230
|
+
props.forEach(({ propName, values, fromSpread }) => {
|
|
208
231
|
if (values.length > 0) {
|
|
209
232
|
const existing = propsMap.get(propName) ?? /* @__PURE__ */ new Set();
|
|
210
233
|
values.forEach((val) => existing.add(val));
|
|
@@ -213,6 +236,13 @@ const purgeFromCodeOptimized = async (code, options) => {
|
|
|
213
236
|
}
|
|
214
237
|
if (fromSpread) unresolvedSpreadProps.add(propName);
|
|
215
238
|
});
|
|
239
|
+
unknownSpreadWrappers.forEach((wrapperName) => {
|
|
240
|
+
if (wrapperName) unresolvedWrapperSpreads.push({
|
|
241
|
+
wrapperName,
|
|
242
|
+
targetComponent: componentName
|
|
243
|
+
});
|
|
244
|
+
if (!wrapperName || !options.deferWrapperSpreadResolution) componentsWithUnknownSpreads.add(componentName);
|
|
245
|
+
});
|
|
216
246
|
componentProps.set(componentName, propsMap);
|
|
217
247
|
componentUnresolvedSpreadProps.set(componentName, unresolvedSpreadProps);
|
|
218
248
|
});
|
|
@@ -242,6 +272,7 @@ const purgeFromCodeOptimized = async (code, options) => {
|
|
|
242
272
|
allComponents.forEach((componentInfo) => {
|
|
243
273
|
const userProps = componentProps.get(componentInfo.name) ?? /* @__PURE__ */ new Map();
|
|
244
274
|
const unresolvedSpreadProps = componentUnresolvedSpreadProps.get(componentInfo.name) ?? /* @__PURE__ */ new Set();
|
|
275
|
+
if (componentsWithUnknownSpreads.has(componentInfo.name)) collectUnknownSpreadClasses(componentInfo, variants, safelist);
|
|
245
276
|
Object.entries(componentInfo.defaultProps).forEach(([propName, propValue]) => {
|
|
246
277
|
if (userProps.has(propName)) return;
|
|
247
278
|
const variantGroup = variants[propName];
|
|
@@ -367,7 +398,8 @@ const purgeFromCodeOptimized = async (code, options) => {
|
|
|
367
398
|
safelist: finalSafelist,
|
|
368
399
|
imports,
|
|
369
400
|
components: [...seenComponents],
|
|
370
|
-
stats
|
|
401
|
+
stats,
|
|
402
|
+
unresolvedWrapperSpreads
|
|
371
403
|
};
|
|
372
404
|
};
|
|
373
405
|
/**
|
|
@@ -400,9 +432,16 @@ const findComponentReferences = (sourceFile, componentName, jsxElementsCache) =>
|
|
|
400
432
|
};
|
|
401
433
|
/**
|
|
402
434
|
* Extract props from a JSX reference.
|
|
435
|
+
*
|
|
436
|
+
* Spread resolution order (UDS-2557):
|
|
437
|
+
* 1. Same-file const object (`traceSpreadInFile`)
|
|
438
|
+
* 2. Wrapper call sites within the project (`resolveSpreadFromUsages`)
|
|
439
|
+
* 3. Otherwise the spread is unknown: reported via `unknownSpreadWrappers` so the
|
|
440
|
+
* caller can over-include variant classes and emit cross-file metadata.
|
|
403
441
|
*/
|
|
404
|
-
const extractPropsFromReference = (reference, stats, sourceFile) => {
|
|
442
|
+
const extractPropsFromReference = (reference, stats, sourceFile, purgeContext) => {
|
|
405
443
|
const props = [];
|
|
444
|
+
const unknownSpreadWrappers = [];
|
|
406
445
|
reference.getAttributes().forEach((attr) => {
|
|
407
446
|
if (attr.asKind(ts_morph.SyntaxKind.JsxAttribute)) {
|
|
408
447
|
const jsxAttr = attr.asKindOrThrow(ts_morph.SyntaxKind.JsxAttribute);
|
|
@@ -427,17 +466,40 @@ const extractPropsFromReference = (reference, stats, sourceFile) => {
|
|
|
427
466
|
});
|
|
428
467
|
}
|
|
429
468
|
if (attr.asKind(ts_morph.SyntaxKind.JsxSpreadAttribute)) {
|
|
430
|
-
|
|
431
|
-
traceSpreadInFile(
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
469
|
+
const spreadName = attr.asKindOrThrow(ts_morph.SyntaxKind.JsxSpreadAttribute).getExpression().getText();
|
|
470
|
+
const resolvedSpreadProps = traceSpreadInFile(spreadName, sourceFile);
|
|
471
|
+
if (resolvedSpreadProps.length > 0) {
|
|
472
|
+
stats.spreadsTraced++;
|
|
473
|
+
resolvedSpreadProps.forEach(([propName, values]) => {
|
|
474
|
+
props.push({
|
|
475
|
+
propName,
|
|
476
|
+
values,
|
|
477
|
+
fromSpread: true
|
|
478
|
+
});
|
|
436
479
|
});
|
|
437
|
-
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
const usageResolved = require_spread.resolveSpreadFromUsages(reference, spreadName, purgeContext);
|
|
483
|
+
if (usageResolved) {
|
|
484
|
+
usageResolved.forEach((values, propName) => {
|
|
485
|
+
props.push({
|
|
486
|
+
propName,
|
|
487
|
+
values,
|
|
488
|
+
fromSpread: true
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
stats.spreadsUnresolved++;
|
|
494
|
+
const parentInfo = require_jsx.getParentComponentInfo(reference);
|
|
495
|
+
const isWrapperParamSpread = parentInfo && (parentInfo.spreadRestIdentifier === spreadName || parentInfo.paramIdentifier === spreadName);
|
|
496
|
+
unknownSpreadWrappers.push(isWrapperParamSpread ? parentInfo.componentName : void 0);
|
|
438
497
|
}
|
|
439
498
|
});
|
|
440
|
-
return
|
|
499
|
+
return {
|
|
500
|
+
props,
|
|
501
|
+
unknownSpreadWrappers
|
|
502
|
+
};
|
|
441
503
|
};
|
|
442
504
|
/**
|
|
443
505
|
* Trace spread props to their source within the same file.
|
|
@@ -514,5 +576,101 @@ const extractGetStylesCalls = (sourceFile, stats, variants) => {
|
|
|
514
576
|
});
|
|
515
577
|
return props;
|
|
516
578
|
};
|
|
579
|
+
/**
|
|
580
|
+
* Over-include every variant-prop class for a component (and its internal deps).
|
|
581
|
+
* Used when a spread's props are unknown: any variant prop could flow through it,
|
|
582
|
+
* so accuracy demands including all of them (UDS-2557).
|
|
583
|
+
*/
|
|
584
|
+
const collectUnknownSpreadClasses = (componentInfo, variants, safelist) => {
|
|
585
|
+
getComponentWithDeps(componentInfo.name).forEach((comp) => {
|
|
586
|
+
Object.keys(comp.propToVariantKeys ?? {}).forEach((propName) => {
|
|
587
|
+
addVariantGroupClasses(variants[propName] ?? autoVariantsCache?.[propName], safelist);
|
|
588
|
+
getVariantKeysForProp(comp, propName).forEach((variantKey) => {
|
|
589
|
+
addVariantGroupClasses(variants[variantKey] ?? autoVariantsCache?.[variantKey], safelist);
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
getComponentAutoVariantKeys(comp.name).forEach((variantKey) => {
|
|
593
|
+
addVariantGroupClasses(autoVariantsCache?.[variantKey], safelist);
|
|
594
|
+
});
|
|
595
|
+
});
|
|
596
|
+
};
|
|
597
|
+
/**
|
|
598
|
+
* Cross-file pass for UDS-2557: resolve wrapper rest-spread props from wrapper
|
|
599
|
+
* call sites found in other scanned files.
|
|
600
|
+
*
|
|
601
|
+
* `purgeFromCodeOptimized` analyzes one file at a time, so when a consumer
|
|
602
|
+
* wrapper (e.g. an i13n wrapper) spreads its props onto a UDS component, the
|
|
603
|
+
* wrapper's call sites usually live in other files. Aggregators pass the
|
|
604
|
+
* unresolved wrapper spreads plus the raw source of every scanned file; this
|
|
605
|
+
* parses only the files that mention a wrapper and maps the explicit prop
|
|
606
|
+
* values at each call site to the target UDS component's classes.
|
|
607
|
+
*
|
|
608
|
+
* Must be called after at least one `purgeFromCodeOptimized` run so the
|
|
609
|
+
* module-level component/autoVariants caches are populated.
|
|
610
|
+
*/
|
|
611
|
+
const resolveWrapperSpreadSafelist = (wrapperSpreads, sources, variants) => {
|
|
612
|
+
if (wrapperSpreads.length === 0) return [];
|
|
613
|
+
const targetsByWrapper = /* @__PURE__ */ new Map();
|
|
614
|
+
wrapperSpreads.forEach(({ wrapperName, targetComponent }) => {
|
|
615
|
+
const targets = targetsByWrapper.get(wrapperName) ?? /* @__PURE__ */ new Set();
|
|
616
|
+
targets.add(targetComponent);
|
|
617
|
+
targetsByWrapper.set(wrapperName, targets);
|
|
618
|
+
});
|
|
619
|
+
const wrapperNames = [...targetsByWrapper.keys()];
|
|
620
|
+
const usageProps = /* @__PURE__ */ new Map();
|
|
621
|
+
sources.filter((code) => wrapperNames.some((name) => code.includes(`<${name}`))).forEach((code) => {
|
|
622
|
+
new ts_morph.Project({ useInMemoryFileSystem: true }).createSourceFile("wrapper-usage.tsx", code, { overwrite: true }).getDescendants().forEach((node) => {
|
|
623
|
+
if (!ts_morph.Node.isJsxOpeningElement(node) && !ts_morph.Node.isJsxSelfClosingElement(node)) return;
|
|
624
|
+
const tagName = node.getTagNameNode().getText();
|
|
625
|
+
if (!targetsByWrapper.has(tagName)) return;
|
|
626
|
+
const propsMap = usageProps.get(tagName) ?? /* @__PURE__ */ new Map();
|
|
627
|
+
node.getAttributes().forEach((attr) => {
|
|
628
|
+
const jsxAttr = attr.asKind(ts_morph.SyntaxKind.JsxAttribute);
|
|
629
|
+
if (!jsxAttr) return;
|
|
630
|
+
const propName = jsxAttr.getNameNode().getText();
|
|
631
|
+
const values = [];
|
|
632
|
+
const initializer = jsxAttr.getInitializer();
|
|
633
|
+
if (initializer && ts_morph.Node.isStringLiteral(initializer)) values.push(initializer.getLiteralText());
|
|
634
|
+
else if (initializer && ts_morph.Node.isJsxExpression(initializer)) {
|
|
635
|
+
const expression = initializer.getExpression();
|
|
636
|
+
if (expression) values.push(...require_expressions.extractStringLiterals(expression));
|
|
637
|
+
}
|
|
638
|
+
if (values.length === 0) return;
|
|
639
|
+
const existing = propsMap.get(propName) ?? /* @__PURE__ */ new Set();
|
|
640
|
+
values.forEach((value) => existing.add(value));
|
|
641
|
+
propsMap.set(propName, existing);
|
|
642
|
+
});
|
|
643
|
+
usageProps.set(tagName, propsMap);
|
|
644
|
+
});
|
|
645
|
+
});
|
|
646
|
+
const safelist = [];
|
|
647
|
+
const pushClass = (variantGroup, value) => {
|
|
648
|
+
const cls = variantGroup?.[value];
|
|
649
|
+
if (cls) safelist.push(cls.replaceAll("\\", ""));
|
|
650
|
+
};
|
|
651
|
+
targetsByWrapper.forEach((targets, wrapperName) => {
|
|
652
|
+
const propsMap = usageProps.get(wrapperName);
|
|
653
|
+
targets.forEach((targetComponent) => {
|
|
654
|
+
const componentInfo = resolveComponentInfo(targetComponent);
|
|
655
|
+
if (!componentInfo) return;
|
|
656
|
+
if (!propsMap || propsMap.size === 0) {
|
|
657
|
+
collectUnknownSpreadClasses(componentInfo, variants, safelist);
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
propsMap.forEach((values, propName) => {
|
|
661
|
+
const variantGroup = variants[propName];
|
|
662
|
+
values.forEach((value) => pushClass(variantGroup, value));
|
|
663
|
+
getComponentWithDeps(componentInfo.name).forEach((comp) => {
|
|
664
|
+
getVariantKeysForProp(comp, propName).forEach((variantKey) => {
|
|
665
|
+
const mappedVariantGroup = variants[variantKey] ?? autoVariantsCache?.[variantKey];
|
|
666
|
+
values.forEach((value) => pushClass(mappedVariantGroup, value));
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
});
|
|
672
|
+
return safelist;
|
|
673
|
+
};
|
|
517
674
|
//#endregion
|
|
518
675
|
exports.purgeFromCodeOptimized = purgeFromCodeOptimized;
|
|
676
|
+
exports.resolveWrapperSpreadSafelist = resolveWrapperSpreadSafelist;
|