@yahoo/uds 3.114.0 → 3.115.0-beta.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/automated-config/dist/mapTextVariantFixtureToValue.cjs +12 -1
- package/dist/automated-config/dist/mapTextVariantFixtureToValue.js +12 -1
- package/dist/automated-config/dist/properties.cjs +1 -1
- package/dist/automated-config/dist/properties.js +1 -1
- package/dist/cli/commands/sync.cjs +1 -3
- package/dist/cli/commands/sync.d.cts +1 -1
- package/dist/cli/commands/sync.d.ts +1 -1
- package/dist/cli/commands/sync.js +1 -3
- package/dist/cli/commands/version.cjs +0 -2
- package/dist/cli/commands/version.d.cts +1 -1
- package/dist/cli/commands/version.d.ts +1 -1
- package/dist/cli/commands/version.js +0 -2
- package/dist/cli/dist/commands/editor-rules.cjs +1 -1
- package/dist/cli/dist/commands/editor-rules.js +1 -1
- package/dist/cli/dist/lib/logger.cjs +66 -0
- package/dist/cli/dist/lib/logger.js +66 -0
- package/dist/cli/runner.cjs +9 -0
- package/dist/cli/runner.js +9 -0
- package/dist/components/client/Menu/Menu.ItemCheckbox.d.cts +1 -1
- package/dist/components/client/Menu/Menu.ItemCheckbox.d.ts +1 -1
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/styles/styler.d.cts +29 -29
- package/dist/styles/styler.d.ts +29 -29
- package/dist/styles/variants.cjs +278 -278
- package/dist/styles/variants.js +278 -278
- package/dist/tailwind/dist/commands/css.cjs +79 -0
- package/dist/tailwind/dist/commands/css.helpers.cjs +32 -0
- package/dist/tailwind/dist/commands/css.helpers.js +28 -0
- package/dist/tailwind/dist/commands/css.js +79 -0
- package/dist/tailwind/dist/commands/generateComponentData.cjs +33 -31
- package/dist/tailwind/dist/commands/generateComponentData.d.ts +1 -1
- package/dist/tailwind/dist/commands/generateComponentData.js +33 -31
- package/dist/tailwind/dist/commands/purge.cjs +3 -4
- package/dist/tailwind/dist/commands/purge.js +3 -4
- package/dist/tailwind/dist/css/generate.cjs +121 -0
- package/dist/tailwind/dist/css/generate.d.cts +30 -0
- package/dist/tailwind/dist/css/generate.d.ts +31 -0
- package/dist/tailwind/dist/css/generate.helpers.cjs +112 -0
- package/dist/tailwind/dist/css/generate.helpers.js +100 -0
- package/dist/tailwind/dist/css/generate.js +116 -0
- package/dist/tailwind/dist/css/nodeUtils.cjs +156 -0
- package/dist/tailwind/dist/css/nodeUtils.js +149 -0
- package/dist/tailwind/dist/css/postcss.cjs +35 -0
- package/dist/tailwind/dist/css/postcss.helpers.cjs +27 -0
- package/dist/tailwind/dist/css/postcss.helpers.js +26 -0
- package/dist/tailwind/dist/css/postcss.js +35 -0
- package/dist/tailwind/dist/css/runner.cjs +279 -0
- package/dist/tailwind/dist/css/runner.helpers.cjs +26 -0
- package/dist/tailwind/dist/css/runner.helpers.js +23 -0
- package/dist/tailwind/dist/css/runner.js +276 -0
- package/dist/tailwind/dist/css/theme.cjs +12 -0
- package/dist/tailwind/dist/css/theme.d.cts +66 -0
- package/dist/tailwind/dist/css/theme.d.ts +66 -0
- package/dist/tailwind/dist/css/theme.js +11 -0
- package/dist/tailwind/dist/css/utils.cjs +72 -0
- package/dist/tailwind/dist/css/utils.js +69 -0
- package/dist/tailwind/dist/index.d.cts +1 -0
- package/dist/tailwind/dist/index.d.ts +2 -4
- package/dist/tailwind/dist/purger/legacy/purgeCSS.cjs +2 -1
- package/dist/tailwind/dist/purger/legacy/purgeCSS.js +2 -1
- package/dist/tailwind/dist/purger/optimized/ast/expressions.cjs +122 -125
- package/dist/tailwind/dist/purger/optimized/ast/expressions.js +122 -125
- package/dist/tailwind/dist/purger/optimized/ast/jsx.cjs +1 -8
- package/dist/tailwind/dist/purger/optimized/ast/jsx.js +1 -8
- package/dist/tailwind/dist/purger/optimized/purge.cjs +9 -8
- package/dist/tailwind/dist/purger/optimized/purge.js +9 -8
- package/dist/tailwind/dist/purger/optimized/purgeFromCode.cjs +238 -127
- package/dist/tailwind/dist/purger/optimized/purgeFromCode.js +238 -127
- package/dist/tailwind/dist/purger/optimized/utils/componentAnalyzer.cjs +352 -260
- package/dist/tailwind/dist/purger/optimized/utils/componentAnalyzer.js +351 -260
- package/dist/tailwind/dist/purger/optimized/utils/files.cjs +4 -3
- package/dist/tailwind/dist/purger/optimized/utils/files.js +4 -3
- package/dist/tailwind/dist/purger/optimized/utils/safelist.cjs +12 -20
- package/dist/tailwind/dist/purger/optimized/utils/safelist.js +12 -20
- package/dist/tailwind/dist/tailwind/components/getResponsiveTextStyles.cjs +1 -1
- package/dist/tailwind/dist/tailwind/components/getResponsiveTextStyles.js +1 -1
- package/dist/tailwind/dist/tailwind/plugins/breakpoints.cjs +1 -1
- package/dist/tailwind/dist/tailwind/plugins/breakpoints.js +1 -1
- package/dist/tailwind/dist/tailwind/plugins/typography.cjs +41 -13
- package/dist/tailwind/dist/tailwind/plugins/typography.js +41 -13
- package/dist/tailwind/dist/tailwind/utils/composeTailwindPlugins.cjs +4 -2
- package/dist/tailwind/dist/tailwind/utils/composeTailwindPlugins.d.cts +10 -1
- package/dist/tailwind/dist/tailwind/utils/composeTailwindPlugins.d.ts +10 -1
- package/dist/tailwind/dist/tailwind/utils/composeTailwindPlugins.js +4 -2
- package/dist/tailwind/dist/utils/optimizeCSS.cjs +405 -0
- package/dist/tailwind/dist/utils/optimizeCSS.js +403 -0
- package/dist/tailwind/dist/utils/postcssPreserveVars.cjs +67 -0
- package/dist/tailwind/dist/utils/postcssPreserveVars.js +65 -0
- package/dist/tailwind/dist/utils/tsMorph.cjs +1 -1
- package/dist/uds/generated/componentData.cjs +1218 -1182
- package/dist/uds/generated/componentData.js +1218 -1182
- package/dist/uds/package.cjs +10 -4
- package/dist/uds/package.js +10 -4
- package/generated/componentData.json +2683 -0
- package/generated/tailwindPurge.ts +4591 -0
- package/package.json +7 -4
- package/dist/tailwind/dist/commands/generatePurgeCSSData.d.ts +0 -3
- package/dist/tailwind/dist/commands/purge.d.ts +0 -4
- package/dist/tailwind/dist/purger/legacy/purgeCSS.d.ts +0 -2
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/*! © 2026 Yahoo, Inc. UDS v0.0.0-development */
|
|
2
|
+
import { gzipSync } from "node:zlib";
|
|
3
|
+
import postcss from "postcss";
|
|
4
|
+
|
|
5
|
+
//#region ../tailwind/dist/utils/optimizeCSS.js
|
|
6
|
+
/*! © 2026 Yahoo, Inc. UDS Tailwind and Purger v0.0.0-development */
|
|
7
|
+
/**
|
|
8
|
+
* Check if a selector is a "meaningful" selector (class, ID, attribute, or pseudo-class)
|
|
9
|
+
* as opposed to just element/type selectors (like `code, kbd, pre` in preflight).
|
|
10
|
+
* Meaningful selectors indicate the font is actually used by components, not just base reset.
|
|
11
|
+
*/
|
|
12
|
+
function isMeaningfulSelector(selector) {
|
|
13
|
+
const parts = selector.split(",").map((s) => s.trim());
|
|
14
|
+
for (const part of parts) if (part.includes(".") || part.includes("#") || part.includes("[") || part.includes(":not(") || part.includes(":where(") || part.includes(":is(") || part.includes(":has(")) return true;
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* PostCSS plugin to remove @font-face declarations for font families that are not used
|
|
19
|
+
* anywhere in the CSS (excluding preflight/reset styles that only use element selectors).
|
|
20
|
+
*/
|
|
21
|
+
function removeUnusedFontFaces() {
|
|
22
|
+
return {
|
|
23
|
+
postcssPlugin: "remove-unused-font-faces",
|
|
24
|
+
Once(root) {
|
|
25
|
+
const fontVarUsage = /* @__PURE__ */ new Map();
|
|
26
|
+
const usedFontFamilies = /* @__PURE__ */ new Set();
|
|
27
|
+
root.walkDecls("font-family", (decl) => {
|
|
28
|
+
if (decl.parent?.type === "atrule" && decl.parent.name === "font-face") return;
|
|
29
|
+
const meaningful = isMeaningfulSelector(decl.parent?.selector || "");
|
|
30
|
+
const value = decl.value;
|
|
31
|
+
const varMatches = value.matchAll(/var\(--uds-font-([^),]+)\)/g);
|
|
32
|
+
for (const match of varMatches) {
|
|
33
|
+
const varName = match[1];
|
|
34
|
+
const existing = fontVarUsage.get(varName) || {
|
|
35
|
+
used: false,
|
|
36
|
+
meaningful: false
|
|
37
|
+
};
|
|
38
|
+
fontVarUsage.set(varName, {
|
|
39
|
+
used: true,
|
|
40
|
+
meaningful: existing.meaningful || meaningful
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if (meaningful) value.split(",").map((font) => font.trim()).map((font) => {
|
|
44
|
+
if (font.startsWith("\"") && font.endsWith("\"") || font.startsWith("'") && font.endsWith("'")) return font.slice(1, -1);
|
|
45
|
+
if (font.startsWith("var(") || [
|
|
46
|
+
"serif",
|
|
47
|
+
"sans-serif",
|
|
48
|
+
"monospace",
|
|
49
|
+
"cursive",
|
|
50
|
+
"fantasy",
|
|
51
|
+
"system-ui",
|
|
52
|
+
"ui-monospace",
|
|
53
|
+
"ui-serif",
|
|
54
|
+
"ui-sans-serif",
|
|
55
|
+
"ui-rounded"
|
|
56
|
+
].includes(font.toLowerCase())) return null;
|
|
57
|
+
return font;
|
|
58
|
+
}).filter(Boolean).forEach((name) => usedFontFamilies.add(name));
|
|
59
|
+
});
|
|
60
|
+
const fontVarDefinitions = /* @__PURE__ */ new Map();
|
|
61
|
+
root.walkDecls((decl) => {
|
|
62
|
+
if (decl.prop.startsWith("--uds-font-") && !decl.prop.includes("size") && !decl.prop.includes("weight") && !decl.prop.includes("slant") && !decl.prop.includes("width")) {
|
|
63
|
+
const varName = decl.prop.replace("--uds-font-", "");
|
|
64
|
+
fontVarDefinitions.set(varName, decl.value);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
const resolveToFontNames = (value, visited = /* @__PURE__ */ new Set()) => {
|
|
68
|
+
const fontNames = [];
|
|
69
|
+
const varMatch = value.match(/var\(--uds-font-([^),]+)\)/);
|
|
70
|
+
if (varMatch) {
|
|
71
|
+
const nestedVarName = varMatch[1];
|
|
72
|
+
if (!visited.has(nestedVarName)) {
|
|
73
|
+
visited.add(nestedVarName);
|
|
74
|
+
const nestedValue = fontVarDefinitions.get(nestedVarName);
|
|
75
|
+
if (nestedValue) {
|
|
76
|
+
const nestedUsage = fontVarUsage.get(nestedVarName);
|
|
77
|
+
if (nestedUsage) {
|
|
78
|
+
if (fontVarUsage.get(value.replace(/var\(--uds-font-([^),]+)\).*/, "$1"))?.meaningful) nestedUsage.meaningful = true;
|
|
79
|
+
}
|
|
80
|
+
fontNames.push(...resolveToFontNames(nestedValue, visited));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
value.split(",").map((font) => font.trim()).forEach((font) => {
|
|
85
|
+
if (font.startsWith("var(")) return;
|
|
86
|
+
if (font.startsWith("\"") && font.endsWith("\"") || font.startsWith("'") && font.endsWith("'")) font = font.slice(1, -1);
|
|
87
|
+
if ([
|
|
88
|
+
"serif",
|
|
89
|
+
"sans-serif",
|
|
90
|
+
"monospace",
|
|
91
|
+
"cursive",
|
|
92
|
+
"fantasy",
|
|
93
|
+
"system-ui",
|
|
94
|
+
"ui-monospace",
|
|
95
|
+
"ui-serif",
|
|
96
|
+
"ui-sans-serif",
|
|
97
|
+
"ui-rounded"
|
|
98
|
+
].includes(font.toLowerCase())) return;
|
|
99
|
+
if (font) fontNames.push(font);
|
|
100
|
+
});
|
|
101
|
+
return fontNames;
|
|
102
|
+
};
|
|
103
|
+
for (const [varName, value] of fontVarDefinitions) {
|
|
104
|
+
const usage = fontVarUsage.get(varName);
|
|
105
|
+
if (usage?.used && usage?.meaningful) resolveToFontNames(value).forEach((name) => usedFontFamilies.add(name));
|
|
106
|
+
}
|
|
107
|
+
const keptFonts = /* @__PURE__ */ new Set();
|
|
108
|
+
root.walkAtRules("font-face", (atRule) => {
|
|
109
|
+
let fontFamily = null;
|
|
110
|
+
atRule.walkDecls("font-family", (decl) => {
|
|
111
|
+
fontFamily = decl.value.replace(/["']/g, "").trim();
|
|
112
|
+
});
|
|
113
|
+
if (fontFamily && !usedFontFamilies.has(fontFamily)) atRule.remove();
|
|
114
|
+
else if (fontFamily) keptFonts.add(fontFamily);
|
|
115
|
+
});
|
|
116
|
+
root.walkDecls((decl) => {
|
|
117
|
+
if (decl.prop.startsWith("--uds-font-") && !decl.prop.includes("size") && !decl.prop.includes("weight") && !decl.prop.includes("slant") && !decl.prop.includes("width")) {
|
|
118
|
+
const fonts = decl.value.split(",").map((f) => f.trim());
|
|
119
|
+
const filteredFonts = fonts.filter((font) => {
|
|
120
|
+
if (font.startsWith("var(")) return true;
|
|
121
|
+
let fontName = font;
|
|
122
|
+
if (font.startsWith("\"") && font.endsWith("\"") || font.startsWith("'") && font.endsWith("'")) fontName = font.slice(1, -1);
|
|
123
|
+
if ([
|
|
124
|
+
"serif",
|
|
125
|
+
"sans-serif",
|
|
126
|
+
"monospace",
|
|
127
|
+
"cursive",
|
|
128
|
+
"fantasy",
|
|
129
|
+
"system-ui",
|
|
130
|
+
"ui-monospace",
|
|
131
|
+
"ui-serif",
|
|
132
|
+
"ui-sans-serif",
|
|
133
|
+
"ui-rounded"
|
|
134
|
+
].includes(fontName.toLowerCase())) return true;
|
|
135
|
+
return keptFonts.has(fontName);
|
|
136
|
+
});
|
|
137
|
+
if (filteredFonts.length < fonts.length && filteredFonts.length > 0) decl.value = filteredFonts.join(", ");
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
removeUnusedFontFaces.postcss = true;
|
|
144
|
+
/**
|
|
145
|
+
* PostCSS plugin to remove redundant .uds-color-mode-light rules.
|
|
146
|
+
* Light mode is the default (set on :root), so the explicit class is unnecessary.
|
|
147
|
+
*/
|
|
148
|
+
function removeRedundantLightModePlugin() {
|
|
149
|
+
return {
|
|
150
|
+
postcssPlugin: "remove-redundant-light-mode",
|
|
151
|
+
Once(root) {
|
|
152
|
+
root.walkRules((rule) => {
|
|
153
|
+
const selectors = rule.selector.split(",").map((s) => s.trim());
|
|
154
|
+
const filteredSelectors = selectors.filter((s) => {
|
|
155
|
+
const normalized = s.replace(/\s+/g, " ").trim();
|
|
156
|
+
return normalized !== ".uds-color-mode-light" && normalized !== ":root .uds-color-mode-light" && normalized !== ":where(.uds-color-mode-light)" && normalized !== ":is(.uds-color-mode-light)";
|
|
157
|
+
});
|
|
158
|
+
if (filteredSelectors.length === 0) rule.remove();
|
|
159
|
+
else if (filteredSelectors.length < selectors.length) rule.selector = filteredSelectors.join(", ");
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
removeRedundantLightModePlugin.postcss = true;
|
|
165
|
+
/**
|
|
166
|
+
* PostCSS plugin to remove empty CSS rules (rules with no declarations).
|
|
167
|
+
* Also aggregates duplicate selectors.
|
|
168
|
+
*/
|
|
169
|
+
function removeEmptyRulesPlugin() {
|
|
170
|
+
return {
|
|
171
|
+
postcssPlugin: "remove-empty-rules",
|
|
172
|
+
Once(root) {
|
|
173
|
+
root.walkRules((rule) => {
|
|
174
|
+
if (!rule.nodes?.some((node) => node.type === "decl" || node.type === "rule" && node.nodes?.length > 0)) rule.remove();
|
|
175
|
+
});
|
|
176
|
+
root.walkAtRules((atRule) => {
|
|
177
|
+
if (atRule.name !== "font-face" && atRule.name !== "keyframes") {
|
|
178
|
+
if (!atRule.nodes?.some((node) => {
|
|
179
|
+
if (node.type === "rule") return node.nodes?.some((n) => n.type === "decl");
|
|
180
|
+
if (node.type === "decl") return true;
|
|
181
|
+
return false;
|
|
182
|
+
}) && atRule.nodes?.length === 0) atRule.remove();
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
removeEmptyRulesPlugin.postcss = true;
|
|
189
|
+
/**
|
|
190
|
+
* PostCSS plugin to aggregate rules with identical selectors.
|
|
191
|
+
*/
|
|
192
|
+
function aggregateDuplicateSelectorsPlugin() {
|
|
193
|
+
return {
|
|
194
|
+
postcssPlugin: "aggregate-duplicate-selectors",
|
|
195
|
+
Once(root) {
|
|
196
|
+
const selectorMap = /* @__PURE__ */ new Map();
|
|
197
|
+
const rulesToProcess = [];
|
|
198
|
+
root.walkRules((rule) => {
|
|
199
|
+
if (rule.parent?.type === "root" || rule.parent?.type === "atrule") rulesToProcess.push(rule);
|
|
200
|
+
});
|
|
201
|
+
for (const rule of rulesToProcess) {
|
|
202
|
+
const selector = rule.selector;
|
|
203
|
+
const parent = rule.parent;
|
|
204
|
+
const key = `${parent?.type === "atrule" ? `@${parent.name}:${parent.params}` : "root"}|${selector}`;
|
|
205
|
+
if (selectorMap.has(key)) {
|
|
206
|
+
const existingRule = selectorMap.get(key);
|
|
207
|
+
rule.walkDecls((decl) => {
|
|
208
|
+
const existingDecl = existingRule.nodes?.find((node) => node.type === "decl" && node.prop === decl.prop);
|
|
209
|
+
if (existingDecl) existingDecl.value = decl.value;
|
|
210
|
+
else existingRule.append(decl.clone());
|
|
211
|
+
});
|
|
212
|
+
rule.remove();
|
|
213
|
+
} else selectorMap.set(key, rule);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
aggregateDuplicateSelectorsPlugin.postcss = true;
|
|
219
|
+
/**
|
|
220
|
+
* Validates CSS syntax and returns any errors found.
|
|
221
|
+
*/
|
|
222
|
+
function validateCSS(css) {
|
|
223
|
+
const errors = [];
|
|
224
|
+
try {
|
|
225
|
+
const result = postcss.parse(css);
|
|
226
|
+
result.walkRules((rule) => {
|
|
227
|
+
if (!rule.selector.trim()) errors.push(`Empty selector found at line ${rule.source?.start?.line}`);
|
|
228
|
+
try {
|
|
229
|
+
if (rule.selector.includes("{{") || rule.selector.includes("}}")) errors.push(`Malformed selector "${rule.selector}" at line ${rule.source?.start?.line}`);
|
|
230
|
+
} catch {
|
|
231
|
+
errors.push(`Invalid selector "${rule.selector}" at line ${rule.source?.start?.line}`);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
result.walkDecls((decl) => {
|
|
235
|
+
if (!decl.value.trim()) errors.push(`Empty value for property "${decl.prop}" at line ${decl.source?.start?.line}`);
|
|
236
|
+
});
|
|
237
|
+
return {
|
|
238
|
+
valid: errors.length === 0,
|
|
239
|
+
errors
|
|
240
|
+
};
|
|
241
|
+
} catch (error) {
|
|
242
|
+
if (error instanceof Error) errors.push(`CSS Parse Error: ${error.message}`);
|
|
243
|
+
else errors.push("Unknown CSS parse error");
|
|
244
|
+
return {
|
|
245
|
+
valid: false,
|
|
246
|
+
errors
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Create a fingerprint for a @font-face rule
|
|
252
|
+
*/
|
|
253
|
+
function getFontFaceFingerprint(atRule) {
|
|
254
|
+
let fontFamily = "";
|
|
255
|
+
let src = "";
|
|
256
|
+
let fontWeight = "normal";
|
|
257
|
+
let fontStyle = "normal";
|
|
258
|
+
atRule.walkDecls((decl) => {
|
|
259
|
+
if (decl.prop === "font-family") fontFamily = decl.value.replace(/["']/g, "").trim();
|
|
260
|
+
else if (decl.prop === "src") src = decl.value;
|
|
261
|
+
else if (decl.prop === "font-weight") fontWeight = decl.value;
|
|
262
|
+
else if (decl.prop === "font-style") fontStyle = decl.value;
|
|
263
|
+
});
|
|
264
|
+
return JSON.stringify({
|
|
265
|
+
fontFamily,
|
|
266
|
+
src,
|
|
267
|
+
fontWeight,
|
|
268
|
+
fontStyle
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Create a fingerprint for a @keyframes rule
|
|
273
|
+
*/
|
|
274
|
+
function getKeyframesFingerprint(atRule) {
|
|
275
|
+
const name = atRule.params;
|
|
276
|
+
const content = atRule.toString().replace(/\s+/g, " ").trim();
|
|
277
|
+
return JSON.stringify({
|
|
278
|
+
name,
|
|
279
|
+
content
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Normalize a selector by removing scope prefix and whitespace
|
|
284
|
+
*/
|
|
285
|
+
function normalizeSelector(selector, scopeClass) {
|
|
286
|
+
let normalized = selector.trim();
|
|
287
|
+
if (scopeClass) {
|
|
288
|
+
const escapedScope = scopeClass.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
289
|
+
normalized = normalized.replace(new RegExp(`${escapedScope}\\s+`, "g"), "").replace(new RegExp(`${escapedScope}\\.`, "g"), ".").replace(new RegExp(`${escapedScope}`, "g"), "");
|
|
290
|
+
}
|
|
291
|
+
return normalized.replace(/\s+/g, " ").trim();
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Create a fingerprint for a CSS rule (selector + declarations)
|
|
295
|
+
*/
|
|
296
|
+
function getRuleFingerprint(rule, scopeClass) {
|
|
297
|
+
const selector = normalizeSelector(rule.selector, scopeClass);
|
|
298
|
+
const declarations = [];
|
|
299
|
+
rule.walkDecls((decl) => {
|
|
300
|
+
declarations.push(`${decl.prop}:${decl.value}`);
|
|
301
|
+
});
|
|
302
|
+
declarations.sort();
|
|
303
|
+
return JSON.stringify({
|
|
304
|
+
selector,
|
|
305
|
+
declarations
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Extract reference data from CSS for deduplication
|
|
310
|
+
*/
|
|
311
|
+
function extractCSSReferenceData(css, scopeClass) {
|
|
312
|
+
const data = {
|
|
313
|
+
fontFaces: /* @__PURE__ */ new Set(),
|
|
314
|
+
keyframes: /* @__PURE__ */ new Set(),
|
|
315
|
+
rules: /* @__PURE__ */ new Set()
|
|
316
|
+
};
|
|
317
|
+
try {
|
|
318
|
+
const root = postcss.parse(css);
|
|
319
|
+
root.walkAtRules((atRule) => {
|
|
320
|
+
if (atRule.name === "font-face") data.fontFaces.add(getFontFaceFingerprint(atRule));
|
|
321
|
+
else if (atRule.name === "keyframes" || atRule.name === "-webkit-keyframes") data.keyframes.add(getKeyframesFingerprint(atRule));
|
|
322
|
+
});
|
|
323
|
+
root.walkRules((rule) => {
|
|
324
|
+
if (rule.parent?.type === "atrule") {
|
|
325
|
+
const parentName = rule.parent.name;
|
|
326
|
+
if (parentName === "font-face" || parentName === "keyframes" || parentName === "-webkit-keyframes") return;
|
|
327
|
+
}
|
|
328
|
+
data.rules.add(getRuleFingerprint(rule, scopeClass));
|
|
329
|
+
});
|
|
330
|
+
} catch {}
|
|
331
|
+
return data;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* PostCSS plugin to remove CSS that exists in reference CSS
|
|
335
|
+
* Handles @font-face, @keyframes, and regular rule deduplication
|
|
336
|
+
*/
|
|
337
|
+
function removeDuplicateCss(referenceData, scopeClass) {
|
|
338
|
+
return {
|
|
339
|
+
postcssPlugin: "remove-duplicate-css",
|
|
340
|
+
Once(root) {
|
|
341
|
+
const toRemove = [];
|
|
342
|
+
root.walkAtRules((atRule) => {
|
|
343
|
+
if (atRule.name === "font-face") {
|
|
344
|
+
const fingerprint = getFontFaceFingerprint(atRule);
|
|
345
|
+
if (referenceData.fontFaces.has(fingerprint)) toRemove.push(atRule);
|
|
346
|
+
} else if (atRule.name === "keyframes" || atRule.name === "-webkit-keyframes") {
|
|
347
|
+
const fingerprint = getKeyframesFingerprint(atRule);
|
|
348
|
+
if (referenceData.keyframes.has(fingerprint)) toRemove.push(atRule);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
root.walkRules((rule) => {
|
|
352
|
+
if (rule.parent?.type === "atrule") {
|
|
353
|
+
const parentName = rule.parent.name;
|
|
354
|
+
if (parentName === "font-face" || parentName === "keyframes" || parentName === "-webkit-keyframes") return;
|
|
355
|
+
}
|
|
356
|
+
const fingerprint = getRuleFingerprint(rule, scopeClass);
|
|
357
|
+
if (referenceData.rules.has(fingerprint)) toRemove.push(rule);
|
|
358
|
+
});
|
|
359
|
+
for (const node of toRemove) node.remove();
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Optimizes CSS by:
|
|
365
|
+
* 1. Removing unused @font-face declarations
|
|
366
|
+
* 2. Removing empty rules
|
|
367
|
+
* 3. Aggregating duplicate selectors
|
|
368
|
+
*
|
|
369
|
+
* Returns the optimized CSS and validation results.
|
|
370
|
+
*/
|
|
371
|
+
async function optimizeCSS(css, options) {
|
|
372
|
+
const originalSize = Buffer.byteLength(css, "utf8");
|
|
373
|
+
const originalSizeGzip = gzipSync(css).length;
|
|
374
|
+
const fontFacesBefore = (css.match(/@font-face/g) || []).length;
|
|
375
|
+
const rulesBefore = (css.match(/\{[^}]*\}/g) || []).length;
|
|
376
|
+
const plugins = [];
|
|
377
|
+
if (options?.removeUnusedFonts !== false) plugins.push(removeUnusedFontFaces());
|
|
378
|
+
plugins.push(removeRedundantLightModePlugin());
|
|
379
|
+
if (options?.referenceCss) {
|
|
380
|
+
const referenceData = extractCSSReferenceData(options.referenceCss);
|
|
381
|
+
if (referenceData.fontFaces.size > 0 || referenceData.keyframes.size > 0 || referenceData.rules.size > 0) plugins.push(removeDuplicateCss(referenceData, options.scopeClass));
|
|
382
|
+
}
|
|
383
|
+
if (options?.removeEmptyRules !== false) plugins.push(removeEmptyRulesPlugin());
|
|
384
|
+
if (options?.aggregateDuplicateSelectors !== false) plugins.push(aggregateDuplicateSelectorsPlugin());
|
|
385
|
+
const optimizedCSS = (plugins.length > 0 ? await postcss(plugins).process(css, { from: void 0 }) : { css }).css;
|
|
386
|
+
const optimizedSize = Buffer.byteLength(optimizedCSS, "utf8");
|
|
387
|
+
const fontFacesAfter = (optimizedCSS.match(/@font-face/g) || []).length;
|
|
388
|
+
const rulesAfter = (optimizedCSS.match(/\{[^}]*\}/g) || []).length;
|
|
389
|
+
return {
|
|
390
|
+
css: optimizedCSS,
|
|
391
|
+
validation: validateCSS(optimizedCSS),
|
|
392
|
+
stats: {
|
|
393
|
+
originalSize,
|
|
394
|
+
originalSizeGzip,
|
|
395
|
+
optimizedSize,
|
|
396
|
+
fontFacesRemoved: fontFacesBefore - fontFacesAfter,
|
|
397
|
+
emptyRulesRemoved: Math.max(0, rulesBefore - rulesAfter)
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
//#endregion
|
|
403
|
+
export { optimizeCSS };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/*! © 2026 Yahoo, Inc. UDS v0.0.0-development */
|
|
2
|
+
|
|
3
|
+
//#region ../tailwind/dist/utils/postcssPreserveVars.js
|
|
4
|
+
/*! © 2026 Yahoo, Inc. UDS Tailwind and Purger v0.0.0-development */
|
|
5
|
+
const PRESERVE_SELECTOR = "._uds-preserve-vars";
|
|
6
|
+
/**
|
|
7
|
+
* PostCSS plugin to preserve CSS variables from being pruned.
|
|
8
|
+
*
|
|
9
|
+
* Runs BEFORE postcss-prune-var to inject fake usages for preserved variables,
|
|
10
|
+
* preventing them from being removed.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* postcss([
|
|
15
|
+
* preserveVars({ preserve: ['--uds-motion-'] }),
|
|
16
|
+
* pruneVar(),
|
|
17
|
+
* preserveVarsCleanup(),
|
|
18
|
+
* ])
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
function preserveVars(options = {}) {
|
|
22
|
+
const { preserve = [] } = options;
|
|
23
|
+
return {
|
|
24
|
+
postcssPlugin: "postcss-preserve-vars",
|
|
25
|
+
Once(root) {
|
|
26
|
+
if (preserve.length === 0) return;
|
|
27
|
+
const declaredVars = /* @__PURE__ */ new Set();
|
|
28
|
+
root.walkDecls((decl) => {
|
|
29
|
+
if (decl.prop.startsWith("--")) declaredVars.add(decl.prop);
|
|
30
|
+
});
|
|
31
|
+
const varsToPreserve = [];
|
|
32
|
+
for (const varName of declaredVars) for (const pattern of preserve) if (pattern instanceof RegExp ? pattern.test(varName) : varName.startsWith(pattern)) {
|
|
33
|
+
varsToPreserve.push(varName);
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
if (varsToPreserve.length === 0) return;
|
|
37
|
+
const varUsages = varsToPreserve.map((v) => `var(${v})`).join(" ");
|
|
38
|
+
root.append({
|
|
39
|
+
selector: PRESERVE_SELECTOR,
|
|
40
|
+
nodes: [{
|
|
41
|
+
prop: "content",
|
|
42
|
+
value: `"${varUsages}"`
|
|
43
|
+
}]
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
preserveVars.postcss = true;
|
|
49
|
+
/**
|
|
50
|
+
* PostCSS plugin to remove the temporary preserve rule after pruning.
|
|
51
|
+
* Run this AFTER postcss-prune-var.
|
|
52
|
+
*/
|
|
53
|
+
function preserveVarsCleanup() {
|
|
54
|
+
return {
|
|
55
|
+
postcssPlugin: "postcss-preserve-vars-cleanup",
|
|
56
|
+
Once(root) {
|
|
57
|
+
root.walkRules((rule) => {
|
|
58
|
+
if (rule.selector === PRESERVE_SELECTOR) rule.remove();
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
preserveVarsCleanup.postcss = true;
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
exports.preserveVars = preserveVars;
|
|
67
|
+
exports.preserveVarsCleanup = preserveVarsCleanup;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/*! © 2026 Yahoo, Inc. UDS v0.0.0-development */
|
|
2
|
+
//#region ../tailwind/dist/utils/postcssPreserveVars.js
|
|
3
|
+
/*! © 2026 Yahoo, Inc. UDS Tailwind and Purger v0.0.0-development */
|
|
4
|
+
const PRESERVE_SELECTOR = "._uds-preserve-vars";
|
|
5
|
+
/**
|
|
6
|
+
* PostCSS plugin to preserve CSS variables from being pruned.
|
|
7
|
+
*
|
|
8
|
+
* Runs BEFORE postcss-prune-var to inject fake usages for preserved variables,
|
|
9
|
+
* preventing them from being removed.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* postcss([
|
|
14
|
+
* preserveVars({ preserve: ['--uds-motion-'] }),
|
|
15
|
+
* pruneVar(),
|
|
16
|
+
* preserveVarsCleanup(),
|
|
17
|
+
* ])
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
function preserveVars(options = {}) {
|
|
21
|
+
const { preserve = [] } = options;
|
|
22
|
+
return {
|
|
23
|
+
postcssPlugin: "postcss-preserve-vars",
|
|
24
|
+
Once(root) {
|
|
25
|
+
if (preserve.length === 0) return;
|
|
26
|
+
const declaredVars = /* @__PURE__ */ new Set();
|
|
27
|
+
root.walkDecls((decl) => {
|
|
28
|
+
if (decl.prop.startsWith("--")) declaredVars.add(decl.prop);
|
|
29
|
+
});
|
|
30
|
+
const varsToPreserve = [];
|
|
31
|
+
for (const varName of declaredVars) for (const pattern of preserve) if (pattern instanceof RegExp ? pattern.test(varName) : varName.startsWith(pattern)) {
|
|
32
|
+
varsToPreserve.push(varName);
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
if (varsToPreserve.length === 0) return;
|
|
36
|
+
const varUsages = varsToPreserve.map((v) => `var(${v})`).join(" ");
|
|
37
|
+
root.append({
|
|
38
|
+
selector: PRESERVE_SELECTOR,
|
|
39
|
+
nodes: [{
|
|
40
|
+
prop: "content",
|
|
41
|
+
value: `"${varUsages}"`
|
|
42
|
+
}]
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
preserveVars.postcss = true;
|
|
48
|
+
/**
|
|
49
|
+
* PostCSS plugin to remove the temporary preserve rule after pruning.
|
|
50
|
+
* Run this AFTER postcss-prune-var.
|
|
51
|
+
*/
|
|
52
|
+
function preserveVarsCleanup() {
|
|
53
|
+
return {
|
|
54
|
+
postcssPlugin: "postcss-preserve-vars-cleanup",
|
|
55
|
+
Once(root) {
|
|
56
|
+
root.walkRules((rule) => {
|
|
57
|
+
if (rule.selector === PRESERVE_SELECTOR) rule.remove();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
preserveVarsCleanup.postcss = true;
|
|
63
|
+
|
|
64
|
+
//#endregion
|
|
65
|
+
export { preserveVars, preserveVarsCleanup };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*! © 2026 Yahoo, Inc. UDS v0.0.0-development */
|
|
2
2
|
const require_runtime = require('../../../_virtual/_rolldown/runtime.cjs');
|
|
3
|
-
let ts_morph = require("ts-morph");
|
|
4
3
|
let lodash_es = require("lodash-es");
|
|
4
|
+
let ts_morph = require("ts-morph");
|
|
5
5
|
|
|
6
6
|
//#region ../tailwind/dist/utils/tsMorph.js
|
|
7
7
|
/*! © 2026 Yahoo, Inc. UDS Tailwind and Purger v0.0.0-development */
|