├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── babel.config.json
├── cjs
├── diff.js
├── dist
│ ├── preact.js
│ ├── signal.js
│ ├── solid.js
│ └── usignal.js
├── html-escaper.js
├── index.js
├── package.json
└── ssr.js
├── es.js
├── esm
├── diff.js
├── dist
│ ├── preact.js
│ ├── signal.js
│ ├── solid.js
│ └── usignal.js
├── html-escaper.js
├── index.js
└── ssr.js
├── index.js
├── package-lock.json
├── package.json
├── preact.js
├── rollup
├── babel.config.js
├── dist.js
├── es.config.js
├── preact.config.js
├── signal.config.js
├── solid.config.js
└── usignal.config.js
├── signal.js
├── solid.js
├── test
├── array.html
├── array.js
├── array.jsx
├── base.js
├── base.jsx
├── children.html
├── children.js
├── children.jsx
├── counter.html
├── counter.js
├── counter.jsx
├── dist
│ ├── preact.html
│ ├── preact.js
│ ├── preact.jsx
│ ├── signal.html
│ ├── signal.js
│ ├── signal.jsx
│ ├── solid.html
│ ├── solid.js
│ ├── solid.jsx
│ ├── usignal.html
│ ├── usignal.js
│ └── usignal.jsx
├── esx-babel.js
├── esx.html
├── esx.js
├── esx.jsx
├── index.html
├── index.js
├── index.jsx
├── ssr.js
├── ssr.jsx
├── uhooks.html
├── uhooks.js
└── uhooks.jsx
└── usignal.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .nyc_output
3 | coverage/
4 | node_modules/
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .nyc_output
3 | .eslintrc.json
4 | .travis.yml
5 | .github
6 | babel.config.json
7 | coverage/
8 | node_modules/
9 | rollup/
10 | test/
11 | tsconfig.json
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | ISC License
2 |
3 | Copyright (c) 2023, Andrea Giammarchi, @WebReflection
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted, provided that the above
7 | copyright notice and this permission notice appear in all copies.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15 | PERFORMANCE OF THIS SOFTWARE.
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # udomsay
2 |
3 | **EXPERIMENTAL** ⚠ **Social Media Image from [Know Your Meme](https://knowyourmeme.com/memes/you-dont-say--3)**
4 |
5 | A stricter, signals driven, **ESX** based library.
6 |
7 | ## What
8 |
9 | This library includes, in about *2.2Kb*, logic to parse [a specialized form of JSX](https://github.com/ungap/babel-plugin-transform-esx#readme), or its [template literal based variant](https://github.com/ungap/esx#reade), and use signals from various authors, handling rendering automatically and avoiding side effects when used as *SSR*.
10 |
11 | ## How
12 |
13 | Given the following `counter.jsx` file:
14 | ```js
15 | // grab signals from various libaries, here the simplest I know
16 | import {Signal, signal, effect} from 'https://unpkg.com/@webreflection/signal';
17 |
18 | // import the `createRender` utility
19 | import createRender from 'https://unpkg.com/udomsay';
20 | const render = createRender({Signal, effect});
21 |
22 | // Counter Component example
23 | function Counter({clicks}) {
24 | return (
25 |
26 |
27 | {clicks}
28 |
29 |
30 | );
31 | }
32 |
33 | render(
34 | ,
35 | document.body
36 | );
37 | ```
38 |
39 | Providing the following `babel.config.json` transformer:
40 | ```json
41 | {
42 | "plugins": [
43 | ["@ungap/babel-plugin-transform-esx"]
44 | ]
45 | }
46 | ```
47 |
48 | The result can be **[tested in CodePen.io](https://codepen.io/WebReflection/pen/vYrYxKY)**.
49 |
50 | ## Custom Signals Library
51 |
52 | Bringing in your favorite signals libraries is almost a no brainer with *udomsay*: check the fews already tested within this project!
53 |
54 | * **[preact](https://www.npmjs.com/package/@preact/signals-core)**, implemented through [this file](./esm/dist/preact.js) and [live tested here](https://webreflection.github.io/udomsay/test/dist/preact.html). Try `import {createRender, signal} from "udomsay/preact"` yourself!
55 | * **[@webreflection/signal](https://www.npmjs.com/package/@webreflection/signal)**, implemented through [this file](./esm/dist/signal.js) and [live tested here](https://webreflection.github.io/udomsay/test/dist/signal.html). Try `import {createRender, signal} from "udomsay/signal"` yourself!
56 | * **[solid-js](https://www.npmjs.com/package/solid-js)**, implemented through [this file](./esm/dist/solid.js) and [live tested here](https://webreflection.github.io/udomsay/test/dist/solid.html). Try `import {createRender, createSignal} from "udomsay/solid"` yourself!
57 |
58 | ### Current udomsay ESX Interpolations Rules
59 |
60 | Following the current set of stricter rules around *JSX* usage and how to avoid/prevent issues:
61 |
62 | * if an interpolation contains a *primitive* value (e.g. a string, a number, a boolean or undefined) or a *signal* which value is primitive, every future update of such interpolation will *expect a primitive* value or *signal* carrying a primitive value. Conditional primitives values or signals are fine, but `{condition ? "string" : }` is **not supported**.
63 | * if a *signal* is used as interpolation and its value is *primitive*, an *effect* is used to update its value on the target text node *only if the signal changes or its value did*. This allows to fine-tune and confine updates per each component or even regular element node, without needing to re-trigger the outer component logic.
64 | * if a *signal* is used as interpolation and its value is *not primitive*, every future update of such interpolation will *expect a signal*. Conditional signals are fine, but `{condition ? signal : ( || "string")}` is **not supported**.
65 | * if an interpolation contains an *array* of items, every future update of such interpolation will *expect an array*. Conditional arrays are fine, but `{condition ? [..items] : ( || "string")}` is **not supported**.
66 |
67 | ### Library Goals
68 |
69 | The goal of this library is:
70 |
71 | * explore if [a better instrumented JSX](https://webreflection.medium.com/jsx-is-inefficient-by-default-but-d1122c992399) can actually help performance and memory consumption
72 | * avoid the need of *vDOM*, still [diffing](https://github.com/WebReflection/udomdiff#readme) when necessary through *arrays* in interpolations
73 | * create once and map on the fly (JIT) templates for both nodes, fragments, and components
74 | * fine-tune operations per each interpolation, such as spread properties VS known static properties, conditional holes or signals and, last but not least, arrays of items
75 |
--------------------------------------------------------------------------------
/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | ["@ungap/transform-esx", { "polyfill": "inline" }]
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/cjs/diff.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /* (c) Andrea Giammarchi - ISC */
3 | // @see https://github.com/WebReflection/udomdiff
4 | const diff = (a, b, before) => {
5 | const {parentNode} = before;
6 | const bLength = b.length;
7 | let aEnd = a.length;
8 | let bEnd = bLength;
9 | let aStart = 0;
10 | let bStart = 0;
11 | let map = null;
12 | while (aStart < aEnd || bStart < bEnd) {
13 | // append head, tail, or nodes in between: fast path
14 | if (aEnd === aStart) {
15 | // we could be in a situation where the rest of nodes that
16 | // need to be added are not at the end, and in such case
17 | // the node to `insertBefore`, if the index is more than 0
18 | // must be retrieved, otherwise it's gonna be the first item.
19 | const node = bEnd < bLength ?
20 | (bStart ?
21 | b[bStart - 1].nextSibling :
22 | b[bEnd - bStart]) :
23 | before;
24 | while (bStart < bEnd)
25 | parentNode.insertBefore(b[bStart++], node);
26 | }
27 | // remove head or tail: fast path
28 | else if (bEnd === bStart) {
29 | while (aStart < aEnd) {
30 | // remove the node only if it's unknown or not live
31 | if (!map || !map.has(a[aStart]))
32 | a[aStart].remove();
33 | aStart++;
34 | }
35 | }
36 | // same node: fast path
37 | else if (a[aStart] === b[bStart]) {
38 | aStart++;
39 | bStart++;
40 | }
41 | // same tail: fast path
42 | else if (a[aEnd - 1] === b[bEnd - 1]) {
43 | aEnd--;
44 | bEnd--;
45 | }
46 | // The once here single last swap "fast path" has been removed in v1.1.0
47 | // https://github.com/WebReflection/udomdiff/blob/single-final-swap/esm/index.js#L69-L85
48 | // reverse swap: also fast path
49 | else if (
50 | a[aStart] === b[bEnd - 1] &&
51 | b[bStart] === a[aEnd - 1]
52 | ) {
53 | // this is a "shrink" operation that could happen in these cases:
54 | // [1, 2, 3, 4, 5]
55 | // [1, 4, 3, 2, 5]
56 | // or asymmetric too
57 | // [1, 2, 3, 4, 5]
58 | // [1, 2, 3, 5, 6, 4]
59 | const node = a[--aEnd].nextSibling;
60 | parentNode.insertBefore(
61 | b[bStart++],
62 | a[aStart++].nextSibling
63 | );
64 | parentNode.insertBefore(b[--bEnd], node);
65 | // mark the future index as identical (yeah, it's dirty, but cheap 👍)
66 | // The main reason to do this, is that when a[aEnd] will be reached,
67 | // the loop will likely be on the fast path, as identical to b[bEnd].
68 | // In the best case scenario, the next loop will skip the tail,
69 | // but in the worst one, this node will be considered as already
70 | // processed, bailing out pretty quickly from the map index check
71 | a[aEnd] = b[bEnd];
72 | }
73 | // map based fallback, "slow" path
74 | else {
75 | // the map requires an O(bEnd - bStart) operation once
76 | // to store all future nodes indexes for later purposes.
77 | // In the worst case scenario, this is a full O(N) cost,
78 | // and such scenario happens at least when all nodes are different,
79 | // but also if both first and last items of the lists are different
80 | if (!map) {
81 | map = new Map;
82 | let i = bStart;
83 | while (i < bEnd)
84 | map.set(b[i], i++);
85 | }
86 | // if it's a future node, hence it needs some handling
87 | if (map.has(a[aStart])) {
88 | // grab the index of such node, 'cause it might have been processed
89 | const index = map.get(a[aStart]);
90 | // if it's not already processed, look on demand for the next LCS
91 | if (bStart < index && index < bEnd) {
92 | let i = aStart;
93 | // counts the amount of nodes that are the same in the future
94 | let sequence = 1;
95 | while (++i < aEnd && i < bEnd && map.get(a[i]) === (index + sequence))
96 | sequence++;
97 | // effort decision here: if the sequence is longer than replaces
98 | // needed to reach such sequence, which would brings again this loop
99 | // to the fast path, prepend the difference before a sequence,
100 | // and move only the future list index forward, so that aStart
101 | // and bStart will be aligned again, hence on the fast path.
102 | // An example considering aStart and bStart are both 0:
103 | // a: [1, 2, 3, 4]
104 | // b: [7, 1, 2, 3, 6]
105 | // this would place 7 before 1 and, from that time on, 1, 2, and 3
106 | // will be processed at zero cost
107 | if (sequence > (index - bStart)) {
108 | const node = a[aStart];
109 | while (bStart < index)
110 | parentNode.insertBefore(b[bStart++], node);
111 | }
112 | // if the effort wasn't good enough, fallback to a replace,
113 | // moving both source and target indexes forward, hoping that some
114 | // similar node will be found later on, to go back to the fast path
115 | else {
116 | parentNode.replaceChild(
117 | b[bStart++],
118 | a[aStart++]
119 | );
120 | }
121 | }
122 | // otherwise move the source forward, 'cause there's nothing to do
123 | else
124 | aStart++;
125 | }
126 | // this node has no meaning in the future list, so it's more than safe
127 | // to remove it, and check the next live node out instead, meaning
128 | // that only the live list index should be forwarded
129 | else
130 | a[aStart++].remove();
131 | }
132 | }
133 | return b;
134 | };
135 | exports.diff = diff;
136 |
--------------------------------------------------------------------------------
/cjs/dist/preact.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {Signal, effect} = require('@preact/signals-core');
3 | (m => Object.keys(m).map(k => k !== 'default' && (exports[k] = m[k])))
4 | (require('@preact/signals-core'));
5 |
6 | const $ = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('../index.js'));
7 | const createRender = (options = {}) =>
8 | $({...options, Signal, effect, isSignal: void 0});
9 | exports.createRender = createRender;
10 |
--------------------------------------------------------------------------------
/cjs/dist/signal.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {Signal, effect} = require('@webreflection/signal');
3 | (m => Object.keys(m).map(k => k !== 'default' && (exports[k] = m[k])))
4 | (require('@webreflection/signal'));
5 |
6 | const $ = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('../index.js'));
7 | const createRender = (options = {}) =>
8 | $({...options, Signal, effect, isSignal: void 0});
9 | exports.createRender = createRender;
10 |
--------------------------------------------------------------------------------
/cjs/dist/solid.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {createSignal: $, createEffect, untrack} = require('solid-js');
3 | (m => Object.keys(m).map(k => k !== 'default' && (exports[k] = m[k])))
4 | (require('solid-js'));
5 |
6 | const _ = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('../index.js'));
7 |
8 | const signals = new WeakSet;
9 | const noop = () => {};
10 |
11 | const createSignal = (value, ...rest) => {
12 | const [signal, update] = $(value, ...rest);
13 | signals.add(signal);
14 | return [signal, update];
15 | };
16 | exports.createSignal = createSignal;
17 |
18 | const createRender = (options = {}) => _({
19 | ...options,
20 | effect: (...args) => (createEffect(...args), noop),
21 | getPeek: s => untrack(s),
22 | getValue: s => s(),
23 | isSignal: s => signals.has(s)
24 | });
25 | exports.createRender = createRender;
26 |
--------------------------------------------------------------------------------
/cjs/dist/usignal.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {Signal, effect} = require('usignal');
3 | (m => Object.keys(m).map(k => k !== 'default' && (exports[k] = m[k])))
4 | (require('usignal'));
5 |
6 | const $ = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('../index.js'));
7 | const createRender = (options = {}) =>
8 | $({...options, Signal, effect, isSignal: void 0});
9 | exports.createRender = createRender;
10 |
--------------------------------------------------------------------------------
/cjs/html-escaper.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | // @see https://github.com/WebReflection/html-escaper#readme
3 | const es = /[&<>'"]/g;
4 | const cape = pe => esca[pe];
5 | const esca = {
6 | '&': '&',
7 | '<': '<',
8 | '>': '>',
9 | "'": ''',
10 | '"': '"'
11 | };
12 |
13 | const {replace} = '';
14 |
15 | const escape = value => replace.call(value, es, cape);
16 | exports.escape = escape;
17 |
--------------------------------------------------------------------------------
/cjs/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /*! (c) Andrea Giammarchi - ISC */
3 |
4 | const EMPTY = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('@webreflection/empty/array'));
5 | const noop = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('@webreflection/empty/function'));
6 |
7 | const {Token} = require('@ungap/esx');
8 | const {diff} = require('./diff.js');
9 |
10 | const {isArray} = Array;
11 | const {entries, getPrototypeOf, prototype: {isPrototypeOf}} = Object;
12 |
13 | const {
14 | COMPONENT,
15 | ELEMENT,
16 | FRAGMENT,
17 | INTERPOLATION,
18 | STATIC
19 | } = Token;
20 |
21 | const UDOMSAY = '🙊';
22 |
23 | // generic utils
24 | const asChildNodes = ({childNodes}) => ({childNodes: [...childNodes]});
25 | const getChild = ({childNodes}, i) => childNodes[i];
26 | const getToken = ({children}, i) => children[i];
27 | const invoke = ({value, properties, children}) => value(properties, ...children);
28 | const isKey = ({name}) => name === 'key';
29 | const reachChild = (c, {content}) => c.reduce(getChild, content);
30 | const reachToken = (c, token) => c.reduce(getToken, token);
31 | const setData = (node, value) => {
32 | const data = value == null ? '' : String(value);
33 | if (data !== node.data)
34 | node.data = data;
35 | };
36 |
37 | /**
38 | * @typedef {Object} RenderOptions utilities to use while rendering.
39 | * @prop {Document} [document] the default document to use. By default it's the global one.
40 | * @prop {[string, (node:Element, current:any, previous:any) => void][]} [plugins] a list of plugins to deal with,
41 | * used with attributes, example: `["stuff", (node, curr, prev) => { ... }]`
42 | * @prop {(fn:function) => function} [effect] an utility to create effects on components.
43 | * It must return a dispose utility to drop previous effect.
44 | * @prop {(s:any) => any} [getPeek] an utility to retrieve a Signal value without side-effects.
45 | * @prop {(s:any) => any} [getValue] an utility to retrieve a Signal value.
46 | * @prop {(s:any) => boolean} [isSignal] an utility to know if a value is a Signal.
47 | * @prop {function} [Signal] an optional signal constructor used to trap-check.
48 | * @prop {function} [diff] an optional function to diff nodes, not available in SSR.
49 | * `isSignal(ref)` utility whenever the `isSignal` field has not been provided.
50 | */
51 |
52 | /**
53 | * Return a `render(what, where)` utility able to deal with provided options.
54 | * @param {RenderOptions} options
55 | */
56 | module.exports = (options = {}) => {
57 | const document = options.document || globalThis.document;
58 | const plugins = new Map(options.plugins || []);
59 | const considerPlugins = !!plugins.size;
60 | const differ = options.diff || diff;
61 | const effect = options.effect || (fn => (fn(), noop));
62 | const getPeek = options.getPeek || (s => s.peek());
63 | const getValue = options.getValue || (s => s.value);
64 | const isSignal = options.isSignal || (
65 | options.Signal ?
66 | isPrototypeOf.bind(options.Signal.prototype) :
67 | () => false
68 | );
69 |
70 | const text = value => document.createTextNode(value);
71 | const comment = () => document.createComment(UDOMSAY);
72 |
73 | const getChildrenID = (node, i) => {
74 | let IDs = views.get(node);
75 | if (!IDs) views.set(node, IDs = {});
76 | return IDs[i] || (IDs[i] = {});
77 | };
78 | const getComponentView = (view, component) => {
79 | view.dispose();
80 | const dispose = effect(() => {
81 | const token = invoke(component);
82 | if (token.id !== view.id)
83 | view = getView(view, token, false);
84 | else
85 | view.update(token);
86 | });
87 | view.dispose = dispose;
88 | return view;
89 | };
90 | const getNewView = (view, token, createEffect) => {
91 | view.dispose();
92 | view = new View(token);
93 | if (createEffect)
94 | view.dispose = effect(() => view.update(token));
95 | else
96 | view.update(token);
97 | return view;
98 | };
99 | const getView = (view, token, createEffect) => token.type === COMPONENT ?
100 | getComponentView(view, token) :
101 | getNewView(view, token, createEffect)
102 | ;
103 |
104 | class View {
105 | constructor(token, shouldParse = true) {
106 | const [updates, content] = shouldParse ? parse(token) : [EMPTY, null];
107 | this._ = shouldParse && token.type === FRAGMENT;
108 | this.id = token.id;
109 | this.updates = updates;
110 | this.content = content;
111 | this.dispose = noop;
112 | }
113 | get $() {
114 | const {content, _} = this;
115 | if (_) {
116 | this._ = !_;
117 | return (this.content = asChildNodes(content)).childNodes;
118 | }
119 | return [content];
120 | }
121 | update(token) {
122 | for (const update of this.updates)
123 | update.call(this, token);
124 | }
125 | }
126 |
127 | const defaultView = new View({id: null}, false);
128 | const defaultEntry = {id: null, view: defaultView};
129 |
130 | const views = new WeakMap;
131 | let isToken;
132 |
133 | const tokens = new WeakMap;
134 | const parse = token => {
135 | let info = tokens.get(token.id);
136 | if (!info) {
137 | const updates = [];
138 | const content = mapToken(token, updates, [], EMPTY, false);
139 | tokens.set(token.id, info = [updates, content]);
140 | }
141 | const [updates, content] = info;
142 | return [updates.slice(), content.cloneNode(true)];
143 | };
144 |
145 | const setAttribute = (node, key, value, set) => {
146 | if (set)
147 | node.setAttribute(key, value);
148 | else
149 | node[key] = value;
150 | };
151 |
152 | const asAttribute = (node, key, value, prev, set) => {
153 | if (isSignal(value)) {
154 | const dispose = UDOMSAY + key;
155 | if (dispose in prev)
156 | prev[dispose]();
157 | prev[dispose] = effect(() => {
158 | setAttribute(node, key, getValue(value), set);
159 | });
160 | }
161 | else
162 | setAttribute(node, key, value, set);
163 | };
164 |
165 | const setProperty = (node, key, value, prev) => {
166 | if (considerPlugins && plugins.has(key))
167 | plugins.get(key)(node, value, prev);
168 | else if (prev[key] !== value) {
169 | prev[key] = value;
170 | switch (key) {
171 | case 'class':
172 | key += 'Name';
173 | case 'className':
174 | case 'textContent':
175 | asAttribute(node, key, value, prev, false);
176 | break;
177 | case 'ref':
178 | value.current = node;
179 | break;
180 | default:
181 | if (key.startsWith('on'))
182 | node[key.toLowerCase()] = value;
183 | else if (key in node)
184 | asAttribute(node, key, value, prev, false);
185 | else {
186 | if (value == null)
187 | node.removeAttribute(key);
188 | else
189 | asAttribute(node, key, value, prev, true);
190 | }
191 | break;
192 | }
193 | }
194 | };
195 |
196 | const handleAttributes = (a, c, i) => function (token) {
197 | const prev = {};
198 | const node = reachChild(c, this);
199 | (this.updates[i] = token => {
200 | const {attributes} = reachToken(c, token);
201 | for (const index of a) {
202 | const entry = attributes[index];
203 | const {type, value} = entry;
204 | if (type < 2)
205 | setProperty(node, entry.name, value, prev);
206 | else {
207 | for (const [name, v] of entries(value))
208 | setProperty(node, name, v, prev);
209 | }
210 | }
211 | })(token);
212 | };
213 |
214 | const handleArray = (c, i) => function (token) {
215 | let diffed = EMPTY, findIndex = true, index = -1;
216 | const node = reachChild(c, this);
217 | const keys = new Map;
218 | (this.updates[i] = token => {
219 | const {value} = reachToken(c, token);
220 | const diffing = [];
221 | for (let i = 0; i < value.length; i++) {
222 | const token = value[i];
223 | if (findIndex) {
224 | findIndex = !findIndex;
225 | index = token.attributes.findIndex(isKey);
226 | }
227 | const key = index < 0 ? i : token.attributes[index].value;
228 | let {id, view} = keys.get(key) || defaultEntry;
229 | if (id !== (token.id || (token.id = getChildrenID(node, i)))) {
230 | view = getView(view, token, false);
231 | keys.set(key, {id: token.id, view});
232 | }
233 | else
234 | view.update(token);
235 | diffing.push(...view.$);
236 | }
237 | if (diffing.length)
238 | diffed = differ(diffed, diffing, node);
239 | else if(diffed !== EMPTY) {
240 | const range = document.createRange();
241 | range.setStartBefore(diffed[0]);
242 | range.setEndAfter(diffed[diffed.length - 1]);
243 | range.deleteContents();
244 | keys.clear();
245 | diffed = EMPTY;
246 | findIndex = true;
247 | }
248 | })(token);
249 | };
250 |
251 | const handleComponent = (c, i) => function (token) {
252 | let diffed = EMPTY, view = defaultView;
253 | const node = reachChild(c, this);
254 | (this.updates[i] = token => {
255 | view = getComponentView(view, reachToken(c, token));
256 | diffed = differ(diffed, view.$, node);
257 | })(token);
258 | };
259 |
260 | const handleContent = (c, i) => function (token) {
261 | const node = reachChild(c, this);
262 | (this.updates[i] = token => {
263 | setData(node, reachToken(c, token).value);
264 | })(token);
265 | };
266 |
267 | const handleSignal = (c, i) => function (token) {
268 | let dispose = noop, signal, fx;
269 | const node = reachChild(c, this);
270 | const {value} = reachToken(c, token);
271 | const update = value => {
272 | if (signal !== value) {
273 | dispose();
274 | signal = value;
275 | dispose = effect(fx);
276 | }
277 | };
278 | if (isToken(getPeek(value))) {
279 | let diffed = EMPTY, view = defaultView;
280 | fx = () => {
281 | const token = getValue(signal);
282 | view = getView(view, token, false);
283 | diffed = differ(diffed, view.$, node);
284 | };
285 | }
286 | else {
287 | const t = text('');
288 | node.replaceWith(t);
289 | fx = () => { setData(t, getValue(signal)) };
290 | }
291 | this.updates[i] = token => update(reachToken(c, token).value);
292 | update(value);
293 | };
294 |
295 | const handleToken = (c, i) => function (token) {
296 | let diffed = EMPTY, view = defaultView, id = null;
297 | const node = reachChild(c, this);
298 | (this.updates[i] = token => {
299 | token = reachToken(c, token).value;
300 | if (id !== token.id) {
301 | id = token.id;
302 | // TODO: should this effect instead?
303 | view = getView(view, token, false);
304 | diffed = differ(diffed, view.$, node);
305 | }
306 | else if (token.type === COMPONENT)
307 | view.update(invoke(token));
308 | else
309 | view.update(token);
310 | })(token);
311 | };
312 |
313 | const addChildren = ({children}, updates, content, c, svg) => {
314 | for (let i = 0; i < children.length; i++) {
315 | const child = children[i];
316 | switch (child.type) {
317 | case STATIC:
318 | content.appendChild(text(child.value));
319 | break;
320 | default:
321 | content.appendChild(
322 | mapToken(children[i], updates, [], c.concat(i), svg)
323 | );
324 | break;
325 | }
326 | }
327 | };
328 |
329 | const mapToken = (token, updates, a, c, svg) => {
330 | let callback, content;
331 | const {length} = updates;
332 | type: switch (token.type) {
333 | case INTERPOLATION: {
334 | const {value} = token;
335 | switch (true) {
336 | case isToken(value):
337 | callback = handleToken;
338 | break;
339 | case isArray(value):
340 | callback = handleArray;
341 | break;
342 | case isSignal(value):
343 | callback = handleSignal;
344 | break;
345 | default: {
346 | content = text('');
347 | updates.push(handleContent(c, length));
348 | break type;
349 | }
350 | }
351 | }
352 | case COMPONENT: {
353 | content = comment();
354 | updates.push((callback || handleComponent)(c, length));
355 | break;
356 | }
357 | case ELEMENT: {
358 | const {attributes, name} = token;
359 | const args = [name];
360 | const attrs = [];
361 | for (let i = 0; i < attributes.length; i++) {
362 | const entry = attributes[i];
363 | if (entry.type === INTERPOLATION || entry.dynamic) {
364 | if (!isKey(entry))
365 | a.push(i);
366 | }
367 | else {
368 | if (entry.name === 'is')
369 | args.push({is: entry.value});
370 | attrs.push(entry);
371 | }
372 | }
373 | if (a.length)
374 | updates.push(handleAttributes(a, c, length));
375 | content = svg || (svg = name === 'svg') ?
376 | document.createElementNS('http://www.w3.org/2000/svg', ...args) :
377 | document.createElement(...args);
378 | for (const {name, value} of attrs)
379 | setAttribute(content, name, value, true);
380 | addChildren(token, updates, content, c, svg);
381 | break;
382 | }
383 | case FRAGMENT: {
384 | content = document.createDocumentFragment();
385 | addChildren(token, updates, content, c, svg);
386 | break;
387 | }
388 | }
389 | return content;
390 | };
391 |
392 | /**
393 | * Reveal some `token` content into a `DOM` element.
394 | * @param {() => Token | Token} what the token to render
395 | * @param {Element} where the DOM node to render such token
396 | */
397 | return (what, where) => {
398 | /** @type {Token} */
399 | const token = typeof what === 'function' ? what() : what;
400 | if (!isToken) isToken = isPrototypeOf.bind(getPrototypeOf(token));
401 | const view = getView(views.get(where) || defaultView, token, true);
402 | views.set(where, view);
403 | where.replaceChildren(...view.$);
404 | };
405 | };
406 |
--------------------------------------------------------------------------------
/cjs/package.json:
--------------------------------------------------------------------------------
1 | {"type":"commonjs"}
--------------------------------------------------------------------------------
/cjs/ssr.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /*! (c) Andrea Giammarchi - ISC */
3 |
4 | const {TEXT_ELEMENTS, VOID_ELEMENTS} = require('domconstants');
5 | const {escape} = require('./html-escaper.js');
6 |
7 | const EMPTY = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('@webreflection/empty/array'));
8 | const noop = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('@webreflection/empty/function'));
9 |
10 | const createRender = (m => /* c8 ignore start */ m.__esModule ? m.default : m /* c8 ignore stop */)(require('./index.js'));
11 |
12 | const cloneAttribute = ({name, value}) => ({name, value});
13 |
14 | const cloneNode = (current, newNode) => {
15 | for (const node of current) {
16 | switch (node.nodeType) {
17 | case 1:
18 | const element = new Element(node.name);
19 | if (node.attributes !== EMPTY)
20 | element.attributes = node.attributes.map(cloneAttribute);
21 | cloneNode(node, element);
22 | newNode.appendChild(element);
23 | break;
24 | case 3:
25 | newNode.appendChild(new Text(node.data));
26 | break;
27 | case 11:
28 | for (const inner of node)
29 | cloneNode(inner, newNode);
30 | break;
31 | }
32 | }
33 | return newNode;
34 | };
35 |
36 | class Text {
37 | constructor(data) {
38 | this.nodeType = 3;
39 | this.parentNode = null;
40 | this.data = data;
41 | }
42 | replaceWith(node) {
43 | const {parentNode} = this;
44 | parentNode.splice(
45 | parentNode.indexOf(this),
46 | 1,
47 | node
48 | );
49 | }
50 | remove() {
51 | Element.prototype.remove.call(this);
52 | }
53 | toString() {
54 | return TEXT_ELEMENTS.test(this.parentNode?.name) ?
55 | this.data : escape(this.data);
56 | }
57 | }
58 |
59 | class Node extends Array {
60 | get childNodes() {
61 | return this;
62 | }
63 | appendChild(node) {
64 | switch (node.nodeType) {
65 | case 1:
66 | case 3:
67 | node.parentNode = this;
68 | this.push(node);
69 | break;
70 | case 11:
71 | for (const inner of node)
72 | this.appendChild(inner);
73 | break;
74 | }
75 | return node;
76 | }
77 | replaceChild(newChild, oldChild) {
78 | this[this.indexOf(oldChild)] = newChild;
79 | newChild.parentNode = this;
80 | return oldChild;
81 | }
82 | }
83 |
84 | class Fragment extends Node {
85 | constructor() {
86 | super();
87 | this.nodeType = 11;
88 | this.parentNode = null;
89 | }
90 | cloneNode() {
91 | return cloneNode(this, new Fragment);
92 | }
93 | toString() {
94 | return this.join('');
95 | }
96 | }
97 |
98 | class Element extends Node {
99 | constructor(name) {
100 | super();
101 | this.nodeType = 1;
102 | this.name = name;
103 | this.attributes = EMPTY;
104 | }
105 | set className(value) {
106 | this.setAttribute('class', value);
107 | }
108 | set textContent(value) {
109 | for (const node of this.splice(0))
110 | node.parentNode = null;
111 | this.appendChild(new Text(value));
112 | }
113 | cloneNode() {
114 | return cloneNode(this, new Element(this.name));
115 | }
116 | remove() {
117 | const {parentNode} = this;
118 | if (parentNode) {
119 | this.parentNode = null;
120 | parentNode.splice(parentNode.indexOf(this), 1);
121 | }
122 | }
123 | removeAttribute() {}
124 | setAttribute(name, value) {
125 | if (this.attributes === EMPTY)
126 | this.attributes = [];
127 | this.attributes.push({name, value});
128 | }
129 | toString() {
130 | const {length, name, attributes} = this;
131 | const html = ['<', name];
132 | for (const {name, value} of attributes) {
133 | if (typeof value === 'boolean') {
134 | if (value)
135 | html.push(` ${name}`);
136 | }
137 | else if (value != null)
138 | html.push(` ${name}="${escape(value)}"`);
139 | }
140 | if (!length && VOID_ELEMENTS.test(name))
141 | html.push(' />');
142 | else
143 | html.push('>', ...this, `${name}>`);
144 | return html.join('');
145 | }
146 | }
147 |
148 | class Range {
149 | constructor() {
150 | this.before = null;
151 | this.after = null;
152 | }
153 | setStartBefore(node) {
154 | this.before = node;
155 | }
156 | setEndAfter(node) {
157 | this.after = node;
158 | }
159 | deleteContents() {
160 | const {parentNode} = this.before;
161 | const i = parentNode.indexOf(this.before);
162 | for (const node of parentNode.splice(i, parentNode.indexOf(this.after) - i + 1))
163 | node.parentNode = null;
164 | }
165 | }
166 |
167 | const document = {
168 | createTextNode: data => new Text(data),
169 | createComment: () => new Text(''),
170 | createDocumentFragment: () => new Fragment,
171 | createElementNS: name => new Element(name),
172 | createElement: (name, options) => {
173 | const element = new Element(name);
174 | if (options && options.is)
175 | element.setAttribute('is', options.is);
176 | return element;
177 | },
178 | createRange: () => new Range
179 | };
180 |
181 | const diff = (_, nodes, before) => {
182 | const {parentNode} = before;
183 | const i = parentNode.indexOf(before);
184 | for (const node of nodes)
185 | node.parentNode = parentNode;
186 | parentNode.splice(i, 0, ...nodes);
187 | };
188 |
189 | /**
190 | * @typedef {Object} RenderOptions utilities to use while rendering.
191 | * @prop {Document} [document] the default document to use. By default it's the global one.
192 | * @prop {[string, (node:Element, current:any, previous:any) => void][]} [plugins] a list of plugins to deal with,
193 | * used with attributes, example: `["stuff", (node, curr, prev) => { ... }]`
194 | * @prop {(fn:function) => function} [effect] an utility to create effects on components.
195 | * It must return a dispose utility to drop previous effect.
196 | * @prop {(s:any) => any} [getPeek] an utility to retrieve a Signal value without side-effects.
197 | * @prop {(s:any) => any} [getValue] an utility to retrieve a Signal value.
198 | * @prop {(s:any) => boolean} [isSignal] an utility to know if a value is a Signal.
199 | * @prop {function} [Signal] an optional signal constructor used to trap-check.
200 | * `isSignal(ref)` utility whenever the `isSignal` field has not been provided.
201 | */
202 |
203 | /**
204 | * Return a `render(what, where)` utility able to deal with provided options.
205 | * @param {RenderOptions} options
206 | */
207 | module.exports = (options = {}) => {
208 | const getPeek = options.getPeek || (s => s.peek());
209 | const render = createRender({
210 | document, diff, getPeek,
211 | plugins: options.plugins || EMPTY,
212 | effect: (fn => (fn(), noop)),
213 | getValue: getPeek,
214 | isSignal: options.isSignal,
215 | Signal: options.Signal
216 | });
217 | return (what, where) => {
218 | render(what, {
219 | replaceChildren(...nodes) {
220 | if (typeof where === 'function') {
221 | let html = nodes.join('');
222 | if (/^(|]+?>)/.test(html))
223 | html = '' + html;
224 | where(html);
225 | }
226 | else {
227 | for (const node of nodes)
228 | where.write(node.toString());
229 | }
230 | }
231 | });
232 | };
233 | };
234 |
--------------------------------------------------------------------------------
/es.js:
--------------------------------------------------------------------------------
1 | var e=(0,Object.freeze)([]),t=()=>{};const s=(e,t,s)=>{const{parentNode:n}=s,i=t.length;let o=e.length,a=i,c=0,l=0,r=null;for(;cs-l){const i=e[c];for(;le[t],p=({children:e},t)=>e[t],h=({value:e,properties:t,children:s})=>e(t,...s),g=({name:e})=>"key"===e,v=(e,{content:t})=>e.reduce(f,t),b=(e,t)=>e.reduce(p,t),N=(e,t)=>{const s=null==t?"":String(t);s!==e.data&&(e.data=s)};
2 | /*! (c) Andrea Giammarchi - ISC */var m=(f={})=>{const p=f.document||globalThis.document,m=new Map(f.plugins||[]),T=!!m.size,w=f.diff||s,y=f.effect||(e=>(e(),t)),E=f.getPeek||(e=>e.peek()),k=f.getValue||(e=>e.value),O=f.isSignal||(f.Signal?a.bind(f.Signal.prototype):()=>!1),A=e=>p.createTextNode(e),C=(e,t)=>{let s=B.get(e);return s||B.set(e,s={}),s[t]||(s[t]={})},M=(e,t)=>{e.dispose();const s=y((()=>{const s=h(t);s.id!==e.id?e=S(e,s,!1):e.update(s)}));return e.dispose=s,e},S=(e,t,s)=>t.type===c?M(e,t):((e,t,s)=>(e.dispose(),e=new I(t),s?e.dispose=y((()=>e.update(t))):e.update(t),e))(e,t,s);class I{constructor(s,n=!0){const[i,o]=n?L(s):[e,null];this._=n&&s.type===r,this.id=s.id,this.updates=i,this.content=o,this.dispose=t}get $(){const{content:e,_:t}=this;return t?(this._=!t,(this.content=(({childNodes:e})=>({childNodes:[...e]}))(e)).childNodes):[e]}update(e){for(const t of this.updates)t.call(this,e)}}const x=new I({id:null},!1),P={id:null,view:x},B=new WeakMap;let R;const $=new WeakMap,L=t=>{let s=$.get(t.id);if(!s){const n=[],i=H(t,n,[],e,!1);$.set(t.id,s=[n,i])}const[n,i]=s;return[n.slice(),i.cloneNode(!0)]},W=(e,t,s,n)=>{n?e.setAttribute(t,s):e[t]=s},j=(e,t,s,n,i)=>{if(O(s)){const o="🙊"+t;o in n&&n[o](),n[o]=y((()=>{W(e,t,k(s),i)}))}else W(e,t,s,i)},F=(e,t,s,n)=>{if(T&&m.has(t))m.get(t)(e,s,n);else if(n[t]!==s)switch(n[t]=s,t){case"class":t+="Name";case"className":case"textContent":j(e,t,s,n,!1);break;case"ref":s.current=e;break;default:t.startsWith("on")?e[t.toLowerCase()]=s:t in e?j(e,t,s,n,!1):null==s?e.removeAttribute(t):j(e,t,s,n,!0)}},_=(e,t,s)=>function(n){const o={},a=v(t,this);(this.updates[s]=s=>{const{attributes:n}=b(t,s);for(const t of e){const e=n[t],{type:s,value:c}=e;if(s<2)F(a,e.name,c,o);else for(const[e,t]of i(c))F(a,e,t,o)}})(n)},z=(t,s)=>function(n){let i=e,o=!0,a=-1;const c=v(t,this),l=new Map;(this.updates[s]=s=>{const{value:n}=b(t,s),r=[];for(let e=0;efunction(n){let i=e,o=x;const a=v(t,this);(this.updates[s]=e=>{o=M(o,b(t,e)),i=w(i,o.$,a)})(n)},D=(e,t)=>function(s){const n=v(e,this);(this.updates[t]=t=>{N(n,b(e,t).value)})(s)},U=(s,n)=>function(i){let o,a,c=t;const l=v(s,this),{value:r}=b(s,i),u=e=>{o!==e&&(c(),o=e,c=y(a))};if(R(E(r))){let t=e,s=x;a=()=>{const e=k(o);s=S(s,e,!1),t=w(t,s.$,l)}}else{const e=A("");l.replaceWith(e),a=()=>{N(e,k(o))}}this.updates[n]=e=>u(b(s,e).value),u(r)},V=(t,s)=>function(n){let i=e,o=x,a=null;const l=v(t,this);(this.updates[s]=e=>{e=b(t,e).value,a!==e.id?(a=e.id,o=S(o,e,!1),i=w(i,o.$,l)):e.type===c?o.update(h(e)):o.update(e)})(n)},q=({children:e},t,s,n,i)=>{for(let o=0;o{let a,d;const{length:f}=t;e:switch(e.type){case u:{const{value:s}=e;switch(!0){case R(s):a=V;break;case n(s):a=z;break;case O(s):a=U;break;default:d=A(""),t.push(D(i,f));break e}}case c:d=p.createComment("🙊"),t.push((a||G)(i,f));break;case l:{const{attributes:n,name:a}=e,c=[a],l=[];for(let e=0;e{const s="function"==typeof e?e():e;R||(R=a.bind(o(s)));const n=S(B.get(t)||x,s,!0);B.set(t,n),t.replaceChildren(...n.$)}};export{m as default};
3 |
--------------------------------------------------------------------------------
/esm/diff.js:
--------------------------------------------------------------------------------
1 | /* (c) Andrea Giammarchi - ISC */
2 | // @see https://github.com/WebReflection/udomdiff
3 | export const diff = (a, b, before) => {
4 | const {parentNode} = before;
5 | const bLength = b.length;
6 | let aEnd = a.length;
7 | let bEnd = bLength;
8 | let aStart = 0;
9 | let bStart = 0;
10 | let map = null;
11 | while (aStart < aEnd || bStart < bEnd) {
12 | // append head, tail, or nodes in between: fast path
13 | if (aEnd === aStart) {
14 | // we could be in a situation where the rest of nodes that
15 | // need to be added are not at the end, and in such case
16 | // the node to `insertBefore`, if the index is more than 0
17 | // must be retrieved, otherwise it's gonna be the first item.
18 | const node = bEnd < bLength ?
19 | (bStart ?
20 | b[bStart - 1].nextSibling :
21 | b[bEnd - bStart]) :
22 | before;
23 | while (bStart < bEnd)
24 | parentNode.insertBefore(b[bStart++], node);
25 | }
26 | // remove head or tail: fast path
27 | else if (bEnd === bStart) {
28 | while (aStart < aEnd) {
29 | // remove the node only if it's unknown or not live
30 | if (!map || !map.has(a[aStart]))
31 | a[aStart].remove();
32 | aStart++;
33 | }
34 | }
35 | // same node: fast path
36 | else if (a[aStart] === b[bStart]) {
37 | aStart++;
38 | bStart++;
39 | }
40 | // same tail: fast path
41 | else if (a[aEnd - 1] === b[bEnd - 1]) {
42 | aEnd--;
43 | bEnd--;
44 | }
45 | // The once here single last swap "fast path" has been removed in v1.1.0
46 | // https://github.com/WebReflection/udomdiff/blob/single-final-swap/esm/index.js#L69-L85
47 | // reverse swap: also fast path
48 | else if (
49 | a[aStart] === b[bEnd - 1] &&
50 | b[bStart] === a[aEnd - 1]
51 | ) {
52 | // this is a "shrink" operation that could happen in these cases:
53 | // [1, 2, 3, 4, 5]
54 | // [1, 4, 3, 2, 5]
55 | // or asymmetric too
56 | // [1, 2, 3, 4, 5]
57 | // [1, 2, 3, 5, 6, 4]
58 | const node = a[--aEnd].nextSibling;
59 | parentNode.insertBefore(
60 | b[bStart++],
61 | a[aStart++].nextSibling
62 | );
63 | parentNode.insertBefore(b[--bEnd], node);
64 | // mark the future index as identical (yeah, it's dirty, but cheap 👍)
65 | // The main reason to do this, is that when a[aEnd] will be reached,
66 | // the loop will likely be on the fast path, as identical to b[bEnd].
67 | // In the best case scenario, the next loop will skip the tail,
68 | // but in the worst one, this node will be considered as already
69 | // processed, bailing out pretty quickly from the map index check
70 | a[aEnd] = b[bEnd];
71 | }
72 | // map based fallback, "slow" path
73 | else {
74 | // the map requires an O(bEnd - bStart) operation once
75 | // to store all future nodes indexes for later purposes.
76 | // In the worst case scenario, this is a full O(N) cost,
77 | // and such scenario happens at least when all nodes are different,
78 | // but also if both first and last items of the lists are different
79 | if (!map) {
80 | map = new Map;
81 | let i = bStart;
82 | while (i < bEnd)
83 | map.set(b[i], i++);
84 | }
85 | // if it's a future node, hence it needs some handling
86 | if (map.has(a[aStart])) {
87 | // grab the index of such node, 'cause it might have been processed
88 | const index = map.get(a[aStart]);
89 | // if it's not already processed, look on demand for the next LCS
90 | if (bStart < index && index < bEnd) {
91 | let i = aStart;
92 | // counts the amount of nodes that are the same in the future
93 | let sequence = 1;
94 | while (++i < aEnd && i < bEnd && map.get(a[i]) === (index + sequence))
95 | sequence++;
96 | // effort decision here: if the sequence is longer than replaces
97 | // needed to reach such sequence, which would brings again this loop
98 | // to the fast path, prepend the difference before a sequence,
99 | // and move only the future list index forward, so that aStart
100 | // and bStart will be aligned again, hence on the fast path.
101 | // An example considering aStart and bStart are both 0:
102 | // a: [1, 2, 3, 4]
103 | // b: [7, 1, 2, 3, 6]
104 | // this would place 7 before 1 and, from that time on, 1, 2, and 3
105 | // will be processed at zero cost
106 | if (sequence > (index - bStart)) {
107 | const node = a[aStart];
108 | while (bStart < index)
109 | parentNode.insertBefore(b[bStart++], node);
110 | }
111 | // if the effort wasn't good enough, fallback to a replace,
112 | // moving both source and target indexes forward, hoping that some
113 | // similar node will be found later on, to go back to the fast path
114 | else {
115 | parentNode.replaceChild(
116 | b[bStart++],
117 | a[aStart++]
118 | );
119 | }
120 | }
121 | // otherwise move the source forward, 'cause there's nothing to do
122 | else
123 | aStart++;
124 | }
125 | // this node has no meaning in the future list, so it's more than safe
126 | // to remove it, and check the next live node out instead, meaning
127 | // that only the live list index should be forwarded
128 | else
129 | a[aStart++].remove();
130 | }
131 | }
132 | return b;
133 | };
134 |
--------------------------------------------------------------------------------
/esm/dist/preact.js:
--------------------------------------------------------------------------------
1 | import {Signal, effect} from '@preact/signals-core';
2 | export * from '@preact/signals-core';
3 |
4 | import $ from '../index.js';
5 | export const createRender = (options = {}) =>
6 | $({...options, Signal, effect, isSignal: void 0});
7 |
--------------------------------------------------------------------------------
/esm/dist/signal.js:
--------------------------------------------------------------------------------
1 | import {Signal, effect} from '@webreflection/signal';
2 | export * from '@webreflection/signal';
3 |
4 | import $ from '../index.js';
5 | export const createRender = (options = {}) =>
6 | $({...options, Signal, effect, isSignal: void 0});
7 |
--------------------------------------------------------------------------------
/esm/dist/solid.js:
--------------------------------------------------------------------------------
1 | import {createSignal as $, createEffect, untrack} from 'solid-js';
2 | export * from 'solid-js';
3 |
4 | import _ from '../index.js';
5 |
6 | const signals = new WeakSet;
7 | const noop = () => {};
8 |
9 | export const createSignal = (value, ...rest) => {
10 | const [signal, update] = $(value, ...rest);
11 | signals.add(signal);
12 | return [signal, update];
13 | };
14 |
15 | export const createRender = (options = {}) => _({
16 | ...options,
17 | effect: (...args) => (createEffect(...args), noop),
18 | getPeek: s => untrack(s),
19 | getValue: s => s(),
20 | isSignal: s => signals.has(s)
21 | });
22 |
--------------------------------------------------------------------------------
/esm/dist/usignal.js:
--------------------------------------------------------------------------------
1 | import {Signal, effect} from 'usignal';
2 | export * from 'usignal';
3 |
4 | import $ from '../index.js';
5 | export const createRender = (options = {}) =>
6 | $({...options, Signal, effect, isSignal: void 0});
7 |
--------------------------------------------------------------------------------
/esm/html-escaper.js:
--------------------------------------------------------------------------------
1 | // @see https://github.com/WebReflection/html-escaper#readme
2 | const es = /[&<>'"]/g;
3 | const cape = pe => esca[pe];
4 | const esca = {
5 | '&': '&',
6 | '<': '<',
7 | '>': '>',
8 | "'": ''',
9 | '"': '"'
10 | };
11 |
12 | const {replace} = '';
13 |
14 | export const escape = value => replace.call(value, es, cape);
15 |
--------------------------------------------------------------------------------
/esm/index.js:
--------------------------------------------------------------------------------
1 | /*! (c) Andrea Giammarchi - ISC */
2 |
3 | import EMPTY from '@webreflection/empty/array';
4 | import noop from '@webreflection/empty/function';
5 |
6 | import {Token} from '@ungap/esx';
7 | import {diff} from './diff.js';
8 |
9 | const {isArray} = Array;
10 | const {entries, getPrototypeOf, prototype: {isPrototypeOf}} = Object;
11 |
12 | const {
13 | COMPONENT,
14 | ELEMENT,
15 | FRAGMENT,
16 | INTERPOLATION,
17 | STATIC
18 | } = Token;
19 |
20 | const UDOMSAY = '🙊';
21 |
22 | // generic utils
23 | const asChildNodes = ({childNodes}) => ({childNodes: [...childNodes]});
24 | const getChild = ({childNodes}, i) => childNodes[i];
25 | const getToken = ({children}, i) => children[i];
26 | const invoke = ({value, properties, children}) => value(properties, ...children);
27 | const isKey = ({name}) => name === 'key';
28 | const reachChild = (c, {content}) => c.reduce(getChild, content);
29 | const reachToken = (c, token) => c.reduce(getToken, token);
30 | const setData = (node, value) => {
31 | const data = value == null ? '' : String(value);
32 | if (data !== node.data)
33 | node.data = data;
34 | };
35 |
36 | /**
37 | * @typedef {Object} RenderOptions utilities to use while rendering.
38 | * @prop {Document} [document] the default document to use. By default it's the global one.
39 | * @prop {[string, (node:Element, current:any, previous:any) => void][]} [plugins] a list of plugins to deal with,
40 | * used with attributes, example: `["stuff", (node, curr, prev) => { ... }]`
41 | * @prop {(fn:function) => function} [effect] an utility to create effects on components.
42 | * It must return a dispose utility to drop previous effect.
43 | * @prop {(s:any) => any} [getPeek] an utility to retrieve a Signal value without side-effects.
44 | * @prop {(s:any) => any} [getValue] an utility to retrieve a Signal value.
45 | * @prop {(s:any) => boolean} [isSignal] an utility to know if a value is a Signal.
46 | * @prop {function} [Signal] an optional signal constructor used to trap-check.
47 | * @prop {function} [diff] an optional function to diff nodes, not available in SSR.
48 | * `isSignal(ref)` utility whenever the `isSignal` field has not been provided.
49 | */
50 |
51 | /**
52 | * Return a `render(what, where)` utility able to deal with provided options.
53 | * @param {RenderOptions} options
54 | */
55 | export default (options = {}) => {
56 | const document = options.document || globalThis.document;
57 | const plugins = new Map(options.plugins || []);
58 | const considerPlugins = !!plugins.size;
59 | const differ = options.diff || diff;
60 | const effect = options.effect || (fn => (fn(), noop));
61 | const getPeek = options.getPeek || (s => s.peek());
62 | const getValue = options.getValue || (s => s.value);
63 | const isSignal = options.isSignal || (
64 | options.Signal ?
65 | isPrototypeOf.bind(options.Signal.prototype) :
66 | () => false
67 | );
68 |
69 | const text = value => document.createTextNode(value);
70 | const comment = () => document.createComment(UDOMSAY);
71 |
72 | const getChildrenID = (node, i) => {
73 | let IDs = views.get(node);
74 | if (!IDs) views.set(node, IDs = {});
75 | return IDs[i] || (IDs[i] = {});
76 | };
77 | const getComponentView = (view, component) => {
78 | view.dispose();
79 | const dispose = effect(() => {
80 | const token = invoke(component);
81 | if (token.id !== view.id)
82 | view = getView(view, token, false);
83 | else
84 | view.update(token);
85 | });
86 | view.dispose = dispose;
87 | return view;
88 | };
89 | const getNewView = (view, token, createEffect) => {
90 | view.dispose();
91 | view = new View(token);
92 | if (createEffect)
93 | view.dispose = effect(() => view.update(token));
94 | else
95 | view.update(token);
96 | return view;
97 | };
98 | const getView = (view, token, createEffect) => token.type === COMPONENT ?
99 | getComponentView(view, token) :
100 | getNewView(view, token, createEffect)
101 | ;
102 |
103 | class View {
104 | constructor(token, shouldParse = true) {
105 | const [updates, content] = shouldParse ? parse(token) : [EMPTY, null];
106 | this._ = shouldParse && token.type === FRAGMENT;
107 | this.id = token.id;
108 | this.updates = updates;
109 | this.content = content;
110 | this.dispose = noop;
111 | }
112 | get $() {
113 | const {content, _} = this;
114 | if (_) {
115 | this._ = !_;
116 | return (this.content = asChildNodes(content)).childNodes;
117 | }
118 | return [content];
119 | }
120 | update(token) {
121 | for (const update of this.updates)
122 | update.call(this, token);
123 | }
124 | }
125 |
126 | const defaultView = new View({id: null}, false);
127 | const defaultEntry = {id: null, view: defaultView};
128 |
129 | const views = new WeakMap;
130 | let isToken;
131 |
132 | const tokens = new WeakMap;
133 | const parse = token => {
134 | let info = tokens.get(token.id);
135 | if (!info) {
136 | const updates = [];
137 | const content = mapToken(token, updates, [], EMPTY, false);
138 | tokens.set(token.id, info = [updates, content]);
139 | }
140 | const [updates, content] = info;
141 | return [updates.slice(), content.cloneNode(true)];
142 | };
143 |
144 | const setAttribute = (node, key, value, set) => {
145 | if (set)
146 | node.setAttribute(key, value);
147 | else
148 | node[key] = value;
149 | };
150 |
151 | const asAttribute = (node, key, value, prev, set) => {
152 | if (isSignal(value)) {
153 | const dispose = UDOMSAY + key;
154 | if (dispose in prev)
155 | prev[dispose]();
156 | prev[dispose] = effect(() => {
157 | setAttribute(node, key, getValue(value), set);
158 | });
159 | }
160 | else
161 | setAttribute(node, key, value, set);
162 | };
163 |
164 | const setProperty = (node, key, value, prev) => {
165 | if (considerPlugins && plugins.has(key))
166 | plugins.get(key)(node, value, prev);
167 | else if (prev[key] !== value) {
168 | prev[key] = value;
169 | switch (key) {
170 | case 'class':
171 | key += 'Name';
172 | case 'className':
173 | case 'textContent':
174 | asAttribute(node, key, value, prev, false);
175 | break;
176 | case 'ref':
177 | value.current = node;
178 | break;
179 | default:
180 | if (key.startsWith('on'))
181 | node[key.toLowerCase()] = value;
182 | else if (key in node)
183 | asAttribute(node, key, value, prev, false);
184 | else {
185 | if (value == null)
186 | node.removeAttribute(key);
187 | else
188 | asAttribute(node, key, value, prev, true);
189 | }
190 | break;
191 | }
192 | }
193 | };
194 |
195 | const handleAttributes = (a, c, i) => function (token) {
196 | const prev = {};
197 | const node = reachChild(c, this);
198 | (this.updates[i] = token => {
199 | const {attributes} = reachToken(c, token);
200 | for (const index of a) {
201 | const entry = attributes[index];
202 | const {type, value} = entry;
203 | if (type < 2)
204 | setProperty(node, entry.name, value, prev);
205 | else {
206 | for (const [name, v] of entries(value))
207 | setProperty(node, name, v, prev);
208 | }
209 | }
210 | })(token);
211 | };
212 |
213 | const handleArray = (c, i) => function (token) {
214 | let diffed = EMPTY, findIndex = true, index = -1;
215 | const node = reachChild(c, this);
216 | const keys = new Map;
217 | (this.updates[i] = token => {
218 | const {value} = reachToken(c, token);
219 | const diffing = [];
220 | for (let i = 0; i < value.length; i++) {
221 | const token = value[i];
222 | if (findIndex) {
223 | findIndex = !findIndex;
224 | index = token.attributes.findIndex(isKey);
225 | }
226 | const key = index < 0 ? i : token.attributes[index].value;
227 | let {id, view} = keys.get(key) || defaultEntry;
228 | if (id !== (token.id || (token.id = getChildrenID(node, i)))) {
229 | view = getView(view, token, false);
230 | keys.set(key, {id: token.id, view});
231 | }
232 | else
233 | view.update(token);
234 | diffing.push(...view.$);
235 | }
236 | if (diffing.length)
237 | diffed = differ(diffed, diffing, node);
238 | else if(diffed !== EMPTY) {
239 | const range = document.createRange();
240 | range.setStartBefore(diffed[0]);
241 | range.setEndAfter(diffed[diffed.length - 1]);
242 | range.deleteContents();
243 | keys.clear();
244 | diffed = EMPTY;
245 | findIndex = true;
246 | }
247 | })(token);
248 | };
249 |
250 | const handleComponent = (c, i) => function (token) {
251 | let diffed = EMPTY, view = defaultView;
252 | const node = reachChild(c, this);
253 | (this.updates[i] = token => {
254 | view = getComponentView(view, reachToken(c, token));
255 | diffed = differ(diffed, view.$, node);
256 | })(token);
257 | };
258 |
259 | const handleContent = (c, i) => function (token) {
260 | const node = reachChild(c, this);
261 | (this.updates[i] = token => {
262 | setData(node, reachToken(c, token).value);
263 | })(token);
264 | };
265 |
266 | const handleSignal = (c, i) => function (token) {
267 | let dispose = noop, signal, fx;
268 | const node = reachChild(c, this);
269 | const {value} = reachToken(c, token);
270 | const update = value => {
271 | if (signal !== value) {
272 | dispose();
273 | signal = value;
274 | dispose = effect(fx);
275 | }
276 | };
277 | if (isToken(getPeek(value))) {
278 | let diffed = EMPTY, view = defaultView;
279 | fx = () => {
280 | const token = getValue(signal);
281 | view = getView(view, token, false);
282 | diffed = differ(diffed, view.$, node);
283 | };
284 | }
285 | else {
286 | const t = text('');
287 | node.replaceWith(t);
288 | fx = () => { setData(t, getValue(signal)) };
289 | }
290 | this.updates[i] = token => update(reachToken(c, token).value);
291 | update(value);
292 | };
293 |
294 | const handleToken = (c, i) => function (token) {
295 | let diffed = EMPTY, view = defaultView, id = null;
296 | const node = reachChild(c, this);
297 | (this.updates[i] = token => {
298 | token = reachToken(c, token).value;
299 | if (id !== token.id) {
300 | id = token.id;
301 | // TODO: should this effect instead?
302 | view = getView(view, token, false);
303 | diffed = differ(diffed, view.$, node);
304 | }
305 | else if (token.type === COMPONENT)
306 | view.update(invoke(token));
307 | else
308 | view.update(token);
309 | })(token);
310 | };
311 |
312 | const addChildren = ({children}, updates, content, c, svg) => {
313 | for (let i = 0; i < children.length; i++) {
314 | const child = children[i];
315 | switch (child.type) {
316 | case STATIC:
317 | content.appendChild(text(child.value));
318 | break;
319 | default:
320 | content.appendChild(
321 | mapToken(children[i], updates, [], c.concat(i), svg)
322 | );
323 | break;
324 | }
325 | }
326 | };
327 |
328 | const mapToken = (token, updates, a, c, svg) => {
329 | let callback, content;
330 | const {length} = updates;
331 | type: switch (token.type) {
332 | case INTERPOLATION: {
333 | const {value} = token;
334 | switch (true) {
335 | case isToken(value):
336 | callback = handleToken;
337 | break;
338 | case isArray(value):
339 | callback = handleArray;
340 | break;
341 | case isSignal(value):
342 | callback = handleSignal;
343 | break;
344 | default: {
345 | content = text('');
346 | updates.push(handleContent(c, length));
347 | break type;
348 | }
349 | }
350 | }
351 | case COMPONENT: {
352 | content = comment();
353 | updates.push((callback || handleComponent)(c, length));
354 | break;
355 | }
356 | case ELEMENT: {
357 | const {attributes, name} = token;
358 | const args = [name];
359 | const attrs = [];
360 | for (let i = 0; i < attributes.length; i++) {
361 | const entry = attributes[i];
362 | if (entry.type === INTERPOLATION || entry.dynamic) {
363 | if (!isKey(entry))
364 | a.push(i);
365 | }
366 | else {
367 | if (entry.name === 'is')
368 | args.push({is: entry.value});
369 | attrs.push(entry);
370 | }
371 | }
372 | if (a.length)
373 | updates.push(handleAttributes(a, c, length));
374 | content = svg || (svg = name === 'svg') ?
375 | document.createElementNS('http://www.w3.org/2000/svg', ...args) :
376 | document.createElement(...args);
377 | for (const {name, value} of attrs)
378 | setAttribute(content, name, value, true);
379 | addChildren(token, updates, content, c, svg);
380 | break;
381 | }
382 | case FRAGMENT: {
383 | content = document.createDocumentFragment();
384 | addChildren(token, updates, content, c, svg);
385 | break;
386 | }
387 | }
388 | return content;
389 | };
390 |
391 | /**
392 | * Reveal some `token` content into a `DOM` element.
393 | * @param {() => Token | Token} what the token to render
394 | * @param {Element} where the DOM node to render such token
395 | */
396 | return (what, where) => {
397 | /** @type {Token} */
398 | const token = typeof what === 'function' ? what() : what;
399 | if (!isToken) isToken = isPrototypeOf.bind(getPrototypeOf(token));
400 | const view = getView(views.get(where) || defaultView, token, true);
401 | views.set(where, view);
402 | where.replaceChildren(...view.$);
403 | };
404 | };
405 |
--------------------------------------------------------------------------------
/esm/ssr.js:
--------------------------------------------------------------------------------
1 | /*! (c) Andrea Giammarchi - ISC */
2 |
3 | import {TEXT_ELEMENTS, VOID_ELEMENTS} from 'domconstants';
4 | import {escape} from './html-escaper.js';
5 |
6 | import EMPTY from '@webreflection/empty/array';
7 | import noop from '@webreflection/empty/function';
8 |
9 | import createRender from './index.js';
10 |
11 | const cloneAttribute = ({name, value}) => ({name, value});
12 |
13 | const cloneNode = (current, newNode) => {
14 | for (const node of current) {
15 | switch (node.nodeType) {
16 | case 1:
17 | const element = new Element(node.name);
18 | if (node.attributes !== EMPTY)
19 | element.attributes = node.attributes.map(cloneAttribute);
20 | cloneNode(node, element);
21 | newNode.appendChild(element);
22 | break;
23 | case 3:
24 | newNode.appendChild(new Text(node.data));
25 | break;
26 | case 11:
27 | for (const inner of node)
28 | cloneNode(inner, newNode);
29 | break;
30 | }
31 | }
32 | return newNode;
33 | };
34 |
35 | class Text {
36 | constructor(data) {
37 | this.nodeType = 3;
38 | this.parentNode = null;
39 | this.data = data;
40 | }
41 | replaceWith(node) {
42 | const {parentNode} = this;
43 | parentNode.splice(
44 | parentNode.indexOf(this),
45 | 1,
46 | node
47 | );
48 | }
49 | remove() {
50 | Element.prototype.remove.call(this);
51 | }
52 | toString() {
53 | return TEXT_ELEMENTS.test(this.parentNode?.name) ?
54 | this.data : escape(this.data);
55 | }
56 | }
57 |
58 | class Node extends Array {
59 | get childNodes() {
60 | return this;
61 | }
62 | appendChild(node) {
63 | switch (node.nodeType) {
64 | case 1:
65 | case 3:
66 | node.parentNode = this;
67 | this.push(node);
68 | break;
69 | case 11:
70 | for (const inner of node)
71 | this.appendChild(inner);
72 | break;
73 | }
74 | return node;
75 | }
76 | replaceChild(newChild, oldChild) {
77 | this[this.indexOf(oldChild)] = newChild;
78 | newChild.parentNode = this;
79 | return oldChild;
80 | }
81 | }
82 |
83 | class Fragment extends Node {
84 | constructor() {
85 | super();
86 | this.nodeType = 11;
87 | this.parentNode = null;
88 | }
89 | cloneNode() {
90 | return cloneNode(this, new Fragment);
91 | }
92 | toString() {
93 | return this.join('');
94 | }
95 | }
96 |
97 | class Element extends Node {
98 | constructor(name) {
99 | super();
100 | this.nodeType = 1;
101 | this.name = name;
102 | this.attributes = EMPTY;
103 | }
104 | set className(value) {
105 | this.setAttribute('class', value);
106 | }
107 | set textContent(value) {
108 | for (const node of this.splice(0))
109 | node.parentNode = null;
110 | this.appendChild(new Text(value));
111 | }
112 | cloneNode() {
113 | return cloneNode(this, new Element(this.name));
114 | }
115 | remove() {
116 | const {parentNode} = this;
117 | if (parentNode) {
118 | this.parentNode = null;
119 | parentNode.splice(parentNode.indexOf(this), 1);
120 | }
121 | }
122 | removeAttribute() {}
123 | setAttribute(name, value) {
124 | if (this.attributes === EMPTY)
125 | this.attributes = [];
126 | this.attributes.push({name, value});
127 | }
128 | toString() {
129 | const {length, name, attributes} = this;
130 | const html = ['<', name];
131 | for (const {name, value} of attributes) {
132 | if (typeof value === 'boolean') {
133 | if (value)
134 | html.push(` ${name}`);
135 | }
136 | else if (value != null)
137 | html.push(` ${name}="${escape(value)}"`);
138 | }
139 | if (!length && VOID_ELEMENTS.test(name))
140 | html.push(' />');
141 | else
142 | html.push('>', ...this, `${name}>`);
143 | return html.join('');
144 | }
145 | }
146 |
147 | class Range {
148 | constructor() {
149 | this.before = null;
150 | this.after = null;
151 | }
152 | setStartBefore(node) {
153 | this.before = node;
154 | }
155 | setEndAfter(node) {
156 | this.after = node;
157 | }
158 | deleteContents() {
159 | const {parentNode} = this.before;
160 | const i = parentNode.indexOf(this.before);
161 | for (const node of parentNode.splice(i, parentNode.indexOf(this.after) - i + 1))
162 | node.parentNode = null;
163 | }
164 | }
165 |
166 | const document = {
167 | createTextNode: data => new Text(data),
168 | createComment: () => new Text(''),
169 | createDocumentFragment: () => new Fragment,
170 | createElementNS: name => new Element(name),
171 | createElement: (name, options) => {
172 | const element = new Element(name);
173 | if (options && options.is)
174 | element.setAttribute('is', options.is);
175 | return element;
176 | },
177 | createRange: () => new Range
178 | };
179 |
180 | const diff = (_, nodes, before) => {
181 | const {parentNode} = before;
182 | const i = parentNode.indexOf(before);
183 | for (const node of nodes)
184 | node.parentNode = parentNode;
185 | parentNode.splice(i, 0, ...nodes);
186 | };
187 |
188 | /**
189 | * @typedef {Object} RenderOptions utilities to use while rendering.
190 | * @prop {Document} [document] the default document to use. By default it's the global one.
191 | * @prop {[string, (node:Element, current:any, previous:any) => void][]} [plugins] a list of plugins to deal with,
192 | * used with attributes, example: `["stuff", (node, curr, prev) => { ... }]`
193 | * @prop {(fn:function) => function} [effect] an utility to create effects on components.
194 | * It must return a dispose utility to drop previous effect.
195 | * @prop {(s:any) => any} [getPeek] an utility to retrieve a Signal value without side-effects.
196 | * @prop {(s:any) => any} [getValue] an utility to retrieve a Signal value.
197 | * @prop {(s:any) => boolean} [isSignal] an utility to know if a value is a Signal.
198 | * @prop {function} [Signal] an optional signal constructor used to trap-check.
199 | * `isSignal(ref)` utility whenever the `isSignal` field has not been provided.
200 | */
201 |
202 | /**
203 | * Return a `render(what, where)` utility able to deal with provided options.
204 | * @param {RenderOptions} options
205 | */
206 | export default (options = {}) => {
207 | const getPeek = options.getPeek || (s => s.peek());
208 | const render = createRender({
209 | document, diff, getPeek,
210 | plugins: options.plugins || EMPTY,
211 | effect: (fn => (fn(), noop)),
212 | getValue: getPeek,
213 | isSignal: options.isSignal,
214 | Signal: options.Signal
215 | });
216 | return (what, where) => {
217 | render(what, {
218 | replaceChildren(...nodes) {
219 | if (typeof where === 'function') {
220 | let html = nodes.join('');
221 | if (/^(|]+?>)/.test(html))
222 | html = '' + html;
223 | where(html);
224 | }
225 | else {
226 | for (const node of nodes)
227 | where.write(node.toString());
228 | }
229 | }
230 | });
231 | };
232 | };
233 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var freeze = Object.freeze;
2 |
3 | var EMPTY = freeze([]);
4 |
5 | var noop = () => {};
6 |
7 | /** (c) Andrea Giammarchi - ISC */
8 |
9 | class Token {
10 | static ATTRIBUTE = 1;
11 | static COMPONENT = 2;
12 | static ELEMENT = 3;
13 | static FRAGMENT = 4;
14 | static INTERPOLATION = 5;
15 | static STATIC = 6;
16 | get properties() {
17 | const {attributes} = this;
18 | if (attributes.length) {
19 | const properties = {};
20 | for (const entry of attributes) {
21 | if (entry.type < 2)
22 | properties[entry.name] = entry.value;
23 | else
24 | Object.assign(properties, entry.value);
25 | }
26 | return properties;
27 | }
28 | return null;
29 | }
30 | }
31 |
32 | /* (c) Andrea Giammarchi - ISC */
33 | // @see https://github.com/WebReflection/udomdiff
34 | const diff = (a, b, before) => {
35 | const {parentNode} = before;
36 | const bLength = b.length;
37 | let aEnd = a.length;
38 | let bEnd = bLength;
39 | let aStart = 0;
40 | let bStart = 0;
41 | let map = null;
42 | while (aStart < aEnd || bStart < bEnd) {
43 | // append head, tail, or nodes in between: fast path
44 | if (aEnd === aStart) {
45 | // we could be in a situation where the rest of nodes that
46 | // need to be added are not at the end, and in such case
47 | // the node to `insertBefore`, if the index is more than 0
48 | // must be retrieved, otherwise it's gonna be the first item.
49 | const node = bEnd < bLength ?
50 | (bStart ?
51 | b[bStart - 1].nextSibling :
52 | b[bEnd - bStart]) :
53 | before;
54 | while (bStart < bEnd)
55 | parentNode.insertBefore(b[bStart++], node);
56 | }
57 | // remove head or tail: fast path
58 | else if (bEnd === bStart) {
59 | while (aStart < aEnd) {
60 | // remove the node only if it's unknown or not live
61 | if (!map || !map.has(a[aStart]))
62 | a[aStart].remove();
63 | aStart++;
64 | }
65 | }
66 | // same node: fast path
67 | else if (a[aStart] === b[bStart]) {
68 | aStart++;
69 | bStart++;
70 | }
71 | // same tail: fast path
72 | else if (a[aEnd - 1] === b[bEnd - 1]) {
73 | aEnd--;
74 | bEnd--;
75 | }
76 | // The once here single last swap "fast path" has been removed in v1.1.0
77 | // https://github.com/WebReflection/udomdiff/blob/single-final-swap/esm/index.js#L69-L85
78 | // reverse swap: also fast path
79 | else if (
80 | a[aStart] === b[bEnd - 1] &&
81 | b[bStart] === a[aEnd - 1]
82 | ) {
83 | // this is a "shrink" operation that could happen in these cases:
84 | // [1, 2, 3, 4, 5]
85 | // [1, 4, 3, 2, 5]
86 | // or asymmetric too
87 | // [1, 2, 3, 4, 5]
88 | // [1, 2, 3, 5, 6, 4]
89 | const node = a[--aEnd].nextSibling;
90 | parentNode.insertBefore(
91 | b[bStart++],
92 | a[aStart++].nextSibling
93 | );
94 | parentNode.insertBefore(b[--bEnd], node);
95 | // mark the future index as identical (yeah, it's dirty, but cheap 👍)
96 | // The main reason to do this, is that when a[aEnd] will be reached,
97 | // the loop will likely be on the fast path, as identical to b[bEnd].
98 | // In the best case scenario, the next loop will skip the tail,
99 | // but in the worst one, this node will be considered as already
100 | // processed, bailing out pretty quickly from the map index check
101 | a[aEnd] = b[bEnd];
102 | }
103 | // map based fallback, "slow" path
104 | else {
105 | // the map requires an O(bEnd - bStart) operation once
106 | // to store all future nodes indexes for later purposes.
107 | // In the worst case scenario, this is a full O(N) cost,
108 | // and such scenario happens at least when all nodes are different,
109 | // but also if both first and last items of the lists are different
110 | if (!map) {
111 | map = new Map;
112 | let i = bStart;
113 | while (i < bEnd)
114 | map.set(b[i], i++);
115 | }
116 | // if it's a future node, hence it needs some handling
117 | if (map.has(a[aStart])) {
118 | // grab the index of such node, 'cause it might have been processed
119 | const index = map.get(a[aStart]);
120 | // if it's not already processed, look on demand for the next LCS
121 | if (bStart < index && index < bEnd) {
122 | let i = aStart;
123 | // counts the amount of nodes that are the same in the future
124 | let sequence = 1;
125 | while (++i < aEnd && i < bEnd && map.get(a[i]) === (index + sequence))
126 | sequence++;
127 | // effort decision here: if the sequence is longer than replaces
128 | // needed to reach such sequence, which would brings again this loop
129 | // to the fast path, prepend the difference before a sequence,
130 | // and move only the future list index forward, so that aStart
131 | // and bStart will be aligned again, hence on the fast path.
132 | // An example considering aStart and bStart are both 0:
133 | // a: [1, 2, 3, 4]
134 | // b: [7, 1, 2, 3, 6]
135 | // this would place 7 before 1 and, from that time on, 1, 2, and 3
136 | // will be processed at zero cost
137 | if (sequence > (index - bStart)) {
138 | const node = a[aStart];
139 | while (bStart < index)
140 | parentNode.insertBefore(b[bStart++], node);
141 | }
142 | // if the effort wasn't good enough, fallback to a replace,
143 | // moving both source and target indexes forward, hoping that some
144 | // similar node will be found later on, to go back to the fast path
145 | else {
146 | parentNode.replaceChild(
147 | b[bStart++],
148 | a[aStart++]
149 | );
150 | }
151 | }
152 | // otherwise move the source forward, 'cause there's nothing to do
153 | else
154 | aStart++;
155 | }
156 | // this node has no meaning in the future list, so it's more than safe
157 | // to remove it, and check the next live node out instead, meaning
158 | // that only the live list index should be forwarded
159 | else
160 | a[aStart++].remove();
161 | }
162 | }
163 | return b;
164 | };
165 |
166 | /*! (c) Andrea Giammarchi - ISC */
167 |
168 | const {isArray} = Array;
169 | const {entries, getPrototypeOf, prototype: {isPrototypeOf}} = Object;
170 |
171 | const {
172 | COMPONENT,
173 | ELEMENT,
174 | FRAGMENT,
175 | INTERPOLATION,
176 | STATIC
177 | } = Token;
178 |
179 | const UDOMSAY = '🙊';
180 |
181 | // generic utils
182 | const asChildNodes = ({childNodes}) => ({childNodes: [...childNodes]});
183 | const getChild = ({childNodes}, i) => childNodes[i];
184 | const getToken = ({children}, i) => children[i];
185 | const invoke = ({value, properties, children}) => value(properties, ...children);
186 | const isKey = ({name}) => name === 'key';
187 | const reachChild = (c, {content}) => c.reduce(getChild, content);
188 | const reachToken = (c, token) => c.reduce(getToken, token);
189 | const setData = (node, value) => {
190 | const data = value == null ? '' : String(value);
191 | if (data !== node.data)
192 | node.data = data;
193 | };
194 |
195 | /**
196 | * @typedef {Object} RenderOptions utilities to use while rendering.
197 | * @prop {Document} [document] the default document to use. By default it's the global one.
198 | * @prop {[string, (node:Element, current:any, previous:any) => void][]} [plugins] a list of plugins to deal with,
199 | * used with attributes, example: `["stuff", (node, curr, prev) => { ... }]`
200 | * @prop {(fn:function) => function} [effect] an utility to create effects on components.
201 | * It must return a dispose utility to drop previous effect.
202 | * @prop {(s:any) => any} [getPeek] an utility to retrieve a Signal value without side-effects.
203 | * @prop {(s:any) => any} [getValue] an utility to retrieve a Signal value.
204 | * @prop {(s:any) => boolean} [isSignal] an utility to know if a value is a Signal.
205 | * @prop {function} [Signal] an optional signal constructor used to trap-check.
206 | * @prop {function} [diff] an optional function to diff nodes, not available in SSR.
207 | * `isSignal(ref)` utility whenever the `isSignal` field has not been provided.
208 | */
209 |
210 | /**
211 | * Return a `render(what, where)` utility able to deal with provided options.
212 | * @param {RenderOptions} options
213 | */
214 | var index = (options = {}) => {
215 | const document = options.document || globalThis.document;
216 | const plugins = new Map(options.plugins || []);
217 | const considerPlugins = !!plugins.size;
218 | const differ = options.diff || diff;
219 | const effect = options.effect || (fn => (fn(), noop));
220 | const getPeek = options.getPeek || (s => s.peek());
221 | const getValue = options.getValue || (s => s.value);
222 | const isSignal = options.isSignal || (
223 | options.Signal ?
224 | isPrototypeOf.bind(options.Signal.prototype) :
225 | () => false
226 | );
227 |
228 | const text = value => document.createTextNode(value);
229 | const comment = () => document.createComment(UDOMSAY);
230 |
231 | const getChildrenID = (node, i) => {
232 | let IDs = views.get(node);
233 | if (!IDs) views.set(node, IDs = {});
234 | return IDs[i] || (IDs[i] = {});
235 | };
236 | const getComponentView = (view, component) => {
237 | view.dispose();
238 | const dispose = effect(() => {
239 | const token = invoke(component);
240 | if (token.id !== view.id)
241 | view = getView(view, token, false);
242 | else
243 | view.update(token);
244 | });
245 | view.dispose = dispose;
246 | return view;
247 | };
248 | const getNewView = (view, token, createEffect) => {
249 | view.dispose();
250 | view = new View(token);
251 | if (createEffect)
252 | view.dispose = effect(() => view.update(token));
253 | else
254 | view.update(token);
255 | return view;
256 | };
257 | const getView = (view, token, createEffect) => token.type === COMPONENT ?
258 | getComponentView(view, token) :
259 | getNewView(view, token, createEffect)
260 | ;
261 |
262 | class View {
263 | constructor(token, shouldParse = true) {
264 | const [updates, content] = shouldParse ? parse(token) : [EMPTY, null];
265 | this._ = shouldParse && token.type === FRAGMENT;
266 | this.id = token.id;
267 | this.updates = updates;
268 | this.content = content;
269 | this.dispose = noop;
270 | }
271 | get $() {
272 | const {content, _} = this;
273 | if (_) {
274 | this._ = !_;
275 | return (this.content = asChildNodes(content)).childNodes;
276 | }
277 | return [content];
278 | }
279 | update(token) {
280 | for (const update of this.updates)
281 | update.call(this, token);
282 | }
283 | }
284 |
285 | const defaultView = new View({id: null}, false);
286 | const defaultEntry = {id: null, view: defaultView};
287 |
288 | const views = new WeakMap;
289 | let isToken;
290 |
291 | const tokens = new WeakMap;
292 | const parse = token => {
293 | let info = tokens.get(token.id);
294 | if (!info) {
295 | const updates = [];
296 | const content = mapToken(token, updates, [], EMPTY, false);
297 | tokens.set(token.id, info = [updates, content]);
298 | }
299 | const [updates, content] = info;
300 | return [updates.slice(), content.cloneNode(true)];
301 | };
302 |
303 | const setAttribute = (node, key, value, set) => {
304 | if (set)
305 | node.setAttribute(key, value);
306 | else
307 | node[key] = value;
308 | };
309 |
310 | const asAttribute = (node, key, value, prev, set) => {
311 | if (isSignal(value)) {
312 | const dispose = UDOMSAY + key;
313 | if (dispose in prev)
314 | prev[dispose]();
315 | prev[dispose] = effect(() => {
316 | setAttribute(node, key, getValue(value), set);
317 | });
318 | }
319 | else
320 | setAttribute(node, key, value, set);
321 | };
322 |
323 | const setProperty = (node, key, value, prev) => {
324 | if (considerPlugins && plugins.has(key))
325 | plugins.get(key)(node, value, prev);
326 | else if (prev[key] !== value) {
327 | prev[key] = value;
328 | switch (key) {
329 | case 'class':
330 | key += 'Name';
331 | case 'className':
332 | case 'textContent':
333 | asAttribute(node, key, value, prev, false);
334 | break;
335 | case 'ref':
336 | value.current = node;
337 | break;
338 | default:
339 | if (key.startsWith('on'))
340 | node[key.toLowerCase()] = value;
341 | else if (key in node)
342 | asAttribute(node, key, value, prev, false);
343 | else {
344 | if (value == null)
345 | node.removeAttribute(key);
346 | else
347 | asAttribute(node, key, value, prev, true);
348 | }
349 | break;
350 | }
351 | }
352 | };
353 |
354 | const handleAttributes = (a, c, i) => function (token) {
355 | const prev = {};
356 | const node = reachChild(c, this);
357 | (this.updates[i] = token => {
358 | const {attributes} = reachToken(c, token);
359 | for (const index of a) {
360 | const entry = attributes[index];
361 | const {type, value} = entry;
362 | if (type < 2)
363 | setProperty(node, entry.name, value, prev);
364 | else {
365 | for (const [name, v] of entries(value))
366 | setProperty(node, name, v, prev);
367 | }
368 | }
369 | })(token);
370 | };
371 |
372 | const handleArray = (c, i) => function (token) {
373 | let diffed = EMPTY, findIndex = true, index = -1;
374 | const node = reachChild(c, this);
375 | const keys = new Map;
376 | (this.updates[i] = token => {
377 | const {value} = reachToken(c, token);
378 | const diffing = [];
379 | for (let i = 0; i < value.length; i++) {
380 | const token = value[i];
381 | if (findIndex) {
382 | findIndex = !findIndex;
383 | index = token.attributes.findIndex(isKey);
384 | }
385 | const key = index < 0 ? i : token.attributes[index].value;
386 | let {id, view} = keys.get(key) || defaultEntry;
387 | if (id !== (token.id || (token.id = getChildrenID(node, i)))) {
388 | view = getView(view, token, false);
389 | keys.set(key, {id: token.id, view});
390 | }
391 | else
392 | view.update(token);
393 | diffing.push(...view.$);
394 | }
395 | if (diffing.length)
396 | diffed = differ(diffed, diffing, node);
397 | else if(diffed !== EMPTY) {
398 | const range = document.createRange();
399 | range.setStartBefore(diffed[0]);
400 | range.setEndAfter(diffed[diffed.length - 1]);
401 | range.deleteContents();
402 | keys.clear();
403 | diffed = EMPTY;
404 | findIndex = true;
405 | }
406 | })(token);
407 | };
408 |
409 | const handleComponent = (c, i) => function (token) {
410 | let diffed = EMPTY, view = defaultView;
411 | const node = reachChild(c, this);
412 | (this.updates[i] = token => {
413 | view = getComponentView(view, reachToken(c, token));
414 | diffed = differ(diffed, view.$, node);
415 | })(token);
416 | };
417 |
418 | const handleContent = (c, i) => function (token) {
419 | const node = reachChild(c, this);
420 | (this.updates[i] = token => {
421 | setData(node, reachToken(c, token).value);
422 | })(token);
423 | };
424 |
425 | const handleSignal = (c, i) => function (token) {
426 | let dispose = noop, signal, fx;
427 | const node = reachChild(c, this);
428 | const {value} = reachToken(c, token);
429 | const update = value => {
430 | if (signal !== value) {
431 | dispose();
432 | signal = value;
433 | dispose = effect(fx);
434 | }
435 | };
436 | if (isToken(getPeek(value))) {
437 | let diffed = EMPTY, view = defaultView;
438 | fx = () => {
439 | const token = getValue(signal);
440 | view = getView(view, token, false);
441 | diffed = differ(diffed, view.$, node);
442 | };
443 | }
444 | else {
445 | const t = text('');
446 | node.replaceWith(t);
447 | fx = () => { setData(t, getValue(signal)); };
448 | }
449 | this.updates[i] = token => update(reachToken(c, token).value);
450 | update(value);
451 | };
452 |
453 | const handleToken = (c, i) => function (token) {
454 | let diffed = EMPTY, view = defaultView, id = null;
455 | const node = reachChild(c, this);
456 | (this.updates[i] = token => {
457 | token = reachToken(c, token).value;
458 | if (id !== token.id) {
459 | id = token.id;
460 | // TODO: should this effect instead?
461 | view = getView(view, token, false);
462 | diffed = differ(diffed, view.$, node);
463 | }
464 | else if (token.type === COMPONENT)
465 | view.update(invoke(token));
466 | else
467 | view.update(token);
468 | })(token);
469 | };
470 |
471 | const addChildren = ({children}, updates, content, c, svg) => {
472 | for (let i = 0; i < children.length; i++) {
473 | const child = children[i];
474 | switch (child.type) {
475 | case STATIC:
476 | content.appendChild(text(child.value));
477 | break;
478 | default:
479 | content.appendChild(
480 | mapToken(children[i], updates, [], c.concat(i), svg)
481 | );
482 | break;
483 | }
484 | }
485 | };
486 |
487 | const mapToken = (token, updates, a, c, svg) => {
488 | let callback, content;
489 | const {length} = updates;
490 | type: switch (token.type) {
491 | case INTERPOLATION: {
492 | const {value} = token;
493 | switch (true) {
494 | case isToken(value):
495 | callback = handleToken;
496 | break;
497 | case isArray(value):
498 | callback = handleArray;
499 | break;
500 | case isSignal(value):
501 | callback = handleSignal;
502 | break;
503 | default: {
504 | content = text('');
505 | updates.push(handleContent(c, length));
506 | break type;
507 | }
508 | }
509 | }
510 | case COMPONENT: {
511 | content = comment();
512 | updates.push((callback || handleComponent)(c, length));
513 | break;
514 | }
515 | case ELEMENT: {
516 | const {attributes, name} = token;
517 | const args = [name];
518 | const attrs = [];
519 | for (let i = 0; i < attributes.length; i++) {
520 | const entry = attributes[i];
521 | if (entry.type === INTERPOLATION || entry.dynamic) {
522 | if (!isKey(entry))
523 | a.push(i);
524 | }
525 | else {
526 | if (entry.name === 'is')
527 | args.push({is: entry.value});
528 | attrs.push(entry);
529 | }
530 | }
531 | if (a.length)
532 | updates.push(handleAttributes(a, c, length));
533 | content = svg || (svg = name === 'svg') ?
534 | document.createElementNS('http://www.w3.org/2000/svg', ...args) :
535 | document.createElement(...args);
536 | for (const {name, value} of attrs)
537 | setAttribute(content, name, value, true);
538 | addChildren(token, updates, content, c, svg);
539 | break;
540 | }
541 | case FRAGMENT: {
542 | content = document.createDocumentFragment();
543 | addChildren(token, updates, content, c, svg);
544 | break;
545 | }
546 | }
547 | return content;
548 | };
549 |
550 | /**
551 | * Reveal some `token` content into a `DOM` element.
552 | * @param {() => Token | Token} what the token to render
553 | * @param {Element} where the DOM node to render such token
554 | */
555 | return (what, where) => {
556 | /** @type {Token} */
557 | const token = typeof what === 'function' ? what() : what;
558 | if (!isToken) isToken = isPrototypeOf.bind(getPrototypeOf(token));
559 | const view = getView(views.get(where) || defaultView, token, true);
560 | views.set(where, view);
561 | where.replaceChildren(...view.$);
562 | };
563 | };
564 |
565 | export { index as default };
566 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "udomsay",
3 | "version": "0.4.19",
4 | "description": "A stricter, signals driven, ESX based library",
5 | "main": "./cjs/index.js",
6 | "scripts": {
7 | "build": "npm run cjs && npm run rollup:es && npm run rollup:babel && npm run build:dist && npm run size && npm run build:size",
8 | "build:array": "babel test/array.jsx -o test/array.js",
9 | "build:dist": "for lib in 'preact' 'signal' 'solid' 'usignal'; do npm run rollup:$lib; done; npm run build:dist:test",
10 | "build:dist:test": "for lib in 'preact' 'signal' 'solid' 'usignal'; do echo \"$lib\" > test/dist/$lib.html && babel test/dist/$lib.jsx -o test/dist/$lib.js; done",
11 | "build:children": "babel test/children.jsx -o test/children.js",
12 | "build:counter": "babel test/counter.jsx -o test/counter.js",
13 | "build:index": "babel test/index.jsx -o test/index.js",
14 | "build:size": "for lib in 'preact' 'signal' 'solid' 'usignal'; do echo -e \"\\x1b[1m$lib\\x1b[0m: $(cat $lib.js | brotli | wc -c)\"; done",
15 | "build:ssr": "babel test/ssr.jsx -o test/ssr.js",
16 | "build:esx": "babel test/esx.jsx -o test/esx-babel.js",
17 | "build:uhooks": "babel test/uhooks.jsx -o test/uhooks.js",
18 | "cjs": "ascjs --no-default esm cjs",
19 | "rollup:es": "rollup --config rollup/es.config.js",
20 | "rollup:babel": "rollup --config rollup/babel.config.js",
21 | "rollup:preact": "rollup --config rollup/preact.config.js",
22 | "rollup:signal": "rollup --config rollup/signal.config.js",
23 | "rollup:solid": "rollup --config rollup/solid.config.js",
24 | "rollup:usignal": "rollup --config rollup/usignal.config.js",
25 | "coveralls": "c8 report --reporter=text-lcov | coveralls",
26 | "size": "echo -e \"\\x1b[1mudomsay\\x1b[0m: $(cat es.js | brotli | wc -c)\"",
27 | "test": "c8 node test/index.js"
28 | },
29 | "keywords": [
30 | "ESX",
31 | "JSX",
32 | "signals",
33 | "library"
34 | ],
35 | "author": "Andrea Giammarchi",
36 | "license": "ISC",
37 | "devDependencies": {
38 | "@babel/cli": "^7.20.7",
39 | "@babel/core": "^7.20.12",
40 | "@preact/signals-core": "^1.2.3",
41 | "@rollup/plugin-node-resolve": "^15.0.1",
42 | "@rollup/plugin-terser": "^0.4.0",
43 | "@ungap/babel-plugin-transform-esx": "^0.4.3",
44 | "@webreflection/signal": "^1.0.1",
45 | "ascjs": "^5.0.1",
46 | "c8": "^7.12.0",
47 | "rollup": "^3.13.0",
48 | "solid-js": "^1.6.10",
49 | "usignal": "^0.8.10"
50 | },
51 | "module": "./esm/index.js",
52 | "type": "module",
53 | "exports": {
54 | ".": {
55 | "import": "./esm/index.js",
56 | "default": "./cjs/index.js"
57 | },
58 | "./preact": {
59 | "import": "./preact.js"
60 | },
61 | "./signal": {
62 | "import": "./signal.js"
63 | },
64 | "./solid": {
65 | "import": "./solid.js"
66 | },
67 | "./usignal": {
68 | "import": "./usignal.js"
69 | },
70 | "./ssr": {
71 | "import": "./esm/ssr.js",
72 | "default": "./cjs/ssr.js"
73 | },
74 | "./package.json": "./package.json"
75 | },
76 | "unpkg": "es.js",
77 | "dependencies": {
78 | "@ungap/esx": "^0.3.1",
79 | "@webreflection/empty": "^0.2.1",
80 | "domconstants": "^1.0.0"
81 | },
82 | "repository": {
83 | "type": "git",
84 | "url": "git+https://github.com/WebReflection/udomsay.git"
85 | },
86 | "bugs": {
87 | "url": "https://github.com/WebReflection/udomsay/issues"
88 | },
89 | "homepage": "https://github.com/WebReflection/udomsay#readme"
90 | }
91 |
--------------------------------------------------------------------------------
/preact.js:
--------------------------------------------------------------------------------
1 | function t(){throw new Error("Cycle detected")}function e(){if(o>1)return void o--;let t,e=!1;for(;void 0!==n;){let i=n;for(n=void 0,r++;void 0!==i;){const s=i.o;if(i.o=void 0,i.f&=-3,!(8&i.f)&&a(i))try{i.c()}catch(i){e||(t=i,e=!0)}i=s}}if(r=0,o--,e)throw t}function i(t){if(o>0)return t();o++;try{return t()}finally{e()}}let s,n,o=0,r=0,c=0;function h(t){if(void 0===s)return;let e=t.n;return void 0===e||e.t!==s?(e={i:0,S:t,p:s.s,n:void 0,t:s,e:void 0,x:void 0,r:e},void 0!==s.s&&(s.s.n=e),s.s=e,t.n=e,32&s.f&&t.S(e),e):-1===e.i?(e.i=0,void 0!==e.n&&(e.n.p=e.p,void 0!==e.p&&(e.p.n=e.n),e.p=s.s,e.n=void 0,s.s.n=e,s.s=e),e):void 0}function f(t){this.v=t,this.i=0,this.n=void 0,this.t=void 0}function u(t){return new f(t)}function a(t){for(let e=t.s;void 0!==e;e=e.n)if(e.S.i!==e.i||!e.S.h()||e.S.i!==e.i)return!0;return!1}function l(t){for(let e=t.s;void 0!==e;e=e.n){const i=e.S.n;if(void 0!==i&&(e.r=i),e.S.n=e,e.i=-1,void 0===e.n){t.s=e;break}}}function d(t){let e,i=t.s;for(;void 0!==i;){const t=i.p;-1===i.i?(i.S.U(i),void 0!==t&&(t.n=i.n),void 0!==i.n&&(i.n.p=t)):e=i,i.S.n=i.r,void 0!==i.r&&(i.r=void 0),i=t}t.s=e}function p(t){f.call(this,void 0),this.x=t,this.s=void 0,this.g=c-1,this.f=4}function v(t){return new p(t)}function y(t){const i=t.u;if(t.u=void 0,"function"==typeof i){o++;const n=s;s=void 0;try{i()}catch(e){throw t.f&=-2,t.f|=8,g(t),e}finally{s=n,e()}}}function g(t){for(let e=t.s;void 0!==e;e=e.n)e.S.U(e);t.x=void 0,t.s=void 0,y(t)}function S(t){if(s!==this)throw new Error("Out-of-order effect");d(this),s=t,this.f&=-2,8&this.f&&g(this),e()}function b(t){this.x=t,this.u=void 0,this.s=void 0,this.o=void 0,this.f=32}function w(t){const e=new b(t);try{e.c()}catch(t){throw e.d(),t}return e.d.bind(e)}f.prototype.h=function(){return!0},f.prototype.S=function(t){this.t!==t&&void 0===t.e&&(t.x=this.t,void 0!==this.t&&(this.t.e=t),this.t=t)},f.prototype.U=function(t){if(void 0!==this.t){const e=t.e,i=t.x;void 0!==e&&(e.x=i,t.e=void 0),void 0!==i&&(i.e=e,t.x=void 0),t===this.t&&(this.t=i)}},f.prototype.subscribe=function(t){const e=this;return w((function(){const i=e.value,s=32&this.f;this.f&=-33;try{t(i)}finally{this.f|=s}}))},f.prototype.valueOf=function(){return this.value},f.prototype.toString=function(){return this.value+""},f.prototype.peek=function(){return this.v},Object.defineProperty(f.prototype,"value",{get(){const t=h(this);return void 0!==t&&(t.i=this.i),this.v},set(i){if(i!==this.v){r>100&&t(),this.v=i,this.i++,c++,o++;try{for(let t=this.t;void 0!==t;t=t.x)t.t.N()}finally{e()}}}}),(p.prototype=new f).h=function(){if(this.f&=-3,1&this.f)return!1;if(32==(36&this.f))return!0;if(this.f&=-5,this.g===c)return!0;if(this.g=c,this.f|=1,this.i>0&&!a(this))return this.f&=-2,!0;const t=s;try{l(this),s=this;const t=this.x();(16&this.f||this.v!==t||0===this.i)&&(this.v=t,this.f&=-17,this.i++)}catch(t){this.v=t,this.f|=16,this.i++}return s=t,d(this),this.f&=-2,!0},p.prototype.S=function(t){if(void 0===this.t){this.f|=36;for(let t=this.s;void 0!==t;t=t.n)t.S.S(t)}f.prototype.S.call(this,t)},p.prototype.U=function(t){if(void 0!==this.t&&(f.prototype.U.call(this,t),void 0===this.t)){this.f&=-33;for(let t=this.s;void 0!==t;t=t.n)t.S.U(t)}},p.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(let t=this.t;void 0!==t;t=t.x)t.t.N()}},p.prototype.peek=function(){if(this.h()||t(),16&this.f)throw this.v;return this.v},Object.defineProperty(p.prototype,"value",{get(){1&this.f&&t();const e=h(this);if(this.h(),void 0!==e&&(e.i=this.i),16&this.f)throw this.v;return this.v}}),b.prototype.c=function(){const t=this.S();try{8&this.f||void 0===this.x||(this.u=this.x())}finally{t()}},b.prototype.S=function(){1&this.f&&t(),this.f|=1,this.f&=-9,y(this),l(this),o++;const e=s;return s=this,S.bind(this,e)},b.prototype.N=function(){2&this.f||(this.f|=2,this.o=n,n=this)},b.prototype.d=function(){this.f|=8,1&this.f||g(this)};var N=(0,Object.freeze)([]),m=()=>{};const x=(t,e,i)=>{const{parentNode:s}=i,n=e.length;let o=t.length,r=n,c=0,h=0,f=null;for(;ci-h){const n=t[c];for(;ht[e],B=({children:t},e)=>t[e],R=({value:t,properties:e,children:i})=>t(e,...i),$=({name:t})=>"key"===t,j=(t,{content:e})=>t.reduce(U,e),L=(t,e)=>t.reduce(B,e),W=(t,e)=>{const i=null==e?"":String(e);i!==t.data&&(t.data=i)};
2 | /*! (c) Andrea Giammarchi - ISC */var F=(t={})=>{const e=t.document||globalThis.document,i=new Map(t.plugins||[]),s=!!i.size,n=t.diff||x,o=t.effect||(t=>(t(),m)),r=t.getPeek||(t=>t.peek()),c=t.getValue||(t=>t.value),h=t.isSignal||(t.Signal?k.bind(t.Signal.prototype):()=>!1),f=t=>e.createTextNode(t),u=(t,e)=>{let i=y.get(t);return i||y.set(t,i={}),i[e]||(i[e]={})},a=(t,e)=>{t.dispose();const i=o((()=>{const i=R(e);i.id!==t.id?t=l(t,i,!1):t.update(i)}));return t.dispose=i,t},l=(t,e,i)=>e.type===C?a(t,e):((t,e,i)=>(t.dispose(),t=new d(e),i?t.dispose=o((()=>t.update(e))):t.update(e),t))(t,e,i);class d{constructor(t,e=!0){const[i,s]=e?b(t):[N,null];this._=e&&t.type===M,this.id=t.id,this.updates=i,this.content=s,this.dispose=m}get $(){const{content:t,_:e}=this;return e?(this._=!e,(this.content=(({childNodes:t})=>({childNodes:[...t]}))(t)).childNodes):[t]}update(t){for(const e of this.updates)e.call(this,t)}}const p=new d({id:null},!1),v={id:null,view:p},y=new WeakMap;let g;const S=new WeakMap,b=t=>{let e=S.get(t.id);if(!e){const i=[],s=H(t,i,[],N,!1);S.set(t.id,e=[i,s])}const[i,s]=e;return[i.slice(),s.cloneNode(!0)]},w=(t,e,i,s)=>{s?t.setAttribute(e,i):t[e]=i},U=(t,e,i,s,n)=>{if(h(i)){const r="🙊"+e;r in s&&s[r](),s[r]=o((()=>{w(t,e,c(i),n)}))}else w(t,e,i,n)},B=(t,e,n,o)=>{if(s&&i.has(e))i.get(e)(t,n,o);else if(o[e]!==n)switch(o[e]=n,e){case"class":e+="Name";case"className":case"textContent":U(t,e,n,o,!1);break;case"ref":n.current=t;break;default:e.startsWith("on")?t[e.toLowerCase()]=n:e in t?U(t,e,n,o,!1):null==n?t.removeAttribute(e):U(t,e,n,o,!0)}},F=(t,e,i)=>function(s){const n={},o=j(e,this);(this.updates[i]=i=>{const{attributes:s}=L(e,i);for(const e of t){const t=s[e],{type:i,value:r}=t;if(i<2)B(o,t.name,r,n);else for(const[t,e]of E(r))B(o,t,e,n)}})(s)},_=(t,i)=>function(s){let o=N,r=!0,c=-1;const h=j(t,this),f=new Map;(this.updates[i]=i=>{const{value:s}=L(t,i),a=[];for(let t=0;tfunction(i){let s=N,o=p;const r=j(t,this);(this.updates[e]=e=>{o=a(o,L(t,e)),s=n(s,o.$,r)})(i)},G=(t,e)=>function(i){const s=j(t,this);(this.updates[e]=e=>{W(s,L(t,e).value)})(i)},D=(t,e)=>function(i){let s,h,u=m;const a=j(t,this),{value:d}=L(t,i),v=t=>{s!==t&&(u(),s=t,u=o(h))};if(g(r(d))){let t=N,e=p;h=()=>{const i=c(s);e=l(e,i,!1),t=n(t,e.$,a)}}else{const t=f("");a.replaceWith(t),h=()=>{W(t,c(s))}}this.updates[e]=e=>v(L(t,e).value),v(d)},V=(t,e)=>function(i){let s=N,o=p,r=null;const c=j(t,this);(this.updates[e]=e=>{e=L(t,e).value,r!==e.id?(r=e.id,o=l(o,e,!1),s=n(s,o.$,c)):e.type===C?o.update(R(e)):o.update(e)})(i)},q=({children:t},e,i,s,n)=>{for(let o=0;o{let r,c;const{length:u}=i;t:switch(t.type){case P:{const{value:e}=t;switch(!0){case g(e):r=V;break;case T(e):r=_;break;case h(e):r=D;break;default:c=f(""),i.push(G(n,u));break t}}case C:c=e.createComment("🙊"),i.push((r||z)(n,u));break;case A:{const{attributes:r,name:h}=t,f=[h],a=[];for(let t=0;t{const i="function"==typeof t?t():t;g||(g=k.bind(O(i)));const s=l(y.get(e)||p,i,!0);y.set(e,s),e.replaceChildren(...s.$)}};const _=(t={})=>F({...t,Signal:f,effect:w,isSignal:void 0});export{f as Signal,i as batch,v as computed,_ as createRender,w as effect,u as signal};
3 |
--------------------------------------------------------------------------------
/rollup/babel.config.js:
--------------------------------------------------------------------------------
1 | import {nodeResolve} from '@rollup/plugin-node-resolve';
2 |
3 | export default {
4 | input: './esm/index.js',
5 | plugins: [
6 | nodeResolve()
7 | ],
8 | output: {
9 | file: './index.js',
10 | format: 'module'
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/rollup/dist.js:
--------------------------------------------------------------------------------
1 | import {nodeResolve} from '@rollup/plugin-node-resolve';
2 | import terser from '@rollup/plugin-terser';
3 |
4 | export default name => ({
5 | input: `./esm/dist/${name}.js`,
6 | plugins: [
7 | nodeResolve(),
8 | terser()
9 | ],
10 | output: {
11 | file: `./${name}.js`,
12 | format: 'module'
13 | }
14 | });
15 |
--------------------------------------------------------------------------------
/rollup/es.config.js:
--------------------------------------------------------------------------------
1 | import {nodeResolve} from '@rollup/plugin-node-resolve';
2 | import terser from '@rollup/plugin-terser';
3 |
4 | export default {
5 | input: './esm/index.js',
6 | plugins: [
7 | nodeResolve(),
8 | terser()
9 | ],
10 | output: {
11 | file: './es.js',
12 | format: 'module'
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/rollup/preact.config.js:
--------------------------------------------------------------------------------
1 | import dist from './dist.js';
2 |
3 | export default dist('preact');
4 |
--------------------------------------------------------------------------------
/rollup/signal.config.js:
--------------------------------------------------------------------------------
1 | import dist from './dist.js';
2 |
3 | export default dist('signal');
4 |
--------------------------------------------------------------------------------
/rollup/solid.config.js:
--------------------------------------------------------------------------------
1 | import dist from './dist.js';
2 |
3 | export default dist('solid');
4 |
--------------------------------------------------------------------------------
/rollup/usignal.config.js:
--------------------------------------------------------------------------------
1 | import dist from './dist.js';
2 |
3 | export default dist('usignal');
4 |
--------------------------------------------------------------------------------
/signal.js:
--------------------------------------------------------------------------------
1 | /*! (c) Andrea Giammarchi */
2 | let e=null;const t=t=>{let s=e;s||(e=new Set);try{t()}finally{if(!s){[e,s]=[null,e];for(const e of s)e._()}}},s=e=>{const t=[...e];return e.clear(),t};class n extends Set{constructor(e){super()._=e}dispose(){for(const e of s(this))e.delete(this),e.dispose?.()}}let i=null;const o=e=>{const t=new n((()=>{const s=i;i=t;try{e()}finally{i=s}}));return t},r=(e,t)=>{const s=o((()=>{t=e(t)}));return i&&i.add(s),s._(),()=>s.dispose()};class a extends Set{constructor(e){super()._=e}get value(){return i&&i.add(this.add(i)),this._}set value(t){if(this._!==t){this._=t;const n=!e;for(const t of s(this))n?t._():e.add(t)}}peek(){return this._}then(e){e(this.value)}toJSON(){return this.value}valueOf(){return this.value}toString(){return String(this.value)}}const l=e=>new a(e);class c extends a{constructor(e,t){super(t).f=e,this.e=null}get value(){return this.e||(this.e=o((()=>{super.value=this.f(this._)})))._(),super.value}set value(e){throw new Error("computed is read-only")}}const u=(e,t)=>new c(e,t);var d=(0,Object.freeze)([]),h=()=>{};const f=(e,t,s)=>{const{parentNode:n}=s,i=t.length;let o=e.length,r=i,a=0,l=0,c=null;for(;as-l){const i=e[a];for(;le[t],E=({children:e},t)=>e[t],O=({value:e,properties:t,children:s})=>e(t,...s),k=({name:e})=>"key"===e,_=(e,{content:t})=>e.reduce(T,t),A=(e,t)=>e.reduce(E,t),C=(e,t)=>{const s=null==t?"":String(t);s!==e.data&&(e.data=s)};
3 | /*! (c) Andrea Giammarchi - ISC */var M=(e={})=>{const t=e.document||globalThis.document,s=new Map(e.plugins||[]),n=!!s.size,i=e.diff||f,o=e.effect||(e=>(e(),h)),r=e.getPeek||(e=>e.peek()),a=e.getValue||(e=>e.value),l=e.isSignal||(e.Signal?b.bind(e.Signal.prototype):()=>!1),c=e=>t.createTextNode(e),u=(e,t)=>{let s=P.get(e);return s||P.set(e,s={}),s[t]||(s[t]={})},T=(e,t)=>{e.dispose();const s=o((()=>{const s=O(t);s.id!==e.id?e=E(e,s,!1):e.update(s)}));return e.dispose=s,e},E=(e,t,s)=>t.type===w?T(e,t):((e,t,s)=>(e.dispose(),e=new M(t),s?e.dispose=o((()=>e.update(t))):e.update(t),e))(e,t,s);class M{constructor(e,t=!0){const[s,n]=t?$(e):[d,null];this._=t&&e.type===y,this.id=e.id,this.updates=s,this.content=n,this.dispose=h}get $(){const{content:e,_:t}=this;return t?(this._=!t,(this.content=(({childNodes:e})=>({childNodes:[...e]}))(e)).childNodes):[e]}update(e){for(const t of this.updates)t.call(this,e)}}const x=new M({id:null},!1),I={id:null,view:x},P=new WeakMap;let B;const R=new WeakMap,$=e=>{let t=R.get(e.id);if(!t){const s=[],n=q(e,s,[],d,!1);R.set(e.id,t=[s,n])}const[s,n]=t;return[s.slice(),n.cloneNode(!0)]},L=(e,t,s,n)=>{n?e.setAttribute(t,s):e[t]=s},W=(e,t,s,n,i)=>{if(l(s)){const r="🙊"+t;r in n&&n[r](),n[r]=o((()=>{L(e,t,a(s),i)}))}else L(e,t,s,i)},j=(e,t,i,o)=>{if(n&&s.has(t))s.get(t)(e,i,o);else if(o[t]!==i)switch(o[t]=i,t){case"class":t+="Name";case"className":case"textContent":W(e,t,i,o,!1);break;case"ref":i.current=e;break;default:t.startsWith("on")?e[t.toLowerCase()]=i:t in e?W(e,t,i,o,!1):null==i?e.removeAttribute(t):W(e,t,i,o,!0)}},F=(e,t,s)=>function(n){const i={},o=_(t,this);(this.updates[s]=s=>{const{attributes:n}=A(t,s);for(const t of e){const e=n[t],{type:s,value:r}=e;if(s<2)j(o,e.name,r,i);else for(const[e,t]of g(r))j(o,e,t,i)}})(n)},z=(e,s)=>function(n){let o=d,r=!0,a=-1;const l=_(e,this),c=new Map;(this.updates[s]=s=>{const{value:n}=A(e,s),h=[];for(let e=0;efunction(s){let n=d,o=x;const r=_(e,this);(this.updates[t]=t=>{o=T(o,A(e,t)),n=i(n,o.$,r)})(s)},D=(e,t)=>function(s){const n=_(e,this);(this.updates[t]=t=>{C(n,A(e,t).value)})(s)},J=(e,t)=>function(s){let n,l,u=h;const f=_(e,this),{value:p}=A(e,s),g=e=>{n!==e&&(u(),n=e,u=o(l))};if(B(r(p))){let e=d,t=x;l=()=>{const s=a(n);t=E(t,s,!1),e=i(e,t.$,f)}}else{const e=c("");f.replaceWith(e),l=()=>{C(e,a(n))}}this.updates[t]=t=>g(A(e,t).value),g(p)},U=(e,t)=>function(s){let n=d,o=x,r=null;const a=_(e,this);(this.updates[t]=t=>{t=A(e,t).value,r!==t.id?(r=t.id,o=E(o,t,!1),n=i(n,o.$,a)):t.type===w?o.update(O(t)):o.update(t)})(s)},V=({children:e},t,s,n,i)=>{for(let o=0;o{let r,a;const{length:u}=s;e:switch(e.type){case m:{const{value:t}=e;switch(!0){case B(t):r=U;break;case p(t):r=z;break;case l(t):r=J;break;default:a=c(""),s.push(D(i,u));break e}}case w:a=t.createComment("🙊"),s.push((r||G)(i,u));break;case N:{const{attributes:r,name:l}=e,c=[l],d=[];for(let e=0;e{const s="function"==typeof e?e():e;B||(B=b.bind(v(s)));const n=E(P.get(t)||x,s,!0);P.set(t,n),t.replaceChildren(...n.$)}};const x=(e={})=>M({...e,Signal:a,effect:r,isSignal:void 0});export{c as Computed,a as Signal,t as batch,u as computed,x as createRender,r as effect,l as signal};
4 |
--------------------------------------------------------------------------------
/solid.js:
--------------------------------------------------------------------------------
1 | let e=1,t=!1,n=!1,o=[],r=null,s=null,l=5,i=0,u=300,c=null,a=null;const f=1073741823;function d(r,d){c||function(){const e=new MessageChannel,t=e.port2;if(c=()=>t.postMessage(null),e.port1.onmessage=()=>{if(null!==a){const e=performance.now();i=e+l;const n=!0;try{a(n,e)?t.postMessage(null):a=null}catch(e){throw t.postMessage(null),e}}},navigator&&navigator.scheduling&&navigator.scheduling.isInputPending){const e=navigator.scheduling;s=()=>{const t=performance.now();return t>=i&&(!!e.isInputPending()||t>=u)}}else s=()=>performance.now()>=i}();let h=performance.now(),g=f;d&&d.timeout&&(g=d.timeout);const v={id:e++,fn:r,startTime:h,expirationTime:h+g};return function(e,t){e.splice(function(){let n=0,o=e.length-1;for(;n<=o;){const r=o+n>>1,s=t.expirationTime-e[r].expirationTime;if(s>0)n=r+1;else{if(!(s<0))return r;o=r-1}}return n}(),0,t)}(o,v),t||n||(t=!0,a=p,c()),v}function h(e){e.fn=null}function p(e,l){t=!1,n=!0;try{return function(e,t){let n=t;r=o[0]||null;for(;null!==r&&(!(r.expirationTime>n)||e&&!s());){const e=r.fn;if(null!==e){r.fn=null;e(r.expirationTime<=n),n=performance.now(),r===o[0]&&o.shift()}else o.shift();r=o[0]||null}return null!==r}(e,l)}finally{r=null,n=!1}}const g={};function v(e){g.context=e}const b=(e,t)=>e===t,w=Symbol("solid-proxy"),y=Symbol("solid-track"),m=Symbol("solid-dev-component"),k={equals:b};let S=null,x=ye;const O=1,A=2,P={owned:null,cleanups:null,context:null,owner:null},T={};var E=null;let M=null,N=null,F=null,C=null,j=null,$=null,V=0;const[q,I]=L(!1);function B(e,t){const n=C,o=E,r=0===e.length,s=r?P:{owned:null,cleanups:null,context:null,owner:t||o},l=r?e:()=>e((()=>J((()=>xe(s)))));E=s,C=null;try{return we(l,!0)}finally{C=n,E=o}}function L(e,t){const n={value:e,observers:null,observerSlots:null,comparator:(t=t?Object.assign({},k,t):k).equals||void 0};return[de.bind(n),e=>("function"==typeof e&&(e=M&&M.running&&M.sources.has(n)?e(n.tValue):e(n.value)),he(n,e))]}function R(e,t,n){const o=ve(e,t,!0,O);N&&M&&M.running?j.push(o):pe(o)}function z(e,t,n){const o=ve(e,t,!1,O);N&&M&&M.running?j.push(o):pe(o)}function W(e,t,n){x=me;const o=ve(e,t,!1,O),r=ce&&Te(E,ce.id);r&&(o.suspense=r),o.user=!0,$?$.push(o):pe(o)}function D(e,t){let n;const o=ve((()=>{n?n():J(e),n=void 0}),void 0,!1,0),r=ce&&Te(E,ce.id);return r&&(o.suspense=r),o.user=!0,e=>{n=e,pe(o)}}function _(e,t,n){n=n?Object.assign({},k,n):k;const o=ve(e,t,!0,0);return o.observers=null,o.observerSlots=null,o.comparator=n.equals||void 0,N&&M&&M.running?(o.tState=O,j.push(o)):pe(o),de.bind(o)}function G(e,t,n){let o,r,s;2===arguments.length&&"object"==typeof t||1===arguments.length?(o=!0,r=e,s=t||{}):(o=e,r=t,s=n||{});let l=null,i=T,u=null,c=!1,a=!1,f="initialValue"in s,d="function"==typeof o&&_(o);const h=new Set,[p,v]=(s.storage||L)(s.initialValue),[b,w]=L(void 0),[y,m]=L(void 0,{equals:!1}),[k,S]=L(f?"ready":"unresolved");if(g.context){let e;u=`${g.context.id}${g.context.count++}`,"initial"===s.ssrLoadFrom?i=s.initialValue:g.load&&(e=g.load(u))&&(i=e[0])}function x(e,t,n,o){return l===e&&(l=null,f=!0,e!==i&&t!==i||!s.onHydrated||queueMicrotask((()=>s.onHydrated(o,{value:t}))),i=T,M&&e&&c?(M.promises.delete(e),c=!1,we((()=>{M.running=!0,O(t,n)}),!1)):O(t,n)),t}function O(e,t){we((()=>{t||v((()=>e)),S(t?"errored":"ready"),w(t);for(const e of h.keys())e.decrement();h.clear()}),!1)}function A(){const e=ce&&Te(E,ce.id),t=p(),n=b();if(n&&!l)throw n;return C&&!C.user&&e&&R((()=>{y(),l&&(e.resolved&&M&&c?M.promises.add(l):h.has(e)||(e.increment(),h.add(e)))})),t}function P(e=!0){if(!1!==e&&a)return;a=!1;const t=d?d():o;if(c=M&&M.running,null==t||!1===t)return void x(l,J(p));M&&l&&M.promises.delete(l);const n=i!==T?i:J((()=>r(t,{value:p(),refetching:e})));return"object"==typeof n&&n&&"then"in n?(l=n,a=!0,queueMicrotask((()=>a=!1)),we((()=>{S(f?"refreshing":"pending"),m()}),!1),n.then((e=>x(n,e,void 0,t)),(e=>x(n,void 0,Ae(e),t)))):(x(l,n,void 0,t),n)}return Object.defineProperties(A,{state:{get:()=>k()},error:{get:()=>b()},loading:{get(){const e=k();return"pending"===e||"refreshing"===e}},latest:{get(){if(!f)return A();const e=b();if(e&&!l)throw e;return p()}}}),d?R((()=>P(!1))):P(!1),[A,{refetch:P,mutate:v}]}function H(e,t){let n,o=t?t.timeoutMs:void 0;const r=ve((()=>(n&&n.fn||(n=d((()=>l((()=>r.value))),void 0!==o?{timeout:o}:void 0)),e())),void 0,!0),[s,l]=L(r.value,t);return pe(r),l((()=>r.value)),s}function U(e,t=b,n){const o=new Map,r=ve((n=>{const r=e();for(const[e,s]of o.entries())if(t(e,r)!==t(e,n))for(const e of s.values())e.state=O,e.pure?j.push(e):$.push(e);return r}),void 0,!0,O);return pe(r),e=>{const n=C;if(n){let t;(t=o.get(e))?t.add(n):o.set(e,t=new Set([n])),Y((()=>{t.delete(n),!t.size&&o.delete(e)}))}return t(e,M&&M.running&&M.sources.has(r)?r.tValue:r.value)}}function K(e){return we(e,!1)}function J(e){const t=C;C=null;try{return e()}finally{C=t}}function Q(e,t,n){const o=Array.isArray(e);let r,s=n&&n.defer;return n=>{let l;if(o){l=Array(e.length);for(let t=0;tt(l,r,n)));return r=l,i}}function X(e){W((()=>J(e)))}function Y(e){return null===E||(null===E.cleanups?E.cleanups=[e]:E.cleanups.push(e)),e}function Z(e){S||(S=Symbol("error")),null===E||(null===E.context?E.context={[S]:[e]}:E.context[S]?E.context[S].push(e):E.context[S]=[e])}function ee(){return C}function te(){return E}function ne(e,t){const n=E,o=C;E=e,C=null;try{return we(t,!0)}catch(e){Pe(e)}finally{E=n,C=o}}function oe(e=d){N=e}function re(e){if(M&&M.running)return e(),M.done;const t=C,n=E;return Promise.resolve().then((()=>{let o;return C=t,E=n,(N||ce)&&(o=M||(M={sources:new Set,effects:[],promises:new Set,disposed:new Set,queue:new Set,running:!0}),o.done||(o.done=new Promise((e=>o.resolve=e))),o.running=!0),we(e,!1),C=E=null,o?o.done:void 0}))}function se(){return[q,re]}function le(e,t){const n=Symbol("context");return{id:n,Provider:Me(n),defaultValue:e}}function ie(e){let t;return void 0!==(t=Te(E,e.id))?t:e.defaultValue}function ue(e){const t=_(e),n=_((()=>Ee(t())));return n.toArray=()=>{const e=n();return Array.isArray(e)?e:null!=e?[e]:[]},n}let ce;function ae(){return ce||(ce=le({}))}function fe(e){if(F){const t=F;F=(n,o)=>{const r=t(n,o),s=e((e=>r.track(e)),o);return{track:e=>s.track(e),dispose(){s.dispose(),r.dispose()}}}}else F=e}function de(){const e=M&&M.running;if(this.sources&&(!e&&this.state||e&&this.tState))if(!e&&this.state===O||e&&this.tState===O)pe(this);else{const e=j;j=null,we((()=>ke(this)),!1),j=e}if(C){const e=this.observers?this.observers.length:0;C.sources?(C.sources.push(this),C.sourceSlots.push(e)):(C.sources=[this],C.sourceSlots=[e]),this.observers?(this.observers.push(C),this.observerSlots.push(C.sources.length-1)):(this.observers=[C],this.observerSlots=[C.sources.length-1])}return e&&M.sources.has(this)?this.tValue:this.value}function he(e,t,n){let o=M&&M.running&&M.sources.has(e)?e.tValue:e.value;if(!e.comparator||!e.comparator(o,t)){if(M){const o=M.running;(o||!n&&M.sources.has(e))&&(M.sources.add(e),e.tValue=t),o||(e.value=t)}else e.value=t;e.observers&&e.observers.length&&we((()=>{for(let t=0;t1e6)throw j=[],new Error}),!1)}return t}function pe(e){if(!e.fn)return;xe(e);const t=E,n=C,o=V;C=E=e,ge(e,M&&M.running&&M.sources.has(e)?e.tValue:e.value,o),M&&!M.running&&M.sources.has(e)&&queueMicrotask((()=>{we((()=>{M&&(M.running=!0),C=E=e,ge(e,e.tValue,o),C=E=null}),!1)})),C=n,E=t}function ge(e,t,n){let o;try{o=e.fn(t)}catch(t){e.pure&&(M&&M.running?(e.tState=O,e.tOwned&&e.tOwned.forEach(xe),e.tOwned=void 0):(e.state=O,e.owned&&e.owned.forEach(xe),e.owned=null)),Pe(t)}(!e.updatedAt||e.updatedAt<=n)&&(null!=e.updatedAt&&"observers"in e?he(e,o,!0):M&&M.running&&e.pure?(M.sources.add(e),e.tValue=o):e.value=o,e.updatedAt=n)}function ve(e,t,n,o=O,r){const s={fn:e,state:o,updatedAt:null,owned:null,sources:null,sourceSlots:null,cleanups:null,value:t,owner:E,context:null,pure:n};if(M&&M.running&&(s.state=0,s.tState=o),null===E||E!==P&&(M&&M.running&&E.pure?E.tOwned?E.tOwned.push(s):E.tOwned=[s]:E.owned?E.owned.push(s):E.owned=[s]),F){const[e,t]=L(void 0,{equals:!1}),n=F(s.fn,t);Y((()=>n.dispose()));const o=()=>re(t).then((()=>r.dispose())),r=F(s.fn,o);s.fn=t=>(e(),M&&M.running?r.track(t):n.track(t))}return s}function be(e){const t=M&&M.running;if(!t&&0===e.state||t&&0===e.tState)return;if(!t&&e.state===A||t&&e.tState===A)return ke(e);if(e.suspense&&J(e.suspense.inFallback))return e.suspense.effects.push(e);const n=[e];for(;(e=e.owner)&&(!e.updatedAt||e.updatedAt=0;o--){if(e=n[o],t){let t=e,r=n[o+1];for(;(t=t.owner)&&t!==r;)if(M.disposed.has(t))return}if(!t&&e.state===O||t&&e.tState===O)pe(e);else if(!t&&e.state===A||t&&e.tState===A){const t=j;j=null,we((()=>ke(e,n[0])),!1),j=t}}}function we(e,t){if(j)return e();let n=!1;t||(j=[]),$?n=!0:$=[],V++;try{const t=e();return function(e){j&&(N&&M&&M.running?function(e){for(let t=0;t{o.delete(n),we((()=>{M.running=!0,be(n)}),!1),M&&(M.running=!1)})))}}(j):ye(j),j=null);if(e)return;let t;if(M)if(M.promises.size||M.queue.size){if(M.running)return M.running=!1,M.effects.push.apply(M.effects,$),$=null,void I(!0)}else{const e=M.sources,n=M.disposed;$.push.apply($,M.effects),t=M.resolve;for(const e of $)"tState"in e&&(e.state=e.tState),delete e.tState;M=null,we((()=>{for(const e of n)xe(e);for(const t of e){if(t.value=t.tValue,t.owned)for(let e=0,n=t.owned.length;ex(n)),!1);t&&t()}(n),t}catch(e){j||($=null),j=null,Pe(e)}}function ye(e){for(let t=0;tn=J((()=>(E.context={[e]:t.value},ue((()=>t.children)))))),void 0),n}}function Ne(e){return{subscribe(t){if(!(t instanceof Object)||null==t)throw new TypeError("Expected the observer to be an object.");const n="function"==typeof t?t:t.next&&t.next.bind(t);if(!n)return{unsubscribe(){}};const o=B((t=>(W((()=>{const t=e();J((()=>n(t)))})),t)));return te()&&Y(o),{unsubscribe(){o()}}},[Symbol.observable||"@@observable"](){return this}}}function Fe(e){const[t,n]=L(void 0,{equals:!1});if("subscribe"in e){const t=e.subscribe((e=>n((()=>e))));Y((()=>"unsubscribe"in t?t.unsubscribe():t()))}else{Y(e(n))}return t}const Ce=Symbol("fallback");function je(e){for(let t=0;t1?[]:null;return Y((()=>je(s))),()=>{let u,c,a=e()||[];return a[y],J((()=>{let e,t,d,h,p,g,v,b,w,y=a.length;if(0===y)0!==l&&(je(s),s=[],o=[],r=[],l=0,i&&(i=[])),n.fallback&&(o=[Ce],r[0]=B((e=>(s[0]=e,n.fallback()))),l=1);else if(0===l){for(r=new Array(y),c=0;c=g&&b>=g&&o[v]===a[b];v--,b--)d[b]=r[v],h[b]=s[v],i&&(p[b]=i[v]);for(e=new Map,t=new Array(b+1),c=b;c>=g;c--)w=a[c],u=e.get(w),t[c]=void 0===u?-1:u,e.set(w,c);for(u=g;u<=v;u++)w=o[u],c=e.get(w),void 0!==c&&-1!==c?(d[c]=r[u],h[c]=s[u],i&&(p[c]=i[u]),c=t[c],e.set(w,c)):s[u]();for(c=g;cje(l))),()=>{const c=e()||[];return c[y],J((()=>{if(0===c.length)return 0!==u&&(je(l),l=[],r=[],s=[],u=0,i=[]),n.fallback&&(r=[Ce],s[0]=B((e=>(l[0]=e,n.fallback()))),u=1),s;for(r[0]===Ce&&(l[0](),l=[],r=[],s=[],u=0),o=0;oc[o])):o>=r.length&&(s[o]=B(a));for(;oe(t||{})));return v(n),o}return J((()=>e(t||{})))}function Le(){return!0}const Re={get:(e,t,n)=>t===w?n:e.get(t),has:(e,t)=>t===w||e.has(t),set:Le,deleteProperty:Le,getOwnPropertyDescriptor:(e,t)=>({configurable:!0,enumerable:!0,get:()=>e.get(t),set:Le,deleteProperty:Le}),ownKeys:e=>e.keys()};function ze(e){return(e="function"==typeof e?e():e)?e:{}}function We(...e){let t=!1;for(let n=0;n=0;n--){const o=ze(e[n])[t];if(void 0!==o)return o}},has(t){for(let n=e.length-1;n>=0;n--)if(t in ze(e[n]))return!0;return!1},keys(){const t=[];for(let n=0;n=0;t--)if(e[t]){const o=Object.getOwnPropertyDescriptors(e[t]);for(const t in o)t in n||Object.defineProperty(n,t,{enumerable:!0,get(){for(let n=e.length-1;n>=0;n--){const o=(e[n]||{})[t];if(void 0!==o)return o}}})}return n}function De(e,...t){const n=new Set(t.flat());if(w in e){const o=t.map((t=>new Proxy({get:n=>t.includes(n)?e[n]:void 0,has:n=>t.includes(n)&&n in e,keys:()=>t.filter((t=>t in e))},Re)));return o.push(new Proxy({get:t=>n.has(t)?void 0:e[t],has:t=>!n.has(t)&&t in e,keys:()=>Object.keys(e).filter((e=>!n.has(e)))},Re)),o}const o=Object.getOwnPropertyDescriptors(e);return t.push(Object.keys(o).filter((e=>!n.has(e)))),t.map((t=>{const n={};for(let r=0;re[s],set:()=>!0,enumerable:!0})}return n}))}function _e(e){let t,n;const o=o=>{const r=g.context;if(r){const[o,s]=L();(n||(n=e())).then((e=>{v(r),s((()=>e.default)),v()})),t=o}else if(!t){const[o]=G((()=>(n||(n=e())).then((e=>e.default))));t=o}let s;return _((()=>(s=t())&&J((()=>{if(!r)return s(o);const e=g.context;v(r);const t=s(o);return v(e),t}))))};return o.preload=()=>n||((n=e()).then((e=>t=()=>e.default)),n),o}let Ge,He=0;function Ue(){const e=g.context;return e?`${e.id}${e.count++}`:"cl-"+He++}function Ke(e){const t="fallback"in e&&{fallback:()=>e.fallback};return _($e((()=>e.each),e.children,t||void 0))}function Je(e){const t="fallback"in e&&{fallback:()=>e.fallback};return _(Ve((()=>e.each),e.children,t||void 0))}function Qe(e){let t=!1;const n=e.keyed,o=_((()=>e.when),void 0,{equals:(e,n)=>t?e===n:!e==!n});return _((()=>{const r=o();if(r){const o=e.children,s="function"==typeof o&&o.length>0;return t=n||s,s?J((()=>o(r))):o}return e.fallback}),void 0,void 0)}function Xe(e){let t=!1,n=!1;const o=ue((()=>e.children)),r=_((()=>{let e=o();Array.isArray(e)||(e=[e]);for(let t=0;te[0]===n[0]&&(t?e[1]===n[1]:!e[1]==!n[1])&&e[2]===n[2]});return _((()=>{const[o,s,l]=r();if(o<0)return e.fallback;const i=l.children,u="function"==typeof i&&i.length>0;return t=n||u,u?J((()=>i(s))):i}),void 0,void 0)}function Ye(e){return e}function Ze(){Ge&&[...Ge].forEach((e=>e()))}function et(e){let t,n;g.context&&g.load&&(n=g.load(g.context.id+g.context.count))&&(t=n[0]);const[o,r]=L(t,void 0);return Ge||(Ge=new Set),Ge.add(r),Y((()=>Ge.delete(r))),_((()=>{let t;if(t=o()){const n=e.fallback,o="function"==typeof n&&n.length?J((()=>n(t,(()=>r())))):n;return Z(r),o}return Z(r),e.children}),void 0,void 0)}const tt=(e,t)=>e.showContent===t.showContent&&e.showFallback===t.showFallback,nt=le();function ot(e){let t,[n,o]=L((()=>({inFallback:!1})));const r=ie(nt),[s,l]=L([]);r&&(t=r.register(_((()=>n()().inFallback))));const i=_((n=>{const o=e.revealOrder,r=e.tail,{showContent:l=!0,showFallback:i=!0}=t?t():{},u=s(),c="backwards"===o;if("together"===o){const e=u.every((e=>!e())),t=u.map((()=>({showContent:e&&l,showFallback:i})));return t.inFallback=!e,t}let a=!1,f=n.inFallback;const d=[];for(let e=0,t=u.length;ei)),Be(nt.Provider,{value:{register:e=>{let t;return l((n=>(t=n.length,[...n,e]))),_((()=>i()[t]),void 0,{equals:tt})}},get children(){return e.children}})}function rt(e){let t,n,o,r,s,l=0;const[i,u]=L(!1),c=ae(),a={increment:()=>{1==++l&&u(!0)},decrement:()=>{0==--l&&u(!1)},inFallback:i,effects:[],resolved:!1},f=te();if(g.context&&g.load){const e=g.context.id+g.context.count;let t=g.load(e);if(t&&(o=t[0])&&"$$f"!==o){"object"==typeof o&&"then"in o||(o=Promise.resolve(o));const[t,l]=L(void 0,{equals:!1});r=t,o.then((t=>{if(t||g.done)return t&&(s=t),l();g.gather(e),v(n),l(),v()}))}}const d=ie(nt);let h;return d&&(t=d.register(a.inFallback)),Y((()=>h&&h())),Be(c.Provider,{value:a,get children(){return _((()=>{if(s)throw s;if(n=g.context,r)return r(),r=void 0;n&&"$$f"===o&&v();const l=_((()=>e.children));return _((r=>{const s=a.inFallback(),{showContent:i=!0,showFallback:u=!0}=t?t():{};return(!s||o&&"$$f"!==o)&&i?(a.resolved=!0,h&&h(),h=n=o=void 0,c=a.effects,$.push.apply($,c),c.length=0,l()):u?h?r:B((t=>(h=t,n&&(v({id:n.id+"f",count:0}),n=void 0),e.fallback)),f):void 0;var c}))}))}})}let st;var lt=(0,Object.freeze)([]),it=()=>{};const ut=(e,t,n)=>{const{parentNode:o}=n,r=t.length;let s=e.length,l=r,i=0,u=0,c=null;for(;in-u){const r=e[i];for(;ue[t],yt=({children:e},t)=>e[t],mt=({value:e,properties:t,children:n})=>e(t,...n),kt=({name:e})=>"key"===e,St=(e,{content:t})=>e.reduce(wt,t),xt=(e,t)=>e.reduce(yt,t),Ot=(e,t)=>{const n=null==t?"":String(t);n!==e.data&&(e.data=n)};
2 | /*! (c) Andrea Giammarchi - ISC */var At=(e={})=>{const t=e.document||globalThis.document,n=new Map(e.plugins||[]),o=!!n.size,r=e.diff||ut,s=e.effect||(e=>(e(),it)),l=e.getPeek||(e=>e.peek()),i=e.getValue||(e=>e.value),u=e.isSignal||(e.Signal?dt.bind(e.Signal.prototype):()=>!1),c=e=>t.createTextNode(e),a=(e,t)=>{let n=v.get(e);return n||v.set(e,n={}),n[t]||(n[t]={})},f=(e,t)=>{e.dispose();const n=s((()=>{const n=mt(t);n.id!==e.id?e=d(e,n,!1):e.update(n)}));return e.dispose=n,e},d=(e,t,n)=>t.type===ht?f(e,t):((e,t,n)=>(e.dispose(),e=new h(t),n?e.dispose=s((()=>e.update(t))):e.update(t),e))(e,t,n);class h{constructor(e,t=!0){const[n,o]=t?y(e):[lt,null];this._=t&&e.type===gt,this.id=e.id,this.updates=n,this.content=o,this.dispose=it}get $(){const{content:e,_:t}=this;return t?(this._=!t,(this.content=(({childNodes:e})=>({childNodes:[...e]}))(e)).childNodes):[e]}update(e){for(const t of this.updates)t.call(this,e)}}const p=new h({id:null},!1),g={id:null,view:p},v=new WeakMap;let b;const w=new WeakMap,y=e=>{let t=w.get(e.id);if(!t){const n=[],o=N(e,n,[],lt,!1);w.set(e.id,t=[n,o])}const[n,o]=t;return[n.slice(),o.cloneNode(!0)]},m=(e,t,n,o)=>{o?e.setAttribute(t,n):e[t]=n},k=(e,t,n,o,r)=>{if(u(n)){const l="🙊"+t;l in o&&o[l](),o[l]=s((()=>{m(e,t,i(n),r)}))}else m(e,t,n,r)},S=(e,t,r,s)=>{if(o&&n.has(t))n.get(t)(e,r,s);else if(s[t]!==r)switch(s[t]=r,t){case"class":t+="Name";case"className":case"textContent":k(e,t,r,s,!1);break;case"ref":r.current=e;break;default:t.startsWith("on")?e[t.toLowerCase()]=r:t in e?k(e,t,r,s,!1):null==r?e.removeAttribute(t):k(e,t,r,s,!0)}},x=(e,t,n)=>function(o){const r={},s=St(t,this);(this.updates[n]=n=>{const{attributes:o}=xt(t,n);for(const t of e){const e=o[t],{type:n,value:l}=e;if(n<2)S(s,e.name,l,r);else for(const[e,t]of at(l))S(s,e,t,r)}})(o)},O=(e,n)=>function(o){let s=lt,l=!0,i=-1;const u=St(e,this),c=new Map;(this.updates[n]=n=>{const{value:o}=xt(e,n),f=[];for(let e=0;efunction(n){let o=lt,s=p;const l=St(e,this);(this.updates[t]=t=>{s=f(s,xt(e,t)),o=r(o,s.$,l)})(n)},P=(e,t)=>function(n){const o=St(e,this);(this.updates[t]=t=>{Ot(o,xt(e,t).value)})(n)},T=(e,t)=>function(n){let o,u,a=it;const f=St(e,this),{value:h}=xt(e,n),g=e=>{o!==e&&(a(),o=e,a=s(u))};if(b(l(h))){let e=lt,t=p;u=()=>{const n=i(o);t=d(t,n,!1),e=r(e,t.$,f)}}else{const e=c("");f.replaceWith(e),u=()=>{Ot(e,i(o))}}this.updates[t]=t=>g(xt(e,t).value),g(h)},E=(e,t)=>function(n){let o=lt,s=p,l=null;const i=St(e,this);(this.updates[t]=t=>{t=xt(e,t).value,l!==t.id?(l=t.id,s=d(s,t,!1),o=r(o,s.$,i)):t.type===ht?s.update(mt(t)):s.update(t)})(n)},M=({children:e},t,n,o,r)=>{for(let s=0;s{let l,i;const{length:a}=n;e:switch(e.type){case vt:{const{value:t}=e;switch(!0){case b(t):l=E;break;case ct(t):l=O;break;case u(t):l=T;break;default:i=c(""),n.push(P(r,a));break e}}case ht:i=t.createComment("🙊"),n.push((l||A)(r,a));break;case pt:{const{attributes:l,name:u}=e,c=[u],f=[];for(let e=0;e{const n="function"==typeof e?e():e;b||(b=dt.bind(ft(n)));const o=d(v.get(t)||p,n,!0);v.set(t,o),t.replaceChildren(...o.$)}};const Pt=new WeakSet,Tt=()=>{},Et=(e,...t)=>{const[n,o]=L(e,...t);return Pt.add(n),[n,o]},Mt=(e={})=>At({...e,effect:(...e)=>(W(...e),Tt),getPeek:e=>J(e),getValue:e=>e(),isSignal:e=>Pt.has(e)});export{m as $DEVCOMP,w as $PROXY,y as $TRACK,st as DEV,et as ErrorBoundary,Ke as For,Je as Index,Ye as Match,Qe as Show,rt as Suspense,ot as SuspenseList,Xe as Switch,K as batch,h as cancelCallback,ue as children,Be as createComponent,R as createComputed,le as createContext,H as createDeferred,W as createEffect,_ as createMemo,D as createReaction,Mt as createRender,z as createRenderEffect,G as createResource,B as createRoot,U as createSelector,Et as createSignal,Ue as createUniqueId,fe as enableExternalSource,Ie as enableHydration,oe as enableScheduling,b as equalFn,Fe as from,ee as getListener,te as getOwner,Ve as indexArray,_e as lazy,$e as mapArray,We as mergeProps,Ne as observable,Q as on,Y as onCleanup,Z as onError,X as onMount,d as requestCallback,Ze as resetErrorBoundaries,ne as runWithOwner,g as sharedConfig,De as splitProps,re as startTransition,J as untrack,ie as useContext,se as useTransition};
3 |
--------------------------------------------------------------------------------
/test/array.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | udomsay
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/array.js:
--------------------------------------------------------------------------------
1 | var _templateReference = {},
2 | _templateReference2 = {},
3 | _templateReference3 = {},
4 | _templateReference4 = {};
5 | globalThis.ESXToken || (globalThis.ESXToken = class ESXToken { static ATTRIBUTE = 1; static COMPONENT = 2; static ELEMENT = 3; static FRAGMENT = 4; static INTERPOLATION = 5; static STATIC = 6; static _ = Object.freeze([]); static a = (dynamic, name, value) => ({ type: 1, dynamic, name, value }); static b = (type, value) => ({ type, value }); constructor(id, type, attributes, children, name, value) { this.id = id; this.type = type; this.attributes = attributes; this.children = children; this.name = name; this.value = value; } get properties() { const { attributes } = this; if (attributes.length) { const properties = {}; for (const entry of attributes) { if (entry.type < 2) properties[entry.name] = entry.value;else Object.assign(properties, entry.value); } return properties; } return null; } });
6 | import createRender from '../index.js';
7 | import { Signal, signal, effect } from 'https://unpkg.com/@webreflection/signal';
8 | const render = createRender({
9 | Signal,
10 | effect
11 | });
12 | function Counter({
13 | signal
14 | }) {
15 | console.log('Counter');
16 | return new ESXToken(_templateReference, 3, ESXToken._, [new ESXToken(null, 3, [ESXToken.a(true, "onclick", () => {
17 | signal.value--;
18 | })], [ESXToken.b(6, "-")], "button", "button"), new ESXToken(null, 3, ESXToken._, [ESXToken.b(5, signal.value)], "span", "span"), new ESXToken(null, 3, [ESXToken.a(true, "onclick", () => {
19 | signal.value++;
20 | })], [ESXToken.b(6, "+")], "button", "button")], "div", "div");
21 | }
22 | const Counters = ({
23 | many
24 | }) => {
25 | console.log('Counters');
26 | const data = [];
27 | for (let i = 0; i < many; i++) data[i] = new ESXToken(_templateReference2, 2, [ESXToken.a(true, "signal", signal(0))], ESXToken._, "Counter", Counter);
28 | return new ESXToken(_templateReference3, 3, ESXToken._, [ESXToken.b(5, data)], "div", "div");
29 | };
30 | render(new ESXToken(_templateReference4, 2, [ESXToken.a(true, "many", 3)], ESXToken._, "Counters", Counters), document.body);
31 |
--------------------------------------------------------------------------------
/test/array.jsx:
--------------------------------------------------------------------------------
1 | import createRender from '../index.js';
2 | import {Signal, signal, effect} from 'https://unpkg.com/@webreflection/signal';
3 | const render = createRender({Signal, effect});
4 |
5 | function Counter({signal}) {
6 | console.log('Counter');
7 | return (
8 |
9 |
10 | { signal.value }
11 |
12 |
13 | );
14 | }
15 |
16 | const Counters = ({many}) => {
17 | console.log('Counters');
18 | const data = [];
19 | for (let i = 0; i < many; i++)
20 | data[i] = ;
21 | return (
22 |
23 | {data}
24 |
25 | );
26 | };
27 |
28 | render(, document.body);
29 |
--------------------------------------------------------------------------------
/test/base.js:
--------------------------------------------------------------------------------
1 | var _templateReference = {};
2 | globalThis.ESXToken || (globalThis.ESXToken = class ESXToken { static ATTRIBUTE = 1; static COMPONENT = 2; static ELEMENT = 3; static FRAGMENT = 4; static INTERPOLATION = 5; static STATIC = 6; static _ = Object.freeze([]); static a = (dynamic, name, value) => ({ type: 1, dynamic, name, value }); static b = (type, value) => ({ type, value }); constructor(id, type, attributes, children, name, value) { this.id = id; this.type = type; this.attributes = attributes; this.children = children; this.name = name; this.value = value; } get properties() { const { attributes } = this; if (attributes.length) { const properties = {}; for (const entry of attributes) { if (entry.type < 2) properties[entry.name] = entry.value;else Object.assign(properties, entry.value); } return properties; } return null; } });
3 | const div = new ESXToken(_templateReference, 3, [ESXToken.a(true, "a", 1), ESXToken.a(false, "b", "2")], ESXToken._, "div", "div");
4 |
--------------------------------------------------------------------------------
/test/base.jsx:
--------------------------------------------------------------------------------
1 | const div = ;
2 |
--------------------------------------------------------------------------------
/test/children.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | udomsay
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/children.js:
--------------------------------------------------------------------------------
1 | var _templateReference = {},
2 | _templateReference2 = {};
3 | globalThis.ESXToken || (globalThis.ESXToken = class ESXToken { static ATTRIBUTE = 1; static COMPONENT = 2; static ELEMENT = 3; static FRAGMENT = 4; static INTERPOLATION = 5; static STATIC = 6; static _ = Object.freeze([]); static a = (dynamic, name, value) => ({ type: 1, dynamic, name, value }); static b = (type, value) => ({ type, value }); constructor(id, type, attributes, children, name, value) { this.id = id; this.type = type; this.attributes = attributes; this.children = children; this.name = name; this.value = value; } get properties() { const { attributes } = this; if (attributes.length) { const properties = {}; for (const entry of attributes) { if (entry.type < 2) properties[entry.name] = entry.value;else Object.assign(properties, entry.value); } return properties; } return null; } });
4 | // grab signals from various libaries, here the simplest I know
5 | import { createRender, signal } from '../signal.js';
6 | const render = createRender();
7 |
8 | // Counter Cmponent example
9 | function Outer(_, ...children) {
10 | return new ESXToken(_templateReference, 3, ESXToken._, [ESXToken.b(5, children)], "ul", "ul");
11 | }
12 | render(new ESXToken(_templateReference2, 2, ESXToken._, [new ESXToken(null, 3, ESXToken._, [ESXToken.b(6, "A")], "li", "li"), new ESXToken(null, 3, ESXToken._, [ESXToken.b(5, 'B')], "li", "li")], "Outer", Outer), document.body);
13 |
--------------------------------------------------------------------------------
/test/children.jsx:
--------------------------------------------------------------------------------
1 | // grab signals from various libaries, here the simplest I know
2 | import {createRender, signal} from '../signal.js';
3 |
4 | const render = createRender();
5 |
6 | // Counter Cmponent example
7 | function Outer(_, ...children) {
8 | return (
9 |
10 | );
11 | }
12 |
13 | render(
14 |
15 | A
16 | {'B'}
17 | ,
18 | document.body
19 | );
20 |
--------------------------------------------------------------------------------
/test/counter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | udomsay
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/counter.js:
--------------------------------------------------------------------------------
1 | var _templateReference = {},
2 | _templateReference2 = {};
3 | globalThis.ESXToken || (globalThis.ESXToken = class ESXToken { static ATTRIBUTE = 1; static COMPONENT = 2; static ELEMENT = 3; static FRAGMENT = 4; static INTERPOLATION = 5; static STATIC = 6; static _ = Object.freeze([]); static a = (dynamic, name, value) => ({ type: 1, dynamic, name, value }); static b = (type, value) => ({ type, value }); constructor(id, type, attributes, children, name, value) { this.id = id; this.type = type; this.attributes = attributes; this.children = children; this.name = name; this.value = value; } get properties() { const { attributes } = this; if (attributes.length) { const properties = {}; for (const entry of attributes) { if (entry.type < 2) properties[entry.name] = entry.value;else Object.assign(properties, entry.value); } return properties; } return null; } });
4 | // grab signals from various libaries, here the simplest I know
5 | import { Signal, signal, effect } from 'https://unpkg.com/@webreflection/signal';
6 |
7 | // import the `createRender` utility
8 | import createRender from '../index.js';
9 | const render = createRender({
10 | Signal,
11 | effect
12 | });
13 |
14 | // Counter Cmponent example
15 | function Counter({
16 | clicks
17 | }) {
18 | const attrs = {
19 | a: 1
20 | };
21 | return new ESXToken(_templateReference, 3, [ESXToken.b(5, attrs)], [new ESXToken(null, 3, [ESXToken.a(true, "onclick", () => {
22 | clicks.value--;
23 | })], [ESXToken.b(6, "-")], "button", "button"), new ESXToken(null, 3, ESXToken._, [ESXToken.b(5, clicks)], "span", "span"), new ESXToken(null, 3, [ESXToken.a(true, "onclick", () => {
24 | clicks.value++;
25 | })], [ESXToken.b(6, "+")], "button", "button")], "div", "div");
26 | }
27 | render(new ESXToken(_templateReference2, 2, [ESXToken.a(true, "clicks", signal(0))], ESXToken._, "Counter", Counter), document.body);
28 |
--------------------------------------------------------------------------------
/test/counter.jsx:
--------------------------------------------------------------------------------
1 | // grab signals from various libaries, here the simplest I know
2 | import {Signal, signal, effect} from 'https://unpkg.com/@webreflection/signal';
3 |
4 | // import the `createRender` utility
5 | import createRender from 'https://unpkg.com/udomsay';
6 | const render = createRender({Signal, effect});
7 |
8 | // Counter Cmponent example
9 | function Counter({clicks}) {
10 | const attrs = {a: 1};
11 | return (
12 |
13 |
14 | {clicks}
15 |
16 |
17 | );
18 | }
19 |
20 | render(
21 | ,
22 | document.body
23 | );
24 |
--------------------------------------------------------------------------------
/test/dist/preact.html:
--------------------------------------------------------------------------------
1 | preact
2 |
--------------------------------------------------------------------------------
/test/dist/preact.js:
--------------------------------------------------------------------------------
1 | var _templateReference = {},
2 | _templateReference2 = {};
3 | globalThis.ESXToken || (globalThis.ESXToken = class ESXToken { static ATTRIBUTE = 1; static COMPONENT = 2; static ELEMENT = 3; static FRAGMENT = 4; static INTERPOLATION = 5; static STATIC = 6; static _ = Object.freeze([]); static a = (dynamic, name, value) => ({ type: 1, dynamic, name, value }); static b = (type, value) => ({ type, value }); constructor(id, type, attributes, children, name, value) { this.id = id; this.type = type; this.attributes = attributes; this.children = children; this.name = name; this.value = value; } get properties() { const { attributes } = this; if (attributes.length) { const properties = {}; for (const entry of attributes) { if (entry.type < 2) properties[entry.name] = entry.value;else Object.assign(properties, entry.value); } return properties; } return null; } });
4 | import { createRender, signal } from '../../preact.js';
5 | const render = createRender();
6 | render(new ESXToken(_templateReference, 2, [ESXToken.a(true, "clicks", signal(0))], ESXToken._, "Counter", Counter), document.body);
7 | function Counter({
8 | clicks
9 | }) {
10 | return new ESXToken(_templateReference2, 3, ESXToken._, [new ESXToken(null, 3, [ESXToken.a(true, "onclick", () => {
11 | clicks.value--;
12 | })], [ESXToken.b(6, "-")], "button", "button"), new ESXToken(null, 3, ESXToken._, [ESXToken.b(5, clicks)], "span", "span"), new ESXToken(null, 3, [ESXToken.a(true, "onclick", () => {
13 | clicks.value++;
14 | })], [ESXToken.b(6, "+")], "button", "button")], "div", "div");
15 | }
16 |
--------------------------------------------------------------------------------
/test/dist/preact.jsx:
--------------------------------------------------------------------------------
1 | import {createRender, signal} from '../../preact.js';
2 | const render = createRender();
3 |
4 | render(
5 | ,
6 | document.body
7 | );
8 |
9 | function Counter({clicks}) {
10 | return (
11 |
12 |
13 | {clicks}
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/test/dist/signal.html:
--------------------------------------------------------------------------------
1 | signal
2 |
--------------------------------------------------------------------------------
/test/dist/signal.js:
--------------------------------------------------------------------------------
1 | var _templateReference = {},
2 | _templateReference2 = {};
3 | globalThis.ESXToken || (globalThis.ESXToken = class ESXToken { static ATTRIBUTE = 1; static COMPONENT = 2; static ELEMENT = 3; static FRAGMENT = 4; static INTERPOLATION = 5; static STATIC = 6; static _ = Object.freeze([]); static a = (dynamic, name, value) => ({ type: 1, dynamic, name, value }); static b = (type, value) => ({ type, value }); constructor(id, type, attributes, children, name, value) { this.id = id; this.type = type; this.attributes = attributes; this.children = children; this.name = name; this.value = value; } get properties() { const { attributes } = this; if (attributes.length) { const properties = {}; for (const entry of attributes) { if (entry.type < 2) properties[entry.name] = entry.value;else Object.assign(properties, entry.value); } return properties; } return null; } });
4 | import { createRender, signal } from '../../signal.js';
5 | const render = createRender();
6 | render(new ESXToken(_templateReference, 2, [ESXToken.a(true, "clicks", signal(0))], ESXToken._, "Counter", Counter), document.body);
7 | function Counter({
8 | clicks
9 | }) {
10 | return new ESXToken(_templateReference2, 3, ESXToken._, [new ESXToken(null, 3, [ESXToken.a(true, "onclick", () => {
11 | clicks.value--;
12 | })], [ESXToken.b(6, "-")], "button", "button"), new ESXToken(null, 3, ESXToken._, [ESXToken.b(5, clicks)], "span", "span"), new ESXToken(null, 3, [ESXToken.a(true, "onclick", () => {
13 | clicks.value++;
14 | })], [ESXToken.b(6, "+")], "button", "button")], "div", "div");
15 | }
16 |
--------------------------------------------------------------------------------
/test/dist/signal.jsx:
--------------------------------------------------------------------------------
1 | import {createRender, signal} from '../../signal.js';
2 | const render = createRender();
3 |
4 | render(
5 | ,
6 | document.body
7 | );
8 |
9 | function Counter({clicks}) {
10 | return (
11 |
12 |
13 | {clicks}
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/test/dist/solid.html:
--------------------------------------------------------------------------------
1 | solid
2 |
--------------------------------------------------------------------------------
/test/dist/solid.js:
--------------------------------------------------------------------------------
1 | var _templateReference = {},
2 | _templateReference2 = {};
3 | globalThis.ESXToken || (globalThis.ESXToken = class ESXToken { static ATTRIBUTE = 1; static COMPONENT = 2; static ELEMENT = 3; static FRAGMENT = 4; static INTERPOLATION = 5; static STATIC = 6; static _ = Object.freeze([]); static a = (dynamic, name, value) => ({ type: 1, dynamic, name, value }); static b = (type, value) => ({ type, value }); constructor(id, type, attributes, children, name, value) { this.id = id; this.type = type; this.attributes = attributes; this.children = children; this.name = name; this.value = value; } get properties() { const { attributes } = this; if (attributes.length) { const properties = {}; for (const entry of attributes) { if (entry.type < 2) properties[entry.name] = entry.value;else Object.assign(properties, entry.value); } return properties; } return null; } });
4 | import { createRender, createSignal } from '../../solid.js';
5 | const render = createRender();
6 | render(new ESXToken(_templateReference, 2, [ESXToken.a(true, "clicks", createSignal(0))], ESXToken._, "Counter", Counter), document.body);
7 | function Counter({
8 | clicks
9 | }) {
10 | const [signal, update] = clicks;
11 | const value = signal();
12 | return new ESXToken(_templateReference2, 3, ESXToken._, [new ESXToken(null, 3, [ESXToken.a(true, "onclick", () => {
13 | update(value - 1);
14 | })], [ESXToken.b(6, "-")], "button", "button"), new ESXToken(null, 3, ESXToken._, [ESXToken.b(5, value)], "span", "span"), new ESXToken(null, 3, [ESXToken.a(true, "onclick", () => {
15 | update(value + 1);
16 | })], [ESXToken.b(6, "+")], "button", "button")], "div", "div");
17 | }
18 |
--------------------------------------------------------------------------------
/test/dist/solid.jsx:
--------------------------------------------------------------------------------
1 | import {createRender, createSignal} from '../../solid.js';
2 | const render = createRender();
3 |
4 | render(
5 | ,
6 | document.body
7 | );
8 |
9 | function Counter({clicks}) {
10 | const [signal, update] = clicks;
11 | const value = signal();
12 | return (
13 |
14 |
15 | {value}
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/test/dist/usignal.html:
--------------------------------------------------------------------------------
1 | usignal
2 |
--------------------------------------------------------------------------------
/test/dist/usignal.js:
--------------------------------------------------------------------------------
1 | var _templateReference = {},
2 | _templateReference2 = {};
3 | globalThis.ESXToken || (globalThis.ESXToken = class ESXToken { static ATTRIBUTE = 1; static COMPONENT = 2; static ELEMENT = 3; static FRAGMENT = 4; static INTERPOLATION = 5; static STATIC = 6; static _ = Object.freeze([]); static a = (dynamic, name, value) => ({ type: 1, dynamic, name, value }); static b = (type, value) => ({ type, value }); constructor(id, type, attributes, children, name, value) { this.id = id; this.type = type; this.attributes = attributes; this.children = children; this.name = name; this.value = value; } get properties() { const { attributes } = this; if (attributes.length) { const properties = {}; for (const entry of attributes) { if (entry.type < 2) properties[entry.name] = entry.value;else Object.assign(properties, entry.value); } return properties; } return null; } });
4 | import { createRender, signal } from '../../usignal.js';
5 | const render = createRender();
6 | render(new ESXToken(_templateReference, 2, [ESXToken.a(true, "clicks", signal(0))], ESXToken._, "Counter", Counter), document.body);
7 | function Counter({
8 | clicks
9 | }) {
10 | return new ESXToken(_templateReference2, 3, ESXToken._, [new ESXToken(null, 3, [ESXToken.a(true, "onclick", () => {
11 | clicks.value--;
12 | })], [ESXToken.b(6, "-")], "button", "button"), new ESXToken(null, 3, ESXToken._, [ESXToken.b(5, clicks)], "span", "span"), new ESXToken(null, 3, [ESXToken.a(true, "onclick", () => {
13 | clicks.value++;
14 | })], [ESXToken.b(6, "+")], "button", "button")], "div", "div");
15 | }
16 |
--------------------------------------------------------------------------------
/test/dist/usignal.jsx:
--------------------------------------------------------------------------------
1 | import {createRender, signal} from '../../usignal.js';
2 | const render = createRender();
3 |
4 | render(
5 | ,
6 | document.body
7 | );
8 |
9 | function Counter({clicks}) {
10 | return (
11 |
12 |
13 | {clicks}
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/test/esx-babel.js:
--------------------------------------------------------------------------------
1 | var _templateReference = {},
2 | _templateReference2 = {},
3 | _templateReference3 = {},
4 | _templateReference4 = {},
5 | _templateReference5 = {},
6 | _templateReference6 = {},
7 | _templateReference7 = {};
8 | globalThis.ESXToken || (globalThis.ESXToken = class ESXToken { static ATTRIBUTE = 1; static COMPONENT = 2; static ELEMENT = 3; static FRAGMENT = 4; static INTERPOLATION = 5; static STATIC = 6; static _ = Object.freeze([]); static a = (dynamic, name, value) => ({ type: 1, dynamic, name, value }); static b = (type, value) => ({ type, value }); constructor(id, type, attributes, children, name, value) { this.id = id; this.type = type; this.attributes = attributes; this.children = children; this.name = name; this.value = value; } get properties() { const { attributes } = this; if (attributes.length) { const properties = {}; for (const entry of attributes) { if (entry.type < 2) properties[entry.name] = entry.value;else Object.assign(properties, entry.value); } return properties; } return null; } });
9 | import { render, signal } from '../index.js';
10 | const s = signal('test');
11 | const c = signal(new ESXToken(_templateReference, 3, ESXToken._, [ESXToken.b(6, "deep")], "strong", "strong"));
12 | function P({
13 | test
14 | }) {
15 | return new ESXToken(_templateReference2, 3, ESXToken._, [ESXToken.b(6, "\n Another "), ESXToken.b(5, c), ESXToken.b(6, " / "), ESXToken.b(5, test)], "p", "p");
16 | }
17 | [1, 2, 3].map(num => new ESXToken(_templateReference3, 3, ESXToken._, [ESXToken.b(5, num)], "span", "span"));
18 | document.body.innerHTML = '';
19 | const {
20 | firstElementChild,
21 | lastElementChild
22 | } = document.body;
23 | const main = test => new ESXToken(_templateReference4, 3, [ESXToken.a(true, "data-test", test)], [ESXToken.b(6, "\n Hello World "), ESXToken.b(5, test), ESXToken.b(6, " "), ESXToken.b(5, s), ESXToken.b(5, [1, 2, 3].map(num => new ESXToken(_templateReference5, 2, [ESXToken.a(true, "num", num)], ESXToken._, "Span", Span))), new ESXToken(null, 2, [ESXToken.a(true, "test", test)], ESXToken._, "P", P)], "div", "div");
24 | render(main('test'), firstElementChild);
25 | render(main('test'), lastElementChild);
26 | setTimeout(() => {
27 | render(main('OK'), firstElementChild);
28 | setTimeout(() => {
29 | s.value = 'signal';
30 | setTimeout(() => {
31 | c.value = new ESXToken(_templateReference6, 3, ESXToken._, [ESXToken.b(6, "deep")], "i", "i");
32 | }, 1000);
33 | }, 1000);
34 | }, 2000);
35 | function Span({
36 | num
37 | }) {
38 | return new ESXToken(_templateReference7, 3, ESXToken._, [ESXToken.b(5, num)], "span", "span");
39 | }
40 |
--------------------------------------------------------------------------------
/test/esx.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | udomsay
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/esx.js:
--------------------------------------------------------------------------------
1 | import {ESX, Token} from '../node_modules/@ungap/esx/esm/index.js';
2 |
3 | import {render, signal} from '../index.js';
4 |
5 | const esx = ESX({P});
6 | const s = signal('test');
7 | const c = signal(esx`deep`);
8 |
9 | function P({test}) {
10 | return esx`
11 |
12 | Another ${c} / ${test}
13 |
14 | `;
15 | }
16 |
17 | document.body.innerHTML = '';
18 | const {firstElementChild, lastElementChild} = document.body;
19 |
20 | const main = (test) => esx`
21 |
22 | Hello World ${test} ${s}
23 | ${[1, 2, 3].map(num => esx`
${num}`)}
24 |
25 |
26 | `;
27 |
28 | render(
29 | main('test'),
30 | firstElementChild
31 | );
32 |
33 | render(
34 | main('test'),
35 | lastElementChild
36 | );
37 |
38 | setTimeout(
39 | () => {
40 | render(
41 | main('OK'),
42 | firstElementChild
43 | );
44 | setTimeout(() => {
45 | s.value = 'signal';
46 | setTimeout(() => {
47 | c.value = esx`deep`;
48 | }, 1000);
49 | }, 1000);
50 | },
51 | 2000
52 | );
53 |
54 |
--------------------------------------------------------------------------------
/test/esx.jsx:
--------------------------------------------------------------------------------
1 | import {render, signal} from '../index.js';
2 |
3 | const s = signal('test');
4 | const c = signal(deep);
5 |
6 | function P({test}) {
7 | return (
8 |
9 | Another {c} / {test}
10 |
11 | );
12 | }
13 |
14 | [1, 2, 3].map(num => {num});
15 |
16 | document.body.innerHTML = '';
17 | const {firstElementChild, lastElementChild} = document.body;
18 |
19 | const main = (test) => (
20 |
21 | Hello World {test} {s}
22 | {[1, 2, 3].map(num =>
)}
23 |
24 |
25 | );
26 |
27 | render(
28 | main('test'),
29 | firstElementChild
30 | );
31 |
32 | render(
33 | main('test'),
34 | lastElementChild
35 | );
36 |
37 | setTimeout(
38 | () => {
39 | render(
40 | main('OK'),
41 | firstElementChild
42 | );
43 | setTimeout(() => {
44 | s.value = 'signal';
45 | setTimeout(() => {
46 | c.value = deep;
47 | }, 1000);
48 | }, 1000);
49 | },
50 | 2000
51 | );
52 |
53 | function Span({num}) {
54 | return {num};
55 | }
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | udomsay
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | var _templateReference = {},
2 | _templateReference2 = {},
3 | _templateReference3 = {},
4 | _templateReference4 = {},
5 | _templateReference5 = {};
6 | globalThis.ESXToken || (globalThis.ESXToken = class ESXToken { static ATTRIBUTE = 1; static COMPONENT = 2; static ELEMENT = 3; static FRAGMENT = 4; static INTERPOLATION = 5; static STATIC = 6; static _ = Object.freeze([]); static a = (dynamic, name, value) => ({ type: 1, dynamic, name, value }); static b = (type, value) => ({ type, value }); constructor(id, type, attributes, children, name, value) { this.id = id; this.type = type; this.attributes = attributes; this.children = children; this.name = name; this.value = value; } get properties() { const { attributes } = this; if (attributes.length) { const properties = {}; for (const entry of attributes) { if (entry.type < 2) properties[entry.name] = entry.value;else Object.assign(properties, entry.value); } return properties; } return null; } });
7 | // import './esm/document.js';
8 | import createRender from '../index.js';
9 | import { Signal, signal, effect } from 'https://unpkg.com/@webreflection/signal';
10 | const A = signal('A');
11 | const B = signal('B');
12 | const C = signal('C');
13 | const p = signal(new ESXToken(_templateReference, 3, [ESXToken.a(false, "is", "built-in-p")], [ESXToken.b(6, "first")], "p", "p"));
14 | customElements.define('built-in-p', class BuiltInP extends HTMLParagraphElement {
15 | connectedCallback() {
16 | console.log('connected', this);
17 | }
18 | }, {
19 | extends: 'p'
20 | });
21 | const div = (A, B) => new ESXToken(_templateReference2, 3, [ESXToken.a(true, "test", A), ESXToken.a(false, "static", "value")], [ESXToken.b(5, B), new ESXToken(null, 2, [ESXToken.a(true, "test", A)], ESXToken._, "Component", Component)], "div", "div");
22 | function Component({
23 | test
24 | }) {
25 | return new ESXToken(_templateReference3, 3, ESXToken._, [new ESXToken(null, 3, [ESXToken.a(true, "test", test)], [ESXToken.b(6, "OK "), ESXToken.b(5, test)], "p", "p"), ESXToken.b(5, p)], "div", "div");
26 | }
27 | function CompA({
28 | name
29 | }) {
30 | return new ESXToken(_templateReference4, 3, ESXToken._, [ESXToken.b(6, "Comp "), ESXToken.b(5, name)], "strong", "strong");
31 | }
32 | function CompB({
33 | name
34 | }) {
35 | return new ESXToken(_templateReference5, 3, ESXToken._, [ESXToken.b(6, "Comp "), ESXToken.b(5, name)], "em", "em");
36 | }
37 | const render = createRender({
38 | Signal,
39 | effect
40 | });
41 |
42 | // render(
43 | // ,
46 | // document.body
47 | // );
48 |
49 | // setInterval(() => { render(<>{Math.random() < .5 ? : }>, document.body) }, 1000);
50 |
51 | // render(, document.body);
52 | // setTimeout(() => render(, document.body), 2000);
53 | // setTimeout(() => render(, document.body), 4000);
54 | // setTimeout(() => render(, document.body), 6000);
55 |
56 | // render(div(A, B), document.body.appendChild(document.createElement('div')));
57 | // render(div(A, C), document.body.appendChild(document.createElement('div')));
58 | // setTimeout(() => A.value = 'B', 2000);
59 | // setTimeout(() => B.value = 'C', 2000);
60 | // setTimeout(() => B.value = 'D', 4000);
61 | // setTimeout(() => p.value = last
, 7000);
62 | // setTimeout(() => {
63 | // render(div(B, C), document.body.firstElementChild);
64 | // render(div(C, A), document.body.lastElementChild);
65 | // }, 10000);
66 |
--------------------------------------------------------------------------------
/test/index.jsx:
--------------------------------------------------------------------------------
1 | // import './esm/document.js';
2 | import createRender from '../index.js';
3 | import {Signal, signal, effect} from 'https://unpkg.com/@webreflection/signal';
4 | const render = createRender({Signal, effect});
5 |
6 | const A = signal('A');
7 | const B = signal('B');
8 | const C = signal('C');
9 | const p = signal(first
);
10 |
11 | customElements.define(
12 | 'built-in-p',
13 | class BuiltInP extends HTMLParagraphElement {
14 | connectedCallback() {
15 | console.log('connected', this);
16 | }
17 | },
18 | {extends: 'p'}
19 | );
20 |
21 | const div = (A, B) => (
22 |
23 | {B}
24 |
25 |
26 | );
27 |
28 | function Component({test}) {
29 | return (
30 |
31 |
OK {test}
32 | {p}
33 |
34 | );
35 | }
36 |
37 | function CompA({name}) {
38 | return Comp {name};
39 | }
40 |
41 | function CompB({name}) {
42 | return Comp {name};
43 | }
44 |
45 | // render(
46 | // ,
49 | // document.body
50 | // );
51 |
52 | // setInterval(() => { render(<>{Math.random() < .5 ? : }>, document.body) }, 1000);
53 |
54 | // render(, document.body);
55 | // setTimeout(() => render(, document.body), 2000);
56 | // setTimeout(() => render(, document.body), 4000);
57 | // setTimeout(() => render(, document.body), 6000);
58 |
59 | // render(div(A, B), document.body.appendChild(document.createElement('div')));
60 | // render(div(A, C), document.body.appendChild(document.createElement('div')));
61 | // setTimeout(() => A.value = 'B', 2000);
62 | // setTimeout(() => B.value = 'C', 2000);
63 | // setTimeout(() => B.value = 'D', 4000);
64 | // setTimeout(() => p.value = last
, 7000);
65 | // setTimeout(() => {
66 | // render(div(B, C), document.body.firstElementChild);
67 | // render(div(C, A), document.body.lastElementChild);
68 | // }, 10000);
69 |
--------------------------------------------------------------------------------
/test/ssr.js:
--------------------------------------------------------------------------------
1 | var _templateReference = {},
2 | _templateReference2 = {},
3 | _templateReference3 = {};
4 | globalThis.ESXToken || (globalThis.ESXToken = class ESXToken { static ATTRIBUTE = 1; static COMPONENT = 2; static ELEMENT = 3; static FRAGMENT = 4; static INTERPOLATION = 5; static STATIC = 6; static _ = Object.freeze([]); static a = (dynamic, name, value) => ({ type: 1, dynamic, name, value }); static b = (type, value) => ({ type, value }); constructor(id, type, attributes, children, name, value) { this.id = id; this.type = type; this.attributes = attributes; this.children = children; this.name = name; this.value = value; } get properties() { const { attributes } = this; if (attributes.length) { const properties = {}; for (const entry of attributes) { if (entry.type < 2) properties[entry.name] = entry.value;else Object.assign(properties, entry.value); } return properties; } return null; } });
5 | import createRender from '../esm/ssr.js';
6 | import { Signal, signal } from '@webreflection/signal';
7 | const render = createRender({
8 | Signal
9 | });
10 | function Counter({
11 | clicks
12 | }) {
13 | return new ESXToken(_templateReference, 3, ESXToken._, [new ESXToken(null, 3, [ESXToken.a(true, "onclick", () => {
14 | clicks.value--;
15 | })], [ESXToken.b(6, "-")], "button", "button"), new ESXToken(null, 3, ESXToken._, [ESXToken.b(5, clicks)], "span", "span"), new ESXToken(null, 3, [ESXToken.a(true, "onclick", () => {
16 | clicks.value++;
17 | })], [ESXToken.b(6, "+")], "button", "button")], "div", "div");
18 | }
19 | const counters = new Array(2).fill();
20 | function test() {
21 | console.time('render');
22 | render(new ESXToken(_templateReference2, 3, [ESXToken.a(false, "lang", "en")], [new ESXToken(null, 3, ESXToken._, [ESXToken.b(6, "not &scaped")], "title", "title"), new ESXToken(null, 3, ESXToken._, [new ESXToken(null, 3, [ESXToken.a(false, "test", "attribute")], [ESXToken.b(5, counters.map((_, i) => new ESXToken(_templateReference3, 3, ESXToken._, [new ESXToken(null, 2, [ESXToken.a(true, "clicks", signal(i))], ESXToken._, "Counter", Counter)], "li", "li")))], "ul", "ul")], "body", "body")], "html", "html"), console.log.bind(console));
23 | console.timeEnd('render');
24 | }
25 | for (let i = 0; i < 5; i++) test();
26 |
--------------------------------------------------------------------------------
/test/ssr.jsx:
--------------------------------------------------------------------------------
1 | import createRender from '../esm/ssr.js';
2 | import {Signal, signal} from '@webreflection/signal';
3 | const render = createRender({Signal});
4 |
5 | function Counter({clicks}) {
6 | return (
7 |
8 |
9 | {clicks}
10 |
11 |
12 | );
13 | }
14 |
15 | const counters = new Array(2).fill();
16 |
17 | function test() {
18 | console.time('render');
19 | render(
20 | (
21 |
22 | not &scaped
23 |
24 |
25 | {counters.map((_, i) => )}
26 |
27 |
28 |
29 | ),
30 | console.log.bind(console)
31 | );
32 | console.timeEnd('render');
33 | }
34 |
35 | for (let i = 0; i < 5; i++) test();
36 |
--------------------------------------------------------------------------------
/test/uhooks.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ESX + uhooks
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/uhooks.js:
--------------------------------------------------------------------------------
1 | var _templateReference = {},
2 | _templateReference2 = {},
3 | _templateReference3 = {},
4 | _templateReference4 = {},
5 | _templateReference5 = {};
6 | globalThis.ESXToken || (globalThis.ESXToken = class ESXToken { static ATTRIBUTE = 1; static COMPONENT = 2; static ELEMENT = 3; static FRAGMENT = 4; static INTERPOLATION = 5; static STATIC = 6; static _ = Object.freeze([]); static a = (dynamic, name, value) => ({ type: 1, dynamic, name, value }); static b = (type, value) => ({ type, value }); constructor(id, type, attributes, children, name, value) { this.id = id; this.type = type; this.attributes = attributes; this.children = children; this.name = name; this.value = value; } get properties() { const { attributes } = this; if (attributes.length) { const properties = {}; for (const entry of attributes) { if (entry.type < 2) properties[entry.name] = entry.value;else Object.assign(properties, entry.value); } return properties; } return null; } });
7 | import { hooked, useState } from 'https://unpkg.com/uhooks?module';
8 | const transform = token => {
9 | switch (token.type) {
10 | case ESXToken.COMPONENT:
11 | {
12 | let id, node;
13 | hooked(() => {
14 | console.time('update');
15 | const result = token.value();
16 | if (id !== result.id) {
17 | id = result.id;
18 | const replace = transform(result);
19 | if (!node) node = replace;else {
20 | node.replaceWith(replace);
21 | node = replace;
22 | }
23 | }
24 | node.update(result);
25 | console.timeEnd('update');
26 | })();
27 | return node;
28 | }
29 | case ESXToken.ELEMENT:
30 | {
31 | const node = document.createElement(token.name);
32 | node.update = ({
33 | attributes,
34 | children
35 | }) => {
36 | node.textContent = children[0].value;
37 | if (attributes.length) node.onclick = attributes[0].value;
38 | };
39 | return node;
40 | }
41 | }
42 | };
43 | const same = () => transform(new ESXToken(_templateReference, 2, ESXToken._, ESXToken._, "Counter", Counter));
44 | console.time('render');
45 | document.body.append(transform(new ESXToken(_templateReference2, 2, ESXToken._, ESXToken._, "Counter", Counter)), same(), same(), same(), transform(new ESXToken(_templateReference3, 2, ESXToken._, ESXToken._, "Counter", Counter)));
46 | console.timeEnd('render');
47 | if (location.search === '?auto') {
48 | requestAnimationFrame(function click() {
49 | const button = document.querySelector('button');
50 | if (button) {
51 | button.click();
52 | requestAnimationFrame(click);
53 | }
54 | });
55 | }
56 | function Counter() {
57 | const [count, update] = useState(0);
58 | return count < 10 ? new ESXToken(_templateReference4, 3, [ESXToken.a(true, "onClick", () => update(count + 1))], [ESXToken.b(5, count)], "button", "button") : new ESXToken(_templateReference5, 3, ESXToken._, [ESXToken.b(6, "You reached 10 \uD83E\uDD73")], "div", "div");
59 | }
60 |
--------------------------------------------------------------------------------
/test/uhooks.jsx:
--------------------------------------------------------------------------------
1 | import {hooked, useState} from 'https://unpkg.com/uhooks?module';
2 |
3 | const transform = token => {
4 | switch (token.type) {
5 | case ESXToken.COMPONENT: {
6 | let id, node;
7 | hooked(() => {
8 | const result = token.value();
9 | if (id !== result.id) {
10 | id = result.id;
11 | const replace = transform(result);
12 | if (!node)
13 | node = replace;
14 | else {
15 | node.replaceWith(replace);
16 | node = replace;
17 | }
18 | }
19 | node.update(result);
20 | })();
21 | return node;
22 | }
23 | case ESXToken.ELEMENT: {
24 | const node = document.createElement(token.name);
25 | node.update = ({attributes, children}) => {
26 | node.textContent = children[0].value;
27 | if (attributes.length)
28 | node.onclick = attributes[0].value;
29 | };
30 | return node;
31 | }
32 | }
33 | };
34 |
35 | const same = () => transform();
36 |
37 | console.time('render');
38 | document.body.append(
39 | transform(),
40 | same(),
41 | same(),
42 | same(),
43 | transform()
44 | );
45 | console.timeEnd('render');
46 |
47 | if (location.search === '?auto') {
48 | requestAnimationFrame(function click() {
49 | const button = document.querySelector('button');
50 | if (button) {
51 | button.click();
52 | requestAnimationFrame(click);
53 | }
54 | });
55 | }
56 |
57 | function Counter() {
58 | const [count, update] = useState(0);
59 | return count < 10 ?
60 | :
61 | You reached 10 🥳
62 | ;
63 | }
64 |
--------------------------------------------------------------------------------
/usignal.js:
--------------------------------------------------------------------------------
1 | /*! (c) Andrea Giammarchi */
2 | const{is:t}=Object;let e;const s=t=>{const s=e;e=s||[];try{if(t(),!s)for(const{value:t}of e);}finally{e=s}};class n{constructor(t){this._=t}then(t){t(this.value)}toJSON(){return this.value}toString(){return this.value}valueOf(){return this.value}}let i;class o extends n{constructor(t,e,s,n){super(t),this.f=n,this.$=!0,this.r=new Set,this.s=new g(e,s)}get value(){if(this.$){const t=i;i=this;try{this.s.value=this._(this.s._)}finally{this.$=!1,i=t}}return this.s.value}}const c={async:!1,equals:!0},r=(t,e,s=c)=>new o(t,e,s,!1);let a;const l=[],u=()=>{},h=({s:t})=>{"function"==typeof t._&&(t._=t._())};class f extends o{constructor(t,e,s){super(t,e,s,!0),this.e=l}run(){return this.$=!0,this.value,this}stop(){this._=u,this.r.clear(),this.s.c.clear()}}class p extends f{constructor(t,e,s){super(t,e,s),this.i=0,this.a=!!s.async,this.m=!0,this.e=[]}get value(){this.a?this.async():this.sync()}async(){this.m&&(this.m=!1,queueMicrotask((()=>{this.m=!0,this.sync()})))}sync(){const t=a;(a=this).i=0,h(this),super.value,a=t}stop(){super.stop(),h(this);for(const t of this.e.splice(0))t.stop()}}const d=()=>!1;class g extends n{constructor(e,{equals:s}){super(e),this.c=new Set,this.s=!0===s?t:s||d}peek(){return this._}get value(){return i&&(this.c.add(i),i.r.add(this)),this._}set value(t){const s=this._;if(!this.s(this._=t,s)&&this.c.size){const t=[],s=[this];for(const e of s)for(const n of e.c)if(!n.$&&n.r.has(e))if(n.r.clear(),n.$=!0,n.f){t.push(n);const e=[n];for(const t of e)for(const s of t.e)s.r.clear(),s.$=!0,e.push(s)}else s.push(n.s);for(const s of t)e?e.push(s):s.value}}}const v=(t,e=c)=>new g(t,e),y={async:!1},b=(t,e)=>((t,e,s=c)=>{let n;if(a){const{i:i,e:o}=a,c=i===o.length;(c||o[i]._!==t)&&(c||o[i].stop(),o[i]=new p(t,e,s).run()),n=o[i],a.i++}else n=new p(t,e,s).run();return()=>{n.stop()}})(t,e,y);var w=(0,Object.freeze)([]),m=()=>{};const N=(t,e,s)=>{const{parentNode:n}=s,i=e.length;let o=t.length,c=i,r=0,a=0,l=null;for(;rs-a){const i=t[r];for(;at[e],x=({children:t},e)=>t[e],I=({value:t,properties:e,children:s})=>t(e,...s),P=({name:t})=>"key"===t,B=(t,{content:e})=>t.reduce(M,e),R=(t,e)=>t.reduce(x,e),L=(t,e)=>{const s=null==e?"":String(e);s!==t.data&&(t.data=s)};
3 | /*! (c) Andrea Giammarchi - ISC */var j=(t={})=>{const e=t.document||globalThis.document,s=new Map(t.plugins||[]),n=!!s.size,i=t.diff||N,o=t.effect||(t=>(t(),m)),c=t.getPeek||(t=>t.peek()),r=t.getValue||(t=>t.value),a=t.isSignal||(t.Signal?O.bind(t.Signal.prototype):()=>!1),l=t=>e.createTextNode(t),u=(t,e)=>{let s=v.get(t);return s||v.set(t,s={}),s[e]||(s[e]={})},h=(t,e)=>{t.dispose();const s=o((()=>{const s=I(e);s.id!==t.id?t=f(t,s,!1):t.update(s)}));return t.dispose=s,t},f=(t,e,s)=>e.type===k?h(t,e):((t,e,s)=>(t.dispose(),t=new p(e),s?t.dispose=o((()=>t.update(e))):t.update(e),t))(t,e,s);class p{constructor(t,e=!0){const[s,n]=e?M(t):[w,null];this._=e&&t.type===$,this.id=t.id,this.updates=s,this.content=n,this.dispose=m}get $(){const{content:t,_:e}=this;return e?(this._=!e,(this.content=(({childNodes:t})=>({childNodes:[...t]}))(t)).childNodes):[t]}update(t){for(const e of this.updates)e.call(this,t)}}const d=new p({id:null},!1),g={id:null,view:d},v=new WeakMap;let y;const b=new WeakMap,M=t=>{let e=b.get(t.id);if(!e){const s=[],n=V(t,s,[],w,!1);b.set(t.id,e=[s,n])}const[s,n]=e;return[s.slice(),n.cloneNode(!0)]},x=(t,e,s,n)=>{n?t.setAttribute(e,s):t[e]=s},j=(t,e,s,n,i)=>{if(a(s)){const c="🙊"+e;c in n&&n[c](),n[c]=o((()=>{x(t,e,r(s),i)}))}else x(t,e,s,i)},W=(t,e,i,o)=>{if(n&&s.has(e))s.get(e)(t,i,o);else if(o[e]!==i)switch(o[e]=i,e){case"class":e+="Name";case"className":case"textContent":j(t,e,i,o,!1);break;case"ref":i.current=t;break;default:e.startsWith("on")?t[e.toLowerCase()]=i:e in t?j(t,e,i,o,!1):null==i?t.removeAttribute(e):j(t,e,i,o,!0)}},q=(t,e,s)=>function(n){const i={},o=B(e,this);(this.updates[s]=s=>{const{attributes:n}=R(e,s);for(const e of t){const t=n[e],{type:s,value:c}=t;if(s<2)W(o,t.name,c,i);else for(const[t,e]of S(c))W(o,t,e,i)}})(n)},z=(t,s)=>function(n){let o=w,c=!0,r=-1;const a=B(t,this),l=new Map;(this.updates[s]=s=>{const{value:n}=R(t,s),h=[];for(let t=0;tfunction(s){let n=w,o=d;const c=B(t,this);(this.updates[e]=e=>{o=h(o,R(t,e)),n=i(n,o.$,c)})(s)},G=(t,e)=>function(s){const n=B(t,this);(this.updates[e]=e=>{L(n,R(t,e).value)})(s)},D=(t,e)=>function(s){let n,a,u=m;const h=B(t,this),{value:p}=R(t,s),g=t=>{n!==t&&(u(),n=t,u=o(a))};if(y(c(p))){let t=w,e=d;a=()=>{const s=r(n);e=f(e,s,!1),t=i(t,e.$,h)}}else{const t=l("");h.replaceWith(t),a=()=>{L(t,r(n))}}this.updates[e]=e=>g(R(t,e).value),g(p)},J=(t,e)=>function(s){let n=w,o=d,c=null;const r=B(t,this);(this.updates[e]=e=>{e=R(t,e).value,c!==e.id?(c=e.id,o=f(o,e,!1),n=i(n,o.$,r)):e.type===k?o.update(I(e)):o.update(e)})(s)},U=({children:t},e,s,n,i)=>{for(let o=0;o{let c,r;const{length:u}=s;t:switch(t.type){case A:{const{value:e}=t;switch(!0){case y(e):c=J;break;case T(e):c=z;break;case a(e):c=D;break;default:r=l(""),s.push(G(i,u));break t}}case k:r=e.createComment("🙊"),s.push((c||F)(i,u));break;case _:{const{attributes:c,name:a}=t,l=[a],h=[];for(let t=0;t{const s="function"==typeof t?t():t;y||(y=O.bind(E(s)));const n=f(v.get(e)||d,s,!0);v.set(e,n),e.replaceChildren(...n.$)}};const W=(t={})=>j({...t,Signal:n,effect:b,isSignal:void 0});export{p as Effect,f as FX,n as Signal,s as batch,r as computed,W as createRender,b as effect,v as signal};
4 |
--------------------------------------------------------------------------------