@symbiotejs/symbiote 3.6.0 → 3.7.0

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/README.md CHANGED
@@ -330,6 +330,7 @@ CSS values are parsed automatically - quoted strings become strings, numbers bec
330
330
  ## Best for
331
331
 
332
332
  - **Complex widgets** embedded in any host application
333
+ - **Low-code HTML-based solutions** - simple declarative everything
333
334
  - **Micro frontends** - standard custom elements, no framework coupling
334
335
  - **Reusable component libraries** - works in React, Vue, Angular, or plain HTML
335
336
  - **SSR-powered apps** - lightweight server rendering without framework lock-in
@@ -359,6 +360,13 @@ All modern browsers: Chrome, Firefox, Safari, Edge, Opera.
359
360
  - [AI Reference](https://github.com/symbiotejs/symbiote.js/blob/main/AI_REFERENCE.md)
360
361
  - [Changelog](https://github.com/symbiotejs/symbiote.js/blob/main/CHANGELOG.md)
361
362
 
363
+ ## Related articles
364
+
365
+ - [Symbiote.js: superpowers for Web Components](https://dev.to/foxeyes/symbiotejs-superpowers-for-web-components-1gid)
366
+ - [Symbiote.js: v3 highlights](https://dev.to/foxeyes/symbiotejs-v3-web-components-with-ssr-in-6kb-10n6)
367
+ - [Symbiote.js vs Lit](https://dev.to/foxeyes/lit-vs-symbiotejs-22gj)
368
+ - [JSDA Stack - A Revolutionary Simple Approach to Build Modern Web](https://dev.to/foxeyes/jsda-kit-a-revolutionary-simple-approach-to-build-modern-web-1dip)
369
+
362
370
  **Questions or proposals? Welcome to [Symbiote Discussions](https://github.com/symbiotejs/symbiote.js/discussions)!** ❤️
363
371
 
364
372
  ---
package/core/html.js CHANGED
@@ -10,6 +10,35 @@ export const RESERVED_ATTRIBUTES = [
10
10
  DICT.CTX_NAME_ATTR,
11
11
  ];
12
12
 
13
+ const RESERVED_ATTRIBUTES_SET = new Set(RESERVED_ATTRIBUTES);
14
+ const SELF_CLOSING_CUSTOM_ELEMENT_RE = /<([a-z][.0-9_a-z]*-[\-.0-9_a-z]*)(\s+(?:"[^"]*"|'[^']*'|[^'"<>])*)?\/>/gi;
15
+
16
+ function hasImmediateClosingTag(htmlString, startIdx, tagName) {
17
+ let idx = startIdx;
18
+ let len = htmlString.length;
19
+ while (idx < len) {
20
+ let code = htmlString.charCodeAt(idx);
21
+ if (code !== 32 && code !== 9 && code !== 10 && code !== 12 && code !== 13) break;
22
+ idx++;
23
+ }
24
+ if (htmlString.charCodeAt(idx) !== 60 || htmlString.charCodeAt(idx + 1) !== 47) return false;
25
+ let tagStart = idx + 2;
26
+ if (htmlString.charCodeAt(tagStart + tagName.length) !== 62) return false;
27
+ let closingTagName = htmlString.slice(tagStart, tagStart + tagName.length);
28
+ return closingTagName === tagName || closingTagName.toLowerCase() === tagName.toLowerCase();
29
+ }
30
+
31
+ function closeSelfClosingCustomElements(resultHtml) {
32
+ if (resultHtml.indexOf('/>') === -1) return resultHtml;
33
+ return resultHtml.replace(SELF_CLOSING_CUSTOM_ELEMENT_RE, (match, tagName, attrs = '', offset, htmlString) => {
34
+ let openTag = `<${tagName}${attrs && attrs.trimEnd()}>`;
35
+ if (hasImmediateClosingTag(htmlString, offset + match.length, tagName)) {
36
+ return openTag;
37
+ }
38
+ return `${openTag}</${tagName}>`;
39
+ });
40
+ }
41
+
13
42
  /** @typedef {Record<keyof import('./Symbiote.js').Symbiote, String>} BindDescriptor */
14
43
 
15
44
  /**
@@ -20,19 +49,20 @@ export const RESERVED_ATTRIBUTES = [
20
49
  */
21
50
  export function html(parts, ...props) {
22
51
  let resultHtml = '';
23
- parts.forEach((part, idx) => {
24
- resultHtml += part;
25
- if (idx >= props.length) return;
52
+ let propsLength = props.length;
53
+ for (let idx = 0; idx < parts.length; idx++) {
54
+ resultHtml += parts[idx];
55
+ if (idx >= propsLength) continue;
26
56
  let val = props[idx];
27
57
  if (val === undefined || val === null) {
28
58
  errMsg(15, val);
29
- return;
59
+ continue;
30
60
  }
31
- if (val?.constructor === Object) {
61
+ if (val.constructor === Object) {
32
62
  let bindStr = '';
33
63
  // @ts-expect-error
34
64
  for (let key in val) {
35
- if (RESERVED_ATTRIBUTES.includes(key)) {
65
+ if (RESERVED_ATTRIBUTES_SET.has(key)) {
36
66
  resultHtml += ` ${key}="${val[key]}"`;
37
67
  } else {
38
68
  bindStr += `${key}:${val[key]};`;
@@ -42,8 +72,8 @@ export function html(parts, ...props) {
42
72
  } else {
43
73
  resultHtml += val;
44
74
  }
45
- });
46
- return resultHtml;
75
+ }
76
+ return closeSelfClosingCustomElements(resultHtml);
47
77
  }
48
78
 
49
79
  export default html;
@@ -105,17 +105,25 @@ function getTextNodesWithTokens(el) {
105
105
  let isCustomEl = el instanceof HTMLElement && el.localName?.includes('-');
106
106
  let node;
107
107
  let result = [];
108
- let walk = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, {
109
- acceptNode: (txt) => {
110
- if (isCustomEl && !isOwnNode(txt, el)) return NodeFilter.FILTER_REJECT;
111
- return !txt.parentElement?.hasAttribute(DICT.TEXT_NODE_SKIP_ATTR)
112
- && txt.textContent.includes(DICT.TEXT_NODE_OPEN_TOKEN)
113
- && txt.textContent.includes(DICT.TEXT_NODE_CLOSE_TOKEN)
114
- && 1;
115
- },
116
- });
117
- while ((node = walk.nextNode())) {
118
- result.push(node);
108
+ let acceptNode = (txt) => {
109
+ if (isCustomEl && !isOwnNode(txt, el)) return NodeFilter.FILTER_REJECT;
110
+ return !txt.parentElement?.hasAttribute(DICT.TEXT_NODE_SKIP_ATTR)
111
+ && txt.textContent.includes(DICT.TEXT_NODE_OPEN_TOKEN)
112
+ && txt.textContent.includes(DICT.TEXT_NODE_CLOSE_TOKEN)
113
+ ? NodeFilter.FILTER_ACCEPT
114
+ : NodeFilter.FILTER_REJECT;
115
+ };
116
+ let walk = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, acceptNode);
117
+ if (globalThis.__SYMBIOTE_SSR) {
118
+ while ((node = walk.nextNode())) {
119
+ if (acceptNode(node) === NodeFilter.FILTER_ACCEPT) {
120
+ result.push(node);
121
+ }
122
+ }
123
+ } else {
124
+ while ((node = walk.nextNode())) {
125
+ result.push(node);
126
+ }
119
127
  }
120
128
  return result;
121
129
  }
package/node/SSR.js CHANGED
@@ -304,11 +304,36 @@ export class SSR {
304
304
  static async init() {
305
305
  // @ts-ignore
306
306
  let { parseHTML } = /** @type {any} */ (await import('linkedom'));
307
- let { document, window, HTMLElement, customElements, DocumentFragment, NodeFilter, MutationObserver } = parseHTML('<!DOCTYPE html><html><head></head><body></body></html>');
307
+ let { document, window, HTMLElement, customElements, DocumentFragment, NodeFilter, MutationObserver, Text } = parseHTML('<!DOCTYPE html><html><head></head><body></body></html>');
308
308
 
309
309
  SSR.#doc = document;
310
310
  SSR.#win = window;
311
311
 
312
+ // Polyfill splitText for linkedom:
313
+ if (!Text.prototype.splitText) {
314
+ Text.prototype.splitText = function(offset) {
315
+ let nextNode = document.createTextNode(this.textContent.substring(offset));
316
+ this.textContent = this.textContent.substring(0, offset);
317
+ if (this.parentNode) {
318
+ this.parentNode.insertBefore(nextNode, this.nextSibling);
319
+ }
320
+ return nextNode;
321
+ };
322
+ }
323
+
324
+ // Polyfill textContent on DocumentFragment for linkedom:
325
+ if (!('textContent' in DocumentFragment.prototype) || !Object.getOwnPropertyDescriptor(DocumentFragment.prototype, 'textContent')?.get) {
326
+ Object.defineProperty(DocumentFragment.prototype, 'textContent', {
327
+ get() {
328
+ return this.childNodes.map(n => n.nodeType === 3 ? n.textContent : (n.textContent || '')).join('');
329
+ },
330
+ set(val) {
331
+ while (this.firstChild) this.removeChild(this.firstChild);
332
+ this.appendChild(document.createTextNode(val));
333
+ }
334
+ });
335
+ }
336
+
312
337
  // Polyfill CSSStyleSheet for linkedom:
313
338
  if (!window.CSSStyleSheet || !('replaceSync' in (window.CSSStyleSheet?.prototype || {}))) {
314
339
  class SSRStyleSheet {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@symbiotejs/symbiote",
4
- "version": "3.6.0",
4
+ "version": "3.7.0",
5
5
  "description": "Symbiote.js - zero-dependency close-to-platform frontend library to build super-powered web components",
6
6
  "author": "team@rnd-pro.com",
7
7
  "license": "MIT",
@@ -1 +1 @@
1
- {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../core/html.js"],"names":[],"mappings":"AAoBA,qBALa,CAAC,SACH,oBAAoB,YACpB,CAAC;QAAO,MAAM;CAAS,GAAG,cAAc,YAAY,CAAC,CAAC,EAAE,UA6BlE;AA1CD,kCADW,QAAQ,CAOjB;;6BAEY,MAAM,CAAC,MAAM,qCAAgC,SAAS"}
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../core/html.js"],"names":[],"mappings":"AAiDA,qBALa,CAAC,SACH,oBAAoB,YACpB,CAAC;QAAO,MAAM;CAAS,GAAG,cAAc,YAAY,CAAC,CAAC,EAAE,UA8BlE;AAxED,kCADW,QAAQ,CAOjB;;6BA+BY,MAAM,CAAC,MAAM,qCAAgC,SAAS"}
@@ -1 +1 @@
1
- {"version":3,"file":"tpl-processors.d.ts","sourceRoot":"","sources":["../../core/tpl-processors.js"],"names":[],"mappings":";0BA2HgD,CAAC,SAApC,qCAAkC,MACpC,gBAAgB,SAChB,CAAC;;iCAzHqB,uBAAuB"}
1
+ {"version":3,"file":"tpl-processors.d.ts","sourceRoot":"","sources":["../../core/tpl-processors.js"],"names":[],"mappings":";0BAmIgD,CAAC,SAApC,qCAAkC,MACpC,gBAAgB,SAChB,CAAC;;iCAjIqB,uBAAuB"}
@@ -1 +1 @@
1
- {"version":3,"file":"SSR.d.ts","sourceRoot":"","sources":["../../node/SSR.js"],"names":[],"mappings":"AAsSA;IAEE,8BAAmB;IACnB,8BAAmB;IAMnB;;;OAkEC;IAMD,uBAYC;IAkBD,yBAZW,MAAM,YACN;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAChB,OAAO,CAAC,MAAM,CAAC,CAgC3B;IAWD,+BALW,MAAM;;iBAEN;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAChB,MAAM,CAwBlB;IAWD,+BALW,MAAM;;iBAEN;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAChB,cAAc,CAAC,MAAM,CAAC,CAqBlC;CACF"}
1
+ {"version":3,"file":"SSR.d.ts","sourceRoot":"","sources":["../../node/SSR.js"],"names":[],"mappings":"AAsSA;IAEE,8BAAmB;IACnB,8BAAmB;IAMnB;;;OA2FC;IAMD,uBAYC;IAkBD,yBAZW,MAAM,YACN;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAChB,OAAO,CAAC,MAAM,CAAC,CAgC3B;IAWD,+BALW,MAAM;;iBAEN;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAChB,MAAM,CAwBlB;IAWD,+BALW,MAAM;;iBAEN;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAChB,cAAc,CAAC,MAAM,CAAC,CAqBlC;CACF"}