ripple 0.3.13 → 0.3.14

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.
Files changed (66) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/package.json +5 -30
  3. package/src/runtime/array.js +38 -38
  4. package/src/runtime/create-subscriber.js +2 -2
  5. package/src/runtime/internal/client/bindings.js +4 -6
  6. package/src/runtime/internal/client/events.js +8 -3
  7. package/src/runtime/internal/client/hmr.js +5 -17
  8. package/src/runtime/internal/client/runtime.js +1 -0
  9. package/src/runtime/internal/server/blocks.js +7 -9
  10. package/src/runtime/internal/server/index.js +14 -22
  11. package/src/runtime/media-query.js +34 -33
  12. package/src/runtime/object.js +7 -10
  13. package/src/runtime/proxy.js +2 -3
  14. package/src/runtime/reactive-value.js +23 -21
  15. package/src/utils/ast.js +1 -1
  16. package/src/utils/attributes.js +43 -0
  17. package/src/utils/builders.js +2 -2
  18. package/tests/client/basic/basic.errors.test.rsrx +1 -1
  19. package/tests/client/basic/basic.styling.test.rsrx +1 -1
  20. package/tests/client/compiler/compiler.assignments.test.rsrx +1 -1
  21. package/tests/client/compiler/compiler.attributes.test.rsrx +1 -1
  22. package/tests/client/compiler/compiler.basic.test.rsrx +13 -13
  23. package/tests/client/compiler/compiler.tracked-access.test.rsrx +1 -1
  24. package/tests/client/compiler/compiler.try-in-function.test.rsrx +1 -1
  25. package/tests/client/compiler/compiler.typescript.test.rsrx +1 -1
  26. package/tests/client/css/global-additional-cases.test.rsrx +1 -1
  27. package/tests/client/css/global-advanced-selectors.test.rsrx +1 -1
  28. package/tests/client/css/global-at-rules.test.rsrx +1 -1
  29. package/tests/client/css/global-basic.test.rsrx +1 -1
  30. package/tests/client/css/global-classes-ids.test.rsrx +1 -1
  31. package/tests/client/css/global-combinators.test.rsrx +1 -1
  32. package/tests/client/css/global-complex-nesting.test.rsrx +1 -1
  33. package/tests/client/css/global-edge-cases.test.rsrx +1 -1
  34. package/tests/client/css/global-keyframes.test.rsrx +1 -1
  35. package/tests/client/css/global-nested.test.rsrx +1 -1
  36. package/tests/client/css/global-pseudo.test.rsrx +1 -1
  37. package/tests/client/css/global-scoping.test.rsrx +1 -1
  38. package/tests/client/css/style-identifier.test.rsrx +1 -1
  39. package/tests/client/return.test.rsrx +1 -1
  40. package/tests/hydration/build-components.js +1 -1
  41. package/tests/server/style-identifier.test.rsrx +1 -1
  42. package/tests/setup-server.js +1 -1
  43. package/tests/utils/compiler-compat-config.test.js +1 -1
  44. package/src/compiler/comment-utils.js +0 -91
  45. package/src/compiler/errors.js +0 -77
  46. package/src/compiler/identifier-utils.js +0 -80
  47. package/src/compiler/index.d.ts +0 -127
  48. package/src/compiler/index.js +0 -89
  49. package/src/compiler/phases/1-parse/index.js +0 -3007
  50. package/src/compiler/phases/1-parse/style.js +0 -704
  51. package/src/compiler/phases/2-analyze/css-analyze.js +0 -160
  52. package/src/compiler/phases/2-analyze/index.js +0 -2208
  53. package/src/compiler/phases/2-analyze/prune.js +0 -1131
  54. package/src/compiler/phases/2-analyze/validation.js +0 -168
  55. package/src/compiler/phases/3-transform/client/index.js +0 -5264
  56. package/src/compiler/phases/3-transform/segments.js +0 -2125
  57. package/src/compiler/phases/3-transform/server/index.js +0 -1749
  58. package/src/compiler/phases/3-transform/stylesheet.js +0 -545
  59. package/src/compiler/scope.js +0 -476
  60. package/src/compiler/source-map-utils.js +0 -358
  61. package/src/compiler/types/acorn.d.ts +0 -11
  62. package/src/compiler/types/estree-jsx.d.ts +0 -11
  63. package/src/compiler/types/estree.d.ts +0 -11
  64. package/src/compiler/types/index.d.ts +0 -1411
  65. package/src/compiler/types/parse.d.ts +0 -1723
  66. 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
- }