ripple 0.4.2 → 0.4.4

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 (98) hide show
  1. package/CHANGELOG.md +164 -0
  2. package/package.json +10 -4
  3. package/src/constants.js +6 -0
  4. package/src/jsx-runtime.d.ts +2 -1
  5. package/src/runtime/array.js +15 -9
  6. package/src/runtime/index-client.js +25 -7
  7. package/src/runtime/index-server.js +1 -0
  8. package/src/runtime/internal/client/attributes.js +439 -0
  9. package/src/runtime/internal/client/blocks.js +20 -20
  10. package/src/runtime/internal/client/component.js +2 -5
  11. package/src/runtime/internal/client/composite.js +295 -95
  12. package/src/runtime/internal/client/constants.js +14 -6
  13. package/src/runtime/internal/client/context.js +3 -2
  14. package/src/runtime/internal/client/css.js +11 -26
  15. package/src/runtime/internal/client/errors.js +188 -0
  16. package/src/runtime/internal/client/events.js +79 -34
  17. package/src/runtime/internal/client/expression.js +25 -120
  18. package/src/runtime/internal/client/for.js +113 -262
  19. package/src/runtime/internal/client/head.js +4 -3
  20. package/src/runtime/internal/client/html.js +5 -3
  21. package/src/runtime/internal/client/hydrate.js +484 -0
  22. package/src/runtime/internal/client/hydration-enabled.js +7 -0
  23. package/src/runtime/internal/client/hydration.js +16 -81
  24. package/src/runtime/internal/client/if.js +69 -10
  25. package/src/runtime/internal/client/index.js +18 -10
  26. package/src/runtime/internal/client/operations.js +11 -7
  27. package/src/runtime/internal/client/portal.js +3 -2
  28. package/src/runtime/internal/client/render.js +42 -325
  29. package/src/runtime/internal/client/root-boundary-enabled.js +7 -0
  30. package/src/runtime/internal/client/runtime.js +157 -371
  31. package/src/runtime/internal/client/template-ns.js +88 -0
  32. package/src/runtime/internal/client/template.js +118 -139
  33. package/src/runtime/internal/client/track-async.js +356 -0
  34. package/src/runtime/internal/client/try.js +133 -319
  35. package/src/runtime/internal/client/types.d.ts +48 -24
  36. package/src/runtime/internal/server/index.js +31 -29
  37. package/src/runtime/internal/server/prerender.js +27 -0
  38. package/src/server/index.js +1 -0
  39. package/src/utils/class-name.js +28 -0
  40. package/tests/client/basic/basic.errors.test.tsrx +3 -3
  41. package/tests/client/compiler/__snapshots__/compiler.typescript.test.tsrx.snap +0 -2
  42. package/tests/client/compiler/compiler.basic.test.tsrx +2 -2
  43. package/tests/client/compiler/compiler.tracked-access.test.tsrx +3 -3
  44. package/tests/client/composite/composite.dynamic-components.test.tsrx +40 -0
  45. package/tests/client/composite/composite.props.test.tsrx +10 -10
  46. package/tests/client/css/scoped-styles.test.tsrx +3 -1
  47. package/tests/client/derived-release.test.tsrx +3 -3
  48. package/tests/client/for-item.test.tsrx +39 -0
  49. package/tests/client/for-selector.test.tsrx +95 -0
  50. package/tests/client/svg.test.tsrx +273 -1
  51. package/tests/hydration/compiled/client/basic.js +88 -87
  52. package/tests/hydration/compiled/client/composite.js +15 -6
  53. package/tests/hydration/compiled/client/events.js +40 -48
  54. package/tests/hydration/compiled/client/for.js +191 -199
  55. package/tests/hydration/compiled/client/fragment-cursor.js +633 -334
  56. package/tests/hydration/compiled/client/head.js +22 -22
  57. package/tests/hydration/compiled/client/hmr.js +14 -5
  58. package/tests/hydration/compiled/client/html-in-template.js +5 -5
  59. package/tests/hydration/compiled/client/html.js +195 -177
  60. package/tests/hydration/compiled/client/if-children.js +136 -63
  61. package/tests/hydration/compiled/client/if-fragment-controlflow.js +63 -43
  62. package/tests/hydration/compiled/client/if.js +78 -30
  63. package/tests/hydration/compiled/client/mixed-control-flow.js +90 -72
  64. package/tests/hydration/compiled/client/nested-control-flow.js +266 -307
  65. package/tests/hydration/compiled/client/portal.js +20 -11
  66. package/tests/hydration/compiled/client/reactivity.js +29 -29
  67. package/tests/hydration/compiled/client/return.js +2 -2
  68. package/tests/hydration/compiled/client/streaming.js +33 -35
  69. package/tests/hydration/compiled/client/switch.js +25 -25
  70. package/tests/hydration/compiled/client/track-async-serialization.js +41 -41
  71. package/tests/hydration/compiled/client/try.js +15 -15
  72. package/tests/hydration/compiled/client/typed-text.js +8 -7
  73. package/tests/hydration/compiled/server/basic.js +6 -6
  74. package/tests/hydration/compiled/server/events.js +8 -8
  75. package/tests/hydration/compiled/server/for.js +22 -22
  76. package/tests/hydration/compiled/server/fragment-cursor.js +188 -52
  77. package/tests/hydration/compiled/server/head.js +6 -6
  78. package/tests/hydration/compiled/server/hmr.js +1 -1
  79. package/tests/hydration/compiled/server/html.js +1 -1
  80. package/tests/hydration/compiled/server/if-children.js +9 -9
  81. package/tests/hydration/compiled/server/if.js +5 -5
  82. package/tests/hydration/compiled/server/mixed-control-flow.js +4 -4
  83. package/tests/hydration/compiled/server/portal.js +1 -1
  84. package/tests/hydration/compiled/server/reactivity.js +9 -9
  85. package/tests/hydration/compiled/server/streaming.js +9 -9
  86. package/tests/hydration/compiled/server/switch.js +4 -4
  87. package/tests/hydration/compiled/server/track-async-serialization.js +16 -16
  88. package/tests/hydration/compiled/server/try.js +4 -4
  89. package/tests/hydration/compiled/server/typed-text.js +1 -1
  90. package/tests/hydration/components/fragment-cursor.tsrx +60 -0
  91. package/tests/hydration/components/track-async-serialization.tsrx +2 -2
  92. package/tests/hydration/fragment-cursor.test.js +34 -1
  93. package/tests/hydration/track-async-serialization.test.js +1 -1
  94. package/tests/server/basic.components.test.tsrx +3 -3
  95. package/tests/server/track-async-serialization.test.tsrx +6 -6
  96. package/tests/utils/tracked-types.test.js +3 -3
  97. package/types/index.d.ts +8 -8
  98. package/types/server.d.ts +17 -0
@@ -0,0 +1,88 @@
1
+ /** @import { NAMESPACE_URI } from './constants.js' */
2
+ import { get_first_child } from './operations.js';
3
+ import { render_component, set_ns, with_ns as run_with_ns } from './runtime.js';
4
+ import { set_ns_parser, template } from './template.js';
5
+
6
+ /**
7
+ * Create fragment with proper namespace using Svelte's wrapping approach
8
+ * @param {string} content
9
+ * @param {'svg' | 'math'} ns
10
+ * @returns {DocumentFragment}
11
+ */
12
+ function from_namespace(content, ns) {
13
+ var wrapped = `<${ns}>${content}</${ns}>`;
14
+
15
+ var elem = document.createElement('template');
16
+ elem.innerHTML = wrapped;
17
+ var fragment = elem.content;
18
+
19
+ var root = /** @type {Element} */ (get_first_child(fragment));
20
+ var result = document.createDocumentFragment();
21
+
22
+ var first;
23
+ while ((first = get_first_child(root))) {
24
+ result.appendChild(/** @type {Node} */ (first));
25
+ }
26
+
27
+ return result;
28
+ }
29
+
30
+ /**
31
+ * Runs `fn` with `namespace` active: content rendered inside it (children
32
+ * passed into an `<svg>`, a component rendered there, a dynamic element's
33
+ * children) creates its DOM in that namespace, and every block created
34
+ * inside records it (see `create_block`) so its reruns do too. Templates
35
+ * cloned in the namespace parse through `from_namespace`, so it is installed
36
+ * here: the compiler only emits this call for the SVG and MathML namespaces.
37
+ * @template T
38
+ * @param {keyof typeof NAMESPACE_URI} namespace
39
+ * @param {() => T} fn
40
+ * @returns {T}
41
+ */
42
+ export function with_ns(namespace, fn) {
43
+ set_ns_parser(from_namespace);
44
+ return run_with_ns(namespace, fn);
45
+ }
46
+
47
+ /**
48
+ * `template()` for content compiled in the SVG or MathML namespace.
49
+ * @param {string} content
50
+ * @param {number} [flags]
51
+ * @param {number} [count]
52
+ * @returns {() => Node}
53
+ */
54
+ export function template_ns(content, flags, count) {
55
+ set_ns_parser(from_namespace);
56
+ return template(content, flags, count);
57
+ }
58
+
59
+ /**
60
+ * Enters `namespace` for the rest of a setup function, as `with_ns` does for
61
+ * a thunk: the compiler emits this where the content that follows is a
62
+ * dynamic element or a component called inside an `<svg>` or `<math>`
63
+ * template. Returns the namespace to restore with `set_ns`.
64
+ * @param {keyof typeof NAMESPACE_URI} namespace
65
+ * @returns {keyof typeof NAMESPACE_URI}
66
+ */
67
+ export function push_ns(namespace) {
68
+ set_ns_parser(from_namespace);
69
+ return set_ns(namespace);
70
+ }
71
+
72
+ /**
73
+ * `render_component` for a component called inside an `<svg>` or `<math>`
74
+ * template: its content renders in that namespace.
75
+ * @param {keyof typeof NAMESPACE_URI} namespace
76
+ * @param {Function} fn
77
+ * @param {Node | import('#client').AppendIntoAnchor} anchor
78
+ * @param {Record<string, any>} props
79
+ * @returns {void}
80
+ */
81
+ export function render_component_ns(namespace, fn, anchor, props) {
82
+ var previous = push_ns(namespace);
83
+ try {
84
+ render_component(fn, anchor, props);
85
+ } finally {
86
+ set_ns(previous);
87
+ }
88
+ }
@@ -6,9 +6,11 @@ import {
6
6
  TEMPLATE_SVG_NAMESPACE,
7
7
  TEMPLATE_MATHML_NAMESPACE,
8
8
  } from '../../../constants.js';
9
- import { hydrate_append, hydrate_node, hydrating } from './hydration.js';
10
- import { create_text, get_first_child, get_next_sibling, is_firefox } from './operations.js';
9
+ import { H, hydrate_node, hydrating } from './hydration.js';
10
+ import { create_text, get_first_child, is_firefox } from './operations.js';
11
11
  import { active_block, active_namespace } from './runtime.js';
12
+ import { DEFAULT_NAMESPACE, NAMESPACE_URI } from './constants.js';
13
+ import { HYDRATION } from 'ripple/internal/client/hydration-enabled';
12
14
 
13
15
  /**
14
16
  * Assigns start and end nodes to the active block's state.
@@ -30,92 +32,46 @@ export function assign_nodes(start, end) {
30
32
  }
31
33
 
32
34
  /**
33
- * Creates a DocumentFragment from an HTML string.
34
- * @param {string} html - The HTML string.
35
- * @param {boolean} use_svg_namespace - Whether to use SVG namespace.
36
- * @param {boolean} use_mathml_namespace - Whether to use MathML namespace.
37
- * @returns {DocumentFragment}
35
+ * Parses content that belongs in the SVG or MathML namespace. Installed by
36
+ * `template-ns.js` when a template or a dynamic element needs it, so an app
37
+ * with HTML templates only ships neither.
38
+ * @type {((html: string, ns: 'svg' | 'math') => DocumentFragment) | null}
38
39
  */
39
- export function create_fragment_from_html(
40
- html,
41
- use_svg_namespace = false,
42
- use_mathml_namespace = false,
43
- ) {
44
- if (use_svg_namespace) {
45
- return from_namespace(html, 'svg');
46
- }
47
- if (use_mathml_namespace) {
48
- return from_namespace(html, 'math');
49
- }
50
- var elem = document.createElement('template');
51
- elem.innerHTML = html;
52
- return elem.content;
53
- }
40
+ var parse_ns = null;
54
41
 
55
42
  /**
56
- * The hydration path of a {@link template} instance: adopts the server node(s)
57
- * at the cursor instead of cloning. Kept out of the clone path so a client-only
58
- * mount never compiles it.
59
- * @param {boolean} is_fragment
60
- * @param {number} count
61
- * @returns {Node}
43
+ * @param {(html: string, ns: 'svg' | 'math') => DocumentFragment} fn
62
44
  */
63
- function hydrate_template(is_fragment, count) {
64
- var node = /** @type {Node} */ (hydrate_node);
65
- var end = is_fragment ? hydrate_fragment_end(node, count) : node;
66
- // assign_nodes, inline: the per-node hydration path stays free of helpers.
67
- var block = /** @type {Block} */ (active_block);
68
- var s = block.s;
69
- if (s === null) {
70
- block.s = { start: node, end };
71
- } else if (s.start === null) {
72
- s.start = node;
73
- s.end = end;
74
- }
75
- return node;
45
+ export function set_ns_parser(fn) {
46
+ parse_ns = fn;
76
47
  }
77
48
 
78
49
  /**
79
- * The last top-level node of a hydrated fragment template. Walks using the
80
- * compiler-provided hop count so hydration never parses template HTML.
81
- * @param {Node} start
82
- * @param {number} count
83
- * @returns {Node}
50
+ * Creates a DocumentFragment from an HTML string.
51
+ * @param {string} html - The HTML string.
52
+ * @param {string} ns - `'svg'`, `'math'`, or `''` for HTML.
53
+ * @returns {DocumentFragment}
84
54
  */
85
- function hydrate_fragment_end(start, count) {
86
- var end = start;
87
-
88
- for (var i = 1; i < count; i++) {
89
- var next = get_next_sibling(end);
90
-
91
- while (next !== null && next.nodeType === Node.COMMENT_NODE) {
92
- next = get_next_sibling(next);
93
- }
94
-
95
- if (next === null) {
96
- break;
97
- }
98
-
99
- end = next;
55
+ export function create_fragment_from_html(html, ns = '') {
56
+ if (ns !== '') {
57
+ return /** @type {NonNullable<typeof parse_ns>} */ (parse_ns)(
58
+ html,
59
+ /** @type {'svg' | 'math'} */ (ns),
60
+ );
100
61
  }
101
-
102
- return end;
62
+ var elem = document.createElement('template');
63
+ elem.innerHTML = html;
64
+ return elem.content;
103
65
  }
104
66
 
105
67
  /**
106
- * A template's parsed content and the flags that shaped it, shared by every
107
- * clone of one `template()` instance.
68
+ * A template's content and flags, shared by every clone of one `template()`
69
+ * instance, with the parsed node cached for the namespace it was parsed in.
108
70
  * @typedef {{
109
- * content: string;
110
- * is_fragment: boolean;
111
- * use_import_node: boolean;
112
- * use_svg_namespace: boolean;
113
- * use_mathml_namespace: boolean;
114
- * is_comment: boolean;
115
- * has_start: boolean;
116
- * node: Node | DocumentFragment | undefined;
117
- * node_svg: boolean;
118
- * node_mathml: boolean;
71
+ * c: string;
72
+ * f: number;
73
+ * n: Node | DocumentFragment | undefined;
74
+ * ns: string | undefined;
119
75
  * }} TemplateState
120
76
  */
121
77
 
@@ -126,24 +82,33 @@ function hydrate_fragment_end(start, count) {
126
82
  * @returns {Node}
127
83
  */
128
84
  function clone_template(t) {
129
- var is_fragment = t.is_fragment;
130
- var is_comment = t.is_comment;
131
- var node = t.node;
132
- // If using runtime namespace, check active_namespace
133
- var svg = !is_comment && (t.use_svg_namespace || active_namespace === 'svg');
134
- var mathml = !is_comment && (t.use_mathml_namespace || active_namespace === 'mathml');
135
-
136
- if (node === undefined || t.node_svg !== svg || t.node_mathml !== mathml) {
137
- node = create_fragment_from_html(t.has_start ? t.content : '<!>' + t.content, svg, mathml);
85
+ var flags = t.f;
86
+ var content = t.c;
87
+ var is_fragment = (flags & TEMPLATE_FRAGMENT) !== 0;
88
+ // The namespace the content parses in: a static one from the flags, else
89
+ // the active namespace (a comment placeholder needs none).
90
+ var ns =
91
+ content === '<!>'
92
+ ? ''
93
+ : (flags & TEMPLATE_SVG_NAMESPACE) !== 0 || active_namespace === 'svg'
94
+ ? 'svg'
95
+ : (flags & TEMPLATE_MATHML_NAMESPACE) !== 0 || active_namespace === 'mathml'
96
+ ? 'math'
97
+ : '';
98
+ var node = t.n;
99
+
100
+ if (node === undefined || t.ns !== ns) {
101
+ // Content that opens with a placeholder comment gets one more in
102
+ // front, so the first child the compiler navigates to is the element.
103
+ node = create_fragment_from_html(content.startsWith('<!>') ? '<!>' + content : content, ns);
138
104
  if (!is_fragment) node = /** @type {Node} */ (get_first_child(node));
139
- t.node = node;
140
- t.node_svg = svg;
141
- t.node_mathml = mathml;
105
+ t.n = node;
106
+ t.ns = ns;
142
107
  }
143
108
 
144
109
  /** @type {DocumentFragment | Node} */
145
110
  var clone =
146
- t.use_import_node || is_firefox
111
+ (flags & TEMPLATE_USE_IMPORT_NODE) !== 0 || is_firefox
147
112
  ? document.importNode(/** @type {Node} */ (node), true)
148
113
  : /** @type {Node} */ (node).cloneNode(true);
149
114
 
@@ -155,15 +120,7 @@ function clone_template(t) {
155
120
  end = /** @type {Node} */ (/** @type {DocumentFragment} */ (clone).lastChild);
156
121
  }
157
122
 
158
- // assign_nodes, inline: every template clone records its range.
159
- var block = /** @type {Block} */ (active_block);
160
- var s = block.s;
161
- if (s === null) {
162
- block.s = { start, end };
163
- } else if (s.start === null) {
164
- s.start = start;
165
- s.end = end;
166
- }
123
+ assign_nodes(start, end);
167
124
 
168
125
  return clone;
169
126
  }
@@ -175,24 +132,70 @@ function clone_template(t) {
175
132
  * @param {number} [count] - Pre-calculated count of top-level nodes (for fragments). When provided, avoids runtime parsing.
176
133
  * @returns {() => Node}
177
134
  */
178
- export function template(content, flags, count = 1) {
135
+ export function template(content, flags = 0, count = 1) {
179
136
  var is_fragment = (flags & TEMPLATE_FRAGMENT) !== 0;
180
- var is_comment = content === '<!>';
181
137
  /** @type {TemplateState} */
182
- var t = {
183
- content,
184
- is_fragment,
185
- use_import_node: (flags & TEMPLATE_USE_IMPORT_NODE) !== 0,
186
- use_svg_namespace: (flags & TEMPLATE_SVG_NAMESPACE) !== 0,
187
- use_mathml_namespace: (flags & TEMPLATE_MATHML_NAMESPACE) !== 0,
188
- is_comment,
189
- has_start: !is_comment && !content.startsWith('<!>'),
190
- node: undefined,
191
- node_svg: false,
192
- node_mathml: false,
193
- };
138
+ var t = { c: content, f: flags, n: undefined, ns: undefined };
194
139
 
195
- return () => (hydrating ? hydrate_template(is_fragment, count) : clone_template(t));
140
+ return () =>
141
+ HYDRATION && hydrating
142
+ ? /** @type {import('./hydrate.js').HydrationRuntime} */ (H).t(is_fragment, count)
143
+ : clone_template(t);
144
+ }
145
+
146
+ /**
147
+ * The document that owns parsed template content: it has no browsing
148
+ * context, so an element created in it does not load its `src` (an image,
149
+ * a video) until a clone is adopted into the page, exactly like a node
150
+ * parsed from a `<template>`.
151
+ * @type {Document | undefined}
152
+ */
153
+ var inert_document;
154
+
155
+ /**
156
+ * A template that is one HTML element with static attributes and at most one
157
+ * text child, built with DOM calls instead of parsed: a `<template>` parse
158
+ * has a fixed cost that dwarfs the element itself, and an app's first render
159
+ * pays it once per distinct template. The compiler decides which templates
160
+ * qualify (`template_el` in the client transform); every other shape still
161
+ * parses. The element is created in the inert template document and in the
162
+ * active namespace, as a parsed template would be.
163
+ * @param {string} tag
164
+ * @param {string[] | null} [attributes] - flat name/value pairs
165
+ * @param {string} [text]
166
+ * @returns {() => Node}
167
+ */
168
+ export function template_el(tag, attributes = null, text = '') {
169
+ /** @type {Element | undefined} */
170
+ var node;
171
+ /** @type {string | undefined} */
172
+ var node_ns;
173
+
174
+ return () => {
175
+ if (HYDRATION && hydrating) {
176
+ return /** @type {import('./hydrate.js').HydrationRuntime} */ (H).t(false, 1);
177
+ }
178
+ var ns = active_namespace;
179
+ if (node === undefined || node_ns !== ns) {
180
+ var doc = (inert_document ??= document.createElement('template').content.ownerDocument);
181
+ node =
182
+ ns === DEFAULT_NAMESPACE
183
+ ? doc.createElement(tag)
184
+ : doc.createElementNS(NAMESPACE_URI[ns], tag);
185
+ if (attributes !== null) {
186
+ for (var i = 0; i < attributes.length; i += 2) {
187
+ node.setAttribute(attributes[i], attributes[i + 1]);
188
+ }
189
+ }
190
+ if (text !== '') {
191
+ node.textContent = text;
192
+ }
193
+ node_ns = ns;
194
+ }
195
+ var clone = is_firefox ? document.importNode(node, true) : node.cloneNode(true);
196
+ assign_nodes(clone, clone);
197
+ return clone;
198
+ };
196
199
  }
197
200
 
198
201
  /**
@@ -201,8 +204,8 @@ export function template(content, flags, count = 1) {
201
204
  * @param {Node} dom - The DOM node to append.
202
205
  */
203
206
  export function append(anchor, dom) {
204
- if (hydrating) {
205
- hydrate_append(anchor, dom);
207
+ if (HYDRATION && hydrating) {
208
+ /** @type {import('./hydrate.js').HydrationRuntime} */ (H).a(anchor, dom);
206
209
  return;
207
210
  }
208
211
  if (/** @type {AppendIntoAnchor} */ (anchor).into === true) {
@@ -217,7 +220,7 @@ export function append(anchor, dom) {
217
220
  }
218
221
 
219
222
  export function text(data = '') {
220
- if (hydrating) {
223
+ if (HYDRATION && hydrating) {
221
224
  assign_nodes(/** @type {Node} */ (hydrate_node), /** @type {Node} */ (hydrate_node));
222
225
  return /** @type {Node} */ (hydrate_node);
223
226
  }
@@ -225,27 +228,3 @@ export function text(data = '') {
225
228
  assign_nodes(node, node);
226
229
  return node;
227
230
  }
228
-
229
- /**
230
- * Create fragment with proper namespace using Svelte's wrapping approach
231
- * @param {string} content
232
- * @param {'svg' | 'math'} ns
233
- * @returns {DocumentFragment}
234
- */
235
- function from_namespace(content, ns = 'svg') {
236
- var wrapped = `<${ns}>${content}</${ns}>`;
237
-
238
- var elem = document.createElement('template');
239
- elem.innerHTML = wrapped;
240
- var fragment = elem.content;
241
-
242
- var root = /** @type {Element} */ (get_first_child(fragment));
243
- var result = document.createDocumentFragment();
244
-
245
- var first;
246
- while ((first = get_first_child(root))) {
247
- result.appendChild(/** @type {Node} */ (first));
248
- }
249
-
250
- return result;
251
- }