chocola 2.0.0-next.7 → 2.0.0-next.9
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 +42 -35
- package/compiler/component-processor.js +218 -37
- package/compiler/config.js +2 -2
- package/compiler/dom-processor.js +6 -4
- package/compiler/index.js +76 -55
- package/compiler/utils.js +78 -0
- package/dev/index.js +3 -3
- package/package.json +2 -2
- package/parser/component.js +140 -1
- package/parser/context.js +11 -10
- package/parser/css.js +2 -2
- package/parser/index.js +3 -3
- package/parser/template.js +15 -15
- package/parser/utils.js +20 -1
- package/runtime/index.js +34 -31
- package/utils.js +28 -3
package/parser/utils.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { restoreTemplateChars } from "../utils.js";
|
|
2
|
+
|
|
1
3
|
const compiledCache = new Map();
|
|
2
4
|
|
|
3
|
-
export function
|
|
5
|
+
export function compileExpr(expr, useCtx) {
|
|
6
|
+
expr = restoreTemplateChars(expr);
|
|
4
7
|
const key = useCtx ? `ctx:${expr}` : `raw:${expr}`;
|
|
5
8
|
let fn = compiledCache.get(key);
|
|
6
9
|
if (!fn) {
|
|
@@ -11,3 +14,19 @@ export function compileExpression(expr, useCtx) {
|
|
|
11
14
|
}
|
|
12
15
|
return fn;
|
|
13
16
|
}
|
|
17
|
+
|
|
18
|
+
const constantEvalError = Symbol("constant-eval-error");
|
|
19
|
+
|
|
20
|
+
export function evaluateConstant(expr) {
|
|
21
|
+
const trap = new Proxy({}, {
|
|
22
|
+
has() { return true; },
|
|
23
|
+
get() { throw constantEvalError; },
|
|
24
|
+
});
|
|
25
|
+
try {
|
|
26
|
+
const fn = compileExpr(expr, true);
|
|
27
|
+
const value = fn(trap);
|
|
28
|
+
return { constant: true, value };
|
|
29
|
+
} catch {
|
|
30
|
+
return { constant: false };
|
|
31
|
+
}
|
|
32
|
+
}
|
package/runtime/index.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
const LBRACE_PH = "_%%CHOCOLA-LBRACE%%_";
|
|
2
|
+
const RBRACE_PH = "_%%CHOCOLA-RBRACE%%_";
|
|
3
|
+
|
|
1
4
|
const exprCache = new Map();
|
|
2
5
|
|
|
3
|
-
function
|
|
6
|
+
function compileExp(expr, useCtx) {
|
|
7
|
+
expr = expr.replace(/_%%CHOCOLA-(?:AMP|LT|GT)\d+%%_/g, char =>
|
|
8
|
+
char.includes("AMP") ? "&" : char.includes("LT") ? "<" : ">"
|
|
9
|
+
);
|
|
4
10
|
const key = useCtx ? "ctx:" + expr : "raw:" + expr;
|
|
5
11
|
let fn = exprCache.get(key);
|
|
6
12
|
if (!fn) {
|
|
@@ -22,7 +28,7 @@ function interpolateNode(root, ctx) {
|
|
|
22
28
|
.replace(new RegExp(RBRACE_PH, "g"), "}");
|
|
23
29
|
node.textContent = text.replace(/\{([^}]+)\}/g, (_, expr) => {
|
|
24
30
|
try {
|
|
25
|
-
return String(
|
|
31
|
+
return String(compileExp(expr, true)(ctx));
|
|
26
32
|
} catch {
|
|
27
33
|
return "";
|
|
28
34
|
}
|
|
@@ -35,30 +41,30 @@ function interpolateNode(root, ctx) {
|
|
|
35
41
|
}
|
|
36
42
|
}
|
|
37
43
|
|
|
38
|
-
function
|
|
44
|
+
function applyDirectiveToEl(child, ctx, chain, hasIf, hasMountIf, hasElif, hasElse) {
|
|
39
45
|
if (hasIf) {
|
|
40
46
|
const expr = child.getAttribute("if").slice(1, -1);
|
|
41
47
|
chain.active = true;
|
|
42
|
-
if (
|
|
48
|
+
if (compileExp(expr, true)(ctx)) {
|
|
43
49
|
chain.rendered = true;
|
|
44
50
|
} else {
|
|
45
51
|
child.style.display = "none";
|
|
46
52
|
chain.rendered = false;
|
|
47
53
|
}
|
|
48
54
|
child.removeAttribute("if");
|
|
49
|
-
} else if (
|
|
50
|
-
const expr = child.getAttribute("
|
|
55
|
+
} else if (hasMountIf) {
|
|
56
|
+
const expr = child.getAttribute("mount:if").slice(1, -1);
|
|
51
57
|
chain.active = true;
|
|
52
|
-
if (
|
|
58
|
+
if (compileExp(expr, true)(ctx)) {
|
|
53
59
|
chain.rendered = true;
|
|
54
60
|
} else {
|
|
55
61
|
child.remove();
|
|
56
62
|
chain.rendered = false;
|
|
57
63
|
}
|
|
58
|
-
child.removeAttribute("
|
|
64
|
+
child.removeAttribute("mount:if");
|
|
59
65
|
} else if (hasElif) {
|
|
60
66
|
const expr = child.getAttribute("elif").slice(1, -1);
|
|
61
|
-
if (
|
|
67
|
+
if (compileExp(expr, true)(ctx)) {
|
|
62
68
|
chain.rendered = true;
|
|
63
69
|
} else {
|
|
64
70
|
child.remove();
|
|
@@ -74,9 +80,6 @@ function applyConditionalToElement(child, ctx, chain, hasIf, hasDelIf, hasElif,
|
|
|
74
80
|
}
|
|
75
81
|
}
|
|
76
82
|
|
|
77
|
-
const LBRACE_PH = "_%%CHOCOLA-LBRACE%%_";
|
|
78
|
-
const RBRACE_PH = "_%%CHOCOLA-RBRACE%%_";
|
|
79
|
-
|
|
80
83
|
function interpolateAttributes(element, ctx) {
|
|
81
84
|
for (const el of [element, ...element.querySelectorAll("*")]) {
|
|
82
85
|
for (const attr of [...el.attributes]) {
|
|
@@ -87,7 +90,7 @@ function interpolateAttributes(element, ctx) {
|
|
|
87
90
|
.replace(new RegExp(RBRACE_PH, "g"), "}");
|
|
88
91
|
attr.value = raw.replace(/\{([^}]+)\}/g, (_, expr) => {
|
|
89
92
|
try {
|
|
90
|
-
return String(
|
|
93
|
+
return String(compileExp(expr, true)(ctx));
|
|
91
94
|
} catch {
|
|
92
95
|
return "";
|
|
93
96
|
}
|
|
@@ -96,10 +99,10 @@ function interpolateAttributes(element, ctx) {
|
|
|
96
99
|
}
|
|
97
100
|
}
|
|
98
101
|
|
|
99
|
-
function
|
|
102
|
+
function processDirectives(element, ctx, chain) {
|
|
100
103
|
for (const child of [...element.children]) {
|
|
101
104
|
const hasIf = child.hasAttribute("if");
|
|
102
|
-
const
|
|
105
|
+
const hasMountIf = child.hasAttribute("mount:if");
|
|
103
106
|
const hasElif = child.hasAttribute("elif");
|
|
104
107
|
const hasElse = child.hasAttribute("else");
|
|
105
108
|
|
|
@@ -109,46 +112,46 @@ function processConditionals(element, ctx, chain) {
|
|
|
109
112
|
continue;
|
|
110
113
|
}
|
|
111
114
|
|
|
112
|
-
|
|
115
|
+
applyDirectiveToEl(child, ctx, chain, hasIf, hasMountIf, hasElif, hasElse);
|
|
113
116
|
|
|
114
117
|
if (child.parentNode) {
|
|
115
|
-
|
|
118
|
+
processDirectives(child, ctx, chain);
|
|
116
119
|
}
|
|
117
120
|
}
|
|
118
121
|
}
|
|
119
122
|
|
|
120
|
-
function processSlots(
|
|
121
|
-
const slots =
|
|
123
|
+
function processSlots(el, projected) {
|
|
124
|
+
const slots = el.querySelectorAll("slot");
|
|
122
125
|
for (const slot of slots) {
|
|
123
|
-
const clone =
|
|
126
|
+
const clone = projected.map(c => c.cloneNode(true));
|
|
124
127
|
slot.replaceWith(...clone);
|
|
125
128
|
}
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
let bindCounter = 0;
|
|
129
132
|
|
|
130
|
-
function
|
|
133
|
+
function processBindings(el) {
|
|
131
134
|
const bindings = [];
|
|
132
|
-
for (const attr of [...
|
|
135
|
+
for (const attr of [...el.attributes]) {
|
|
133
136
|
if (attr.name.startsWith("bind:")) {
|
|
134
137
|
const prop = attr.name.slice(5);
|
|
135
138
|
const varName = attr.value;
|
|
136
139
|
const bindId = "b" + (bindCounter++);
|
|
137
|
-
|
|
140
|
+
el.setAttribute("data-chbind-" + bindId, "");
|
|
138
141
|
bindings.push({ prop, varName, bindId });
|
|
139
|
-
|
|
142
|
+
el.removeAttribute(attr.name);
|
|
140
143
|
}
|
|
141
144
|
}
|
|
142
145
|
return bindings;
|
|
143
146
|
}
|
|
144
147
|
|
|
145
|
-
function
|
|
148
|
+
function resolveBindings(el) {
|
|
146
149
|
const ids = [];
|
|
147
|
-
for (const attr of [...
|
|
150
|
+
for (const attr of [...el.attributes]) {
|
|
148
151
|
const match = attr.name.match(/^data-chbind-(b\d+)$/);
|
|
149
152
|
if (match) {
|
|
150
153
|
ids.push(match[1]);
|
|
151
|
-
|
|
154
|
+
el.removeAttribute(attr.name);
|
|
152
155
|
}
|
|
153
156
|
}
|
|
154
157
|
return ids;
|
|
@@ -159,8 +162,8 @@ function collectBindings(root) {
|
|
|
159
162
|
const idToEl = {};
|
|
160
163
|
|
|
161
164
|
for (const el of [root, ...root.querySelectorAll("*")]) {
|
|
162
|
-
bindingDefs.push(...
|
|
163
|
-
for (const id of
|
|
165
|
+
bindingDefs.push(...processBindings(el));
|
|
166
|
+
for (const id of resolveBindings(el)) {
|
|
164
167
|
idToEl[id] = el;
|
|
165
168
|
}
|
|
166
169
|
}
|
|
@@ -222,7 +225,7 @@ class ChocolaComponent {
|
|
|
222
225
|
}
|
|
223
226
|
|
|
224
227
|
processSlots(root, this.#children || []);
|
|
225
|
-
|
|
228
|
+
processDirectives(root, mergedCtx, { active: false, rendered: false });
|
|
226
229
|
interpolateAttributes(root, mergedCtx);
|
|
227
230
|
interpolateNode(root, mergedCtx);
|
|
228
231
|
root.classList.add(this.#hash);
|
|
@@ -255,7 +258,7 @@ class ChocolaComponent {
|
|
|
255
258
|
for (const el of els) {
|
|
256
259
|
const props = {};
|
|
257
260
|
for (const attr of [...el.attributes]) {
|
|
258
|
-
if (attr.name.startsWith("data-ch") || attr.name === "if" || attr.name === "
|
|
261
|
+
if (attr.name.startsWith("data-ch") || attr.name === "if" || attr.name === "mount:if" || attr.name === "elif" || attr.name === "else") continue;
|
|
259
262
|
props[attr.name] = attr.value;
|
|
260
263
|
}
|
|
261
264
|
const instance = new compClass();
|
package/utils.js
CHANGED
|
@@ -2,7 +2,7 @@ import { promises as fs } from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { throwError } from "./compiler/utils.js";
|
|
4
4
|
|
|
5
|
-
export async function
|
|
5
|
+
export async function getConfig(__rootdir) {
|
|
6
6
|
try {
|
|
7
7
|
const config = await fs.readFile(path.join(__rootdir, "chocola.config.json"), "utf-8");
|
|
8
8
|
return JSON.parse(config);
|
|
@@ -13,15 +13,40 @@ export async function getChocolaConfig(__rootdir) {
|
|
|
13
13
|
|
|
14
14
|
const LBRACE_PH = "_%%CHOCOLA-LBRACE%%_";
|
|
15
15
|
const RBRACE_PH = "_%%CHOCOLA-RBRACE%%_";
|
|
16
|
+
const PROTECTED_REGEX = /_%%CHOCOLA-(?:AMP|LT|GT)\d+%%_/g;
|
|
17
|
+
|
|
18
|
+
let protectSeq = 0;
|
|
16
19
|
|
|
17
20
|
export function protectCurlyBraces(html) {
|
|
18
21
|
return html
|
|
19
22
|
.replace(/&(?:lbrace|#123|#x7B);/gi, LBRACE_PH)
|
|
20
|
-
.replace(/&(?:rcub|#125|#x7D);/gi, RBRACE_PH)
|
|
23
|
+
.replace(/&(?:rcub|#125|#x7D);/gi, RBRACE_PH)
|
|
24
|
+
.split(/(<script\b[^>]*>[\s\S]*?<\/script>|<style\b[^>]*>[\s\S]*?<\/style>)/gi)
|
|
25
|
+
.map((part, i) => i % 2 === 0
|
|
26
|
+
? part.replace(/\{([^{}]*)\}/g, (m, inner) =>
|
|
27
|
+
`{${inner
|
|
28
|
+
.replace(/&/g, () => `_%%CHOCOLA-AMP${protectSeq++}%%_`)
|
|
29
|
+
.replace(/</g, () => `_%%CHOCOLA-LT${protectSeq++}%%_`)
|
|
30
|
+
.replace(/>/g, () => `_%%CHOCOLA-GT${protectSeq++}%%_`)}}`)
|
|
31
|
+
: part)
|
|
32
|
+
.join("");
|
|
21
33
|
}
|
|
22
34
|
|
|
23
35
|
export function restoreCurlyBraces(html) {
|
|
24
36
|
return html
|
|
25
37
|
.replace(/_%%CHOCOLA-LBRACE%%_/g, "{")
|
|
26
|
-
.replace(/_%%CHOCOLA-RBRACE%%_/g, "}")
|
|
38
|
+
.replace(/_%%CHOCOLA-RBRACE%%_/g, "}")
|
|
39
|
+
.replace(PROTECTED_REGEX, char => {
|
|
40
|
+
if (char.includes("AMP")) return "&";
|
|
41
|
+
if (char.includes("LT")) return "<";
|
|
42
|
+
return ">";
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function restoreTemplateChars(str) {
|
|
47
|
+
return str.replace(PROTECTED_REGEX, char => {
|
|
48
|
+
if (char.includes("AMP")) return "&";
|
|
49
|
+
if (char.includes("LT")) return "<";
|
|
50
|
+
return ">";
|
|
51
|
+
});
|
|
27
52
|
}
|