dom-expressions 0.40.10 → 0.40.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dom-expressions",
3
3
  "description": "A Fine-Grained Runtime for Performant DOM Rendering",
4
- "version": "0.40.10",
4
+ "version": "0.40.12",
5
5
  "author": "Ryan Carniato",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -20,14 +20,14 @@
20
20
  },
21
21
  "peerDependencies": {
22
22
  "csstype": "^3.0",
23
- "seroval": "~1.5.0",
24
- "seroval-plugins": "~1.5.0"
23
+ "seroval": "~1.6.8",
24
+ "seroval-plugins": "~1.6.8"
25
25
  },
26
26
  "devDependencies": {
27
- "babel-plugin-jsx-dom-expressions": "^0.40.10",
27
+ "babel-plugin-jsx-dom-expressions": "^0.40.11",
28
28
  "csstype": "^3.1",
29
- "seroval": "~1.5.0",
30
- "seroval-plugins": "~1.5.0"
29
+ "seroval": "~1.6.8",
30
+ "seroval-plugins": "~1.6.8"
31
31
  },
32
- "gitHead": "727298ff79a54ef5299d3649ab7f4105a326e9b3"
32
+ "gitHead": "27a88bea8ffa97400bbcc66d380e40daed9e1cf1"
33
33
  }
package/src/client.js CHANGED
@@ -54,9 +54,10 @@ export function render(code, element, init, options = {}) {
54
54
  let disposer;
55
55
  root(dispose => {
56
56
  disposer = dispose;
57
+ // Hydration owns the whole container, including nodes streamed in after init.
57
58
  element === document
58
59
  ? code()
59
- : insert(element, code(), element.firstChild ? null : undefined, init);
60
+ : insert(element, code(), !init && element.firstChild ? null : undefined, init);
60
61
  }, options.owner);
61
62
  return () => {
62
63
  disposer();
@@ -448,27 +449,35 @@ function eventHandler(e) {
448
449
  // cancel hydration
449
450
  if (sharedConfig.registry && !sharedConfig.done) sharedConfig.done = _$HY.done = true;
450
451
 
451
- if (e.composedPath) {
452
- const path = e.composedPath();
453
- retarget(path[0]);
454
- for (let i = 0; i < path.length - 2; i++) {
455
- node = path[i];
456
- if (!handleNode()) break;
457
- if (node._$host) {
458
- node = node._$host;
459
- // bubble up from portal mount instead of composedPath
460
- walkUpTree();
461
- break;
462
- }
463
- if (node.parentNode === oriCurrentTarget) {
464
- break; // don't bubble above root of event delegation
452
+ try {
453
+ if (e.composedPath) {
454
+ const path = e.composedPath();
455
+ retarget(path[0]);
456
+ for (let i = 0; i < path.length - 2; i++) {
457
+ node = path[i];
458
+ if (!handleNode()) break;
459
+ if (node._$host) {
460
+ node = node._$host;
461
+ // bubble up from portal mount instead of composedPath
462
+ walkUpTree();
463
+ break;
464
+ }
465
+ if (node.parentNode === oriCurrentTarget) {
466
+ break; // don't bubble above root of event delegation
467
+ }
465
468
  }
466
469
  }
470
+ // fallback for browsers that don't support composedPath
471
+ else walkUpTree();
472
+ // Mixing portals and shadow dom can lead to a nonstandard target, so reset here.
473
+ retarget(oriTarget);
474
+ } finally {
475
+ // The currentTarget getter shares this closure scope. Chromium keeps the
476
+ // getter from the first event of each event interface reachable for the
477
+ // page's lifetime (via the interface's shared map descriptors), so release
478
+ // the event and the last walked node to not pin a detached DOM tree.
479
+ e = node = null;
467
480
  }
468
- // fallback for browsers that don't support composedPath
469
- else walkUpTree();
470
- // Mixing portals and shadow dom can lead to a nonstandard target, so reset here.
471
- retarget(oriTarget);
472
481
  }
473
482
 
474
483
  function insertExpression(parent, value, current, marker, unwrapArray) {
package/src/server.js CHANGED
@@ -20,6 +20,39 @@ const VOID_ELEMENTS =
20
20
  /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i;
21
21
  const REPLACE_SCRIPT = `function $df(e,n,o,t){if(n=document.getElementById(e),o=document.getElementById("pl-"+e)){for(;o&&8!==o.nodeType&&o.nodeValue!=="pl-"+e;)t=o.nextSibling,o.remove(),o=t;_$HY.done?o.remove():o.replaceWith(n.content)}n.remove(),_$HY.fe(e)}`;
22
22
 
23
+ // A name without these characters (and starting with an ASCII letter for a tag)
24
+ // is read back by the HTML parser as exactly that one name.
25
+ const INVALID_NAME = /[\0-\x20\x7F-\x9F"'<>/=]/;
26
+ const TAG_START = /^[a-zA-Z]/;
27
+ // Bounded: spread keys, style keys and dynamic tags can be any runtime string.
28
+ const validTags = new Set();
29
+ const attrNames = new Map();
30
+ const styleNames = new Map();
31
+ function checkTag(tag) {
32
+ if (validTags.has(tag)) return;
33
+ if (!TAG_START.test(tag) || INVALID_NAME.test(tag))
34
+ throw new Error(`"${tag}" is not a valid tag name`);
35
+ if (validTags.size < 512) validTags.add(tag);
36
+ }
37
+ // The attribute name written for a spread key, or "" when it is not a valid name.
38
+ function attrName(prop, prefixed) {
39
+ let name = attrNames.get(prop);
40
+ if (name !== undefined) return name;
41
+ const raw = prefixed ? prop.slice(5) : prop;
42
+ if (raw === "" || INVALID_NAME.test(raw)) return "";
43
+ name = (!prefixed && Aliases[raw]) || escape(raw);
44
+ if (attrNames.size < 512) attrNames.set(prop, name);
45
+ return name;
46
+ }
47
+ function styleName(name) {
48
+ let s = styleNames.get(name);
49
+ if (s === undefined) {
50
+ s = escape(name, true);
51
+ if (styleNames.size < 512) styleNames.set(name, s);
52
+ }
53
+ return s;
54
+ }
55
+
23
56
  export function renderToString(code, options = {}) {
24
57
  const { renderId } = options;
25
58
  let scripts = "";
@@ -333,7 +366,7 @@ export function ssrClassList(value) {
333
366
  classValue = !!value[key];
334
367
  if (!key || key === "undefined" || !classValue) continue;
335
368
  i && (result += " ");
336
- result += escape(key);
369
+ result += escape(key, true);
337
370
  }
338
371
  return result;
339
372
  }
@@ -351,16 +384,17 @@ export function ssrStyle(value) {
351
384
  if (i) result += ";";
352
385
  const r = escape(v, true);
353
386
  if (r != undefined && r !== "undefined") {
354
- result += `${s}:${r}`;
387
+ result += `${styleName(s)}:${r}`;
355
388
  }
356
389
  }
357
390
  }
358
391
  return result;
359
392
  }
360
393
  export function ssrStyleProperty(name, value) {
361
- return value != null ? name + value : "";
394
+ return value != null ? styleName(name) + value : "";
362
395
  }
363
396
  export function ssrElement(tag, props, children, needsId) {
397
+ checkTag(tag);
364
398
  if (props == null) props = {};
365
399
  else if (typeof props === "function") props = props();
366
400
  const skipChildren = VOID_ELEMENTS.test(tag);
@@ -400,11 +434,17 @@ export function ssrElement(tag, props, children, needsId) {
400
434
  continue;
401
435
  } else if (prop.slice(0, 5) === "bool:") {
402
436
  if (!value) continue;
403
- result += escape(prop.slice(5));
437
+ const name = attrName(prop, true);
438
+ if (!name) continue;
439
+ result += name;
404
440
  } else if (prop.slice(0, 5) === "attr:") {
405
- result += `${escape(prop.slice(5))}="${escape(value, true)}"`;
441
+ const name = attrName(prop, true);
442
+ if (!name) continue;
443
+ result += `${name}="${escape(value, true)}"`;
406
444
  } else {
407
- result += `${Aliases[prop] || escape(prop)}="${escape(value, true)}"`;
445
+ const name = attrName(prop, false);
446
+ if (!name) continue;
447
+ result += `${name}="${escape(value, true)}"`;
408
448
  }
409
449
  if (i !== keys.length - 1) result += " ";
410
450
  }
@@ -691,9 +731,10 @@ export function ssrSpread(props, isSVG, skipChildren) {
691
731
  } else if (prop === "class" || prop === "className" || prop === "classList") {
692
732
  if (classResolved) continue;
693
733
  let n;
694
- result += `class="${(n = props.class) ? n + " " : ""}${
695
- (n = props.className) ? n + " " : ""
696
- }${ssrClassList(props.classList)}"`;
734
+ result += `class="${
735
+ escape(((n = props.class) ? n + " " : "") + ((n = props.className) ? n + " " : ""), true) +
736
+ ssrClassList(props.classList)
737
+ }"`;
697
738
  classResolved = true;
698
739
  } else if (prop !== "value" && Properties.has(prop)) {
699
740
  if (value) result += prop;
@@ -707,11 +748,17 @@ export function ssrSpread(props, isSVG, skipChildren) {
707
748
  continue;
708
749
  } else if (prop.slice(0, 5) === "bool:") {
709
750
  if (!value) continue;
710
- result += escape(prop.slice(5));
751
+ const name = attrName(prop, true);
752
+ if (!name) continue;
753
+ result += name;
711
754
  } else if (prop.slice(0, 5) === "attr:") {
712
- result += `${escape(prop.slice(5))}="${escape(value, true)}"`;
755
+ const name = attrName(prop, true);
756
+ if (!name) continue;
757
+ result += `${name}="${escape(value, true)}"`;
713
758
  } else {
714
- result += `${Aliases[prop] || escape(prop)}="${escape(value, true)}"`;
759
+ const name = attrName(prop, false);
760
+ if (!name) continue;
761
+ result += `${name}="${escape(value, true)}"`;
715
762
  }
716
763
  if (i !== keys.length - 1) result += " ";
717
764
  }
package/src/universal.js CHANGED
@@ -63,7 +63,7 @@ export function createRenderer({
63
63
  if (current.length === 0) {
64
64
  appendNodes(parent, array, marker);
65
65
  } else reconcileArrays(parent, current, array);
66
- } else if (current == null || current === "") {
66
+ } else if (current == null || !getFirstChild(parent)) {
67
67
  appendNodes(parent, array);
68
68
  } else {
69
69
  reconcileArrays(parent, (multi && current) || [getFirstChild(parent)], array);
@@ -74,7 +74,7 @@ export function createRenderer({
74
74
  if (Array.isArray(current)) {
75
75
  if (multi) return (current = cleanChildren(parent, current, marker, value));
76
76
  cleanChildren(parent, current, null, value);
77
- } else if (current == null || current === "" || !getFirstChild(parent)) {
77
+ } else if (current == null || !getFirstChild(parent)) {
78
78
  insertNode(parent, value);
79
79
  } else replaceNode(parent, value, getFirstChild(parent));
80
80
  current = value;