ripple 0.4.1 → 0.4.3
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 +162 -0
- package/package.json +9 -3
- package/src/constants.js +4 -0
- package/src/jsx-runtime.d.ts +2 -1
- package/src/runtime/array.js +15 -9
- package/src/runtime/index-client.js +25 -7
- package/src/runtime/index-server.js +1 -0
- package/src/runtime/internal/client/attributes.js +313 -0
- package/src/runtime/internal/client/blocks.js +4 -14
- package/src/runtime/internal/client/component.js +2 -5
- package/src/runtime/internal/client/composite.js +14 -6
- package/src/runtime/internal/client/constants.js +12 -6
- package/src/runtime/internal/client/context.js +3 -2
- package/src/runtime/internal/client/css.js +11 -26
- package/src/runtime/internal/client/errors.js +188 -0
- package/src/runtime/internal/client/events.js +79 -34
- package/src/runtime/internal/client/expression.js +21 -116
- package/src/runtime/internal/client/for.js +272 -222
- package/src/runtime/internal/client/head.js +4 -3
- package/src/runtime/internal/client/html.js +5 -3
- package/src/runtime/internal/client/hydrate.js +484 -0
- package/src/runtime/internal/client/hydration-enabled.js +7 -0
- package/src/runtime/internal/client/hydration.js +16 -97
- package/src/runtime/internal/client/if.js +13 -9
- package/src/runtime/internal/client/index.js +16 -8
- package/src/runtime/internal/client/operations.js +12 -9
- package/src/runtime/internal/client/portal.js +3 -2
- package/src/runtime/internal/client/render.js +75 -321
- package/src/runtime/internal/client/root-boundary-enabled.js +7 -0
- package/src/runtime/internal/client/runtime.js +129 -362
- package/src/runtime/internal/client/template-ns.js +47 -0
- package/src/runtime/internal/client/template.js +63 -140
- package/src/runtime/internal/client/track-async.js +356 -0
- package/src/runtime/internal/client/try.js +133 -319
- package/src/runtime/internal/client/types.d.ts +48 -24
- package/src/runtime/internal/server/index.js +38 -33
- package/src/runtime/internal/server/prerender.js +27 -0
- package/src/server/index.js +1 -0
- package/src/utils/class-name.js +28 -0
- package/tests/client/basic/basic.errors.test.tsrx +3 -3
- package/tests/client/compiler/__snapshots__/compiler.typescript.test.tsrx.snap +0 -2
- package/tests/client/compiler/compiler.basic.test.tsrx +4 -4
- package/tests/client/compiler/compiler.tracked-access.test.tsrx +3 -3
- package/tests/client/composite/composite.props.test.tsrx +10 -10
- package/tests/client/derived-release.test.tsrx +3 -3
- package/tests/client/for-item.test.tsrx +443 -0
- package/tests/client/for-selector.test.tsrx +95 -0
- package/tests/hydration/compiled/client/basic.js +97 -123
- package/tests/hydration/compiled/client/composite.js +5 -5
- package/tests/hydration/compiled/client/events.js +84 -79
- package/tests/hydration/compiled/client/for.js +229 -233
- package/tests/hydration/compiled/client/fragment-cursor.js +781 -481
- package/tests/hydration/compiled/client/head.js +23 -26
- package/tests/hydration/compiled/client/hmr.js +4 -4
- package/tests/hydration/compiled/client/html-in-template.js +5 -5
- package/tests/hydration/compiled/client/html.js +174 -208
- package/tests/hydration/compiled/client/if-children.js +71 -70
- package/tests/hydration/compiled/client/if-fragment-controlflow.js +46 -46
- package/tests/hydration/compiled/client/if.js +26 -26
- package/tests/hydration/compiled/client/mixed-control-flow.js +63 -105
- package/tests/hydration/compiled/client/nested-control-flow.js +170 -390
- package/tests/hydration/compiled/client/portal.js +10 -10
- package/tests/hydration/compiled/client/reactivity.js +36 -50
- package/tests/hydration/compiled/client/return.js +2 -2
- package/tests/hydration/compiled/client/streaming.js +34 -40
- package/tests/hydration/compiled/client/switch.js +25 -25
- package/tests/hydration/compiled/client/track-async-serialization.js +41 -41
- package/tests/hydration/compiled/client/try.js +15 -15
- package/tests/hydration/compiled/client/typed-text.js +8 -19
- package/tests/hydration/compiled/server/basic.js +6 -6
- package/tests/hydration/compiled/server/events.js +13 -61
- package/tests/hydration/compiled/server/for.js +23 -31
- package/tests/hydration/compiled/server/fragment-cursor.js +214 -366
- package/tests/hydration/compiled/server/head.js +6 -6
- package/tests/hydration/compiled/server/hmr.js +1 -1
- package/tests/hydration/compiled/server/html.js +1 -1
- package/tests/hydration/compiled/server/if-children.js +9 -9
- package/tests/hydration/compiled/server/if.js +5 -5
- package/tests/hydration/compiled/server/mixed-control-flow.js +4 -4
- package/tests/hydration/compiled/server/portal.js +1 -1
- package/tests/hydration/compiled/server/reactivity.js +9 -9
- package/tests/hydration/compiled/server/streaming.js +9 -9
- package/tests/hydration/compiled/server/switch.js +4 -4
- package/tests/hydration/compiled/server/track-async-serialization.js +16 -16
- package/tests/hydration/compiled/server/try.js +4 -4
- package/tests/hydration/compiled/server/typed-text.js +1 -1
- package/tests/hydration/components/fragment-cursor.tsrx +60 -0
- package/tests/hydration/components/track-async-serialization.tsrx +2 -2
- package/tests/hydration/fragment-cursor.test.js +34 -1
- package/tests/hydration/track-async-serialization.test.js +1 -1
- package/tests/server/basic.components.test.tsrx +3 -3
- package/tests/server/basic.test.tsrx +23 -0
- package/tests/server/track-async-serialization.test.tsrx +6 -6
- package/tests/utils/tracked-types.test.js +3 -3
- package/types/index.d.ts +8 -8
- package/types/server.d.ts +17 -0
|
@@ -48,6 +48,11 @@ export type AppendIntoAnchor = {
|
|
|
48
48
|
parent: Node;
|
|
49
49
|
/** Marks the sentinel; a missing-property read on a DOM node is cheaper than `in`. */
|
|
50
50
|
into: true;
|
|
51
|
+
/**
|
|
52
|
+
* The sentinel is the tail of `parent`: nothing the framework renders
|
|
53
|
+
* follows what it anchors, so an `@if` there never needs an anchor node.
|
|
54
|
+
*/
|
|
55
|
+
tail: boolean;
|
|
51
56
|
};
|
|
52
57
|
|
|
53
58
|
export type Block = {
|
|
@@ -77,30 +82,50 @@ export type TryPendingFunction = (anchor: Node) => void;
|
|
|
77
82
|
* helpers are module functions that compile only when a boundary uses them.
|
|
78
83
|
*/
|
|
79
84
|
export type TryState = {
|
|
80
|
-
anchor
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
/** anchor */
|
|
86
|
+
a: Node;
|
|
87
|
+
/** try function */
|
|
88
|
+
fn: (anchor: Node, block?: Block) => void;
|
|
89
|
+
/** catch function */
|
|
90
|
+
c: TryCatchFunction | null;
|
|
91
|
+
/** pending function */
|
|
92
|
+
p: TryPendingFunction | null;
|
|
84
93
|
/** the catch branch's `reset` callback, created on first use */
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
r: (() => void) | null;
|
|
95
|
+
/** pending request count */
|
|
96
|
+
n: number;
|
|
97
|
+
/** request version */
|
|
98
|
+
v: number;
|
|
99
|
+
/** active request ids */
|
|
100
|
+
q: Set<number>;
|
|
101
|
+
/** the try block */
|
|
102
|
+
b: Block | null;
|
|
103
|
+
/** resolved branch */
|
|
104
|
+
rb: Block | null;
|
|
105
|
+
/** pending branch */
|
|
106
|
+
pb: Block | null;
|
|
107
|
+
/** catch branch */
|
|
108
|
+
cb: Block | null;
|
|
109
|
+
/** fragment holding the resolved branch while it is offscreen */
|
|
110
|
+
o: DocumentFragment | null;
|
|
111
|
+
/** has resolved */
|
|
112
|
+
h: boolean;
|
|
113
|
+
/** mode: 0 resolved, 1 pending, 2 catch */
|
|
114
|
+
m: 0 | 1 | 2;
|
|
115
|
+
/** deferred rejections by request id */
|
|
116
|
+
d: Map<number, (reason: any) => void>;
|
|
117
|
+
/** blocks paused on a pending read */
|
|
118
|
+
z: Set<Block>;
|
|
98
119
|
/** a streamed slot this boundary hydrated, until its chunk activates it */
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
120
|
+
si: string | null;
|
|
121
|
+
/** the streamed slot errored */
|
|
122
|
+
se: boolean;
|
|
123
|
+
/** the streamed slot carries a hydratable fallback */
|
|
124
|
+
sf: boolean;
|
|
125
|
+
/** streamed slot open marker */
|
|
126
|
+
so: Comment | null;
|
|
127
|
+
/** streamed slot close marker */
|
|
128
|
+
sc: Comment | null;
|
|
104
129
|
};
|
|
105
130
|
|
|
106
131
|
export type BlockWithTryBoundary = Omit<Block, 's'> & {
|
|
@@ -108,7 +133,7 @@ export type BlockWithTryBoundary = Omit<Block, 's'> & {
|
|
|
108
133
|
};
|
|
109
134
|
|
|
110
135
|
export type BlockWithTryBoundaryAndCatch = Omit<BlockWithTryBoundary, 's'> & {
|
|
111
|
-
s: TryState & {
|
|
136
|
+
s: TryState & { c: TryCatchFunction };
|
|
112
137
|
};
|
|
113
138
|
|
|
114
139
|
export type RootBoundaryOptions = {
|
|
@@ -144,7 +169,6 @@ declare global {
|
|
|
144
169
|
|
|
145
170
|
interface Element {
|
|
146
171
|
__attributes?: {
|
|
147
|
-
checked?: boolean;
|
|
148
172
|
value?: string;
|
|
149
173
|
};
|
|
150
174
|
__click?: () => void;
|
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
is_boolean_attribute,
|
|
61
61
|
normalize_css_property_name,
|
|
62
62
|
} from '@tsrx/core/runtime/html';
|
|
63
|
-
import {
|
|
63
|
+
import { class_name } from '../../../utils/class-name.js';
|
|
64
64
|
import { create_ref_prop } from '@tsrx/core/runtime/ref';
|
|
65
65
|
import {
|
|
66
66
|
BLOCK_CLOSE,
|
|
@@ -1258,7 +1258,6 @@ export async function render(component, options = {}) {
|
|
|
1258
1258
|
}
|
|
1259
1259
|
}
|
|
1260
1260
|
|
|
1261
|
-
var CONTENT_SPECIAL = /[&<]/;
|
|
1262
1261
|
var ATTR_SPECIAL = /[&"<]/;
|
|
1263
1262
|
var SCRIPT_SPECIAL = /[<>]/;
|
|
1264
1263
|
|
|
@@ -1275,15 +1274,19 @@ function escape_inline_script(str) {
|
|
|
1275
1274
|
|
|
1276
1275
|
/**
|
|
1277
1276
|
* Escapes text or attribute content. Strings without a character to escape
|
|
1278
|
-
* (the common case) return as they are
|
|
1279
|
-
*
|
|
1277
|
+
* (the common case) return as they are; anything else goes through the general
|
|
1278
|
+
* escaper. Text content is checked with two single-character searches: on the
|
|
1279
|
+
* short strings a page is made of they beat one character-class regex test.
|
|
1280
1280
|
* @param {unknown} value
|
|
1281
1281
|
* @param {boolean} [is_attr]
|
|
1282
1282
|
* @returns {string}
|
|
1283
1283
|
*/
|
|
1284
1284
|
export function escape(value, is_attr) {
|
|
1285
1285
|
var str = typeof value === 'string' ? value : value == null ? '' : String(value);
|
|
1286
|
-
|
|
1286
|
+
if (is_attr) {
|
|
1287
|
+
return ATTR_SPECIAL.test(str) ? escape_html(str, true) : str;
|
|
1288
|
+
}
|
|
1289
|
+
return str.includes('&') || str.includes('<') ? escape_html(str, false) : str;
|
|
1287
1290
|
}
|
|
1288
1291
|
|
|
1289
1292
|
/**
|
|
@@ -1596,7 +1599,7 @@ export function attr(name, value, is_boolean = false) {
|
|
|
1596
1599
|
}
|
|
1597
1600
|
if (value == null || (!value && is_boolean)) return '';
|
|
1598
1601
|
const normalized = (name in replacements && replacements[name].get(value)) || value;
|
|
1599
|
-
let value_to_escape = name === 'class' ?
|
|
1602
|
+
let value_to_escape = name === 'class' ? class_name(normalized) : normalized;
|
|
1600
1603
|
value_to_escape =
|
|
1601
1604
|
name === 'style'
|
|
1602
1605
|
? typeof value !== 'string'
|
|
@@ -1716,15 +1719,6 @@ class TrackedValue {
|
|
|
1716
1719
|
set value(v) {
|
|
1717
1720
|
set(/** @type {Tracked} */ (this), v);
|
|
1718
1721
|
}
|
|
1719
|
-
/**
|
|
1720
|
-
* A read-only view: a derived over this value, for a receiver that should
|
|
1721
|
-
* read but not write it. Equivalent to `track(() => tracked.value)`. The
|
|
1722
|
-
* view has no hash of its own: it is not a serialized value.
|
|
1723
|
-
* @returns {Derived}
|
|
1724
|
-
*/
|
|
1725
|
-
readOnly() {
|
|
1726
|
-
return derived(() => get_tracked(/** @type {Tracked} */ (this)));
|
|
1727
|
-
}
|
|
1728
1722
|
}
|
|
1729
1723
|
|
|
1730
1724
|
class DerivedValue {
|
|
@@ -1762,17 +1756,6 @@ class DerivedValue {
|
|
|
1762
1756
|
set value(v) {
|
|
1763
1757
|
set(/** @type {Derived} */ (this), v);
|
|
1764
1758
|
}
|
|
1765
|
-
/**
|
|
1766
|
-
* A read-only view (see `TrackedValue#readOnly`). A derived without a
|
|
1767
|
-
* setter is already read-only and is returned as is; a writable one is
|
|
1768
|
-
* wrapped in a derived that follows it.
|
|
1769
|
-
* @returns {Derived}
|
|
1770
|
-
*/
|
|
1771
|
-
readOnly() {
|
|
1772
|
-
return this.a.set === undefined
|
|
1773
|
-
? /** @type {Derived} */ (this)
|
|
1774
|
-
: derived(() => get_derived(/** @type {Derived} */ (this)));
|
|
1775
|
-
}
|
|
1776
1759
|
}
|
|
1777
1760
|
|
|
1778
1761
|
/**
|
|
@@ -1801,6 +1784,25 @@ function derived(v, hash, get, set) {
|
|
|
1801
1784
|
);
|
|
1802
1785
|
}
|
|
1803
1786
|
|
|
1787
|
+
/**
|
|
1788
|
+
* A read-only view of a tracked or derived value (see the client runtime's
|
|
1789
|
+
* `track_read_only`). The view has no hash of its own: it is not a serialized
|
|
1790
|
+
* value.
|
|
1791
|
+
* @param {any} value
|
|
1792
|
+
* @returns {any}
|
|
1793
|
+
*/
|
|
1794
|
+
export function track_read_only(value) {
|
|
1795
|
+
if (!is_ripple_object(value)) {
|
|
1796
|
+
return value;
|
|
1797
|
+
}
|
|
1798
|
+
if ((value.f & DERIVED) !== 0) {
|
|
1799
|
+
var d = /** @type {Derived} */ (value);
|
|
1800
|
+
return d.a.set === undefined ? d : derived(() => get_derived(d));
|
|
1801
|
+
}
|
|
1802
|
+
var t = /** @type {Tracked} */ (value);
|
|
1803
|
+
return derived(() => get_tracked(t));
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1804
1806
|
/**
|
|
1805
1807
|
* @param {any} v
|
|
1806
1808
|
* @param {string} hash
|
|
@@ -2275,7 +2277,7 @@ function run_track_async(t, fn, block, dr, dj) {
|
|
|
2275
2277
|
/**
|
|
2276
2278
|
* The hashes of the compile-time tracked values a trackAsync read, recorded
|
|
2277
2279
|
* in its hydration envelope so the client re-subscribes to them. A dependency
|
|
2278
|
-
* without a hash that is itself a derived (a `
|
|
2280
|
+
* without a hash that is itself a derived (a `trackReadOnly()` view, a derived made
|
|
2279
2281
|
* outside a compiled call) stands for what it read: its own dependencies are
|
|
2280
2282
|
* recorded in its place.
|
|
2281
2283
|
* @param {Dependency | null} head
|
|
@@ -2527,35 +2529,38 @@ export function ripple_array(...elements) {
|
|
|
2527
2529
|
}
|
|
2528
2530
|
|
|
2529
2531
|
/**
|
|
2532
|
+
* `RippleArray.from(...)`
|
|
2530
2533
|
* @template T
|
|
2531
2534
|
* @param {ArrayLike<T> | Iterable<T>} arrayLike
|
|
2532
2535
|
* @param {(v: T, k: number) => any | undefined} [map_fn]
|
|
2533
2536
|
* @param {any} [thisArg]
|
|
2534
2537
|
* @returns {T[]}
|
|
2535
2538
|
*/
|
|
2536
|
-
|
|
2539
|
+
export function ripple_array_from(arrayLike, map_fn, thisArg) {
|
|
2537
2540
|
return map_fn ? Array.from(arrayLike, map_fn, thisArg) : Array.from(arrayLike);
|
|
2538
|
-
}
|
|
2541
|
+
}
|
|
2539
2542
|
|
|
2540
2543
|
/**
|
|
2544
|
+
* `RippleArray.of(...)`
|
|
2541
2545
|
* @template T
|
|
2542
2546
|
* @param {...T} items
|
|
2543
2547
|
* @returns {T[]}
|
|
2544
2548
|
*/
|
|
2545
|
-
|
|
2549
|
+
export function ripple_array_of(...items) {
|
|
2546
2550
|
return Array.of(...items);
|
|
2547
|
-
}
|
|
2551
|
+
}
|
|
2548
2552
|
|
|
2549
2553
|
/**
|
|
2554
|
+
* `RippleArray.fromAsync(...)`
|
|
2550
2555
|
* @template T
|
|
2551
2556
|
* @param {ArrayLike<T> | Iterable<T>} arrayLike
|
|
2552
2557
|
* @param {(v: T, k: number) => any | undefined} [map_fn]
|
|
2553
2558
|
* @param {any} [thisArg]
|
|
2554
2559
|
* @returns {Promise<T[]>}
|
|
2555
2560
|
*/
|
|
2556
|
-
|
|
2561
|
+
export async function ripple_array_from_async(arrayLike, map_fn, thisArg) {
|
|
2557
2562
|
return map_fn ? Array.fromAsync(arrayLike, map_fn, thisArg) : Array.fromAsync(arrayLike);
|
|
2558
|
-
}
|
|
2563
|
+
}
|
|
2559
2564
|
|
|
2560
2565
|
/**
|
|
2561
2566
|
* @param {object} obj
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** @import { RenderComponent } from './index.js' */
|
|
2
|
+
/** @import { RenderResult } from 'ripple/server' */
|
|
3
|
+
/** @import { RootBoundaryOptions } from '#public' */
|
|
4
|
+
|
|
5
|
+
import { render } from './index.js';
|
|
6
|
+
import { get_css_text } from './css-registry.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Renders a component to static HTML: buffered, every `@try` boundary and
|
|
10
|
+
* `trackAsync` settled before it resolves, with the scoped CSS as text. The
|
|
11
|
+
* static-generation counterpart of `render()`, for HTML written at build time
|
|
12
|
+
* that `hydrate()` picks up like any server-rendered page.
|
|
13
|
+
* @param {RenderComponent} component
|
|
14
|
+
* @param {{ rootBoundary?: RootBoundaryOptions }} [options]
|
|
15
|
+
* @returns {Promise<{ head: string, body: string, css: string, topLevelError: Error | null }>}
|
|
16
|
+
*/
|
|
17
|
+
export async function prerender(component, options = {}) {
|
|
18
|
+
const result = /** @type {RenderResult} */ (
|
|
19
|
+
await render(component, { rootBoundary: options.rootBoundary })
|
|
20
|
+
);
|
|
21
|
+
return {
|
|
22
|
+
head: result.head,
|
|
23
|
+
body: result.body,
|
|
24
|
+
css: get_css_text(result.css),
|
|
25
|
+
topLevelError: result.topLevelError ?? null,
|
|
26
|
+
};
|
|
27
|
+
}
|
package/src/server/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// SSR helpers
|
|
2
2
|
export { create_ssr_stream as createStream, render } from '../runtime/internal/server/index.js';
|
|
3
3
|
export { get_css_text as getCss } from '../runtime/internal/server/css-registry.js';
|
|
4
|
+
export { prerender } from '../runtime/internal/server/prerender.js';
|
|
4
5
|
export { executeServerFunction } from '../runtime/internal/server/rpc.js';
|
|
5
6
|
|
|
6
7
|
// Re-export server runtime for components compiled for SSR
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* clsx-style class composition, shared by the client and server renderers: a
|
|
3
|
+
* string as it is, a truthy number, the truthy items of an array, the keys of
|
|
4
|
+
* an object with truthy values, nested arrays flattened, everything else
|
|
5
|
+
* dropped.
|
|
6
|
+
* @param {any} value
|
|
7
|
+
* @returns {string}
|
|
8
|
+
*/
|
|
9
|
+
export function class_name(value) {
|
|
10
|
+
if (typeof value === 'string') {
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
if (typeof value !== 'object' || value === null) {
|
|
14
|
+
return typeof value === 'number' && value ? '' + value : '';
|
|
15
|
+
}
|
|
16
|
+
var str = '';
|
|
17
|
+
if (Array.isArray(value)) {
|
|
18
|
+
for (var i = 0; i < value.length; i++) {
|
|
19
|
+
var item = value[i] && class_name(value[i]);
|
|
20
|
+
if (item) str = str ? str + ' ' + item : item;
|
|
21
|
+
}
|
|
22
|
+
} else {
|
|
23
|
+
for (var key in value) {
|
|
24
|
+
if (value[key]) str = str ? str + ' ' + key : key;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return str;
|
|
28
|
+
}
|
|
@@ -126,7 +126,7 @@ describe('basic client > errors', () => {
|
|
|
126
126
|
|
|
127
127
|
render(Basic);
|
|
128
128
|
|
|
129
|
-
expect(error).
|
|
129
|
+
expect(error).toContain(
|
|
130
130
|
'Assignments or updates to tracked values are not allowed during computed "track(() => ...)" evaluation',
|
|
131
131
|
);
|
|
132
132
|
});
|
|
@@ -148,7 +148,7 @@ describe('basic client > errors', () => {
|
|
|
148
148
|
|
|
149
149
|
render(Basic);
|
|
150
150
|
|
|
151
|
-
expect(error).
|
|
151
|
+
expect(error).toContain(
|
|
152
152
|
'Assignments or updates to tracked values are not allowed during computed "track(() => ...)" evaluation',
|
|
153
153
|
);
|
|
154
154
|
});
|
|
@@ -169,7 +169,7 @@ describe('basic client > errors', () => {
|
|
|
169
169
|
|
|
170
170
|
render(Basic);
|
|
171
171
|
|
|
172
|
-
expect(error).
|
|
172
|
+
expect(error).toContain(
|
|
173
173
|
'Assignments or updates to tracked values are not allowed during computed "track(() => ...)" evaluation',
|
|
174
174
|
);
|
|
175
175
|
});
|
|
@@ -110,8 +110,8 @@ describe('compiler > basics', () => {
|
|
|
110
110
|
|
|
111
111
|
const { code } = compile(source, 'stringish-text.tsrx', { mode: 'client' });
|
|
112
112
|
|
|
113
|
-
expect(code).toContain('_$_.
|
|
114
|
-
expect(code).toContain('
|
|
113
|
+
expect(code).toContain('_$_.set_text_content');
|
|
114
|
+
expect(code).toContain('textContent = title');
|
|
115
115
|
expect(count_occurrences(code, '_$_.expression')).toBe(2);
|
|
116
116
|
});
|
|
117
117
|
|
|
@@ -213,8 +213,8 @@ export function App() @{
|
|
|
213
213
|
|
|
214
214
|
// Already a string: no String(… ?? '') double wrap around it, and the
|
|
215
215
|
// scope wrapper sits on the user call, not on the coercion.
|
|
216
|
-
expect(code).
|
|
217
|
-
expect(code).not.
|
|
216
|
+
expect(code).toMatch(/String\(_\$_\.with_scope\(__prev\._[a-z]+, fetchLabel\)\)/);
|
|
217
|
+
expect(code).not.toMatch(/_\$_\.with_scope\(__prev\._[a-z]+, \(\) => String\(/);
|
|
218
218
|
expect(code).not.toContain('String(String(');
|
|
219
219
|
expect(code).not.toContain('?? \'\'');
|
|
220
220
|
});
|
|
@@ -5,7 +5,7 @@ const value_message = /Accessing a tracked object directly is not allowed, use `
|
|
|
5
5
|
describe('Compiler: Tracked Object Direct Access Checks', () => {
|
|
6
6
|
it('should error on direct access to __v of a tracked object', () => {
|
|
7
7
|
const code = `
|
|
8
|
-
import { track } from 'ripple';
|
|
8
|
+
import { track, trackReadOnly } from 'ripple';
|
|
9
9
|
export default function App() @{
|
|
10
10
|
let count = track(0);
|
|
11
11
|
console.log(count.__v);
|
|
@@ -200,12 +200,12 @@ import { track } from 'ripple';
|
|
|
200
200
|
expect(() => compile(code, 'test.tsrx')).not.toThrow();
|
|
201
201
|
});
|
|
202
202
|
|
|
203
|
-
it('should allow .
|
|
203
|
+
it('should allow .trackReadOnly() on a tracked object', () => {
|
|
204
204
|
const code = `
|
|
205
205
|
import { track } from 'ripple';
|
|
206
206
|
export default function App() @{
|
|
207
207
|
let count = track(0);
|
|
208
|
-
const view = count
|
|
208
|
+
const view = trackReadOnly(count);
|
|
209
209
|
<>{view.value}</>
|
|
210
210
|
}
|
|
211
211
|
`;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Derived, Tracked, Props } from 'ripple';
|
|
2
|
-
import { effect, flushSync, track } from 'ripple';
|
|
2
|
+
import { effect, flushSync, track, trackReadOnly } from 'ripple';
|
|
3
3
|
|
|
4
4
|
describe('composite > props', () => {
|
|
5
|
-
it('hands a child a read-only view through
|
|
5
|
+
it('hands a child a read-only view through trackReadOnly()', () => {
|
|
6
6
|
function Child({ count }: { count: Derived<number> }) @{
|
|
7
7
|
<span class="child">{count.value}</span>
|
|
8
8
|
}
|
|
@@ -10,7 +10,7 @@ describe('composite > props', () => {
|
|
|
10
10
|
function App() @{
|
|
11
11
|
const count = track(0);
|
|
12
12
|
<>
|
|
13
|
-
<Child count={count
|
|
13
|
+
<Child count={trackReadOnly(count)} />
|
|
14
14
|
<button onClick={() => count.value++}>{'inc'}</button>
|
|
15
15
|
</>
|
|
16
16
|
}
|
|
@@ -24,13 +24,13 @@ describe('composite > props', () => {
|
|
|
24
24
|
expect(container.querySelector('.child').textContent).toBe('1');
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
it('returns a read-only derived from
|
|
27
|
+
it('returns a read-only derived from trackReadOnly() as is and wraps a writable one', () => {
|
|
28
28
|
function App() @{
|
|
29
29
|
const count = track(1);
|
|
30
30
|
const double = track(() => count.value * 2);
|
|
31
31
|
const draft = track(() => count.value, undefined, true);
|
|
32
|
-
const double_view = double
|
|
33
|
-
const draft_view = draft
|
|
32
|
+
const double_view = trackReadOnly(double);
|
|
33
|
+
const draft_view = trackReadOnly(draft);
|
|
34
34
|
expect(double_view).toBe(double);
|
|
35
35
|
expect(draft_view).not.toBe(draft);
|
|
36
36
|
<>
|
|
@@ -51,7 +51,7 @@ describe('composite > props', () => {
|
|
|
51
51
|
expect(container.querySelector('.view').textContent).toBe('42');
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
it('releases a
|
|
54
|
+
it('releases a trackReadOnly() view with the block that created it', () => {
|
|
55
55
|
let set_rows: (rows: number[]) => void = () => {};
|
|
56
56
|
let bump: () => void = () => {};
|
|
57
57
|
let source: Tracked<number> | undefined;
|
|
@@ -73,7 +73,7 @@ describe('composite > props', () => {
|
|
|
73
73
|
<ul>
|
|
74
74
|
@for (const row of rows.value; key row) {
|
|
75
75
|
<li>
|
|
76
|
-
<Child count={count
|
|
76
|
+
<Child count={trackReadOnly(count)} />
|
|
77
77
|
</li>
|
|
78
78
|
}
|
|
79
79
|
</ul>
|
|
@@ -106,12 +106,12 @@ describe('composite > props', () => {
|
|
|
106
106
|
expect(subscribers()).toBe(0);
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
-
it('keeps a
|
|
109
|
+
it('keeps a trackReadOnly() view from writing through to its source', () => {
|
|
110
110
|
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
111
111
|
try {
|
|
112
112
|
function App() @{
|
|
113
113
|
const count = track(1);
|
|
114
|
-
const view = count
|
|
114
|
+
const view = trackReadOnly(count);
|
|
115
115
|
const write = () => {
|
|
116
116
|
view.value = 99;
|
|
117
117
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Derived, Tracked } from 'ripple';
|
|
2
|
-
import { effect, flushSync, track } from 'ripple';
|
|
2
|
+
import { effect, flushSync, track, trackReadOnly } from 'ripple';
|
|
3
3
|
|
|
4
4
|
/** The runtime's own subscriber list of a tracked value (`sb` / `sn`). */
|
|
5
5
|
function subscribers(source: Tracked<number>) {
|
|
@@ -34,7 +34,7 @@ describe('derived release', () => {
|
|
|
34
34
|
const label = track(() => 'n=' + count.value);
|
|
35
35
|
<>
|
|
36
36
|
<span class="label">{label.value}</span>
|
|
37
|
-
<Child count={count
|
|
37
|
+
<Child count={trackReadOnly(count)} />
|
|
38
38
|
</>
|
|
39
39
|
}
|
|
40
40
|
</>
|
|
@@ -73,7 +73,7 @@ describe('derived release', () => {
|
|
|
73
73
|
<ul>
|
|
74
74
|
@for (const row of rows.value; key row) {
|
|
75
75
|
<li>
|
|
76
|
-
<Child count={count
|
|
76
|
+
<Child count={trackReadOnly(count)} />
|
|
77
77
|
</li>
|
|
78
78
|
}
|
|
79
79
|
</ul>
|