@ikas/component-cli 1.4.0-beta.41 → 1.4.0-beta.43
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/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +168 -5
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/create.d.ts +3 -8
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +227 -8
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/get-available-values.d.ts +3 -0
- package/dist/commands/get-available-values.d.ts.map +1 -0
- package/dist/commands/get-available-values.js +49 -0
- package/dist/commands/get-available-values.js.map +1 -0
- package/dist/commands/proxy.d.ts.map +1 -1
- package/dist/commands/proxy.js +4 -6
- package/dist/commands/proxy.js.map +1 -1
- package/dist/commands/set-dynamic-value.d.ts +3 -0
- package/dist/commands/set-dynamic-value.d.ts.map +1 -0
- package/dist/commands/set-dynamic-value.js +54 -0
- package/dist/commands/set-dynamic-value.js.map +1 -0
- package/dist/commands/update-section-props.d.ts +3 -0
- package/dist/commands/update-section-props.d.ts.map +1 -0
- package/dist/commands/update-section-props.js +43 -0
- package/dist/commands/update-section-props.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -21
- package/dist/index.js.map +1 -1
- package/dist/utils/compile.d.ts +1 -4
- package/dist/utils/compile.d.ts.map +1 -1
- package/dist/utils/compile.js +40 -500
- package/dist/utils/compile.js.map +1 -1
- package/dist/utils/editor-action-client.d.ts +1 -1
- package/dist/utils/editor-action-client.d.ts.map +1 -1
- package/dist/utils/editor-action-client.js.map +1 -1
- package/package.json +1 -1
- package/dist/commands/create-design-tokens.d.ts +0 -7
- package/dist/commands/create-design-tokens.d.ts.map +0 -1
- package/dist/commands/create-design-tokens.js +0 -127
- package/dist/commands/create-design-tokens.js.map +0 -1
- package/dist/commands/create-global-variable.d.ts +0 -3
- package/dist/commands/create-global-variable.d.ts.map +0 -1
- package/dist/commands/create-global-variable.js +0 -53
- package/dist/commands/create-global-variable.js.map +0 -1
- package/dist/commands/delete-theme-globals.d.ts +0 -4
- package/dist/commands/delete-theme-globals.d.ts.map +0 -1
- package/dist/commands/delete-theme-globals.js +0 -48
- package/dist/commands/delete-theme-globals.js.map +0 -1
- package/dist/commands/list-theme-globals.d.ts +0 -3
- package/dist/commands/list-theme-globals.d.ts.map +0 -1
- package/dist/commands/list-theme-globals.js +0 -22
- package/dist/commands/list-theme-globals.js.map +0 -1
- package/dist/commands/update-global-variable.d.ts +0 -3
- package/dist/commands/update-global-variable.d.ts.map +0 -1
- package/dist/commands/update-global-variable.js +0 -47
- package/dist/commands/update-global-variable.js.map +0 -1
package/dist/utils/compile.js
CHANGED
|
@@ -95,15 +95,11 @@ export async function compileComponent(entryPath, stylesPath, componentId) {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
/**
|
|
98
|
-
* Scope CSS selectors with a component-specific class prefix
|
|
99
|
-
* document-global CSS identifiers (`@keyframes`, `@font-face` font-family,
|
|
100
|
-
* `@counter-style`) DEFINED in this component so two components — or the host theme —
|
|
101
|
-
* declaring the same name can't collide in the page's global namespace.
|
|
98
|
+
* Scope CSS selectors with a component-specific class prefix
|
|
102
99
|
*/
|
|
103
100
|
export function scopeCSS(css, componentId) {
|
|
104
101
|
const scopeClass = `cc_${componentId.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
105
|
-
|
|
106
|
-
return renameCollidingGlobals(scoped, `${scopeClass}_`);
|
|
102
|
+
return scopeCSSWithClasses(css, [scopeClass]);
|
|
107
103
|
}
|
|
108
104
|
/**
|
|
109
105
|
* Scope CSS selectors with multiple class prefixes (for shared chunks).
|
|
@@ -113,519 +109,63 @@ export function scopeCSSMulti(css, componentIds) {
|
|
|
113
109
|
const scopeClasses = componentIds.map(id => `cc_${id.replace(/[^a-zA-Z0-9]/g, "_")}`);
|
|
114
110
|
return scopeCSSWithClasses(css, scopeClasses);
|
|
115
111
|
}
|
|
116
|
-
// Conditional group at-rules whose blocks nest normal style rules and must be
|
|
117
|
-
// scoped recursively. This is an ALLOWLIST on purpose: at-rules NOT listed here
|
|
118
|
-
// (@keyframes, @font-face, @page, @property, @counter-style, ...) have non-selector
|
|
119
|
-
// bodies — e.g. @keyframes contains `0%`/`from` keyframe selectors that must NOT be
|
|
120
|
-
// prefixed — so their bodies are copied through untouched. Vendor-prefixed conditional
|
|
121
|
-
// rules (@-moz-document) match via the optional prefix group; @-webkit-keyframes does
|
|
122
|
-
// NOT match (the alternation excludes "keyframes"), which is what we want.
|
|
123
|
-
//
|
|
124
|
-
// NOTE: This scoper is duplicated in editor-models/src/helpers/code-component-css-scope.ts
|
|
125
|
-
// (the editor-preview / code-generator path). The CLI ships standalone to npm and cannot
|
|
126
|
-
// import that private package, so the two intentionally diverge. Keep parsing fixes in sync.
|
|
127
|
-
const NESTING_AT_RULES = /^@(?:-\w+-)?(media|supports|container|layer|document|scope|starting-style)\b/i;
|
|
128
112
|
/**
|
|
129
|
-
* Internal: scope CSS selectors with one or more scope class prefixes
|
|
130
|
-
*
|
|
131
|
-
* Character-based walker, NOT line-based: a rule's selector list is everything up
|
|
132
|
-
* to its opening `{`, so lists written across multiple lines (`.a,\n.b { ... }`)
|
|
133
|
-
* are scoped as a whole. The previous line-based implementation only scoped the
|
|
134
|
-
* selector fragment sharing a line with `{`, leaving the other selectors unscoped
|
|
135
|
-
* (they either lost the cascade to scoped rules or leaked into other components).
|
|
136
|
-
*
|
|
137
|
-
* Comments, string literals, and `\` escapes are treated as inert spans (via
|
|
138
|
-
* `skipInertSpan`) by every structural scanner, so a `{`, `}`, `;`, or `,` that
|
|
139
|
-
* appears inside a comment / string / escape can never be mistaken for real
|
|
140
|
-
* structure (which would otherwise drop the scope prefix and leak styles globally).
|
|
113
|
+
* Internal: scope CSS selectors with one or more scope class prefixes
|
|
141
114
|
*/
|
|
142
115
|
function scopeCSSWithClasses(css, scopeClasses) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
out += css.slice(i, end + 2);
|
|
152
|
-
i = end + 2;
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
// Copy whitespace and stray closing braces
|
|
156
|
-
if (/\s/.test(ch) || ch === "}") {
|
|
157
|
-
out += ch;
|
|
158
|
-
i++;
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
if (ch === "@") {
|
|
162
|
-
const headerEnd = findAtRuleHeaderEnd(css, i);
|
|
163
|
-
// Block-less at-rule (@import, @charset, @layer a;, ...) — copy through
|
|
164
|
-
if (headerEnd >= css.length || css[headerEnd] === ";") {
|
|
165
|
-
out += css.slice(i, Math.min(headerEnd + 1, css.length));
|
|
166
|
-
i = headerEnd + 1;
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
const header = css.slice(i, headerEnd);
|
|
170
|
-
const blockEnd = findBlockEnd(css, headerEnd + 1);
|
|
171
|
-
const body = css.slice(headerEnd + 1, blockEnd);
|
|
172
|
-
const transformed = NESTING_AT_RULES.test(header.trim())
|
|
173
|
-
? scopeCSSWithClasses(body, scopeClasses)
|
|
174
|
-
: body;
|
|
175
|
-
// Always emit the closing brace: when the source block is unterminated
|
|
176
|
-
// (blockEnd === css.length) this repairs it, so concatenated component CSS
|
|
177
|
-
// can't leak one component's block over the next.
|
|
178
|
-
out += `${header}{${transformed}}`;
|
|
179
|
-
i = blockEnd + 1;
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
// Normal style rule: selector list runs up to the rule's opening `{`
|
|
183
|
-
const selEnd = findSelectorListEnd(css, i);
|
|
184
|
-
if (selEnd >= css.length) {
|
|
185
|
-
// Trailing content with no block (e.g. a dangling declaration at EOF) — copy through
|
|
186
|
-
out += css.slice(i);
|
|
187
|
-
break;
|
|
188
|
-
}
|
|
189
|
-
if (css[selEnd] === ";") {
|
|
190
|
-
// A stray top-level `;` (or a stray declaration fragment ending in `;`) — not a rule.
|
|
191
|
-
// DROP it. Copying it through is not enough: per the CSS syntax spec a browser folds a
|
|
192
|
-
// stray `;` into the NEXT rule's prelude when consuming a qualified rule, which makes
|
|
193
|
-
// that selector invalid and drops the whole following rule. Dropping the stray token
|
|
194
|
-
// keeps the next rule valid and correctly scoped.
|
|
195
|
-
i = selEnd + 1;
|
|
196
|
-
continue;
|
|
197
|
-
}
|
|
198
|
-
if (css[selEnd] === "}") {
|
|
199
|
-
// Content before a stray closing brace — copy through verbatim and let the main loop
|
|
200
|
-
// emit the `}`.
|
|
201
|
-
out += css.slice(i, selEnd);
|
|
202
|
-
i = selEnd;
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
const selectorList = css.slice(i, selEnd);
|
|
206
|
-
const blockEnd = findBlockEnd(css, selEnd + 1);
|
|
207
|
-
const body = css.slice(selEnd + 1, blockEnd);
|
|
208
|
-
const scopedSelector = splitSelectorList(selectorList)
|
|
209
|
-
.map(s => s.trim())
|
|
210
|
-
.filter(Boolean)
|
|
211
|
-
.flatMap(s => scopeClasses.map(cls => `.${cls} ${s}`))
|
|
212
|
-
.join(", ");
|
|
213
|
-
// Always emit the closing brace (see at-rule branch above for rationale).
|
|
214
|
-
out += `${scopedSelector} {${body}}`;
|
|
215
|
-
i = blockEnd + 1;
|
|
216
|
-
}
|
|
217
|
-
return out;
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* If an inert span — a `/* ... *\/` comment, a `"..."` / `'...'` string literal, an
|
|
221
|
-
* unquoted `url(...)` token, or a `\x` escape — starts at `s[i]`, return the index just
|
|
222
|
-
* past it; otherwise return -1.
|
|
223
|
-
*
|
|
224
|
-
* Every structural scanner below funnels through this so that delimiters (`{ } ; , ( )`)
|
|
225
|
-
* living inside comments, strings, urls, or escapes are never treated as structure. String
|
|
226
|
-
* scanning honors `\` escapes and stops at a raw newline, matching CSS "bad-string" error
|
|
227
|
-
* recovery (an unterminated string ends at the line break) so a missing quote can't
|
|
228
|
-
* swallow every rule that follows it.
|
|
229
|
-
*/
|
|
230
|
-
function skipInertSpan(s, i) {
|
|
231
|
-
const c = s[i];
|
|
232
|
-
if (c === "/" && s[i + 1] === "*") {
|
|
233
|
-
const end = s.indexOf("*/", i + 2);
|
|
234
|
-
return end === -1 ? s.length : end + 2;
|
|
235
|
-
}
|
|
236
|
-
if (c === '"' || c === "'") {
|
|
237
|
-
for (let j = i + 1; j < s.length; j++) {
|
|
238
|
-
const d = s[j];
|
|
239
|
-
if (d === "\\") {
|
|
240
|
-
j++; // escape: skip the next char
|
|
241
|
-
continue;
|
|
242
|
-
}
|
|
243
|
-
if (d === c)
|
|
244
|
-
return j + 1; // closing quote
|
|
245
|
-
if (d === "\n" || d === "\r" || d === "\f")
|
|
246
|
-
return j; // bad-string ends at newline
|
|
247
|
-
}
|
|
248
|
-
return s.length;
|
|
249
|
-
}
|
|
250
|
-
// Unquoted url(...) token: per the CSS tokenizer its content is opaque, so braces,
|
|
251
|
-
// semicolons, and parens inside it must NOT be read as structure (an unbalanced `}` in
|
|
252
|
-
// `url(x}y.png)` or `{` in an inline SVG data-uri would otherwise mis-terminate the
|
|
253
|
-
// enclosing block). A QUOTED url (`url("...")`) is a normal function + string and is left
|
|
254
|
-
// to the string branch above. `url` must be a standalone ident, not the tail of a longer
|
|
255
|
-
// identifier like `myurl(`.
|
|
256
|
-
if ((c === "u" || c === "U") && /^url\(/i.test(s.slice(i, i + 4))) {
|
|
257
|
-
const prev = i > 0 ? s[i - 1] : "";
|
|
258
|
-
if (!/[A-Za-z0-9_-]/.test(prev)) {
|
|
259
|
-
let j = i + 4;
|
|
260
|
-
while (j < s.length && /\s/.test(s[j]))
|
|
261
|
-
j++; // optional leading whitespace
|
|
262
|
-
if (s[j] !== '"' && s[j] !== "'") {
|
|
263
|
-
for (; j < s.length; j++) {
|
|
264
|
-
if (s[j] === "\\") {
|
|
265
|
-
j++; // escaped char inside the url (e.g. `url(foo\)bar)`)
|
|
266
|
-
continue;
|
|
267
|
-
}
|
|
268
|
-
if (s[j] === ")")
|
|
269
|
-
return j + 1; // end of url token
|
|
270
|
-
}
|
|
271
|
-
return s.length; // unterminated url() — consume to EOF (bad-url)
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
if (c === "\\")
|
|
276
|
-
return i + 2; // escaped char outside a string (e.g. `.a\,b`)
|
|
277
|
-
return -1;
|
|
278
|
-
}
|
|
279
|
-
/** Find the first top-level `{` or `;` that ends an at-rule's header. Respects `()` / `[]`
|
|
280
|
-
* and inert spans (strings/comments/escapes). */
|
|
281
|
-
function findAtRuleHeaderEnd(css, start) {
|
|
282
|
-
let pDepth = 0;
|
|
283
|
-
for (let j = start; j < css.length;) {
|
|
284
|
-
const skip = skipInertSpan(css, j);
|
|
285
|
-
if (skip !== -1) {
|
|
286
|
-
j = skip;
|
|
287
|
-
continue;
|
|
288
|
-
}
|
|
289
|
-
const c = css[j];
|
|
290
|
-
if (c === "(" || c === "[")
|
|
291
|
-
pDepth++;
|
|
292
|
-
else if (c === ")" || c === "]")
|
|
293
|
-
pDepth--;
|
|
294
|
-
else if (pDepth === 0 && (c === "{" || c === ";"))
|
|
295
|
-
return j;
|
|
296
|
-
j++;
|
|
297
|
-
}
|
|
298
|
-
return css.length;
|
|
299
|
-
}
|
|
300
|
-
/**
|
|
301
|
-
* Find the matching `}` for the block whose body starts at `blockStart` (char after `{`).
|
|
302
|
-
* Inert spans are skipped so braces inside `content: "}"`, comments, or escapes don't
|
|
303
|
-
* terminate the block early.
|
|
304
|
-
*/
|
|
305
|
-
function findBlockEnd(css, blockStart) {
|
|
306
|
-
let depth = 1;
|
|
307
|
-
for (let j = blockStart; j < css.length;) {
|
|
308
|
-
const skip = skipInertSpan(css, j);
|
|
309
|
-
if (skip !== -1) {
|
|
310
|
-
j = skip;
|
|
311
|
-
continue;
|
|
312
|
-
}
|
|
313
|
-
const c = css[j];
|
|
314
|
-
if (c === "{")
|
|
315
|
-
depth++;
|
|
316
|
-
else if (c === "}") {
|
|
317
|
-
depth--;
|
|
318
|
-
if (depth === 0)
|
|
319
|
-
return j;
|
|
320
|
-
}
|
|
321
|
-
j++;
|
|
322
|
-
}
|
|
323
|
-
return css.length;
|
|
324
|
-
}
|
|
325
|
-
/** Find the `{`, `}`, or `;` that terminates a selector list. A `}` or `;` here means the
|
|
326
|
-
* run was not a real style rule. Respects `()` / `[]` and inert spans. */
|
|
327
|
-
function findSelectorListEnd(css, start) {
|
|
328
|
-
let pDepth = 0;
|
|
329
|
-
for (let j = start; j < css.length;) {
|
|
330
|
-
const skip = skipInertSpan(css, j);
|
|
331
|
-
if (skip !== -1) {
|
|
332
|
-
j = skip;
|
|
116
|
+
const lines = css.split("\n");
|
|
117
|
+
const scopedLines = [];
|
|
118
|
+
let inMediaQuery = false;
|
|
119
|
+
let keyframesDepth = 0;
|
|
120
|
+
for (const line of lines) {
|
|
121
|
+
const trimmed = line.trim();
|
|
122
|
+
if (!trimmed) {
|
|
123
|
+
scopedLines.push(line);
|
|
333
124
|
continue;
|
|
334
125
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
else if (c === ")" || c === "]")
|
|
339
|
-
pDepth--;
|
|
340
|
-
else if (pDepth === 0 && (c === "{" || c === "}" || c === ";"))
|
|
341
|
-
return j;
|
|
342
|
-
j++;
|
|
343
|
-
}
|
|
344
|
-
return css.length;
|
|
345
|
-
}
|
|
346
|
-
/** Split a selector list on top-level commas. Respects `()` / `[]` and inert spans, so a
|
|
347
|
-
* comma inside a comment, string, or `\` escape (e.g. `.a\,b`) does not split the list. */
|
|
348
|
-
function splitSelectorList(s) {
|
|
349
|
-
const result = [];
|
|
350
|
-
let depth = 0;
|
|
351
|
-
let start = 0;
|
|
352
|
-
for (let i = 0; i < s.length;) {
|
|
353
|
-
const skip = skipInertSpan(s, i);
|
|
354
|
-
if (skip !== -1) {
|
|
355
|
-
i = skip;
|
|
126
|
+
if (trimmed.startsWith("@media")) {
|
|
127
|
+
inMediaQuery = true;
|
|
128
|
+
scopedLines.push(line);
|
|
356
129
|
continue;
|
|
357
130
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
else if (ch === ")" || ch === "]")
|
|
362
|
-
depth--;
|
|
363
|
-
else if (ch === "," && depth === 0) {
|
|
364
|
-
result.push(s.slice(start, i));
|
|
365
|
-
start = i + 1;
|
|
366
|
-
}
|
|
367
|
-
i++;
|
|
368
|
-
}
|
|
369
|
-
result.push(s.slice(start));
|
|
370
|
-
return result;
|
|
371
|
-
}
|
|
372
|
-
/* ============================================================================
|
|
373
|
-
* Global-identifier namespacing.
|
|
374
|
-
*
|
|
375
|
-
* `@keyframes`, `@font-face` font-family names, and `@counter-style` names live in a
|
|
376
|
-
* DOCUMENT-GLOBAL namespace — scoping selectors with `.cc_<id>` does not isolate them, so
|
|
377
|
-
* two components (or the theme) defining the same name would collide (last definition wins
|
|
378
|
-
* document-wide). `renameCollidingGlobals` prefixes every such name DEFINED in this CSS,
|
|
379
|
-
* plus its references within the SAME CSS, with the component's scope prefix.
|
|
380
|
-
*
|
|
381
|
-
* Only names defined in THIS css are renamed — a reference to a name defined elsewhere
|
|
382
|
-
* (global.css, a shared chunk, the theme) is left untouched, so cross-file references keep
|
|
383
|
-
* resolving. Shared-chunk CSS (scopeCSSMulti) is intentionally NOT renamed, to avoid
|
|
384
|
-
* breaking a chunk-defined keyframe referenced from a consuming component's own CSS.
|
|
385
|
-
*
|
|
386
|
-
* NOTE: ported from packages/editor-models/src/helpers/code-component-css-scope.ts
|
|
387
|
-
* (renameCollidingGlobals + helpers, the global.css path's `cc_<projectId>_` variant).
|
|
388
|
-
* Keep parsing/rename fixes in sync between the two.
|
|
389
|
-
* ============================================================================ */
|
|
390
|
-
// Declarations whose VALUE may reference a global identifier of the given type.
|
|
391
|
-
const KEYFRAME_REF_PROPS = new Set(["animation", "animation-name"]);
|
|
392
|
-
const FONT_FAMILY_REF_PROPS = new Set(["font", "font-family"]);
|
|
393
|
-
const COUNTER_STYLE_REF_PROPS = new Set(["list-style", "list-style-type"]);
|
|
394
|
-
function renameCollidingGlobals(css, prefix) {
|
|
395
|
-
// Comment/string/url-masked copy: defining-site regex scans run against this so a name
|
|
396
|
-
// inside a comment or `content: "..."` string can't contribute a rename or be rewritten.
|
|
397
|
-
const scanCss = maskInertSpans(css);
|
|
398
|
-
const keyframeNames = new Set();
|
|
399
|
-
const fontFamilyNames = new Set();
|
|
400
|
-
const counterStyleNames = new Set();
|
|
401
|
-
let out = css;
|
|
402
|
-
out = out.replace(/@keyframes(\s+)([\w-]+)/g, (m, ws, name, offset) => {
|
|
403
|
-
if (scanCss.substr(offset, 10) !== "@keyframes")
|
|
404
|
-
return m;
|
|
405
|
-
keyframeNames.add(name);
|
|
406
|
-
return `@keyframes${ws}${prefix}${name}`;
|
|
407
|
-
});
|
|
408
|
-
out = out.replace(/@counter-style(\s+)([\w-]+)/g, (m, ws, name, offset) => {
|
|
409
|
-
if (scanCss.substr(offset, 14) !== "@counter-style")
|
|
410
|
-
return m;
|
|
411
|
-
counterStyleNames.add(name);
|
|
412
|
-
return `@counter-style${ws}${prefix}${name}`;
|
|
413
|
-
});
|
|
414
|
-
out = rewriteFontFaceBlocks(out, fontFamilyNames, prefix);
|
|
415
|
-
if (keyframeNames.size === 0 && fontFamilyNames.size === 0 && counterStyleNames.size === 0) {
|
|
416
|
-
return out;
|
|
417
|
-
}
|
|
418
|
-
return rewriteDeclarationReferences(out, keyframeNames, fontFamilyNames, counterStyleNames, prefix);
|
|
419
|
-
}
|
|
420
|
-
/** Replace every inert span (comment / string / url() / escape) with equal-length spaces so
|
|
421
|
-
* position-based regex scans still align but can't match tokens hidden inside them. */
|
|
422
|
-
function maskInertSpans(css) {
|
|
423
|
-
let out = "";
|
|
424
|
-
for (let i = 0; i < css.length;) {
|
|
425
|
-
const skip = skipInertSpan(css, i);
|
|
426
|
-
if (skip !== -1) {
|
|
427
|
-
out += " ".repeat(skip - i);
|
|
428
|
-
i = skip;
|
|
131
|
+
if (trimmed.startsWith("@keyframes") || trimmed.startsWith("@-webkit-keyframes")) {
|
|
132
|
+
keyframesDepth = 1;
|
|
133
|
+
scopedLines.push(line);
|
|
429
134
|
continue;
|
|
430
135
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
css.replace(/@font-face\s*\{/g, (m, offset) => {
|
|
439
|
-
matches.push({ index: offset, length: m.length });
|
|
440
|
-
return m;
|
|
441
|
-
});
|
|
442
|
-
if (matches.length === 0)
|
|
443
|
-
return css;
|
|
444
|
-
let out = "";
|
|
445
|
-
let lastIdx = 0;
|
|
446
|
-
for (const match of matches) {
|
|
447
|
-
const openBraceIdx = match.index + match.length - 1;
|
|
448
|
-
if (openBraceIdx < lastIdx)
|
|
449
|
-
continue;
|
|
450
|
-
const endIdx = findBlockEnd(css, openBraceIdx + 1);
|
|
451
|
-
const before = css.slice(lastIdx, openBraceIdx + 1);
|
|
452
|
-
const body = css.slice(openBraceIdx + 1, endIdx);
|
|
453
|
-
const rewrittenBody = body.replace(/(font-family\s*:\s*)(?:"([^"]+)"|'([^']+)'|([\w-]+))/i, (_m, pre, dq, sq, bare) => {
|
|
454
|
-
const name = dq || sq || bare;
|
|
455
|
-
if (!name)
|
|
456
|
-
return _m;
|
|
457
|
-
collect.add(name);
|
|
458
|
-
const prefixed = `${prefix}${name}`;
|
|
459
|
-
if (dq)
|
|
460
|
-
return `${pre}"${prefixed}"`;
|
|
461
|
-
if (sq)
|
|
462
|
-
return `${pre}'${prefixed}'`;
|
|
463
|
-
return `${pre}${prefixed}`;
|
|
464
|
-
});
|
|
465
|
-
out += before + rewrittenBody + "}";
|
|
466
|
-
lastIdx = endIdx + 1;
|
|
467
|
-
}
|
|
468
|
-
out += css.slice(lastIdx);
|
|
469
|
-
return out;
|
|
470
|
-
}
|
|
471
|
-
function rewriteDeclarationReferences(css, keyframeNames, fontFamilyNames, counterStyleNames, prefix) {
|
|
472
|
-
const keyframeRe = buildBareIdentRegex(keyframeNames);
|
|
473
|
-
const fontFamilyRe = buildFontFamilyValueRegex(fontFamilyNames);
|
|
474
|
-
const counterStyleRe = buildBareIdentRegex(counterStyleNames);
|
|
475
|
-
// counter(<name>, <style>?) / counters(<name>, <string>, <style>?) — the counter-style
|
|
476
|
-
// identifier, when present, is always the LAST optional argument.
|
|
477
|
-
const counterFnRe = counterStyleNames.size > 0
|
|
478
|
-
? new RegExp(`(\\bcounters?\\([^)]*?,\\s*)(${bareUnion(counterStyleNames)})(\\s*\\))`, "gi")
|
|
479
|
-
: null;
|
|
480
|
-
return mapRuleBlocks(css, block => {
|
|
481
|
-
return mapDeclarations(block, (prop, value) => {
|
|
482
|
-
let v = value;
|
|
483
|
-
const lower = prop.toLowerCase();
|
|
484
|
-
if (keyframeRe && KEYFRAME_REF_PROPS.has(lower)) {
|
|
485
|
-
v = v.replace(keyframeRe, m => `${prefix}${m}`);
|
|
486
|
-
}
|
|
487
|
-
if (fontFamilyRe && FONT_FAMILY_REF_PROPS.has(lower)) {
|
|
488
|
-
v = v.replace(fontFamilyRe, (m, dq, sq, bare) => {
|
|
489
|
-
const name = dq || sq || bare;
|
|
490
|
-
if (!name || !fontFamilyNames.has(name))
|
|
491
|
-
return m;
|
|
492
|
-
if (dq !== undefined)
|
|
493
|
-
return `"${prefix}${name}"`;
|
|
494
|
-
if (sq !== undefined)
|
|
495
|
-
return `'${prefix}${name}'`;
|
|
496
|
-
return `${prefix}${name}`;
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
if (counterStyleRe && COUNTER_STYLE_REF_PROPS.has(lower)) {
|
|
500
|
-
v = v.replace(counterStyleRe, m => `${prefix}${m}`);
|
|
136
|
+
// Inside @keyframes: track brace depth, pass lines through unscoped
|
|
137
|
+
if (keyframesDepth > 0) {
|
|
138
|
+
for (const ch of trimmed) {
|
|
139
|
+
if (ch === "{")
|
|
140
|
+
keyframesDepth++;
|
|
141
|
+
else if (ch === "}")
|
|
142
|
+
keyframesDepth--;
|
|
501
143
|
}
|
|
502
|
-
|
|
503
|
-
v = v.replace(counterFnRe, (_m, pre, name, post) => `${pre}${prefix}${name}${post}`);
|
|
504
|
-
}
|
|
505
|
-
return v;
|
|
506
|
-
});
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
function bareUnion(names) {
|
|
510
|
-
return Array.from(names).map(escapeRegex).join("|");
|
|
511
|
-
}
|
|
512
|
-
function buildBareIdentRegex(names) {
|
|
513
|
-
if (names.size === 0)
|
|
514
|
-
return null;
|
|
515
|
-
return new RegExp(`(?<![\\w-])(?:${bareUnion(names)})(?![\\w-])`, "g");
|
|
516
|
-
}
|
|
517
|
-
function buildFontFamilyValueRegex(names) {
|
|
518
|
-
if (names.size === 0)
|
|
519
|
-
return null;
|
|
520
|
-
const u = bareUnion(names);
|
|
521
|
-
return new RegExp(`"(${u})"|'(${u})'|(?<![\\w-])(${u})(?![\\w-])`, "g");
|
|
522
|
-
}
|
|
523
|
-
function escapeRegex(s) {
|
|
524
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
525
|
-
}
|
|
526
|
-
/**
|
|
527
|
-
* Walk every top-level rule block `{ ... }` (recursing into nested at-rule contents) and
|
|
528
|
-
* pass the block body through `transform`. Inert spans are skipped so a `{` inside a string
|
|
529
|
-
* or `url(a{b)` is not mistaken for a block. At-rule preambles and inter-rule whitespace
|
|
530
|
-
* are left untouched.
|
|
531
|
-
*/
|
|
532
|
-
function mapRuleBlocks(css, transform) {
|
|
533
|
-
let out = "";
|
|
534
|
-
for (let i = 0; i < css.length;) {
|
|
535
|
-
const skip = skipInertSpan(css, i);
|
|
536
|
-
if (skip !== -1) {
|
|
537
|
-
out += css.slice(i, skip);
|
|
538
|
-
i = skip;
|
|
539
|
-
continue;
|
|
540
|
-
}
|
|
541
|
-
if (css[i] === "{") {
|
|
542
|
-
const blockEnd = findBlockEnd(css, i + 1);
|
|
543
|
-
const body = css.slice(i + 1, blockEnd);
|
|
544
|
-
const processed = hasTopLevelBrace(body) ? mapRuleBlocks(body, transform) : transform(body);
|
|
545
|
-
out += "{" + processed + "}";
|
|
546
|
-
i = blockEnd + 1;
|
|
144
|
+
scopedLines.push(line);
|
|
547
145
|
continue;
|
|
548
146
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
return out;
|
|
553
|
-
}
|
|
554
|
-
/** Inert-span-aware: true iff a real `{` appears outside comments/strings/url()/escapes. */
|
|
555
|
-
function hasTopLevelBrace(s) {
|
|
556
|
-
for (let i = 0; i < s.length;) {
|
|
557
|
-
const skip = skipInertSpan(s, i);
|
|
558
|
-
if (skip !== -1) {
|
|
559
|
-
i = skip;
|
|
147
|
+
if (trimmed === "}" && inMediaQuery) {
|
|
148
|
+
inMediaQuery = false;
|
|
149
|
+
scopedLines.push(line);
|
|
560
150
|
continue;
|
|
561
151
|
}
|
|
562
|
-
if (
|
|
563
|
-
|
|
564
|
-
i++;
|
|
565
|
-
}
|
|
566
|
-
return false;
|
|
567
|
-
}
|
|
568
|
-
/**
|
|
569
|
-
* Split a rule block body into `prop: value` declarations on top-level `;`, apply
|
|
570
|
-
* `transform(prop, value)` to each, and rejoin preserving separators. Inert spans are
|
|
571
|
-
* skipped so a `;` inside a string or `url(a;b)` doesn't split a declaration.
|
|
572
|
-
*/
|
|
573
|
-
function mapDeclarations(body, transform) {
|
|
574
|
-
const parts = [];
|
|
575
|
-
let start = 0;
|
|
576
|
-
let pDepth = 0;
|
|
577
|
-
for (let i = 0; i < body.length;) {
|
|
578
|
-
const skip = skipInertSpan(body, i);
|
|
579
|
-
if (skip !== -1) {
|
|
580
|
-
i = skip;
|
|
152
|
+
if (!trimmed.includes("{") || trimmed.startsWith("@")) {
|
|
153
|
+
scopedLines.push(line);
|
|
581
154
|
continue;
|
|
582
155
|
}
|
|
583
|
-
const
|
|
584
|
-
if (
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
start = i + 1;
|
|
156
|
+
const [selector, rest] = line.split("{");
|
|
157
|
+
if (selector && rest !== undefined) {
|
|
158
|
+
const scopedSelector = selector
|
|
159
|
+
.split(",")
|
|
160
|
+
.flatMap((s) => scopeClasses.map(cls => `.${cls} ${s.trim()}`))
|
|
161
|
+
.join(", ");
|
|
162
|
+
scopedLines.push(`${scopedSelector} {${rest}`);
|
|
591
163
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
parts.push(body.slice(start));
|
|
595
|
-
return parts
|
|
596
|
-
.map((part, idx) => {
|
|
597
|
-
const isLast = idx === parts.length - 1;
|
|
598
|
-
const colonIdx = findTopLevelColon(part);
|
|
599
|
-
if (colonIdx === -1)
|
|
600
|
-
return part + (isLast ? "" : ";");
|
|
601
|
-
const valueRaw = part.slice(colonIdx + 1);
|
|
602
|
-
const leading = valueRaw.match(/^\s*/)?.[0] ?? "";
|
|
603
|
-
const trailing = valueRaw.match(/\s*$/)?.[0] ?? "";
|
|
604
|
-
const prop = part.slice(0, colonIdx).trim();
|
|
605
|
-
const value = valueRaw.slice(leading.length, valueRaw.length - trailing.length);
|
|
606
|
-
const transformed = transform(prop, value);
|
|
607
|
-
return part.slice(0, colonIdx) + ":" + leading + transformed + trailing + (isLast ? "" : ";");
|
|
608
|
-
})
|
|
609
|
-
.join("");
|
|
610
|
-
}
|
|
611
|
-
function findTopLevelColon(s) {
|
|
612
|
-
let pDepth = 0;
|
|
613
|
-
for (let i = 0; i < s.length;) {
|
|
614
|
-
const skip = skipInertSpan(s, i);
|
|
615
|
-
if (skip !== -1) {
|
|
616
|
-
i = skip;
|
|
617
|
-
continue;
|
|
164
|
+
else {
|
|
165
|
+
scopedLines.push(line);
|
|
618
166
|
}
|
|
619
|
-
const c = s[i];
|
|
620
|
-
if (c === "(" || c === "[")
|
|
621
|
-
pDepth++;
|
|
622
|
-
else if (c === ")" || c === "]")
|
|
623
|
-
pDepth--;
|
|
624
|
-
else if (c === ":" && pDepth === 0)
|
|
625
|
-
return i;
|
|
626
|
-
i++;
|
|
627
167
|
}
|
|
628
|
-
return
|
|
168
|
+
return scopedLines.join("\n");
|
|
629
169
|
}
|
|
630
170
|
/**
|
|
631
171
|
* Common esbuild options shared between server and client builds
|