Skip to content

Commit ae8bf8f

Browse files
committed
5.0.0
1 parent c8ea32d commit ae8bf8f

28 files changed

Lines changed: 738 additions & 45 deletions

.gitignore

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,6 @@
1-
.nyc_output/
1+
.DS_Store
2+
.nyc_output
3+
v8.log
24
coverage/
5+
dist/
36
node_modules/
4-
types/
5-
cjs/*
6-
!cjs/package.json
7-
dom.js
8-
esm/init*.js
9-
init*.js
10-
worker.js
11-
keyed.js
12-
!esm/keyed.js
13-
!esm/dom/keyed.js
14-
index.js
15-
!esm/index.js
16-
!esm/dom/index.js
17-
node.js
18-
!esm/node.js
19-
!esm/dom/node.js
20-
!test/dom/node.js
21-
reactive.js
22-
!esm/reactive.js
23-
!esm/render/reactive.js
24-
signal.js
25-
!esm/signal.js
26-
!esm/render/signal.js
27-
preactive.js
28-
!test/preactive.js

.npmignore

Lines changed: 0 additions & 14 deletions
This file was deleted.

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,14 @@ On top of *JSX* like features, there are other attributes with a special meaning
7878

7979
All other attributes will be handled via standard `setAttribute` or `removeAttribute` when the passed value is either `null` or `undefined`.
8080

81+
### Special Elements
82+
83+
Elements that contain *data* such as `<script>` or `<style>`, or those that contains text such as `<textarea>` require *explicit closing tag* to avoid having in between templates able to break the layout.
84+
85+
This is nothing new to learn, it's just how the Web works, so that one cannot have `</script>` within a `<script>` tag content and the same applies in here.
86+
87+
In *debugging* mode, an error telling you which template is malformed will be triggered in these cases.
88+
8189
### About Comments
8290

8391
Useful for developers but never really relevant for end users, *comments* are ignored by default in *uhtml* except for those flagged as "*very important*".

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
{
22
"name": "@webreflection/uhtml",
3-
"version": "4.7.1",
3+
"version": "5.0.0",
44
"type": "module",
55
"scripts": {
6-
"build": "npm run types && npm test && npm run build:prod && npm run build:dev && npm run size",
6+
"build": "npm run types && npm run build:js",
7+
"build:js": "npm test && npm run build:prod && npm run build:dev && npm run size",
78
"build:dev": "sed -i 's/false/true/' src/debug.js && rollup -c build/dev.js",
89
"build:prod": "sed -i 's/true/false/' src/debug.js && rollup -c build/prod.js",
910
"coverage": "mkdir -p ./coverage; c8 report --reporter=text-lcov > ./coverage/lcov.info",
1011
"size": "echo \"dom\t\t$(cat dist/prod/dom.js | brotli | wc -c)\"; echo \"json\t\t$(cat dist/prod/json.js | brotli | wc -c)\"; echo \"parser\t\t$(cat dist/prod/parser.js | brotli | wc -c)\"",
11-
"test": "node test/json.js && c8 node test/parser.js",
12+
"test": "c8 node test/parser.js",
13+
"test:json": "node test/json.js",
14+
"test:all": "npm run test:json && npm run test",
1215
"types": "tsc --allowJs --checkJs --lib dom,esnext --moduleResolution nodenext --module NodeNext --target esnext -d --emitDeclarationOnly --outDir ./types ./src/*.js ./src/*/*.js"
1316
},
1417
"files": [

src/dom/index.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//@ts-check
2+
3+
import DEBUG from '../debug.js';
4+
5+
//@ts-ignore
6+
import { effectScope } from '@webreflection/alien-signals';
7+
export { signal, computed, effect, untracked, batch } from './signals.js';
8+
9+
import {
10+
Comment,
11+
DocumentType,
12+
Text,
13+
Fragment,
14+
Element,
15+
Component,
16+
} from './ish.js';
17+
18+
import parser from '../parser/index.js';
19+
import { Hole, dom } from './rabbit.js';
20+
import { Keyed } from './keyed.js';
21+
import { isKeyed, fragment, update } from './update.js';
22+
import { diffFragment } from './persistent-fragment.js';
23+
24+
import { _get as getDirect, _set as setDirect } from './direct.js';
25+
26+
import { unsafe } from '../utils.js';
27+
export { Hole, fragment, unsafe };
28+
29+
/** @typedef {globalThis.Element | globalThis.HTMLElement | globalThis.SVGSVGElement | globalThis.DocumentFragment} Container */
30+
31+
const parse = parser({
32+
Comment,
33+
DocumentType,
34+
Text,
35+
Fragment,
36+
Element,
37+
Component,
38+
update,
39+
});
40+
41+
/**
42+
* @param {boolean} xml
43+
* @param {WeakMap<TemplateStringsArray | string[], [any, any[], Keyed?]>} twm
44+
* @returns
45+
*/
46+
const create = (xml, twm = new WeakMap) =>
47+
/**
48+
* @param {TemplateStringsArray | string[]} template
49+
* @param {unknown[]} values
50+
* @returns {Hole}
51+
*/
52+
(template, ...values) => {
53+
let parsed = twm.get(template);
54+
if (!parsed) {
55+
parsed = parse(template, values, xml);
56+
parsed.push(isKeyed() ? new Keyed : null);
57+
if (DEBUG) parsed.push(template);
58+
parsed[0] = fragment(parsed[0].toString(), xml);
59+
twm.set(template, parsed);
60+
}
61+
return new Hole(parsed, values);
62+
};
63+
64+
const htmlHole = create(false);
65+
const svgHole = create(true);
66+
67+
const rendered = new WeakMap;
68+
69+
/**
70+
* @param {TemplateStringsArray | string[]} template
71+
* @param {any[]} values
72+
* @returns {Node | HTMLElement | Hole}
73+
*/
74+
export function html(template, ...values) {
75+
const hole = htmlHole.apply(null, arguments);
76+
return getDirect() ? hole.valueOf(true) : hole;
77+
}
78+
79+
/**
80+
* @param {TemplateStringsArray | string[]} template
81+
* @param {any[]} values
82+
* @returns {Node | SVGSVGElement | Hole}
83+
*/
84+
export function svg(template, ...values) {
85+
const hole = svgHole.apply(null, arguments);
86+
return getDirect() ? hole.valueOf(true) : hole;
87+
}
88+
89+
/**
90+
* @param {Container} where
91+
* @param {Function | Node | Container} what
92+
* @returns
93+
*/
94+
export const render = (where, what) => {
95+
const known = rendered.get(where);
96+
if (known) known[0]();
97+
if (typeof what === 'function') {
98+
setDirect(false);
99+
let hole;
100+
const scope = effectScope(() => { hole = what() });
101+
//@ts-ignore
102+
if (!known || known[1].t !== hole.t) {
103+
//@ts-ignore
104+
const d = hole.valueOf(false);
105+
where.replaceChildren(d);
106+
}
107+
else known[1].update(hole);
108+
rendered.set(where, [scope, hole]);
109+
}
110+
else {
111+
setDirect(true);
112+
rendered.delete(where);
113+
where.replaceChildren(what instanceof Hole ? dom(what) : diffFragment(what, 1));
114+
}
115+
return where;
116+
};

src/dom/keyed.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export const keyed = new WeakMap;
2+
3+
//@ts-ignore
4+
export class Keyed extends Map {
5+
constructor() {
6+
//@ts-ignore
7+
super()._ = new FinalizationRegistry(key => this.delete(key));
8+
}
9+
10+
get(key) {
11+
const node = super.get(key)?.deref();
12+
return node && keyed.get(node);
13+
}
14+
15+
/**
16+
* @param {any} key
17+
* @param {Node} node
18+
* @param {import('./rabbit.js').Hole} hole
19+
*/
20+
//@ts-ignore
21+
set(key, node, hole) {
22+
keyed.set(node, hole);
23+
//@ts-ignore
24+
this._.register(node, key);
25+
super.set(key, new WeakRef(node));
26+
}
27+
}

src/json/index.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import DEBUG from '../debug.js';
2+
import errors from '../errors.js';
3+
import { assign } from '../utils.js';
4+
5+
import {
6+
Comment,
7+
DocumentType,
8+
Text,
9+
Fragment,
10+
Element,
11+
Component,
12+
fromJSON,
13+
} from '../dom/ish.js';
14+
15+
import parser from '../parser/index.js';
16+
import resolve from './resolve.js';
17+
import { COMPONENT, KEY, comment, update } from './update.js';
18+
19+
const textParser = parser({
20+
Comment,
21+
DocumentType,
22+
Text,
23+
Fragment,
24+
Element,
25+
Component,
26+
update,
27+
});
28+
29+
const { parse, stringify } = JSON;
30+
31+
const create = xml => {
32+
const twm = new WeakMap;
33+
const cache = (template, values) => {
34+
const parsed = textParser(template, values, xml);
35+
parsed[0] = parse(stringify(parsed[0]));
36+
twm.set(template, parsed);
37+
return parsed;
38+
};
39+
return (template, ...values) => {
40+
const [json, updates] = twm.get(template) || cache(template, values);
41+
const root = fromJSON(json);
42+
const length = values.length;
43+
if (length === updates.length) {
44+
const components = [];
45+
for (let node, prev, i = 0; i < length; i++) {
46+
const [path, update, type] = updates[i];
47+
const value = values[i];
48+
if (prev !== path) {
49+
node = resolve(root, path);
50+
prev = path;
51+
if (DEBUG && !node) throw errors.invalid_path(path);
52+
}
53+
if (type === KEY) continue;
54+
if (type === COMPONENT) components.push(update(node, value));
55+
else update(node, value);
56+
}
57+
for (const [node, Component] of components) {
58+
const props = assign({ children: node.children }, node.props);
59+
comment(node, Component(props));
60+
}
61+
}
62+
else if (DEBUG) throw errors.invalid_template();
63+
return root;
64+
};
65+
};
66+
67+
export const html = create(false);
68+
export const svg = create(true);

0 commit comments

Comments
 (0)