chocola 1.6.0 → 2.0.0-next.10
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 +34 -36
- package/compiler/chalk.js +34 -0
- package/compiler/component-processor.js +526 -398
- package/compiler/config.js +3 -56
- package/compiler/dom-processor.js +12 -75
- package/compiler/fs.js +0 -11
- package/compiler/index.js +126 -254
- package/compiler/module-graph.js +210 -0
- package/compiler/pipeline.js +54 -196
- package/compiler/render.js +181 -0
- package/compiler/runtime-generator.js +20 -15
- package/compiler/utils.js +79 -126
- package/dev/index.js +28 -11
- package/index.js +0 -31
- package/package.json +11 -13
- package/parser/component.js +268 -0
- package/parser/context.js +59 -0
- package/parser/css.js +96 -0
- package/parser/index.js +5 -0
- package/parser/template.js +120 -0
- package/parser/utils.js +32 -0
- package/runtime/index.js +312 -0
- package/server/index.js +402 -0
- package/utils.js +41 -6
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
export function extractPropsDefaults(script) {
|
|
2
|
+
if (!script) return [];
|
|
3
|
+
const propsRegex = /export\s+let\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*(?:=\s*([^;]+))?;/g;
|
|
4
|
+
let props = [];
|
|
5
|
+
let match;
|
|
6
|
+
while ((match = propsRegex.exec(script)) !== null) {
|
|
7
|
+
props.push({ name: match[1].trim(), defaultValue: match[2]?.trim() });
|
|
8
|
+
}
|
|
9
|
+
return props;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function extractRuntime(script, compName) {
|
|
13
|
+
const startRegex = /(?:async\s+)?function\s+\$runtime\(([^)]*)\)\s*\{/;
|
|
14
|
+
const match = script.match(startRegex);
|
|
15
|
+
if (!match) return null;
|
|
16
|
+
|
|
17
|
+
const startIndexBrace = match.index + match[0].length - 1;
|
|
18
|
+
let bracesCount = 0;
|
|
19
|
+
let inString = false;
|
|
20
|
+
let stringChar = null;
|
|
21
|
+
|
|
22
|
+
for (let i = startIndexBrace; i < script.length; i++) {
|
|
23
|
+
const ch = script[i];
|
|
24
|
+
|
|
25
|
+
if (inString) {
|
|
26
|
+
if (ch === '\\') { i++; continue; }
|
|
27
|
+
if (ch === stringChar) inString = false;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (ch === "'" || ch === '"' || ch === '`') {
|
|
32
|
+
inString = true;
|
|
33
|
+
stringChar = ch;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (ch === '{') {
|
|
38
|
+
bracesCount++;
|
|
39
|
+
} else if (ch === '}') {
|
|
40
|
+
bracesCount--;
|
|
41
|
+
if (bracesCount === 0) {
|
|
42
|
+
return script.substring(match.index, i + 1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
throw new Error(`${compName} $runtime function has unclosed curly braces`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function extractTopLevelVariables(script) {
|
|
51
|
+
if (!script) return [];
|
|
52
|
+
const vars = [];
|
|
53
|
+
let i = 0;
|
|
54
|
+
let depth = 0;
|
|
55
|
+
let parenDepth = 0;
|
|
56
|
+
let bracketDepth = 0;
|
|
57
|
+
let inStr = false;
|
|
58
|
+
let strChar = null;
|
|
59
|
+
|
|
60
|
+
while (i < script.length) {
|
|
61
|
+
const ch = script[i];
|
|
62
|
+
|
|
63
|
+
if (inStr) {
|
|
64
|
+
if (ch === "\\") i++;
|
|
65
|
+
else if (ch === strChar) inStr = false;
|
|
66
|
+
i++;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
71
|
+
inStr = true;
|
|
72
|
+
strChar = ch;
|
|
73
|
+
i++;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (ch === "/" && script[i + 1] === "/") {
|
|
78
|
+
while (i < script.length && script[i] !== "\n") i++;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (ch === "/" && script[i + 1] === "*") {
|
|
82
|
+
i += 2;
|
|
83
|
+
while (i < script.length && !(script[i] === "*" && script[i + 1] === "/")) i++;
|
|
84
|
+
i += 2;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (ch === "{") { depth++; i++; continue; }
|
|
89
|
+
if (ch === "}") { depth--; i++; continue; }
|
|
90
|
+
if (ch === "(") { parenDepth++; i++; continue; }
|
|
91
|
+
if (ch === ")") { parenDepth--; i++; continue; }
|
|
92
|
+
if (ch === "[") { bracketDepth++; i++; continue; }
|
|
93
|
+
if (ch === "]") { bracketDepth--; i++; continue; }
|
|
94
|
+
|
|
95
|
+
if (depth === 0 && parenDepth === 0 && bracketDepth === 0) {
|
|
96
|
+
const rest = script.slice(i);
|
|
97
|
+
const m = rest.match(/^(let|const)\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*/);
|
|
98
|
+
if (m) {
|
|
99
|
+
const keyword = m[1];
|
|
100
|
+
const before = script.slice(0, i).trimEnd();
|
|
101
|
+
if (!before.endsWith("export")) {
|
|
102
|
+
let name = m[2];
|
|
103
|
+
let p = i + m[0].length;
|
|
104
|
+
while (p < script.length && /\s/.test(script[p])) p++;
|
|
105
|
+
i = p;
|
|
106
|
+
|
|
107
|
+
while (p < script.length) {
|
|
108
|
+
i = p;
|
|
109
|
+
let q = p;
|
|
110
|
+
|
|
111
|
+
if (script[q] === "=") {
|
|
112
|
+
q++;
|
|
113
|
+
while (q < script.length && /\s/.test(script[q])) q++;
|
|
114
|
+
const delim = findDeclaratorEnd(script, q);
|
|
115
|
+
if (name !== "self" && name !== "ctx") {
|
|
116
|
+
vars.push({ keyword, name, value: script.slice(q, delim).trim() });
|
|
117
|
+
}
|
|
118
|
+
if (script[delim] === ";") { i = delim + 1; break; }
|
|
119
|
+
p = delim + 1;
|
|
120
|
+
while (p < script.length && /\s/.test(script[p])) p++;
|
|
121
|
+
} else {
|
|
122
|
+
if (name !== "self" && name !== "ctx") {
|
|
123
|
+
vars.push({ keyword, name, value: undefined });
|
|
124
|
+
}
|
|
125
|
+
if (script[q] === ";") { i = q + 1; break; }
|
|
126
|
+
if (script[q] !== ",") { i = q; break; }
|
|
127
|
+
p = q + 1;
|
|
128
|
+
while (p < script.length && /\s/.test(script[p])) p++;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const nameMatch = script.slice(p).match(/^([a-zA-Z_$][0-9a-zA-Z_$]*)\s*/);
|
|
132
|
+
if (!nameMatch) { i = p; break; }
|
|
133
|
+
name = nameMatch[1];
|
|
134
|
+
p += nameMatch[0].length;
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
i++;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return vars;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function findDeclaratorEnd(script, start) {
|
|
147
|
+
let i = start;
|
|
148
|
+
let parens = 0;
|
|
149
|
+
let brackets = 0;
|
|
150
|
+
let braces = 0;
|
|
151
|
+
let inStr = false;
|
|
152
|
+
let strChar = null;
|
|
153
|
+
|
|
154
|
+
while (i < script.length) {
|
|
155
|
+
const c = script[i];
|
|
156
|
+
|
|
157
|
+
if (inStr) {
|
|
158
|
+
if (c === "\\") i++;
|
|
159
|
+
else if (c === strChar) inStr = false;
|
|
160
|
+
i++;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (c === "/" && script[i + 1] === "/") {
|
|
165
|
+
while (i < script.length && script[i] !== "\n") i++;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (c === "/" && script[i + 1] === "*") {
|
|
169
|
+
i += 2;
|
|
170
|
+
while (i < script.length && !(script[i] === "*" && script[i + 1] === "/")) i++;
|
|
171
|
+
i += 2;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (c === '"' || c === "'" || c === "`") { inStr = true; strChar = c; i++; continue; }
|
|
176
|
+
if (c === "(") { parens++; i++; continue; }
|
|
177
|
+
if (c === ")") { parens--; i++; continue; }
|
|
178
|
+
if (c === "[") { brackets++; i++; continue; }
|
|
179
|
+
if (c === "]") { brackets--; i++; continue; }
|
|
180
|
+
if (c === "{") { braces++; i++; continue; }
|
|
181
|
+
if (c === "}") { braces--; i++; continue; }
|
|
182
|
+
if ((c === ";" || c === ",") && parens === 0 && brackets === 0 && braces === 0) return i;
|
|
183
|
+
i++;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return i;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function extractTopLevelFunctions(script, excludeName) {
|
|
190
|
+
const funcs = [];
|
|
191
|
+
let i = 0;
|
|
192
|
+
let depth = 0;
|
|
193
|
+
let inStr = false;
|
|
194
|
+
let strChar = null;
|
|
195
|
+
|
|
196
|
+
while (i < script.length) {
|
|
197
|
+
const ch = script[i];
|
|
198
|
+
|
|
199
|
+
if (inStr) {
|
|
200
|
+
if (ch === '\\') i++;
|
|
201
|
+
else if (ch === strChar) inStr = false;
|
|
202
|
+
i++;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
207
|
+
inStr = true;
|
|
208
|
+
strChar = ch;
|
|
209
|
+
i++;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (ch === '{') { depth++; i++; continue; }
|
|
214
|
+
if (ch === '}') { depth--; i++; continue; }
|
|
215
|
+
|
|
216
|
+
if (depth === 0) {
|
|
217
|
+
const rest = script.slice(i);
|
|
218
|
+
const m = rest.match(/^(?:async\s+)?function\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*\(/);
|
|
219
|
+
if (m) {
|
|
220
|
+
const name = m[1];
|
|
221
|
+
const pastParen = i + m[0].length;
|
|
222
|
+
|
|
223
|
+
let parenDepth = 1;
|
|
224
|
+
let p = pastParen;
|
|
225
|
+
while (p < script.length && parenDepth > 0) {
|
|
226
|
+
if (script[p] === '(') parenDepth++;
|
|
227
|
+
if (script[p] === ')') parenDepth--;
|
|
228
|
+
p++;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
while (p < script.length && script[p] !== '{') p++;
|
|
232
|
+
if (p >= script.length) { i++; continue; }
|
|
233
|
+
|
|
234
|
+
let bDepth = 1;
|
|
235
|
+
let bodyStr = false;
|
|
236
|
+
let bodyStrChar = null;
|
|
237
|
+
let end = p + 1;
|
|
238
|
+
while (end < script.length && bDepth > 0) {
|
|
239
|
+
const c = script[end];
|
|
240
|
+
if (bodyStr) {
|
|
241
|
+
if (c === '\\') end++;
|
|
242
|
+
else if (c === bodyStrChar) bodyStr = false;
|
|
243
|
+
end++;
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
247
|
+
bodyStr = true;
|
|
248
|
+
bodyStrChar = c;
|
|
249
|
+
end++;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (c === '{') bDepth++;
|
|
253
|
+
if (c === '}') bDepth--;
|
|
254
|
+
end++;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (name !== excludeName) {
|
|
258
|
+
funcs.push(script.slice(i, end));
|
|
259
|
+
}
|
|
260
|
+
i = end;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
i++;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return funcs;
|
|
268
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { compileExpr } from "./utils.js";
|
|
2
|
+
import { restoreTemplateChars } from "../utils.js";
|
|
3
|
+
|
|
4
|
+
export function extractCtxFromEl(element, globalCtx = null) {
|
|
5
|
+
const ctx = {};
|
|
6
|
+
const proxy = globalCtx
|
|
7
|
+
? new Proxy(globalCtx, { has() { return true; }, get(t, k) { return t[k]; } })
|
|
8
|
+
: null;
|
|
9
|
+
for (const attr of element.attributes) {
|
|
10
|
+
const key = attr.name;
|
|
11
|
+
const val = restoreTemplateChars(attr.value);
|
|
12
|
+
if (!val.includes("{")) { ctx[key] = val; continue; }
|
|
13
|
+
const matches = [...val.matchAll(/\{([^}]+)\}/g)];
|
|
14
|
+
if (matches.length === 1 && matches[0][0] === val) {
|
|
15
|
+
try {
|
|
16
|
+
let evaluated;
|
|
17
|
+
if (proxy) {
|
|
18
|
+
evaluated = compileExpr(matches[0][1], true)(proxy);
|
|
19
|
+
} else {
|
|
20
|
+
evaluated = compileExpr(matches[0][1], false)();
|
|
21
|
+
}
|
|
22
|
+
if (evaluated !== undefined) {
|
|
23
|
+
ctx[key] = evaluated;
|
|
24
|
+
}
|
|
25
|
+
// if evaluated is undefined, leave key unset so component default can apply
|
|
26
|
+
continue;
|
|
27
|
+
} catch {}
|
|
28
|
+
// If evaluation with globalCtx fails, keep raw braces for later interpolation
|
|
29
|
+
}
|
|
30
|
+
if (proxy) {
|
|
31
|
+
// Interpolate any braces using globalCtx for string props like "Hello {name}"
|
|
32
|
+
try {
|
|
33
|
+
const interpolated = val.replace(/\{([^}]+)\}/g, (_, expr) => {
|
|
34
|
+
try {
|
|
35
|
+
const v = compileExpr(expr, true)(proxy);
|
|
36
|
+
return v === undefined ? "" : String(v);
|
|
37
|
+
} catch { return ""; }
|
|
38
|
+
});
|
|
39
|
+
// If interpolation left empty and original had only braces, don't set to avoid "undefined"
|
|
40
|
+
if (interpolated !== "" || !val.match(/^\{[^}]+\}$/)) {
|
|
41
|
+
ctx[key] = interpolated;
|
|
42
|
+
}
|
|
43
|
+
continue;
|
|
44
|
+
} catch {}
|
|
45
|
+
}
|
|
46
|
+
ctx[key] = val;
|
|
47
|
+
}
|
|
48
|
+
return ctx;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function hasMountIf(el) {
|
|
52
|
+
return el.hasAttribute("mount:if");
|
|
53
|
+
}
|
|
54
|
+
export function getMountIf(el) {
|
|
55
|
+
return el.getAttribute("mount:if");
|
|
56
|
+
}
|
|
57
|
+
export function removeMountIf(el) {
|
|
58
|
+
el.removeAttribute("mount:if");
|
|
59
|
+
}
|
package/parser/css.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
function hasCombinator(sel) {
|
|
2
|
+
let depth = 0;
|
|
3
|
+
for (const ch of sel) {
|
|
4
|
+
if (ch === '[') depth++;
|
|
5
|
+
else if (ch === ']') depth--;
|
|
6
|
+
else if (depth === 0 && (ch === ' ' || ch === '>' || ch === '+' || ch === '~')) return true;
|
|
7
|
+
}
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function scopeSelector(sel, cssId) {
|
|
12
|
+
const s = sel.trim();
|
|
13
|
+
if (s.startsWith("." + cssId)) return s;
|
|
14
|
+
if (hasCombinator(s)) return `.${cssId} ${s}`;
|
|
15
|
+
return `.${cssId}${s}, .${cssId} ${s}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function scopeCss(cssString, cssId) {
|
|
19
|
+
const keyframeRegex = /@(-webkit-)?keyframes\s+(\S+)/gi;
|
|
20
|
+
const animNames = new Set();
|
|
21
|
+
let match;
|
|
22
|
+
while ((match = keyframeRegex.exec(cssString))) {
|
|
23
|
+
animNames.add(match[2]);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (animNames.size > 0) {
|
|
27
|
+
const sorted = [...animNames].sort((a, b) => b.length - a.length);
|
|
28
|
+
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
29
|
+
for (const name of sorted) {
|
|
30
|
+
cssString = cssString.replace(
|
|
31
|
+
new RegExp(`(@(?:-webkit-)?keyframes\\s+)${esc(name)}`, "gi"),
|
|
32
|
+
`$1${name}-${cssId}`
|
|
33
|
+
);
|
|
34
|
+
cssString = cssString.replace(
|
|
35
|
+
new RegExp(`((?:animation|animation-name)\\s*:\\s*[^;{]*?)\\b${esc(name)}\\b`, "gi"),
|
|
36
|
+
`$1${name}-${cssId}`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function findClosure(s, openIndex) {
|
|
42
|
+
let depth = 0;
|
|
43
|
+
for (let i = openIndex; i < s.length; i++) {
|
|
44
|
+
const ch = s[i];
|
|
45
|
+
if (ch === '{') depth++;
|
|
46
|
+
else if (ch === '}') {
|
|
47
|
+
depth--;
|
|
48
|
+
if (depth === 0) return i;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return s.length - 1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const scopeInnerAtRules = new Set(["@media", "@supports", "@container", "@layer"]);
|
|
55
|
+
|
|
56
|
+
function processBlock(str) {
|
|
57
|
+
str = str.replaceAll(":root", `.${cssId}`);
|
|
58
|
+
|
|
59
|
+
let out = "";
|
|
60
|
+
let i = 0;
|
|
61
|
+
while (i < str.length) {
|
|
62
|
+
const braceIndex = str.indexOf('{', i);
|
|
63
|
+
if (braceIndex === -1) {
|
|
64
|
+
out += str.slice(i);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
const header = str.substring(i, braceIndex);
|
|
68
|
+
const endBrace = findClosure(str, braceIndex);
|
|
69
|
+
const inner = str.substring(braceIndex + 1, endBrace);
|
|
70
|
+
|
|
71
|
+
if (header.trim().startsWith("@")) {
|
|
72
|
+
const atKeyword = header.trim().split(/\s+/)[0].toLowerCase();
|
|
73
|
+
if (scopeInnerAtRules.has(atKeyword)) {
|
|
74
|
+
out += header + "{" + processBlock(inner) + "}";
|
|
75
|
+
} else {
|
|
76
|
+
out += header + "{" + inner + "}";
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
const innerScoped = processBlock(inner);
|
|
80
|
+
const selectors = header
|
|
81
|
+
.split(",")
|
|
82
|
+
.map(s => s.trim())
|
|
83
|
+
.filter(Boolean);
|
|
84
|
+
const scopedHeader = selectors.length > 0
|
|
85
|
+
? selectors.map(sel => scopeSelector(sel, cssId)).join(", ")
|
|
86
|
+
: header;
|
|
87
|
+
out += scopedHeader + "{" + innerScoped + "}";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
i = endBrace + 1;
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return processBlock(cssString);
|
|
96
|
+
}
|
package/parser/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { extractPropsDefaults, extractRuntime, extractTopLevelFunctions, extractTopLevelVariables } from "./component.js";
|
|
2
|
+
export { extractCtxFromEl, hasMountIf, getMountIf, removeMountIf } from "./context.js";
|
|
3
|
+
export { reservedAttrs, getLineNumber, validateChainStructure, applyConditionalToElement, interpolateNode } from "./template.js";
|
|
4
|
+
export { scopeCss } from "./css.js";
|
|
5
|
+
export { compileExpr, evaluateConstant } from "./utils.js";
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { compileExpr } from "./utils.js";
|
|
2
|
+
import { hasMountIf, getMountIf, removeMountIf } from "./context.js";
|
|
3
|
+
|
|
4
|
+
export const reservedAttrs = ["if", "mount:if", "elif", "else"];
|
|
5
|
+
|
|
6
|
+
export function getLineNumber(srcContent, parentContent, idxInParent) {
|
|
7
|
+
if (srcContent === parentContent) {
|
|
8
|
+
return parentContent.substring(0, idxInParent).split("\n").length;
|
|
9
|
+
}
|
|
10
|
+
const templateStartMatch = srcContent.match(/<template[^>]*>/);
|
|
11
|
+
if (templateStartMatch) {
|
|
12
|
+
const contentStart = templateStartMatch.index + templateStartMatch[0].length;
|
|
13
|
+
const beforeContent = srcContent.substring(0, contentStart);
|
|
14
|
+
const linesInBefore = beforeContent.split("\n").length;
|
|
15
|
+
const linesInTemplate = parentContent.substring(0, idxInParent).split("\n").length;
|
|
16
|
+
return linesInBefore + linesInTemplate - 1;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function validateChainStructure(parent, sourceFile, sourceContent, parentContent) {
|
|
22
|
+
const children = [...parent.children];
|
|
23
|
+
let chainActive = false;
|
|
24
|
+
|
|
25
|
+
for (const child of children) {
|
|
26
|
+
const hasIf = child.hasAttribute("if");
|
|
27
|
+
const hasDelIf = hasMountIf(child);
|
|
28
|
+
const hasElif = child.hasAttribute("elif");
|
|
29
|
+
const hasElse = child.hasAttribute("else");
|
|
30
|
+
|
|
31
|
+
if (hasElif || hasElse) {
|
|
32
|
+
if (!chainActive) {
|
|
33
|
+
const tag = child.tagName.toLowerCase();
|
|
34
|
+
const attr = hasElif ? "elif" : "else";
|
|
35
|
+
let loc = sourceFile;
|
|
36
|
+
if (sourceContent && parentContent) {
|
|
37
|
+
const idx = parentContent.indexOf(child.outerHTML);
|
|
38
|
+
if (idx !== -1) {
|
|
39
|
+
const lineNum = getLineNumber(sourceContent, parentContent, idx);
|
|
40
|
+
if (lineNum !== null) loc = `${sourceFile}:${lineNum}`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
throw new Error(`${loc}\n <${tag}> has ${attr} without a preceding if/mount:if sibling`);
|
|
44
|
+
}
|
|
45
|
+
if (hasElse) {
|
|
46
|
+
chainActive = false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (hasIf || hasDelIf) {
|
|
51
|
+
chainActive = true;
|
|
52
|
+
} else if (!hasElif && !hasElse) {
|
|
53
|
+
chainActive = false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
validateChainStructure(child, sourceFile, sourceContent, parentContent);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function applyConditionalToElement(child, ctxProxy, condChain, hasIf, hasDelIf, hasElif, hasElse) {
|
|
61
|
+
if (hasIf) {
|
|
62
|
+
const expr = child.getAttribute("if").slice(1, -1);
|
|
63
|
+
const fn = compileExpr(expr, true);
|
|
64
|
+
condChain.active = true;
|
|
65
|
+
if (fn(ctxProxy)) {
|
|
66
|
+
condChain.rendered = true;
|
|
67
|
+
} else {
|
|
68
|
+
child.style.display = "none";
|
|
69
|
+
condChain.rendered = false;
|
|
70
|
+
}
|
|
71
|
+
child.removeAttribute("if");
|
|
72
|
+
} else if (hasDelIf) {
|
|
73
|
+
const expr = getMountIf(child).slice(1, -1);
|
|
74
|
+
const fn = compileExpr(expr, true);
|
|
75
|
+
condChain.active = true;
|
|
76
|
+
if (fn(ctxProxy)) {
|
|
77
|
+
condChain.rendered = true;
|
|
78
|
+
} else {
|
|
79
|
+
child.remove();
|
|
80
|
+
condChain.rendered = false;
|
|
81
|
+
}
|
|
82
|
+
removeMountIf(child);
|
|
83
|
+
} else if (hasElif) {
|
|
84
|
+
const expr = child.getAttribute("elif").slice(1, -1);
|
|
85
|
+
const fn = compileExpr(expr, true);
|
|
86
|
+
if (fn(ctxProxy)) {
|
|
87
|
+
condChain.rendered = true;
|
|
88
|
+
} else {
|
|
89
|
+
child.remove();
|
|
90
|
+
}
|
|
91
|
+
child.removeAttribute("elif");
|
|
92
|
+
} else if (hasElse) {
|
|
93
|
+
condChain.rendered = true;
|
|
94
|
+
condChain.active = false;
|
|
95
|
+
child.removeAttribute("else");
|
|
96
|
+
} else {
|
|
97
|
+
condChain.active = false;
|
|
98
|
+
condChain.rendered = false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function interpolateNode(root, ctxProxy) {
|
|
103
|
+
const stack = [root];
|
|
104
|
+
while (stack.length) {
|
|
105
|
+
const node = stack.pop();
|
|
106
|
+
if (node.nodeType === 3) {
|
|
107
|
+
node.textContent = node.textContent.replace(/\{([^}]+)\}/g, (_, expr) => {
|
|
108
|
+
try {
|
|
109
|
+
return compileExpr(expr, true)(ctxProxy);
|
|
110
|
+
} catch {
|
|
111
|
+
return "";
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
} else {
|
|
115
|
+
for (let i = node.childNodes.length - 1; i >= 0; i--) {
|
|
116
|
+
stack.push(node.childNodes[i]);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
package/parser/utils.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { restoreTemplateChars } from "../utils.js";
|
|
2
|
+
|
|
3
|
+
const compiledCache = new Map();
|
|
4
|
+
|
|
5
|
+
export function compileExpr(expr, useCtx) {
|
|
6
|
+
expr = restoreTemplateChars(expr);
|
|
7
|
+
const key = useCtx ? `ctx:${expr}` : `raw:${expr}`;
|
|
8
|
+
let fn = compiledCache.get(key);
|
|
9
|
+
if (!fn) {
|
|
10
|
+
fn = useCtx
|
|
11
|
+
? new Function("ctx", `with(ctx) { return (${expr}); }`)
|
|
12
|
+
: new Function(`"use strict"; return (${expr})`);
|
|
13
|
+
compiledCache.set(key, fn);
|
|
14
|
+
}
|
|
15
|
+
return fn;
|
|
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
|
+
}
|