@yahoo/uds 3.174.0 → 3.175.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/automated-config/dist/utils/index.cjs +3 -2
- package/dist/automated-config/dist/utils/index.js +3 -2
- package/dist/css/dist/commands/css.cjs +1 -0
- package/dist/css/dist/commands/css.helpers.cjs +6 -0
- package/dist/css/dist/commands/css.helpers.js +6 -0
- package/dist/css/dist/commands/css.js +1 -0
- package/dist/css/dist/css/generate.cjs +7 -2
- package/dist/css/dist/css/generate.d.cts +21 -1
- package/dist/css/dist/css/generate.d.ts +21 -1
- package/dist/css/dist/css/generate.helpers.cjs +11 -5
- package/dist/css/dist/css/generate.helpers.d.cts +7 -1
- package/dist/css/dist/css/generate.helpers.d.ts +7 -1
- package/dist/css/dist/css/generate.helpers.js +11 -5
- package/dist/css/dist/css/generate.js +7 -2
- package/dist/css/dist/css/runner.cjs +9 -2
- package/dist/css/dist/css/runner.js +9 -2
- package/dist/css/dist/postcss/filterUnusedVariantRules.cjs +71 -0
- package/dist/css/dist/postcss/filterUnusedVariantRules.js +70 -0
- package/dist/tailwind-internal/dist/packages/automated-config/dist/utils/index.cjs +3 -2
- package/dist/tailwind-internal/dist/packages/automated-config/dist/utils/index.js +3 -2
- package/dist/tailwind-internal/dist/utils/getShadowStyles.d.cts +2 -2
- package/dist/tailwind-internal/dist/utils/getShadowStyles.d.ts +2 -2
- package/dist/uds/generated/componentData.cjs +175 -175
- package/dist/uds/generated/componentData.js +175 -175
- package/generated/componentData.json +242 -242
- package/package.json +1 -1
|
@@ -970,7 +970,7 @@ function generateConfigStyles(config, schema, theme, previewOptions, emit, gener
|
|
|
970
970
|
* @param schema - Production schema with defaults and variables
|
|
971
971
|
* @param theme - Tailwind theme function to resolve design tokens
|
|
972
972
|
* @param previewOptions - Options for generating preview mode class modifiers
|
|
973
|
-
* @returns CSS styles object with
|
|
973
|
+
* @returns CSS styles object with separate twin rules for default aliases
|
|
974
974
|
*/
|
|
975
975
|
const generateStylesInner = (config, schema, theme, previewOptions, generateStylesContext) => {
|
|
976
976
|
const styles = generateConfigStyles(config, schema, theme, previewOptions, previewOptions.emit ?? "exhaustive", generateStylesContext);
|
|
@@ -1035,7 +1035,8 @@ const generateStylesInner = (config, schema, theme, previewOptions, generateStyl
|
|
|
1035
1035
|
finalStyles[selector] = declarations;
|
|
1036
1036
|
continue;
|
|
1037
1037
|
}
|
|
1038
|
-
finalStyles[
|
|
1038
|
+
finalStyles[selector] = declarations;
|
|
1039
|
+
finalStyles[defaultsSelector] = declarations;
|
|
1039
1040
|
}
|
|
1040
1041
|
return finalStyles;
|
|
1041
1042
|
};
|
|
@@ -969,7 +969,7 @@ function generateConfigStyles(config, schema, theme, previewOptions, emit, gener
|
|
|
969
969
|
* @param schema - Production schema with defaults and variables
|
|
970
970
|
* @param theme - Tailwind theme function to resolve design tokens
|
|
971
971
|
* @param previewOptions - Options for generating preview mode class modifiers
|
|
972
|
-
* @returns CSS styles object with
|
|
972
|
+
* @returns CSS styles object with separate twin rules for default aliases
|
|
973
973
|
*/
|
|
974
974
|
const generateStylesInner = (config, schema, theme, previewOptions, generateStylesContext) => {
|
|
975
975
|
const styles = generateConfigStyles(config, schema, theme, previewOptions, previewOptions.emit ?? "exhaustive", generateStylesContext);
|
|
@@ -1034,7 +1034,8 @@ const generateStylesInner = (config, schema, theme, previewOptions, generateStyl
|
|
|
1034
1034
|
finalStyles[selector] = declarations;
|
|
1035
1035
|
continue;
|
|
1036
1036
|
}
|
|
1037
|
-
finalStyles[
|
|
1037
|
+
finalStyles[selector] = declarations;
|
|
1038
|
+
finalStyles[defaultsSelector] = declarations;
|
|
1038
1039
|
}
|
|
1039
1040
|
return finalStyles;
|
|
1040
1041
|
};
|
|
@@ -27,6 +27,7 @@ const showHelp = () => {
|
|
|
27
27
|
require_print.print(` ${require_colors.cyan("--verbose, -v")} List every scanned file used for purging`);
|
|
28
28
|
require_print.print(` ${require_colors.cyan("--workers <n>")} Number of parallel workers (default: auto = CPU count - 1)`);
|
|
29
29
|
require_print.print(` ${require_colors.cyan("--emit <mode>")} Emission mode: exhaustive (default) | selective`);
|
|
30
|
+
require_print.print(` ${require_colors.cyan("--utilities <mode>")} Utility ownership: emit (default) | app-sheet (omit app utilities; requires an onlyTheme app sheet with the same config)`);
|
|
30
31
|
require_print.print(` ${require_colors.cyan("--include-dev-files")} Also scan test/spec/story files (excluded by default)`);
|
|
31
32
|
require_print.print(` ${require_colors.cyan("--include-stories")} Also scan story files, keeping test/spec excluded`);
|
|
32
33
|
require_print.print(` ${require_colors.cyan("--force")} Overwrite existing uds.theme.ts (for init)`);
|
|
@@ -26,6 +26,11 @@ const parseEmitOption = (value) => {
|
|
|
26
26
|
if (value === "exhaustive" || value === "selective") return value;
|
|
27
27
|
throw new Error(`Invalid --emit "${String(value)}". Expected 'exhaustive' or 'selective'.`);
|
|
28
28
|
};
|
|
29
|
+
const parseUtilitiesOption = (value) => {
|
|
30
|
+
if (value === void 0 || value === null) return;
|
|
31
|
+
if (value === "emit" || value === "app-sheet") return value;
|
|
32
|
+
throw new Error(`Invalid --utilities "${String(value)}". Expected 'emit' or 'app-sheet'.`);
|
|
33
|
+
};
|
|
29
34
|
const getCssRunOptions = (workspaceDir, options) => {
|
|
30
35
|
return {
|
|
31
36
|
workspaceDir,
|
|
@@ -35,6 +40,7 @@ const getCssRunOptions = (workspaceDir, options) => {
|
|
|
35
40
|
entryOption: entryOption(options.entry),
|
|
36
41
|
configOption: stringOption(options.config),
|
|
37
42
|
emit: parseEmitOption(options.emit),
|
|
43
|
+
utilities: parseUtilitiesOption(options.utilities),
|
|
38
44
|
includeDevFiles: isTruthyFlag(options["include-dev-files"]) || isTruthyFlag(options.includeDevFiles) ? true : void 0,
|
|
39
45
|
includeStories: isTruthyFlag(options["include-stories"]) || isTruthyFlag(options.includeStories) ? true : void 0,
|
|
40
46
|
watch: isTruthyFlag(options.watch) || isTruthyFlag(options.w),
|
|
@@ -26,6 +26,11 @@ const parseEmitOption = (value) => {
|
|
|
26
26
|
if (value === "exhaustive" || value === "selective") return value;
|
|
27
27
|
throw new Error(`Invalid --emit "${String(value)}". Expected 'exhaustive' or 'selective'.`);
|
|
28
28
|
};
|
|
29
|
+
const parseUtilitiesOption = (value) => {
|
|
30
|
+
if (value === void 0 || value === null) return;
|
|
31
|
+
if (value === "emit" || value === "app-sheet") return value;
|
|
32
|
+
throw new Error(`Invalid --utilities "${String(value)}". Expected 'emit' or 'app-sheet'.`);
|
|
33
|
+
};
|
|
29
34
|
const getCssRunOptions = (workspaceDir, options) => {
|
|
30
35
|
return {
|
|
31
36
|
workspaceDir,
|
|
@@ -35,6 +40,7 @@ const getCssRunOptions = (workspaceDir, options) => {
|
|
|
35
40
|
entryOption: entryOption(options.entry),
|
|
36
41
|
configOption: stringOption(options.config),
|
|
37
42
|
emit: parseEmitOption(options.emit),
|
|
43
|
+
utilities: parseUtilitiesOption(options.utilities),
|
|
38
44
|
includeDevFiles: isTruthyFlag(options["include-dev-files"]) || isTruthyFlag(options.includeDevFiles) ? true : void 0,
|
|
39
45
|
includeStories: isTruthyFlag(options["include-stories"]) || isTruthyFlag(options.includeStories) ? true : void 0,
|
|
40
46
|
watch: isTruthyFlag(options.watch) || isTruthyFlag(options.w),
|
|
@@ -27,6 +27,7 @@ const showHelp = () => {
|
|
|
27
27
|
print(` ${cyan("--verbose, -v")} List every scanned file used for purging`);
|
|
28
28
|
print(` ${cyan("--workers <n>")} Number of parallel workers (default: auto = CPU count - 1)`);
|
|
29
29
|
print(` ${cyan("--emit <mode>")} Emission mode: exhaustive (default) | selective`);
|
|
30
|
+
print(` ${cyan("--utilities <mode>")} Utility ownership: emit (default) | app-sheet (omit app utilities; requires an onlyTheme app sheet with the same config)`);
|
|
30
31
|
print(` ${cyan("--include-dev-files")} Also scan test/spec/story files (excluded by default)`);
|
|
31
32
|
print(` ${cyan("--include-stories")} Also scan story files, keeping test/spec excluded`);
|
|
32
33
|
print(` ${cyan("--force")} Overwrite existing uds.theme.ts (for init)`);
|
|
@@ -9,6 +9,7 @@ const require_nodeUtils = require("./nodeUtils.cjs");
|
|
|
9
9
|
const require_spinner = require("../packages/cli/dist/lib/spinner.cjs");
|
|
10
10
|
const require_index = require("../packages/config/dist/index.cjs");
|
|
11
11
|
const require_safelist = require("../purger/optimized/utils/safelist.cjs");
|
|
12
|
+
const require_filterUnusedVariantRules = require("../postcss/filterUnusedVariantRules.cjs");
|
|
12
13
|
const require_generate_helpers = require("./generate.helpers.cjs");
|
|
13
14
|
let node_fs = require("node:fs");
|
|
14
15
|
node_fs = require_runtime.__toESM(node_fs, 1);
|
|
@@ -48,12 +49,14 @@ const generateCSS = async (safelist, config, options) => {
|
|
|
48
49
|
config,
|
|
49
50
|
enablePreflight: cssFlags.enablePreflight,
|
|
50
51
|
enableFontFaceDeclarations: cssFlags.enableFontFaceDeclarations,
|
|
51
|
-
emit: options?.cssOptions?.emit
|
|
52
|
+
emit: options?.cssOptions?.emit,
|
|
53
|
+
utilities: options?.cssOptions?.utilities
|
|
52
54
|
}),
|
|
53
55
|
shouldPruneVars: cssFlags.shouldPruneVars,
|
|
54
56
|
safeVarPrefixes: options?.safeVarPrefixes ?? [],
|
|
55
57
|
scopeClass,
|
|
56
|
-
otherBoundaryScopes: options?.otherBoundaryScopes ?? []
|
|
58
|
+
otherBoundaryScopes: options?.otherBoundaryScopes ?? [],
|
|
59
|
+
unusedVariantClasses: options?.variantClassMaps ? require_filterUnusedVariantRules.buildUnusedVariantClassSet(safelist, options.variantClassMaps) : void 0
|
|
57
60
|
});
|
|
58
61
|
const result = await require_perf.measureAsync("gen:postcss", () => (0, postcss.default)(plugins).process(sourceCSS, { from: void 0 }));
|
|
59
62
|
let css = await require_perf.measureAsync("gen:scoped-color-fix", () => require_generate_helpers.applyScopedColorModeFix(result.css, scopeClass));
|
|
@@ -106,6 +109,8 @@ const generateSimpleModeCSS = async (options) => {
|
|
|
106
109
|
]), tokensConfig, {
|
|
107
110
|
scope: options.scope,
|
|
108
111
|
contentDir: entryDirs,
|
|
112
|
+
variantClassMaps: [options.variants, options.autoVariants],
|
|
113
|
+
cssOptions: { utilities: options.utilities },
|
|
109
114
|
safeVarPrefixes: [...require_utils.getMotionVarPrefixes(options.componentData, [...allComponents]), ...require_utils.getConfigurableCssVariables()]
|
|
110
115
|
});
|
|
111
116
|
const outputPath = node_path.default.isAbsolute(options.outFile) ? options.outFile : node_path.default.join(options.workspaceDir, options.outFile);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import { EmitMode } from "../packages/automated-config/dist/utils/index.cjs";
|
|
3
|
-
import { TailwindSafelistConfig } from "./generate.helpers.cjs";
|
|
3
|
+
import { TailwindSafelistConfig, UtilitiesMode } from "./generate.helpers.cjs";
|
|
4
4
|
import { UDSCSSOptimizationOptions } from "./theme.cjs";
|
|
5
5
|
|
|
6
6
|
//#region ../css/dist/css/generate.d.mts
|
|
@@ -27,6 +27,26 @@ interface UDSCSSOptions {
|
|
|
27
27
|
* (`scoped` / `excludeBoundaries`) + the isolation fence for coexistence safety.
|
|
28
28
|
*/
|
|
29
29
|
emit?: EmitMode;
|
|
30
|
+
/**
|
|
31
|
+
* Who owns utility (non-component) CSS in this bundle.
|
|
32
|
+
*
|
|
33
|
+
* - `'emit'` (default): compile utilities from the entry content scan into
|
|
34
|
+
* this file using THIS theme's uds.config tokens — prefixed with `scope`
|
|
35
|
+
* when set, so this theme's values win inside the scope even if the app
|
|
36
|
+
* sheet's theme differs. Required when this file is the app's only
|
|
37
|
+
* stylesheet, or when the root Tailwind config diverges from uds.config.
|
|
38
|
+
*
|
|
39
|
+
* - `'app-sheet'`: omit them — the app's root Tailwind sheet owns all
|
|
40
|
+
* utilities, including inside `scope`. Only valid in the two-sheet
|
|
41
|
+
* architecture where the root config uses
|
|
42
|
+
* `tailwindPlugin({ onlyTheme: true })` with the SAME uds.config, so
|
|
43
|
+
* utility values are identical and only specificity changes. Explicit
|
|
44
|
+
* `css.safelist` entries are still emitted.
|
|
45
|
+
*
|
|
46
|
+
* Ignored (forced to `'emit'`, with a warning) for `scoped:` package
|
|
47
|
+
* bundles — their sources aren't part of any app sheet's content scan.
|
|
48
|
+
*/
|
|
49
|
+
utilities?: UtilitiesMode;
|
|
30
50
|
/**
|
|
31
51
|
* Additional selectors whose subtrees this theme's bundles must not match.
|
|
32
52
|
* Every emitted bundle (main and each `scoped:` entry) gets a
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import { EmitMode } from "../packages/automated-config/dist/utils/index.js";
|
|
3
|
-
import { TailwindSafelistConfig } from "./generate.helpers.js";
|
|
3
|
+
import { TailwindSafelistConfig, UtilitiesMode } from "./generate.helpers.js";
|
|
4
4
|
import { UDSCSSOptimizationOptions } from "./theme.js";
|
|
5
5
|
|
|
6
6
|
//#region ../css/dist/css/generate.d.mts
|
|
@@ -27,6 +27,26 @@ interface UDSCSSOptions {
|
|
|
27
27
|
* (`scoped` / `excludeBoundaries`) + the isolation fence for coexistence safety.
|
|
28
28
|
*/
|
|
29
29
|
emit?: EmitMode;
|
|
30
|
+
/**
|
|
31
|
+
* Who owns utility (non-component) CSS in this bundle.
|
|
32
|
+
*
|
|
33
|
+
* - `'emit'` (default): compile utilities from the entry content scan into
|
|
34
|
+
* this file using THIS theme's uds.config tokens — prefixed with `scope`
|
|
35
|
+
* when set, so this theme's values win inside the scope even if the app
|
|
36
|
+
* sheet's theme differs. Required when this file is the app's only
|
|
37
|
+
* stylesheet, or when the root Tailwind config diverges from uds.config.
|
|
38
|
+
*
|
|
39
|
+
* - `'app-sheet'`: omit them — the app's root Tailwind sheet owns all
|
|
40
|
+
* utilities, including inside `scope`. Only valid in the two-sheet
|
|
41
|
+
* architecture where the root config uses
|
|
42
|
+
* `tailwindPlugin({ onlyTheme: true })` with the SAME uds.config, so
|
|
43
|
+
* utility values are identical and only specificity changes. Explicit
|
|
44
|
+
* `css.safelist` entries are still emitted.
|
|
45
|
+
*
|
|
46
|
+
* Ignored (forced to `'emit'`, with a warning) for `scoped:` package
|
|
47
|
+
* bundles — their sources aren't part of any app sheet's content scan.
|
|
48
|
+
*/
|
|
49
|
+
utilities?: UtilitiesMode;
|
|
30
50
|
/**
|
|
31
51
|
* Additional selectors whose subtrees this theme's bundles must not match.
|
|
32
52
|
* Every emitted bundle (main and each `scoped:` entry) gets a
|
|
@@ -8,6 +8,7 @@ const require_entryPoints = require("../../../tailwind-internal/dist/utils/entry
|
|
|
8
8
|
const require_pruneVars = require("../postcss/pruneVars.cjs");
|
|
9
9
|
const require_safelist$1 = require("../purger/optimized/utils/safelist.cjs");
|
|
10
10
|
const require_runner_helpers = require("./runner.helpers.cjs");
|
|
11
|
+
const require_filterUnusedVariantRules = require("../postcss/filterUnusedVariantRules.cjs");
|
|
11
12
|
const require_optimizeCSS = require("../utils/optimizeCSS.cjs");
|
|
12
13
|
const require_postcss = require("./postcss.cjs");
|
|
13
14
|
let node_fs = require("node:fs");
|
|
@@ -46,6 +47,10 @@ const getRawContentEntries = (rawContents) => rawContents.map((rawContent) => ({
|
|
|
46
47
|
raw: rawContent,
|
|
47
48
|
extension: "html"
|
|
48
49
|
}));
|
|
50
|
+
const getTailwindContentSources = (options) => {
|
|
51
|
+
const sources = [...options.utilities === "app-sheet" ? [] : getContentGlobs(options.contentDir), ...getRawContentEntries(options.rawContents)];
|
|
52
|
+
return sources.length > 0 ? sources : getRawContentEntries([""]);
|
|
53
|
+
};
|
|
49
54
|
const getTailwindEntrypointCandidates = () => [new URL("../../../tailwind-internal/dist/vendor/tailwindcss/lib/index.js", require("url").pathToFileURL(__filename).href), new URL("../vendor/tailwindcss/lib/index.js", require("url").pathToFileURL(__filename).href)];
|
|
50
55
|
const loadTailwindcss = async () => {
|
|
51
56
|
for (const candidate of getTailwindEntrypointCandidates()) {
|
|
@@ -69,11 +74,11 @@ const getInternalIconModeCss = (scopeClass) => {
|
|
|
69
74
|
};
|
|
70
75
|
const createTailwindPlugin = async (options) => {
|
|
71
76
|
return (await loadTailwindcss())({
|
|
72
|
-
content:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
...
|
|
76
|
-
|
|
77
|
+
content: getTailwindContentSources({
|
|
78
|
+
contentDir: options.contentDir,
|
|
79
|
+
utilities: options.utilities,
|
|
80
|
+
rawContents: [...getInternalSafelistRawContent(options.safelist), ...options.rawContents ?? []]
|
|
81
|
+
}),
|
|
77
82
|
safelist: options.safelist,
|
|
78
83
|
corePlugins: { preflight: options.enablePreflight },
|
|
79
84
|
plugins: [require_tailwindPlugin.tailwindPlugin({
|
|
@@ -86,6 +91,7 @@ const createTailwindPlugin = async (options) => {
|
|
|
86
91
|
};
|
|
87
92
|
const buildPostcssPlugins = (options) => {
|
|
88
93
|
const plugins = [options.tailwindPlugin, (0, autoprefixer.default)()];
|
|
94
|
+
if (options.unusedVariantClasses && options.unusedVariantClasses.size > 0) plugins.splice(1, 0, require_filterUnusedVariantRules.filterUnusedVariantRulesPlugin(options.unusedVariantClasses));
|
|
89
95
|
if (options.shouldPruneVars) plugins.push(...require_pruneVars.getPruneVarPlugins(options.safeVarPrefixes));
|
|
90
96
|
if (options.scopeClass) plugins.push((0, postcss_scope.default)(options.scopeClass));
|
|
91
97
|
const otherBoundaryScopes = options.otherBoundaryScopes ?? [];
|
|
@@ -5,5 +5,11 @@ type TailwindSafelistConfig = string | {
|
|
|
5
5
|
pattern: RegExp;
|
|
6
6
|
variants?: string[];
|
|
7
7
|
};
|
|
8
|
+
/**
|
|
9
|
+
* Who owns utility (non-component) CSS in a generated bundle. `'emit'`
|
|
10
|
+
* compiles utilities from the entry content scan into the bundle;
|
|
11
|
+
* `'app-sheet'` omits them because the app's own Tailwind sheet supplies them.
|
|
12
|
+
*/
|
|
13
|
+
type UtilitiesMode = 'emit' | 'app-sheet';
|
|
8
14
|
//#endregion
|
|
9
|
-
export { TailwindSafelistConfig };
|
|
15
|
+
export { TailwindSafelistConfig, UtilitiesMode };
|
|
@@ -7,5 +7,11 @@ type TailwindSafelistConfig = string | {
|
|
|
7
7
|
pattern: RegExp;
|
|
8
8
|
variants?: string[];
|
|
9
9
|
};
|
|
10
|
+
/**
|
|
11
|
+
* Who owns utility (non-component) CSS in a generated bundle. `'emit'`
|
|
12
|
+
* compiles utilities from the entry content scan into the bundle;
|
|
13
|
+
* `'app-sheet'` omits them because the app's own Tailwind sheet supplies them.
|
|
14
|
+
*/
|
|
15
|
+
type UtilitiesMode = 'emit' | 'app-sheet';
|
|
10
16
|
//#endregion
|
|
11
|
-
export { TailwindSafelistConfig };
|
|
17
|
+
export { TailwindSafelistConfig, UtilitiesMode };
|
|
@@ -7,6 +7,7 @@ import { hasAllowedEntryFileExtension } from "../../../tailwind-internal/dist/ut
|
|
|
7
7
|
import { getPruneVarPlugins } from "../postcss/pruneVars.js";
|
|
8
8
|
import { getInternalSafelistClasses } from "../purger/optimized/utils/safelist.js";
|
|
9
9
|
import { formatCssDuration } from "./runner.helpers.js";
|
|
10
|
+
import { filterUnusedVariantRulesPlugin } from "../postcss/filterUnusedVariantRules.js";
|
|
10
11
|
import { optimizeCSS } from "../utils/optimizeCSS.js";
|
|
11
12
|
import { appendIsolationExclusionPlugin, fixScopedColorModeSelectorsPlugin, fixScopedSelfOrParentSelectorsPlugin } from "./postcss.js";
|
|
12
13
|
import fs from "node:fs";
|
|
@@ -40,6 +41,10 @@ const getRawContentEntries = (rawContents) => rawContents.map((rawContent) => ({
|
|
|
40
41
|
raw: rawContent,
|
|
41
42
|
extension: "html"
|
|
42
43
|
}));
|
|
44
|
+
const getTailwindContentSources = (options) => {
|
|
45
|
+
const sources = [...options.utilities === "app-sheet" ? [] : getContentGlobs(options.contentDir), ...getRawContentEntries(options.rawContents)];
|
|
46
|
+
return sources.length > 0 ? sources : getRawContentEntries([""]);
|
|
47
|
+
};
|
|
43
48
|
const getTailwindEntrypointCandidates = () => [new URL("../../../tailwind-internal/dist/vendor/tailwindcss/lib/index.js", import.meta.url), new URL("../vendor/tailwindcss/lib/index.js", import.meta.url)];
|
|
44
49
|
const loadTailwindcss = async () => {
|
|
45
50
|
for (const candidate of getTailwindEntrypointCandidates()) {
|
|
@@ -63,11 +68,11 @@ const getInternalIconModeCss = (scopeClass) => {
|
|
|
63
68
|
};
|
|
64
69
|
const createTailwindPlugin = async (options) => {
|
|
65
70
|
return (await loadTailwindcss())({
|
|
66
|
-
content:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
...
|
|
70
|
-
|
|
71
|
+
content: getTailwindContentSources({
|
|
72
|
+
contentDir: options.contentDir,
|
|
73
|
+
utilities: options.utilities,
|
|
74
|
+
rawContents: [...getInternalSafelistRawContent(options.safelist), ...options.rawContents ?? []]
|
|
75
|
+
}),
|
|
71
76
|
safelist: options.safelist,
|
|
72
77
|
corePlugins: { preflight: options.enablePreflight },
|
|
73
78
|
plugins: [tailwindPlugin({
|
|
@@ -80,6 +85,7 @@ const createTailwindPlugin = async (options) => {
|
|
|
80
85
|
};
|
|
81
86
|
const buildPostcssPlugins = (options) => {
|
|
82
87
|
const plugins = [options.tailwindPlugin, autoprefixer()];
|
|
88
|
+
if (options.unusedVariantClasses && options.unusedVariantClasses.size > 0) plugins.splice(1, 0, filterUnusedVariantRulesPlugin(options.unusedVariantClasses));
|
|
83
89
|
if (options.shouldPruneVars) plugins.push(...getPruneVarPlugins(options.safeVarPrefixes));
|
|
84
90
|
if (options.scopeClass) plugins.push(postcssScope(options.scopeClass));
|
|
85
91
|
const otherBoundaryScopes = options.otherBoundaryScopes ?? [];
|
|
@@ -8,6 +8,7 @@ import { loadConfigFile, scanDirectoriesForSafelist } from "./nodeUtils.js";
|
|
|
8
8
|
import { spinStop } from "../packages/cli/dist/lib/spinner.js";
|
|
9
9
|
import { defaultTokensConfig } from "../packages/config/dist/index.js";
|
|
10
10
|
import { deduplicateSafelist, getInternalSafelistClasses, getThemeAndScaleClasses } from "../purger/optimized/utils/safelist.js";
|
|
11
|
+
import { buildUnusedVariantClassSet } from "../postcss/filterUnusedVariantRules.js";
|
|
11
12
|
import { applyScopedColorModeFix, buildPostcssPlugins, createTailwindPlugin, getCssFeatureFlags, getInternalIconModeCss, optimizeGeneratedCss, printSimpleModeStats } from "./generate.helpers.js";
|
|
12
13
|
import fs from "node:fs";
|
|
13
14
|
import path from "node:path";
|
|
@@ -44,12 +45,14 @@ const generateCSS = async (safelist, config, options) => {
|
|
|
44
45
|
config,
|
|
45
46
|
enablePreflight: cssFlags.enablePreflight,
|
|
46
47
|
enableFontFaceDeclarations: cssFlags.enableFontFaceDeclarations,
|
|
47
|
-
emit: options?.cssOptions?.emit
|
|
48
|
+
emit: options?.cssOptions?.emit,
|
|
49
|
+
utilities: options?.cssOptions?.utilities
|
|
48
50
|
}),
|
|
49
51
|
shouldPruneVars: cssFlags.shouldPruneVars,
|
|
50
52
|
safeVarPrefixes: options?.safeVarPrefixes ?? [],
|
|
51
53
|
scopeClass,
|
|
52
|
-
otherBoundaryScopes: options?.otherBoundaryScopes ?? []
|
|
54
|
+
otherBoundaryScopes: options?.otherBoundaryScopes ?? [],
|
|
55
|
+
unusedVariantClasses: options?.variantClassMaps ? buildUnusedVariantClassSet(safelist, options.variantClassMaps) : void 0
|
|
53
56
|
});
|
|
54
57
|
const result = await measureAsync("gen:postcss", () => postcss(plugins).process(sourceCSS, { from: void 0 }));
|
|
55
58
|
let css = await measureAsync("gen:scoped-color-fix", () => applyScopedColorModeFix(result.css, scopeClass));
|
|
@@ -102,6 +105,8 @@ const generateSimpleModeCSS = async (options) => {
|
|
|
102
105
|
]), tokensConfig, {
|
|
103
106
|
scope: options.scope,
|
|
104
107
|
contentDir: entryDirs,
|
|
108
|
+
variantClassMaps: [options.variants, options.autoVariants],
|
|
109
|
+
cssOptions: { utilities: options.utilities },
|
|
105
110
|
safeVarPrefixes: [...getMotionVarPrefixes(options.componentData, [...allComponents]), ...getConfigurableCssVariables()]
|
|
106
111
|
});
|
|
107
112
|
const outputPath = path.isAbsolute(options.outFile) ? options.outFile : path.join(options.workspaceDir, options.outFile);
|
|
@@ -151,6 +151,7 @@ const runSimpleMode = async (options, context) => {
|
|
|
151
151
|
autoVariants: context.autoVariants,
|
|
152
152
|
componentData: context.componentData,
|
|
153
153
|
scope: options.scope,
|
|
154
|
+
utilities: options.utilities,
|
|
154
155
|
configPath,
|
|
155
156
|
isWatch: options.watch,
|
|
156
157
|
silent: options.silent,
|
|
@@ -348,6 +349,8 @@ const runThemeMode = async (options, context) => {
|
|
|
348
349
|
]);
|
|
349
350
|
const allMotionComponents = [...inheritedComponents];
|
|
350
351
|
const emit = options.emit ?? themeConfig.css?.emit ?? "exhaustive";
|
|
352
|
+
const utilities = options.utilities ?? themeConfig.css?.utilities ?? "emit";
|
|
353
|
+
if (utilities === "app-sheet" && scopedPackageTargets.length > 0) genLog.warn(`css.utilities 'app-sheet' ignored for scoped bundles (${scopedPackageTargets.map((target) => target.packageName).join(", ")}): package sources aren't in any app sheet's content scan; utilities still emitted (scoped).`);
|
|
351
354
|
const allBoundarySelectors = [...emit === "selective" ? scopedPackageTargets.map((target) => target.scopeClass) : [], ...themeConfig.css?.excludeBoundaries ?? []];
|
|
352
355
|
require_perf.captureMemory("before-gen");
|
|
353
356
|
const mainCssResult = await require_perf.measureAsync("gen", () => require_generate.generateCSS([...themeConfig.css?.safelist ?? [], ...mainSafelist], appConfig, {
|
|
@@ -355,9 +358,11 @@ const runThemeMode = async (options, context) => {
|
|
|
355
358
|
contentDir: entryDirs,
|
|
356
359
|
cssOptions: {
|
|
357
360
|
...themeConfig.css,
|
|
358
|
-
emit
|
|
361
|
+
emit,
|
|
362
|
+
utilities
|
|
359
363
|
},
|
|
360
364
|
otherBoundaryScopes: allBoundarySelectors,
|
|
365
|
+
variantClassMaps: [context.variants, context.autoVariants],
|
|
361
366
|
safeVarPrefixes: [
|
|
362
367
|
...require_utils.getMotionVarPrefixes(context.componentData, allMotionComponents),
|
|
363
368
|
...require_utils.getConfigurableCssVariables(),
|
|
@@ -390,9 +395,11 @@ const runThemeMode = async (options, context) => {
|
|
|
390
395
|
contentDir: scopedPackageTarget.entryDirs,
|
|
391
396
|
cssOptions: {
|
|
392
397
|
...themeConfig.css,
|
|
393
|
-
emit
|
|
398
|
+
emit,
|
|
399
|
+
utilities: "emit"
|
|
394
400
|
},
|
|
395
401
|
otherBoundaryScopes: allBoundarySelectors.filter((selector) => selector !== scopedPackageTarget.scopeClass),
|
|
402
|
+
variantClassMaps: [context.variants, context.autoVariants],
|
|
396
403
|
referenceCss: themeConfig.css?.optimization?.deduplicateScopedCss === false ? void 0 : mainCssResult.css,
|
|
397
404
|
safeVarPrefixes: [
|
|
398
405
|
...require_utils.getMotionVarPrefixes(context.componentData, [...packageScanResult.components]),
|
|
@@ -149,6 +149,7 @@ const runSimpleMode = async (options, context) => {
|
|
|
149
149
|
autoVariants: context.autoVariants,
|
|
150
150
|
componentData: context.componentData,
|
|
151
151
|
scope: options.scope,
|
|
152
|
+
utilities: options.utilities,
|
|
152
153
|
configPath,
|
|
153
154
|
isWatch: options.watch,
|
|
154
155
|
silent: options.silent,
|
|
@@ -346,6 +347,8 @@ const runThemeMode = async (options, context) => {
|
|
|
346
347
|
]);
|
|
347
348
|
const allMotionComponents = [...inheritedComponents];
|
|
348
349
|
const emit = options.emit ?? themeConfig.css?.emit ?? "exhaustive";
|
|
350
|
+
const utilities = options.utilities ?? themeConfig.css?.utilities ?? "emit";
|
|
351
|
+
if (utilities === "app-sheet" && scopedPackageTargets.length > 0) genLog.warn(`css.utilities 'app-sheet' ignored for scoped bundles (${scopedPackageTargets.map((target) => target.packageName).join(", ")}): package sources aren't in any app sheet's content scan; utilities still emitted (scoped).`);
|
|
349
352
|
const allBoundarySelectors = [...emit === "selective" ? scopedPackageTargets.map((target) => target.scopeClass) : [], ...themeConfig.css?.excludeBoundaries ?? []];
|
|
350
353
|
captureMemory("before-gen");
|
|
351
354
|
const mainCssResult = await measureAsync("gen", () => generateCSS([...themeConfig.css?.safelist ?? [], ...mainSafelist], appConfig, {
|
|
@@ -353,9 +356,11 @@ const runThemeMode = async (options, context) => {
|
|
|
353
356
|
contentDir: entryDirs,
|
|
354
357
|
cssOptions: {
|
|
355
358
|
...themeConfig.css,
|
|
356
|
-
emit
|
|
359
|
+
emit,
|
|
360
|
+
utilities
|
|
357
361
|
},
|
|
358
362
|
otherBoundaryScopes: allBoundarySelectors,
|
|
363
|
+
variantClassMaps: [context.variants, context.autoVariants],
|
|
359
364
|
safeVarPrefixes: [
|
|
360
365
|
...getMotionVarPrefixes(context.componentData, allMotionComponents),
|
|
361
366
|
...getConfigurableCssVariables(),
|
|
@@ -388,9 +393,11 @@ const runThemeMode = async (options, context) => {
|
|
|
388
393
|
contentDir: scopedPackageTarget.entryDirs,
|
|
389
394
|
cssOptions: {
|
|
390
395
|
...themeConfig.css,
|
|
391
|
-
emit
|
|
396
|
+
emit,
|
|
397
|
+
utilities: "emit"
|
|
392
398
|
},
|
|
393
399
|
otherBoundaryScopes: allBoundarySelectors.filter((selector) => selector !== scopedPackageTarget.scopeClass),
|
|
400
|
+
variantClassMaps: [context.variants, context.autoVariants],
|
|
394
401
|
referenceCss: themeConfig.css?.optimization?.deduplicateScopedCss === false ? void 0 : mainCssResult.css,
|
|
395
402
|
safeVarPrefixes: [
|
|
396
403
|
...getMotionVarPrefixes(context.componentData, [...packageScanResult.components]),
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*! © 2026 Yahoo, Inc. UDS v0.0.0-development */
|
|
2
|
+
//#region ../css/dist/postcss/filterUnusedVariantRules.mjs
|
|
3
|
+
/*! © 2026 Yahoo, Inc. UDS CSS v0.0.0-development */
|
|
4
|
+
const isPatternEntry = (entry) => typeof entry === "object" && entry !== null && entry.pattern instanceof RegExp;
|
|
5
|
+
/**
|
|
6
|
+
* Computes the variant classes that are enumerated by the variant class maps
|
|
7
|
+
* but absent from the safelist, for every axis the scan DID find at least one
|
|
8
|
+
* class of.
|
|
9
|
+
*
|
|
10
|
+
* Rationale: Tailwind v3 emits an `addComponents` rule when ANY class in its
|
|
11
|
+
* selector matches a candidate, so variant-agnostic classes shared across
|
|
12
|
+
* selectors (componentState classes like `…-value-checked-root`, default
|
|
13
|
+
* aliases) ship rules for variants the app never uses. Restricting to axes
|
|
14
|
+
* with at least one safelisted member keeps the filter conservative: an axis
|
|
15
|
+
* the scanner never matched contributes nothing, so components that only
|
|
16
|
+
* appear through another bundle or through raw `content` strings are left
|
|
17
|
+
* untouched.
|
|
18
|
+
*/
|
|
19
|
+
const buildUnusedVariantClassSet = (safelist, variantClassMaps) => {
|
|
20
|
+
const safelistedClasses = new Set(safelist.filter((entry) => typeof entry === "string"));
|
|
21
|
+
const patterns = safelist.filter(isPatternEntry).map((entry) => entry.pattern);
|
|
22
|
+
const isSafelisted = (className) => safelistedClasses.has(className) || patterns.some((pattern) => pattern.test(className));
|
|
23
|
+
const axes = /* @__PURE__ */ new Map();
|
|
24
|
+
for (const map of variantClassMaps) for (const [axisKey, values] of Object.entries(map)) {
|
|
25
|
+
const axisClasses = axes.get(axisKey) ?? /* @__PURE__ */ new Set();
|
|
26
|
+
for (const className of Object.values(values)) if (typeof className === "string" && !/\s/.test(className)) axisClasses.add(className);
|
|
27
|
+
axes.set(axisKey, axisClasses);
|
|
28
|
+
}
|
|
29
|
+
const unused = /* @__PURE__ */ new Set();
|
|
30
|
+
for (const axisClasses of axes.values()) {
|
|
31
|
+
let axisIsScanned = false;
|
|
32
|
+
for (const className of axisClasses) if (isSafelisted(className)) {
|
|
33
|
+
axisIsScanned = true;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
if (!axisIsScanned) continue;
|
|
37
|
+
for (const className of axisClasses) if (!isSafelisted(className)) unused.add(className);
|
|
38
|
+
}
|
|
39
|
+
return unused;
|
|
40
|
+
};
|
|
41
|
+
const CLASS_TOKEN_RE = /\.((?:[a-zA-Z0-9_-]|\\.)+)/g;
|
|
42
|
+
const selectorReferencesUnusedClass = (selector, unused) => {
|
|
43
|
+
for (const match of selector.matchAll(CLASS_TOKEN_RE)) if (unused.has(match[1])) return true;
|
|
44
|
+
return false;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* PostCSS plugin dropping selector alternatives (and whole rules) that
|
|
48
|
+
* reference a variant class in `unused`. Runs on the Tailwind-generated
|
|
49
|
+
* bundle, after which emptied `@media`/`@supports` blocks are removed. Placed
|
|
50
|
+
* before var pruning so variables only referenced by dropped rules get pruned
|
|
51
|
+
* too.
|
|
52
|
+
*/
|
|
53
|
+
const filterUnusedVariantRulesPlugin = (unused) => ({
|
|
54
|
+
postcssPlugin: "uds-filter-unused-variant-rules",
|
|
55
|
+
Once(root) {
|
|
56
|
+
if (unused.size === 0) return;
|
|
57
|
+
root.walkRules((rule) => {
|
|
58
|
+
if (!rule.selector.includes(".uds-")) return;
|
|
59
|
+
const kept = rule.selectors.filter((selector) => !selectorReferencesUnusedClass(selector, unused));
|
|
60
|
+
if (kept.length === rule.selectors.length) return;
|
|
61
|
+
if (kept.length === 0) rule.remove();
|
|
62
|
+
else rule.selector = kept.join(", ");
|
|
63
|
+
});
|
|
64
|
+
root.walkAtRules((atRule) => {
|
|
65
|
+
if ((atRule.name === "media" || atRule.name === "supports") && (atRule.nodes?.length ?? 0) === 0) atRule.remove();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
//#endregion
|
|
70
|
+
exports.buildUnusedVariantClassSet = buildUnusedVariantClassSet;
|
|
71
|
+
exports.filterUnusedVariantRulesPlugin = filterUnusedVariantRulesPlugin;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/*! © 2026 Yahoo, Inc. UDS v0.0.0-development */
|
|
2
|
+
//#region ../css/dist/postcss/filterUnusedVariantRules.mjs
|
|
3
|
+
/*! © 2026 Yahoo, Inc. UDS CSS v0.0.0-development */
|
|
4
|
+
const isPatternEntry = (entry) => typeof entry === "object" && entry !== null && entry.pattern instanceof RegExp;
|
|
5
|
+
/**
|
|
6
|
+
* Computes the variant classes that are enumerated by the variant class maps
|
|
7
|
+
* but absent from the safelist, for every axis the scan DID find at least one
|
|
8
|
+
* class of.
|
|
9
|
+
*
|
|
10
|
+
* Rationale: Tailwind v3 emits an `addComponents` rule when ANY class in its
|
|
11
|
+
* selector matches a candidate, so variant-agnostic classes shared across
|
|
12
|
+
* selectors (componentState classes like `…-value-checked-root`, default
|
|
13
|
+
* aliases) ship rules for variants the app never uses. Restricting to axes
|
|
14
|
+
* with at least one safelisted member keeps the filter conservative: an axis
|
|
15
|
+
* the scanner never matched contributes nothing, so components that only
|
|
16
|
+
* appear through another bundle or through raw `content` strings are left
|
|
17
|
+
* untouched.
|
|
18
|
+
*/
|
|
19
|
+
const buildUnusedVariantClassSet = (safelist, variantClassMaps) => {
|
|
20
|
+
const safelistedClasses = new Set(safelist.filter((entry) => typeof entry === "string"));
|
|
21
|
+
const patterns = safelist.filter(isPatternEntry).map((entry) => entry.pattern);
|
|
22
|
+
const isSafelisted = (className) => safelistedClasses.has(className) || patterns.some((pattern) => pattern.test(className));
|
|
23
|
+
const axes = /* @__PURE__ */ new Map();
|
|
24
|
+
for (const map of variantClassMaps) for (const [axisKey, values] of Object.entries(map)) {
|
|
25
|
+
const axisClasses = axes.get(axisKey) ?? /* @__PURE__ */ new Set();
|
|
26
|
+
for (const className of Object.values(values)) if (typeof className === "string" && !/\s/.test(className)) axisClasses.add(className);
|
|
27
|
+
axes.set(axisKey, axisClasses);
|
|
28
|
+
}
|
|
29
|
+
const unused = /* @__PURE__ */ new Set();
|
|
30
|
+
for (const axisClasses of axes.values()) {
|
|
31
|
+
let axisIsScanned = false;
|
|
32
|
+
for (const className of axisClasses) if (isSafelisted(className)) {
|
|
33
|
+
axisIsScanned = true;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
if (!axisIsScanned) continue;
|
|
37
|
+
for (const className of axisClasses) if (!isSafelisted(className)) unused.add(className);
|
|
38
|
+
}
|
|
39
|
+
return unused;
|
|
40
|
+
};
|
|
41
|
+
const CLASS_TOKEN_RE = /\.((?:[a-zA-Z0-9_-]|\\.)+)/g;
|
|
42
|
+
const selectorReferencesUnusedClass = (selector, unused) => {
|
|
43
|
+
for (const match of selector.matchAll(CLASS_TOKEN_RE)) if (unused.has(match[1])) return true;
|
|
44
|
+
return false;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* PostCSS plugin dropping selector alternatives (and whole rules) that
|
|
48
|
+
* reference a variant class in `unused`. Runs on the Tailwind-generated
|
|
49
|
+
* bundle, after which emptied `@media`/`@supports` blocks are removed. Placed
|
|
50
|
+
* before var pruning so variables only referenced by dropped rules get pruned
|
|
51
|
+
* too.
|
|
52
|
+
*/
|
|
53
|
+
const filterUnusedVariantRulesPlugin = (unused) => ({
|
|
54
|
+
postcssPlugin: "uds-filter-unused-variant-rules",
|
|
55
|
+
Once(root) {
|
|
56
|
+
if (unused.size === 0) return;
|
|
57
|
+
root.walkRules((rule) => {
|
|
58
|
+
if (!rule.selector.includes(".uds-")) return;
|
|
59
|
+
const kept = rule.selectors.filter((selector) => !selectorReferencesUnusedClass(selector, unused));
|
|
60
|
+
if (kept.length === rule.selectors.length) return;
|
|
61
|
+
if (kept.length === 0) rule.remove();
|
|
62
|
+
else rule.selector = kept.join(", ");
|
|
63
|
+
});
|
|
64
|
+
root.walkAtRules((atRule) => {
|
|
65
|
+
if ((atRule.name === "media" || atRule.name === "supports") && (atRule.nodes?.length ?? 0) === 0) atRule.remove();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
//#endregion
|
|
70
|
+
export { buildUnusedVariantClassSet, filterUnusedVariantRulesPlugin };
|
|
@@ -956,7 +956,7 @@ function generateConfigStyles(config, schema, theme, previewOptions, emit, gener
|
|
|
956
956
|
* @param schema - Production schema with defaults and variables
|
|
957
957
|
* @param theme - Tailwind theme function to resolve design tokens
|
|
958
958
|
* @param previewOptions - Options for generating preview mode class modifiers
|
|
959
|
-
* @returns CSS styles object with
|
|
959
|
+
* @returns CSS styles object with separate twin rules for default aliases
|
|
960
960
|
*/
|
|
961
961
|
const generateStylesInner = (config, schema, theme, previewOptions, generateStylesContext) => {
|
|
962
962
|
const styles = generateConfigStyles(config, schema, theme, previewOptions, previewOptions.emit ?? "exhaustive", generateStylesContext);
|
|
@@ -1021,7 +1021,8 @@ const generateStylesInner = (config, schema, theme, previewOptions, generateStyl
|
|
|
1021
1021
|
finalStyles[selector] = declarations;
|
|
1022
1022
|
continue;
|
|
1023
1023
|
}
|
|
1024
|
-
finalStyles[
|
|
1024
|
+
finalStyles[selector] = declarations;
|
|
1025
|
+
finalStyles[defaultsSelector] = declarations;
|
|
1025
1026
|
}
|
|
1026
1027
|
return finalStyles;
|
|
1027
1028
|
};
|
|
@@ -955,7 +955,7 @@ function generateConfigStyles(config, schema, theme, previewOptions, emit, gener
|
|
|
955
955
|
* @param schema - Production schema with defaults and variables
|
|
956
956
|
* @param theme - Tailwind theme function to resolve design tokens
|
|
957
957
|
* @param previewOptions - Options for generating preview mode class modifiers
|
|
958
|
-
* @returns CSS styles object with
|
|
958
|
+
* @returns CSS styles object with separate twin rules for default aliases
|
|
959
959
|
*/
|
|
960
960
|
const generateStylesInner = (config, schema, theme, previewOptions, generateStylesContext) => {
|
|
961
961
|
const styles = generateConfigStyles(config, schema, theme, previewOptions, previewOptions.emit ?? "exhaustive", generateStylesContext);
|
|
@@ -1020,7 +1020,8 @@ const generateStylesInner = (config, schema, theme, previewOptions, generateStyl
|
|
|
1020
1020
|
finalStyles[selector] = declarations;
|
|
1021
1021
|
continue;
|
|
1022
1022
|
}
|
|
1023
|
-
finalStyles[
|
|
1023
|
+
finalStyles[selector] = declarations;
|
|
1024
|
+
finalStyles[defaultsSelector] = declarations;
|
|
1024
1025
|
}
|
|
1025
1026
|
return finalStyles;
|
|
1026
1027
|
};
|