solarite 0.1.1 → 0.2.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.
Files changed (45) hide show
  1. package/benchmarks/naive/Solarite.min.js +4 -0
  2. package/benchmarks/naive/index.html +14 -0
  3. package/benchmarks/naive/main.js +339 -0
  4. package/benchmarks/naive/package-lock.json +13 -0
  5. package/benchmarks/naive/package.json +23 -0
  6. package/benchmarks/readme.md +48 -0
  7. package/build/build.bat +2 -2
  8. package/build/build.js +1 -1
  9. package/dist/Solarite-debug.js +1344 -1532
  10. package/dist/Solarite.js +1298 -1334
  11. package/dist/Solarite.min.js +3 -3
  12. package/dist/udomdiff-license.txt +18 -0
  13. package/docs/index.md +440 -130
  14. package/docs/js/Playground.js +1 -1
  15. package/docs/js/codemirror/codemirror6.js +3683 -3206
  16. package/docs/js/codemirror/themeSolarIce.js +1 -1
  17. package/docs/js/documentation.js +1 -1
  18. package/docs/js/ui/CodeEditor.js +183 -45
  19. package/docs/js/ui/FlexResizer.js +14 -3
  20. package/docs/js/util/Errors.js +11 -0
  21. package/docs/media/documentation.css +6 -3
  22. package/index.html +449 -30
  23. package/package.json +1 -1
  24. package/readme.md +1 -1
  25. package/src/solarite/ExprPath.js +349 -151
  26. package/src/solarite/Globals.js +43 -1
  27. package/src/solarite/NodeGroup.js +275 -293
  28. package/src/solarite/Shell.js +26 -28
  29. package/src/solarite/Solarite.js +12 -0
  30. package/src/solarite/Template.js +55 -128
  31. package/src/solarite/Util.js +131 -7
  32. package/src/solarite/createSolarite.js +14 -19
  33. package/src/solarite/getArg.js +1 -1
  34. package/src/solarite/hash.js +18 -35
  35. package/src/solarite/r.js +127 -127
  36. package/src/solarite/watch3.js +18 -11
  37. package/src/{solarite → unused}/NodeGroupManager.js +81 -109
  38. package/src/{solarite → unused}/watch.js +1 -1
  39. package/src/{solarite → unused}/watch2.js +1 -2
  40. package/src/{solarite → util}/MultiValueMap.js +18 -11
  41. package/src/util/WeakArray.js +33 -0
  42. package/tests/Solarite.test.js +862 -167
  43. package/tests/index.html +4 -2
  44. package/tests/run.bat +1 -1
  45. package/deno.lock +0 -176
@@ -3,32 +3,14 @@ let objectIds = new WeakMap();
3
3
 
4
4
  /**
5
5
  * @param obj {Object|string|Node}
6
- * @param prefix
7
6
  * @returns {string} */
8
- export function getObjectId(obj, prefix=null) {
9
-
10
-
11
-
12
- //#IFDEV
13
- // Slower but useful for debugging:
14
- if (!prefix) {
15
- if (Array.isArray(obj))
16
- prefix = 'Array';
17
- else if (typeof obj === 'function')
18
- prefix = 'Func';
19
- else if (typeof obj === 'object')
20
- prefix = 'Obj'
21
- }
22
- //#ENDIF
23
-
24
- prefix = prefix || '~\f';
25
-
7
+ export function getObjectId(obj) {
26
8
  // if (typeof obj === 'function')
27
- // return obj.toString();
9
+ // return obj.toString(); // This fails to detect when a function's bound variables changes.
28
10
 
29
11
  let result = objectIds.get(obj);
30
12
  if (!result) { // convert to string, store in result, then add 1 to lastObjectId.
31
- result = prefix+(lastObjectId++); // We use a unique prefix to ensure it doesn't collide w/ strings not from getObjectId()
13
+ result = (lastObjectId++); // We use a unique prefix to ensure it doesn't collide w/ strings not from getObjectId()
32
14
  objectIds.set(obj, result)
33
15
  }
34
16
  return result;
@@ -41,12 +23,20 @@ export function getObjectId(obj, prefix=null) {
41
23
  * Adding a toJSON method globally on these object prototypes doesn't incur that performance penalty. */
42
24
  let isHashing = true;
43
25
  function toJSON() {
44
- //return (isHashing && !Array.isArray(this)) ? getObjectId(this) : this
45
26
  return isHashing ? getObjectId(this) : this
46
27
  }
28
+
29
+
47
30
  // Node.prototype.toJSON = toJSON;
48
31
  // Function.prototype.toJSON = toJSON;
49
-
32
+ // Sometimes these get unassigned by Chrome and Brave 119, as well as Firefox, seemingly randomly!
33
+ // The same tests sometimes pass, sometimes fail, even after browser and OS restarts.
34
+ // So we check the assignments on every run of getObjectHash()
35
+ if (Node.prototype.toJSON !== toJSON) {
36
+ Node.prototype.toJSON = toJSON;
37
+ if (Function.prototype.toJSON !== toJSON) // Will it only unmap one but not the other?
38
+ Function.prototype.toJSON = toJSON;
39
+ }
50
40
 
51
41
  /**
52
42
  * Get a string that uniquely maps to the values of the given object.
@@ -55,25 +45,18 @@ function toJSON() {
55
45
  *
56
46
  * Relies on the Node and Function prototypes being overridden above.
57
47
  *
48
+ * Note that passing an integer may collide with the number we get from hashing an object.
49
+ * But we don't handle that case because we need max performance and Solarite never passes integers to this function.
50
+ *
58
51
  * @param obj {*}
59
52
  * @returns {string} */
60
53
  export function getObjectHash(obj) {
61
-
62
- // Sometimes these get unassigned by Chrome and Brave 119, as well as Firefox, seemingly randomly!
63
- // The same tests sometimes pass, sometimes fail, even after browser and OS restarts.
64
- // So we check the assignments on every run of getObjectHash()
65
- if (Node.prototype.toJSON !== toJSON) {
66
- Node.prototype.toJSON = toJSON;
67
- if (Function.prototype.toJSON !== toJSON) // Will it only unmap one but not the other?
68
- Function.prototype.toJSON = toJSON;
69
- }
70
-
71
54
  let result;
72
55
  isHashing = true;
73
56
  try {
74
57
  result = JSON.stringify(obj);
75
58
  }
76
- catch(e){
59
+ catch(e) {
77
60
  result = getObjectHashCircular(obj);
78
61
  }
79
62
  isHashing = false;
@@ -81,7 +64,7 @@ export function getObjectHash(obj) {
81
64
  }
82
65
 
83
66
  /**
84
- * Having this separate might help the optimzer for getObjectHash() ?
67
+ * Slower hashing method that supports.
85
68
  * @param obj
86
69
  * @returns {string} */
87
70
  function getObjectHashCircular(obj) {
package/src/solarite/r.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import Template from "./Template.js";
2
- import NodeGroupManager from "./NodeGroupManager.js";
2
+ import Util from "./Util.js";
3
+ import Globals from "./Globals.js";
3
4
 
4
5
  /**
5
6
  * Convert strings to HTMLNodes.
@@ -14,140 +15,139 @@ import NodeGroupManager from "./NodeGroupManager.js";
14
15
  * 5. TODO: list more
15
16
  *
16
17
  * Currently supported:
17
- * 1. r`<b>Hello${'World'}!` // Create Template that can later be used to create nodes.
18
+ * 1. r(el, options)`<b>${'Hi'}</b>` // Create template and render its nodes to el.
19
+ * 2. r(el, template, ?options) // Render the Template created by #1 to element.
18
20
  *
19
- * 2. r(el, template, ?options) // Render the template created by #1 to element.
20
- * 3. r(el, options)`<b>${'Hi'}</b>` // Create template and render its nodes to el.
21
+ * 3. r`<b>Hello</b> ${'World'}!` // Create Template that can later be used to create nodes.
21
22
  *
22
23
  * 4. r('Hello'); // Create single text node.
23
24
  * 5. r('<b>Hello</b>'); // Create single HTMLElement
24
25
  * 6. r('<b>Hello</b><u>Goodbye</u>'); // Create document fragment because there's more than one node.
25
- * 7. r()`Hello<b>${'World'}!</b>` // Same as 4-6, but evaluates the string as a Solarite template, which includes properly handling nested components and r`` sub-expressions.
26
+ * 7. r()`Hello<b>${'World'}!</b>` // Same as 4-6, but evaluates the string as a Solarite template, which
27
+ * // includes properly handling nested components and r`` sub-expressions.
26
28
  * 8. r(template) // Render Template created by #1.
27
- * 9. r(() => r`<b>Hello</b>`, {...}); // Create dynamic element that has a render() function.
28
29
  *
29
- * @param htmlStrings {?HTMLElement|string|string[]|function():Template}
30
+ * 9. r({render(){...}}) // Pass an object with a render method, and optionally other props/methods.
31
+ *
32
+ * @param htmlStrings {?HTMLElement|string|string[]|function():Template|{render:function()}}
30
33
  * @param exprs {*[]|string|Template|Object}
31
34
  * @return {Node|HTMLElement|Template} */
32
35
  export default function r(htmlStrings=undefined, ...exprs) {
33
36
 
34
- // 1. Path if used as a template tag.
35
- if (Array.isArray(htmlStrings)) {
36
- return new Template(htmlStrings, exprs);
37
- }
38
-
39
- else if (htmlStrings instanceof Node) {
40
- let parent = htmlStrings, template = exprs[0];
41
-
42
- // 2. Render template created by #4 to element.
43
- if (exprs[0] instanceof Template) {
44
- let options = exprs[1];
45
- template.render(parent, options);
46
-
47
- // Append on the first go.
48
- if (!parent.childNodes.length && this) {
49
- // TODO: Is htis ever executed?
50
- debugger;
51
- parent.append(this.rootNg.getParentNode());
52
- }
53
- }
54
-
55
- // 3
56
- else if (!exprs.length || exprs[0]) {
57
- if (parent.shadowRoot)
58
- parent.innerHTML = ''; // Remove shadowroot. TODO: This could mess up paths?
59
-
60
- let options = exprs[0];
61
- return (htmlStrings, ...exprs) => {
62
- rendered.add(parent)
63
- let template = r(htmlStrings, ...exprs);
64
- return template.render(parent, options);
65
- }
66
- }
67
-
68
- // null for expr[0], remove whole element.
69
- // This path never happens?
70
- else {
71
- throw new Error('unsupported');
72
- //let ngm = NodeGroupManager.get(parent);
73
- //ngm.render(null, exprs[1])
74
- }
75
- }
76
-
77
- else if (typeof htmlStrings === 'string' || htmlStrings instanceof String) {
78
- // If it starts with a string, trim both ends.
79
- // TODO: Also trim if it ends with whitespace?
80
- if (htmlStrings.match(/^\s^</))
81
- htmlStrings = htmlStrings.trim();
82
-
83
- // We create a new one each time because otherwise
84
- // the returned fragment will have its content replaced by a subsequent call.
85
- let templateEl = document.createElement('template');
86
- templateEl.innerHTML = htmlStrings;
87
-
88
- // 4+5. Return Node if there's one child.
89
- if (templateEl.content.childNodes.length === 1)
90
- return templateEl.content.firstChild;
91
-
92
- // 6. Otherwise return DocumentFragment.
93
- return templateEl.content;
94
- }
95
-
96
- // 7. Create a static element
97
- else if (htmlStrings === undefined) {
98
- return (htmlStrings, ...exprs) => {
99
- //rendered.add(parent)
100
- let template = r(htmlStrings, ...exprs);
101
- return template.render();
102
- }
103
- }
104
-
105
- // 8.
106
- else if (htmlStrings instanceof Template) {
107
- return htmlStrings.render();
108
- }
109
-
110
-
111
- // 9. Create dynamic element with render() function.
112
- else if (typeof htmlStrings === 'function') {
113
- let getTemplate = htmlStrings;
114
- let template = getTemplate();
115
-
116
- if (typeof template === 'string')
117
- throw new Error(`Please add the "r" prefix before the string "${template}"`)
118
-
119
- template.replaceMode = true;
120
- let el = template.render();
121
-
122
- // Create the render() function from the function we were given.
123
- el.render = (function() {
124
- template = getTemplate();
125
- template.render(el);
126
- }).bind(el);
127
-
128
-
129
- // The second argument was an object of additional properties to add.
130
- let props = exprs[0];
131
- for (let name in props)
132
- if (typeof props[name] === 'function')
133
- el[name] = props[name].bind(el);
134
- else
135
- el[name] = props[name];
136
-
137
- return el;
138
- }
139
-
140
- else
141
- throw new Error('Unsupported arguments.')
142
- }
143
-
144
-
145
-
146
-
147
-
148
-
149
- /**
150
- * Elements that have been rendered to by r() at least once.
151
- * @type {WeakSet<HTMLElement>} */
152
- let rendered = new WeakSet();
153
- export {rendered}
37
+ // TODO: Make this a more flat if/else and call other functions for the logic.
38
+ if (htmlStrings instanceof Node) {
39
+ let parent = htmlStrings, template = exprs[0];
40
+
41
+ // 1
42
+ if (!(exprs[0] instanceof Template)) {
43
+ if (parent.shadowRoot)
44
+ parent.innerHTML = ''; // Remove shadowroot. TODO: This could mess up paths?
45
+
46
+ let options = exprs[0];
47
+
48
+ // Return a tagged template function that applies the tagged themplate to parent.
49
+ let taggedTemplate = (htmlStrings, ...exprs) => {
50
+ Globals.rendered.add(parent)
51
+ let template = new Template(htmlStrings, exprs);
52
+ return template.render(parent, options);
53
+ }
54
+ return taggedTemplate;
55
+ }
56
+
57
+ // 2. Render template created by #4 to element.
58
+ else if (exprs[0] instanceof Template) {
59
+ let options = exprs[1];
60
+ template.render(parent, options);
61
+
62
+ // Append on the first go.
63
+ if (!parent.childNodes.length && this) {
64
+ // TODO: Is this ever executed?
65
+ debugger;
66
+ parent.append(this.rootNg.getParentNode());
67
+ }
68
+ }
69
+
70
+
71
+
72
+ // null for expr[0], remove whole element.
73
+ // This path never happens?
74
+ else {
75
+ throw new Error('unsupported');
76
+ //let ngm = NodeGroupManager.get(parent);
77
+ //ngm.render(null, exprs[1])
78
+ }
79
+ }
80
+
81
+ // 3. Path if used as a template tag.
82
+ else if (Array.isArray(htmlStrings)) {
83
+ return new Template(htmlStrings, exprs);
84
+ }
85
+
86
+ else if (typeof htmlStrings === 'string' || htmlStrings instanceof String) {
87
+ // If it starts with a string, trim both ends.
88
+ // TODO: Also trim if it ends with whitespace?
89
+ if (htmlStrings.match(/^\s^</))
90
+ htmlStrings = htmlStrings.trim();
91
+
92
+ // We create a new one each time because otherwise
93
+ // the returned fragment will have its content replaced by a subsequent call.
94
+ let templateEl = document.createElement('template');
95
+ templateEl.innerHTML = htmlStrings;
96
+
97
+ // 4+5. Return Node if there's one child.
98
+ let relevantNodes = Util.trimEmptyNodes(templateEl.content.childNodes);
99
+ if (relevantNodes.length === 1)
100
+ return relevantNodes[0];
101
+
102
+ // 6. Otherwise return DocumentFragment.
103
+ return templateEl.content;
104
+ }
105
+
106
+ // 7. Create a static element
107
+ else if (htmlStrings === undefined) {
108
+ return (htmlStrings, ...exprs) => {
109
+ //Globals.rendered.add(parent)
110
+ let template = r(htmlStrings, ...exprs);
111
+ return template.render();
112
+ }
113
+ }
114
+
115
+ // 8.
116
+ else if (htmlStrings instanceof Template) {
117
+ return htmlStrings.render();
118
+ }
119
+
120
+
121
+ // 9. Create dynamic element with render() function.
122
+ else if (typeof htmlStrings === 'object') {
123
+ let obj = htmlStrings;
124
+
125
+ // Special rebound render path, called by normal path.
126
+ if (Globals.objToEl.has(obj)) {
127
+ return function(...args) {
128
+ let template = r(...args);
129
+ let el = template.render();
130
+ Globals.objToEl.set(obj, el);
131
+ }.bind(obj);
132
+ }
133
+
134
+ // Normal path
135
+ else {
136
+ Globals.objToEl.set(obj, null);
137
+ obj.render(); // Calls the Special rebound render path above, when the render function calls r(this)
138
+ let el = Globals.objToEl.get(obj);
139
+ Globals.objToEl.delete(obj);
140
+
141
+ for (let name in obj)
142
+ if (typeof obj[name] === 'function')
143
+ el[name] = obj[name].bind(el);
144
+ else
145
+ el[name] = obj[name];
146
+
147
+ return el;
148
+ }
149
+ }
150
+
151
+ else
152
+ throw new Error('Unsupported arguments.')
153
+ }
@@ -47,10 +47,17 @@ a.items.push({name: 'Fred'});
47
47
 
48
48
 
49
49
  import Globals from "./Globals.js";
50
- import NodeGroupManager from "./NodeGroupManager.js";
50
+ import delve from "../util/delve.js";
51
+ //import NodeGroupManager from "./NodeGroupManager.js";
51
52
 
53
+ let unusedArg = Symbol('unusedArg');
52
54
 
53
- export default function watch3(root, path) {
55
+ /**
56
+ *
57
+ * @param root {Object}
58
+ * @param path {string}
59
+ * @param value {string|Symbol} */
60
+ export default function watch3(root, path, value=unusedArg) {
54
61
 
55
62
  /** @type {ExprPath} The ExprPath that's using this variable.*/
56
63
  let exprPath;
@@ -58,10 +65,12 @@ export default function watch3(root, path) {
58
65
  /** @type {function} Function evaluated by the ExprPath. */
59
66
  let exprFunction;
60
67
 
61
- // Store internal value used by get/set.
62
- let value = root[path];
68
+ if (value !== unusedArg)
69
+ root[path] = value;
63
70
 
64
- let ngm = NodeGroupManager.get(root);
71
+ // Store internal value used by get/set.
72
+ else
73
+ value = root[path];
65
74
 
66
75
  Object.defineProperty(root, path, {
67
76
  get() {
@@ -76,14 +85,12 @@ export default function watch3(root, path) {
76
85
  value = val;
77
86
 
78
87
  if (exprFunction) {
79
- let ng = exprPath.parentNg;
80
-
81
- ng.applyExprs([exprFunction], [exprPath]); // TODO: Will fail for attribute w/ a value having multiple ExprPaths.
82
88
 
83
- // TODO: This doesn't cascade upward.
84
- //ng.exactKey = getObjectHash(ng.template);
89
+ // TODO: Will fail for attribute w/ a value having multiple ExprPaths.
90
+ // TODO: This won't update a component's expressions.
91
+ exprPath.apply(exprFunction);
85
92
 
86
- ngm.reset(); // TODO: This could be skipped if applyExprs() never marked them as in-use.
93
+ exprPath.freeNodeGroups(); // TODO: This could be skipped if applyExprs() never marked them as in-use.
87
94
 
88
95
  }
89
96
  }