ripple 0.3.13 → 0.3.15
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/CHANGELOG.md +35 -0
- package/package.json +5 -30
- package/src/runtime/array.js +38 -38
- package/src/runtime/create-subscriber.js +2 -2
- package/src/runtime/internal/client/bindings.js +4 -6
- package/src/runtime/internal/client/events.js +8 -3
- package/src/runtime/internal/client/hmr.js +5 -17
- package/src/runtime/internal/client/runtime.js +1 -0
- package/src/runtime/internal/server/blocks.js +7 -9
- package/src/runtime/internal/server/index.js +14 -22
- package/src/runtime/media-query.js +34 -33
- package/src/runtime/object.js +7 -10
- package/src/runtime/proxy.js +2 -3
- package/src/runtime/reactive-value.js +23 -21
- package/src/utils/ast.js +1 -1
- package/src/utils/attributes.js +43 -0
- package/src/utils/builders.js +2 -2
- package/tests/client/basic/basic.components.test.rsrx +103 -1
- package/tests/client/basic/basic.errors.test.rsrx +1 -1
- package/tests/client/basic/basic.styling.test.rsrx +1 -1
- package/tests/client/compiler/compiler.assignments.test.rsrx +1 -1
- package/tests/client/compiler/compiler.attributes.test.rsrx +1 -1
- package/tests/client/compiler/compiler.basic.test.rsrx +51 -14
- package/tests/client/compiler/compiler.tracked-access.test.rsrx +1 -1
- package/tests/client/compiler/compiler.try-in-function.test.rsrx +1 -1
- package/tests/client/compiler/compiler.typescript.test.rsrx +1 -1
- package/tests/client/css/global-additional-cases.test.rsrx +1 -1
- package/tests/client/css/global-advanced-selectors.test.rsrx +1 -1
- package/tests/client/css/global-at-rules.test.rsrx +1 -1
- package/tests/client/css/global-basic.test.rsrx +1 -1
- package/tests/client/css/global-classes-ids.test.rsrx +1 -1
- package/tests/client/css/global-combinators.test.rsrx +1 -1
- package/tests/client/css/global-complex-nesting.test.rsrx +1 -1
- package/tests/client/css/global-edge-cases.test.rsrx +1 -1
- package/tests/client/css/global-keyframes.test.rsrx +1 -1
- package/tests/client/css/global-nested.test.rsrx +1 -1
- package/tests/client/css/global-pseudo.test.rsrx +1 -1
- package/tests/client/css/global-scoping.test.rsrx +1 -1
- package/tests/client/css/style-identifier.test.rsrx +1 -1
- package/tests/client/return.test.rsrx +1 -1
- package/tests/hydration/build-components.js +1 -1
- package/tests/server/basic.components.test.rsrx +114 -0
- package/tests/server/compiler.test.rsrx +38 -1
- package/tests/server/style-identifier.test.rsrx +1 -1
- package/tests/setup-server.js +1 -1
- package/tests/utils/compiler-compat-config.test.js +1 -1
- package/types/index.d.ts +1 -1
- package/src/compiler/comment-utils.js +0 -91
- package/src/compiler/errors.js +0 -77
- package/src/compiler/identifier-utils.js +0 -80
- package/src/compiler/index.d.ts +0 -127
- package/src/compiler/index.js +0 -89
- package/src/compiler/phases/1-parse/index.js +0 -3007
- package/src/compiler/phases/1-parse/style.js +0 -704
- package/src/compiler/phases/2-analyze/css-analyze.js +0 -160
- package/src/compiler/phases/2-analyze/index.js +0 -2208
- package/src/compiler/phases/2-analyze/prune.js +0 -1131
- package/src/compiler/phases/2-analyze/validation.js +0 -168
- package/src/compiler/phases/3-transform/client/index.js +0 -5264
- package/src/compiler/phases/3-transform/segments.js +0 -2125
- package/src/compiler/phases/3-transform/server/index.js +0 -1749
- package/src/compiler/phases/3-transform/stylesheet.js +0 -545
- package/src/compiler/scope.js +0 -476
- package/src/compiler/source-map-utils.js +0 -358
- package/src/compiler/types/acorn.d.ts +0 -11
- package/src/compiler/types/estree-jsx.d.ts +0 -11
- package/src/compiler/types/estree.d.ts +0 -11
- package/src/compiler/types/index.d.ts +0 -1411
- package/src/compiler/types/parse.d.ts +0 -1723
- package/src/compiler/utils.js +0 -1258
|
@@ -1,545 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
@import * as AST from 'estree';
|
|
3
|
-
@import { Visitors } from '#compiler';
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
@typedef {{
|
|
8
|
-
code: MagicString;
|
|
9
|
-
hash: string;
|
|
10
|
-
minify: boolean;
|
|
11
|
-
selector: string;
|
|
12
|
-
keyframes: Record<string, {
|
|
13
|
-
indexes: number[];
|
|
14
|
-
local: boolean | undefined;
|
|
15
|
-
}>;
|
|
16
|
-
specificity: {
|
|
17
|
-
bumped: boolean
|
|
18
|
-
}
|
|
19
|
-
}} State
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import MagicString from 'magic-string';
|
|
23
|
-
import { walk } from 'zimmerframe';
|
|
24
|
-
|
|
25
|
-
const regex_css_browser_prefix = /^-((webkit)|(moz)|(o)|(ms))-/;
|
|
26
|
-
const regex_css_name_boundary = /^[\s,;}]$/;
|
|
27
|
-
|
|
28
|
-
/** @param {AST.CSS.Atrule} node */
|
|
29
|
-
const is_keyframes_node = (node) => remove_css_prefix(node.name) === 'keyframes';
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* @param {string} name
|
|
33
|
-
* @returns {string}
|
|
34
|
-
*/
|
|
35
|
-
function remove_css_prefix(name) {
|
|
36
|
-
return name.replace(regex_css_browser_prefix, '');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Walk backwards until we find a non-whitespace character
|
|
41
|
-
* @param {number} end
|
|
42
|
-
* @param {State} state
|
|
43
|
-
*/
|
|
44
|
-
function remove_preceding_whitespace(end, state) {
|
|
45
|
-
let start = end;
|
|
46
|
-
while (/\s/.test(state.code.original[start - 1])) start--;
|
|
47
|
-
if (start < end) state.code.remove(start, end);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** @param {AST.CSS.Rule} rule */
|
|
51
|
-
function is_used(rule) {
|
|
52
|
-
return rule.prelude.children.some((selector) => selector.metadata.used);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* @param {Array<AST.CSS.Node>} path
|
|
57
|
-
*/
|
|
58
|
-
function is_in_global_block(path) {
|
|
59
|
-
return path.some((node) => node.type === 'Rule' && node.metadata.is_global_block);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Check if we're inside a pseudo-class selector that's INSIDE a :global() wrapper
|
|
64
|
-
* or adjacent to a :global modifier
|
|
65
|
-
* @param {AST.CSS.Node[]} path
|
|
66
|
-
*/
|
|
67
|
-
function is_in_global_pseudo(path) {
|
|
68
|
-
// Walk up the path to find if we're inside a :global() pseudo-class selector with args
|
|
69
|
-
// or if we're in a pseudo-class that's in the same RelativeSelector as a :global modifier
|
|
70
|
-
for (let i = path.length - 1; i >= 0; i--) {
|
|
71
|
-
const node = path[i];
|
|
72
|
-
|
|
73
|
-
// Case 1: :global(...) with args - we're inside it
|
|
74
|
-
if (node.type === 'PseudoClassSelector' && node.name === 'global' && node.args !== null) {
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Case 2: We're in a PseudoClassSelector (like :is, :where, :has, :not)
|
|
79
|
-
// Check if there's a :global modifier in the same RelativeSelector
|
|
80
|
-
if (
|
|
81
|
-
node.type === 'PseudoClassSelector' &&
|
|
82
|
-
(node.name === 'is' || node.name === 'where' || node.name === 'has' || node.name === 'not')
|
|
83
|
-
) {
|
|
84
|
-
// Look for the parent RelativeSelector
|
|
85
|
-
for (let j = i - 1; j >= 0; j--) {
|
|
86
|
-
const ancestor = path[j];
|
|
87
|
-
if (ancestor.type === 'RelativeSelector') {
|
|
88
|
-
// Check if this RelativeSelector has a :global modifier (no args)
|
|
89
|
-
const hasGlobalModifier = ancestor.selectors.some(
|
|
90
|
-
(s) => s.type === 'PseudoClassSelector' && s.name === 'global' && s.args === null,
|
|
91
|
-
);
|
|
92
|
-
if (hasGlobalModifier) {
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Check if a rule has :global in the middle (like `div :global p`)
|
|
105
|
-
* These rules should treat nested selectors as global
|
|
106
|
-
* @param {AST.CSS.Rule} rule
|
|
107
|
-
*/
|
|
108
|
-
function has_global_in_middle(rule) {
|
|
109
|
-
for (const complex_selector of rule.prelude.children) {
|
|
110
|
-
for (let i = 0; i < complex_selector.children.length; i++) {
|
|
111
|
-
const child = complex_selector.children[i];
|
|
112
|
-
// Check if this is a :global selector that's not at the start
|
|
113
|
-
if (i > 0 && child.metadata.is_global) {
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* @param {AST.CSS.PseudoClassSelector} selector
|
|
123
|
-
* @param {AST.CSS.Combinator | null} combinator
|
|
124
|
-
* @param {State} state
|
|
125
|
-
*/
|
|
126
|
-
function remove_global_pseudo_class(selector, combinator, state) {
|
|
127
|
-
if (selector.args === null) {
|
|
128
|
-
let start = selector.start;
|
|
129
|
-
if (combinator?.name === ' ') {
|
|
130
|
-
// div :global.x becomes div.x
|
|
131
|
-
while (/\s/.test(state.code.original[start - 1])) start--;
|
|
132
|
-
}
|
|
133
|
-
state.code.remove(start, selector.start + ':global'.length);
|
|
134
|
-
} else {
|
|
135
|
-
state.code
|
|
136
|
-
.remove(selector.start, selector.start + ':global('.length)
|
|
137
|
-
.remove(selector.end - 1, selector.end);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* @param {AST.CSS.Rule} node
|
|
143
|
-
* @param {MagicString} code
|
|
144
|
-
*/
|
|
145
|
-
function escape_comment_close(node, code) {
|
|
146
|
-
let escaped = false;
|
|
147
|
-
let in_comment = false;
|
|
148
|
-
|
|
149
|
-
for (let i = node.start; i < node.end; i++) {
|
|
150
|
-
if (escaped) {
|
|
151
|
-
escaped = false;
|
|
152
|
-
} else {
|
|
153
|
-
const char = code.original[i];
|
|
154
|
-
if (in_comment) {
|
|
155
|
-
if (char === '*' && code.original[i + 1] === '/') {
|
|
156
|
-
code.prependRight(++i, '\\');
|
|
157
|
-
in_comment = false;
|
|
158
|
-
}
|
|
159
|
-
} else if (char === '\\') {
|
|
160
|
-
escaped = true;
|
|
161
|
-
} else if (char === '/' && code.original[++i] === '*') {
|
|
162
|
-
in_comment = true;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* @param {State} state
|
|
170
|
-
* @param {number} index
|
|
171
|
-
*/
|
|
172
|
-
function append_hash(state, index) {
|
|
173
|
-
state.code.prependRight(index, `${state.hash}-`);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* @param {AST.CSS.Rule} rule
|
|
178
|
-
* @param {boolean} is_in_global_block
|
|
179
|
-
*/
|
|
180
|
-
function is_empty(rule, is_in_global_block) {
|
|
181
|
-
if (rule.metadata.is_global_block) {
|
|
182
|
-
return rule.block.children.length === 0;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Rules with :global in the middle (like `div :global p`) should treat nested rules as global
|
|
186
|
-
const has_mid_global = has_global_in_middle(rule);
|
|
187
|
-
|
|
188
|
-
for (const child of rule.block.children) {
|
|
189
|
-
if (child.type === 'Declaration') {
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (child.type === 'Rule') {
|
|
194
|
-
if (
|
|
195
|
-
(is_used(child) || is_in_global_block || has_mid_global) &&
|
|
196
|
-
!is_empty(child, is_in_global_block || has_mid_global)
|
|
197
|
-
) {
|
|
198
|
-
return false;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (child.type === 'Atrule') {
|
|
203
|
-
if (child.block === null || child.block.children.length > 0) return false;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return true;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/** @type {Visitors<AST.CSS.Node, State>} */
|
|
211
|
-
const visitors = {
|
|
212
|
-
_: (node, context) => {
|
|
213
|
-
context.state.code.addSourcemapLocation(node.start);
|
|
214
|
-
context.state.code.addSourcemapLocation(node.end);
|
|
215
|
-
context.next();
|
|
216
|
-
},
|
|
217
|
-
Atrule(node, { state, next, path }) {
|
|
218
|
-
if (is_keyframes_node(node)) {
|
|
219
|
-
let start = node.start + node.name.length + 1;
|
|
220
|
-
while (state.code.original[start] === ' ') start += 1;
|
|
221
|
-
let end = start;
|
|
222
|
-
while (state.code.original[end] !== '{' && state.code.original[end] !== ' ') end += 1;
|
|
223
|
-
|
|
224
|
-
if (node.prelude.startsWith('-global-')) {
|
|
225
|
-
state.code.remove(start, start + 8);
|
|
226
|
-
} else if (!is_in_global_block(path)) {
|
|
227
|
-
append_hash(state, start);
|
|
228
|
-
state.keyframes[node.prelude]?.indexes.forEach((index) => append_hash(state, index));
|
|
229
|
-
state.keyframes[node.prelude] = { indexes: [], local: true };
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return; // don't transform anything within
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
next();
|
|
236
|
-
},
|
|
237
|
-
Declaration(node, { state }) {
|
|
238
|
-
const property = node.property && remove_css_prefix(node.property.toLowerCase());
|
|
239
|
-
if (property === 'animation' || property === 'animation-name') {
|
|
240
|
-
let index = node.start + node.property.length + 1;
|
|
241
|
-
/** @type {string} */
|
|
242
|
-
let name = '';
|
|
243
|
-
|
|
244
|
-
while (index < state.code.original.length) {
|
|
245
|
-
const character = state.code.original[index];
|
|
246
|
-
|
|
247
|
-
if (regex_css_name_boundary.test(character)) {
|
|
248
|
-
if (name) {
|
|
249
|
-
const append_index = index - name.length;
|
|
250
|
-
state.keyframes[name] ??= { indexes: [], local: undefined };
|
|
251
|
-
if (state.keyframes[name].local) {
|
|
252
|
-
append_hash(state, append_index);
|
|
253
|
-
} else {
|
|
254
|
-
state.keyframes[name].indexes.push(append_index);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (character === ';' || character === '}') {
|
|
259
|
-
break;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
name = '';
|
|
263
|
-
} else {
|
|
264
|
-
name += character;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
index++;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
},
|
|
271
|
-
Rule(node, { state, next, visit, path }) {
|
|
272
|
-
if (is_empty(node, is_in_global_block(path))) {
|
|
273
|
-
state.code.prependRight(node.start, '/* (empty) ');
|
|
274
|
-
state.code.appendLeft(node.end, '*/');
|
|
275
|
-
escape_comment_close(node, state.code);
|
|
276
|
-
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (!is_used(node) && !is_in_global_block(path)) {
|
|
281
|
-
state.code.prependRight(node.start, '/* (unused) ');
|
|
282
|
-
state.code.appendLeft(node.end, '*/');
|
|
283
|
-
escape_comment_close(node, state.code);
|
|
284
|
-
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (node.metadata.is_global_block) {
|
|
289
|
-
const selector = node.prelude.children[0];
|
|
290
|
-
|
|
291
|
-
if (selector.children.length === 1 && selector.children[0].selectors.length === 1) {
|
|
292
|
-
// `:global {...}`
|
|
293
|
-
if (state.minify) {
|
|
294
|
-
state.code.remove(node.start, node.block.start + 1);
|
|
295
|
-
state.code.remove(node.block.end - 1, node.end);
|
|
296
|
-
} else {
|
|
297
|
-
state.code.prependRight(node.start, '/* ');
|
|
298
|
-
state.code.appendLeft(node.block.start + 1, '*/');
|
|
299
|
-
|
|
300
|
-
state.code.prependRight(node.block.end - 1, '/*');
|
|
301
|
-
state.code.appendLeft(node.block.end, '*/');
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// don't recurse into selectors but visit the body
|
|
305
|
-
visit(node.block);
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
next();
|
|
311
|
-
},
|
|
312
|
-
SelectorList(node, { state, next, path }) {
|
|
313
|
-
// Only add comments if we're not inside a complex selector that itself is unused or a global block
|
|
314
|
-
// or inside a pseudo-class that's part of a global selector
|
|
315
|
-
if (
|
|
316
|
-
!is_in_global_block(path) &&
|
|
317
|
-
!is_in_global_pseudo(path) &&
|
|
318
|
-
!path.find((n) => n.type === 'ComplexSelector' && !n.metadata.used)
|
|
319
|
-
) {
|
|
320
|
-
const children = node.children;
|
|
321
|
-
let pruning = false;
|
|
322
|
-
let prune_start = children[0].start;
|
|
323
|
-
let last = prune_start;
|
|
324
|
-
let has_previous_used = false;
|
|
325
|
-
|
|
326
|
-
for (let i = 0; i < children.length; i += 1) {
|
|
327
|
-
const selector = children[i];
|
|
328
|
-
|
|
329
|
-
if (selector.metadata.used === pruning) {
|
|
330
|
-
if (pruning) {
|
|
331
|
-
let i = selector.start;
|
|
332
|
-
while (state.code.original[i] !== ',') i--;
|
|
333
|
-
|
|
334
|
-
if (state.minify) {
|
|
335
|
-
state.code.remove(prune_start, has_previous_used ? i : i + 1);
|
|
336
|
-
} else {
|
|
337
|
-
state.code.appendRight(has_previous_used ? i : i + 1, '*/');
|
|
338
|
-
}
|
|
339
|
-
} else {
|
|
340
|
-
if (i === 0) {
|
|
341
|
-
if (state.minify) {
|
|
342
|
-
prune_start = selector.start;
|
|
343
|
-
} else {
|
|
344
|
-
state.code.prependRight(selector.start, '/* (unused) ');
|
|
345
|
-
}
|
|
346
|
-
} else {
|
|
347
|
-
if (state.minify) {
|
|
348
|
-
prune_start = last;
|
|
349
|
-
} else {
|
|
350
|
-
state.code.overwrite(last, selector.start, ` /* (unused) `);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
pruning = !pruning;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
if (!pruning && selector.metadata.used) {
|
|
359
|
-
has_previous_used = true;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
last = selector.end;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (pruning) {
|
|
366
|
-
if (state.minify) {
|
|
367
|
-
state.code.remove(prune_start, last);
|
|
368
|
-
} else {
|
|
369
|
-
state.code.appendLeft(last, '*/');
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// if we're in a `:is(...)` or whatever, keep existing specificity bump state
|
|
375
|
-
let specificity = state.specificity;
|
|
376
|
-
|
|
377
|
-
// if this selector list belongs to a rule, require a specificity bump for the
|
|
378
|
-
// first scoped selector but only if we're at the top level
|
|
379
|
-
let parent = path.at(-1);
|
|
380
|
-
if (parent?.type === 'Rule') {
|
|
381
|
-
specificity = { bumped: false };
|
|
382
|
-
|
|
383
|
-
/** @type {AST.CSS.Rule | null} */
|
|
384
|
-
let rule = parent.metadata.parent_rule;
|
|
385
|
-
|
|
386
|
-
while (rule) {
|
|
387
|
-
if (rule.metadata.has_local_selectors) {
|
|
388
|
-
specificity = { bumped: true };
|
|
389
|
-
break;
|
|
390
|
-
}
|
|
391
|
-
rule = rule.metadata.parent_rule;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
next({ ...state, specificity });
|
|
396
|
-
},
|
|
397
|
-
ComplexSelector(node, context) {
|
|
398
|
-
const before_bumped = context.state.specificity.bumped;
|
|
399
|
-
|
|
400
|
-
// Check if we're inside a :has/:is/:where/:not pseudo-class that's part of a global selector
|
|
401
|
-
// In that case, we should still scope the contents even though the parent is global
|
|
402
|
-
const parentPath = context.path;
|
|
403
|
-
let insideScopingPseudo = false;
|
|
404
|
-
|
|
405
|
-
// Walk up the path to find if we're inside args of :has/:is/:where/:not
|
|
406
|
-
for (let i = parentPath.length - 1; i >= 0; i--) {
|
|
407
|
-
const pathNode = parentPath[i];
|
|
408
|
-
|
|
409
|
-
// Check if we're inside a SelectorList that belongs to a scoping pseudo-class
|
|
410
|
-
if (pathNode.type === 'SelectorList' && i > 0) {
|
|
411
|
-
const parent = parentPath[i - 1];
|
|
412
|
-
if (
|
|
413
|
-
parent.type === 'PseudoClassSelector' &&
|
|
414
|
-
(parent.name === 'has' ||
|
|
415
|
-
parent.name === 'is' ||
|
|
416
|
-
parent.name === 'where' ||
|
|
417
|
-
parent.name === 'not')
|
|
418
|
-
) {
|
|
419
|
-
// Now check if this pseudo-class is part of a global RelativeSelector
|
|
420
|
-
for (let j = i - 2; j >= 0; j--) {
|
|
421
|
-
if (
|
|
422
|
-
parentPath[j].type === 'RelativeSelector' &&
|
|
423
|
-
/** @type {AST.CSS.RelativeSelector} */ (parentPath[j]).metadata?.is_global
|
|
424
|
-
) {
|
|
425
|
-
insideScopingPseudo = true;
|
|
426
|
-
break;
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
break;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
for (const relative_selector of node.children) {
|
|
435
|
-
if (relative_selector.metadata.is_global && !insideScopingPseudo) {
|
|
436
|
-
const global = /** @type {AST.CSS.PseudoClassSelector} */ (relative_selector.selectors[0]);
|
|
437
|
-
remove_global_pseudo_class(global, relative_selector.combinator, context.state);
|
|
438
|
-
|
|
439
|
-
if (
|
|
440
|
-
node.metadata.rule?.metadata.parent_rule &&
|
|
441
|
-
global.args === null &&
|
|
442
|
-
relative_selector.combinator === null
|
|
443
|
-
) {
|
|
444
|
-
// div { :global.x { ... } } becomes div { &.x { ... } }
|
|
445
|
-
context.state.code.prependRight(global.start, '&');
|
|
446
|
-
}
|
|
447
|
-
continue;
|
|
448
|
-
} else {
|
|
449
|
-
// for any :global() or :global at the middle of compound selector
|
|
450
|
-
for (const selector of relative_selector.selectors) {
|
|
451
|
-
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
|
|
452
|
-
remove_global_pseudo_class(selector, null, context.state);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// Skip scoping if we're inside a global block
|
|
458
|
-
if (relative_selector.metadata.scoped && !is_in_global_block(context.path)) {
|
|
459
|
-
if (relative_selector.selectors.length === 1) {
|
|
460
|
-
// skip standalone :is/:where/& selectors
|
|
461
|
-
const selector = relative_selector.selectors[0];
|
|
462
|
-
if (
|
|
463
|
-
selector.type === 'PseudoClassSelector' &&
|
|
464
|
-
(selector.name === 'is' || selector.name === 'where')
|
|
465
|
-
) {
|
|
466
|
-
continue;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
if (relative_selector.selectors.some((s) => s.type === 'NestingSelector')) {
|
|
471
|
-
continue;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
// for the first occurrence, we use a classname selector, so that every
|
|
475
|
-
// encapsulated selector gets a +0-1-0 specificity bump. thereafter,
|
|
476
|
-
// we use a `:where` selector, which does not affect specificity
|
|
477
|
-
let modifier = context.state.selector;
|
|
478
|
-
if (context.state.specificity.bumped) modifier = `:where(${modifier})`;
|
|
479
|
-
|
|
480
|
-
context.state.specificity.bumped = true;
|
|
481
|
-
|
|
482
|
-
let i = relative_selector.selectors.length;
|
|
483
|
-
while (i--) {
|
|
484
|
-
const selector = relative_selector.selectors[i];
|
|
485
|
-
|
|
486
|
-
if (
|
|
487
|
-
selector.type === 'PseudoElementSelector' ||
|
|
488
|
-
selector.type === 'PseudoClassSelector'
|
|
489
|
-
) {
|
|
490
|
-
if (selector.name !== 'root' && selector.name !== 'host') {
|
|
491
|
-
if (i === 0) context.state.code.prependRight(selector.start, modifier);
|
|
492
|
-
}
|
|
493
|
-
continue;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
if (selector.type === 'TypeSelector' && selector.name === '*') {
|
|
497
|
-
context.state.code.update(selector.start, selector.end, modifier);
|
|
498
|
-
} else {
|
|
499
|
-
context.state.code.appendLeft(selector.end, modifier);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
break;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
context.next();
|
|
508
|
-
|
|
509
|
-
context.state.specificity.bumped = before_bumped;
|
|
510
|
-
},
|
|
511
|
-
PseudoClassSelector(node, context) {
|
|
512
|
-
if (node.name === 'is' || node.name === 'where' || node.name === 'has' || node.name === 'not') {
|
|
513
|
-
context.next();
|
|
514
|
-
}
|
|
515
|
-
},
|
|
516
|
-
};
|
|
517
|
-
|
|
518
|
-
/**
|
|
519
|
-
* Render stylesheets to CSS string
|
|
520
|
-
* @param {AST.CSS.StyleSheet[]} stylesheets
|
|
521
|
-
* @param {boolean} [minify]
|
|
522
|
-
* @returns {string}
|
|
523
|
-
*/
|
|
524
|
-
export function render_stylesheets(stylesheets, minify = false) {
|
|
525
|
-
let css = '';
|
|
526
|
-
|
|
527
|
-
for (const stylesheet of stylesheets) {
|
|
528
|
-
const code = new MagicString(stylesheet.source);
|
|
529
|
-
const state = {
|
|
530
|
-
code,
|
|
531
|
-
hash: stylesheet.hash,
|
|
532
|
-
minify,
|
|
533
|
-
selector: `.${stylesheet.hash}`,
|
|
534
|
-
keyframes: {},
|
|
535
|
-
specificity: {
|
|
536
|
-
bumped: false,
|
|
537
|
-
},
|
|
538
|
-
};
|
|
539
|
-
|
|
540
|
-
walk(stylesheet, state, visitors);
|
|
541
|
-
css += code.toString();
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
return css;
|
|
545
|
-
}
|