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/README.md
CHANGED
|
@@ -1,35 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
</
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
1
|
+
<picture>
|
|
2
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/banner_dark.jpg">
|
|
3
|
+
<img src="assets/banner.jpg" alt="Chocola • The sweetest way to build the web" />
|
|
4
|
+
</picture>
|
|
5
|
+
|
|
6
|
+
## What is Chocola
|
|
7
|
+
|
|
8
|
+
Chocola is a new and sweeter way to build your web apps.
|
|
9
|
+
|
|
10
|
+
No bundler config. No virtual DOM. No hydration ceremony. Just `.html` files with `<template>`, `<script>`, and `<style>` — compiled to static HTML with scoped CSS, and optional runtime when you need it.
|
|
11
|
+
|
|
12
|
+
Import components. Instantiate them. Mount, update, remove. All client-side. Same file, zero overhead.
|
|
13
|
+
|
|
14
|
+
```html
|
|
15
|
+
<script>
|
|
16
|
+
let self;
|
|
17
|
+
let input;
|
|
18
|
+
|
|
19
|
+
export let title = "Hello";
|
|
20
|
+
|
|
21
|
+
function $runtime() {
|
|
22
|
+
input.focus();
|
|
23
|
+
}
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<div>
|
|
28
|
+
<h1>{title}</h1>
|
|
29
|
+
<input bind:self="input" type="text" placeholder="Your name">
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<style>
|
|
34
|
+
h1 { color: chocolate; }
|
|
35
|
+
</style>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Chocola 2 is in current development and will add CSR SPA and much more features!
|
|
39
|
+
|
|
40
|
+
## Documentation
|
|
41
|
+
|
|
42
|
+
https://github.com/chocolajs/chocola/wiki/Overview
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import path from "path";
|
|
2
|
-
import {
|
|
2
|
+
import { parseHTML } from "linkedom";
|
|
3
3
|
import { protectCurlyBraces } from "../utils.js";
|
|
4
|
-
import { genRandomId, incrementAlfabet, throwError, deterministicHash } from "./utils.js";
|
|
4
|
+
import { genRandomId, incrementAlfabet, throwError, deterministicHash, warnConstantCondition, warnUnusedDeclaration, findElementLine } from "./utils.js";
|
|
5
5
|
import {
|
|
6
|
-
extractPropsDefaults, extractRuntime, extractTopLevelFunctions,
|
|
7
|
-
|
|
6
|
+
extractPropsDefaults, extractRuntime, extractTopLevelFunctions, extractTopLevelVariables,
|
|
7
|
+
extractCtxFromEl, hasMountIf, getMountIf,
|
|
8
8
|
reservedAttrs, validateChainStructure, applyConditionalToElement, interpolateNode,
|
|
9
|
-
scopeCss,
|
|
9
|
+
scopeCss, compileExpr, evaluateConstant,
|
|
10
10
|
} from "../parser/index.js";
|
|
11
11
|
import chalk from "./chalk.js";
|
|
12
12
|
|
|
@@ -24,6 +24,7 @@ class ProcessContext {
|
|
|
24
24
|
this.scopedStyles = scopedStyles;
|
|
25
25
|
this.staticCtxRegistry = staticCtxRegistry;
|
|
26
26
|
this.csrClasses = csrClasses;
|
|
27
|
+
this.unusedWarned = new Set();
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -31,6 +32,17 @@ function escapeForTemplateLiteral(str) {
|
|
|
31
32
|
return str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\${/g, "\\${");
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
function parseFragment(html, doc) {
|
|
36
|
+
const fragment = doc.createDocumentFragment();
|
|
37
|
+
const temp = doc.createElement("div");
|
|
38
|
+
temp.innerHTML = html;
|
|
39
|
+
const children = [...temp.childNodes];
|
|
40
|
+
for (const child of children) {
|
|
41
|
+
fragment.appendChild(child);
|
|
42
|
+
}
|
|
43
|
+
return fragment;
|
|
44
|
+
}
|
|
45
|
+
|
|
34
46
|
function generateCSRClass(compName, cx, explicitClassName) {
|
|
35
47
|
if (cx.csrClasses.has(compName)) return;
|
|
36
48
|
|
|
@@ -38,8 +50,8 @@ function generateCSRClass(compName, cx, explicitClassName) {
|
|
|
38
50
|
if (!instance) return;
|
|
39
51
|
|
|
40
52
|
instance = protectCurlyBraces(instance);
|
|
41
|
-
const dom =
|
|
42
|
-
const doc = dom.
|
|
53
|
+
const dom = parseHTML(instance);
|
|
54
|
+
const doc = dom.document;
|
|
43
55
|
const script = doc.querySelector("script")?.innerHTML;
|
|
44
56
|
const template = doc.querySelector("template")?.innerHTML;
|
|
45
57
|
const styles = doc.querySelector("style")?.innerHTML;
|
|
@@ -48,6 +60,7 @@ function generateCSRClass(compName, cx, explicitClassName) {
|
|
|
48
60
|
|
|
49
61
|
const compProps = extractPropsDefaults(script);
|
|
50
62
|
const topFuncSrc = extractTopLevelFunctions(script || "", RUNTIME_KW);
|
|
63
|
+
const topVars = extractTopLevelVariables(script || "");
|
|
51
64
|
let runtime = extractRuntime(script || "", compName);
|
|
52
65
|
|
|
53
66
|
if (!cx.cssScopesMap.has(compName)) {
|
|
@@ -61,7 +74,7 @@ function generateCSRClass(compName, cx, explicitClassName) {
|
|
|
61
74
|
|
|
62
75
|
const childMappings = [];
|
|
63
76
|
const bindVarNames = new Set();
|
|
64
|
-
const templateDoc =
|
|
77
|
+
const templateDoc = parseFragment(template, doc);
|
|
65
78
|
const seenTags = new Set();
|
|
66
79
|
for (const el of templateDoc.querySelectorAll("*")) {
|
|
67
80
|
const tag = el.tagName.toLowerCase();
|
|
@@ -82,15 +95,25 @@ function generateCSRClass(compName, cx, explicitClassName) {
|
|
|
82
95
|
}
|
|
83
96
|
|
|
84
97
|
let csrRuntimeSource = null;
|
|
98
|
+
const injectedNames = new Set(compProps.map(p => p.name));
|
|
99
|
+
for (const varName of bindVarNames) injectedNames.add(varName);
|
|
100
|
+
const topVarsToInject = topVars.filter(v => !injectedNames.has(v.name));
|
|
85
101
|
if (runtime) {
|
|
86
102
|
let injectCode = "";
|
|
87
103
|
for (const { name, defaultValue } of compProps) {
|
|
88
104
|
if (defaultValue !== undefined) {
|
|
89
|
-
injectCode += `let ${name} = ctx.${name}
|
|
105
|
+
injectCode += `let ${name} = ctx.${name}??(${defaultValue});\n`;
|
|
90
106
|
} else {
|
|
91
107
|
injectCode += `let ${name} = ctx.${name};\n`;
|
|
92
108
|
}
|
|
93
109
|
}
|
|
110
|
+
for (const { keyword, name, value } of topVarsToInject) {
|
|
111
|
+
if (value !== undefined) {
|
|
112
|
+
injectCode += `${keyword} ${name} = ctx.${name}??(${value});\n`;
|
|
113
|
+
} else {
|
|
114
|
+
injectCode += `${keyword} ${name};\n`;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
94
117
|
for (const varName of bindVarNames) {
|
|
95
118
|
if (varName !== "self") {
|
|
96
119
|
injectCode += `let ${varName} = ctx.${varName};\n`;
|
|
@@ -105,20 +128,110 @@ function generateCSRClass(compName, cx, explicitClassName) {
|
|
|
105
128
|
}
|
|
106
129
|
|
|
107
130
|
const className = explicitClassName || compName.replace(".html", "").replace(/^\w/, c => c.toUpperCase());
|
|
108
|
-
const
|
|
131
|
+
const propsParts = [];
|
|
109
132
|
for (const { name, defaultValue } of compProps) {
|
|
110
|
-
|
|
133
|
+
propsParts.push(`${JSON.stringify(name)}: ${defaultValue !== undefined ? defaultValue : "null"}`);
|
|
134
|
+
}
|
|
135
|
+
for (const { name, value } of topVarsToInject) {
|
|
136
|
+
if (value !== undefined) {
|
|
137
|
+
propsParts.push(`${JSON.stringify(name)}: ${value}`);
|
|
138
|
+
}
|
|
111
139
|
}
|
|
112
140
|
|
|
113
141
|
const childrenPart = childMappings.length > 0
|
|
114
142
|
? ",\n children: [" + childMappings.map(m => `{tag:"${m.tag}",compClass:${m.compClass}}`).join(",") + "]"
|
|
115
143
|
: "";
|
|
116
144
|
const runtimePart = csrRuntimeSource ? `,\n runtime: ${csrRuntimeSource}` : "";
|
|
117
|
-
const
|
|
145
|
+
const propsPart = propsParts.length > 0 ? `{ ${propsParts.join(", ")} }` : "{}";
|
|
146
|
+
const classDef = `class ${className} extends ChocolaComponent {\n constructor() {\n super({\n template: \`${escapeForTemplateLiteral(template)}\`,\n hash: "${cssId}",\n props: ${propsPart}${runtimePart}${childrenPart}\n });\n }\n}`;
|
|
118
147
|
cx.csrClasses.set(compName, classDef);
|
|
119
148
|
}
|
|
120
149
|
|
|
150
|
+
const BARE_IDENTIFIER_RE = /(?<![.\w$])[a-zA-Z_$][0-9a-zA-Z_$]*/g;
|
|
151
|
+
const PROPERTY_IDENTIFIER_RE = /\.([a-zA-Z_$][0-9a-zA-Z_$]*)/g;
|
|
152
|
+
|
|
153
|
+
function countIdentifiers(text, counts) {
|
|
154
|
+
for (const re of [BARE_IDENTIFIER_RE, PROPERTY_IDENTIFIER_RE]) {
|
|
155
|
+
re.lastIndex = 0;
|
|
156
|
+
for (const m of text.matchAll(re)) counts.set(m[1] ?? m[0], (counts.get(m[1] ?? m[0]) || 0) + 1);
|
|
157
|
+
}
|
|
158
|
+
return counts;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function escapeNameForRegex(name) {
|
|
162
|
+
return name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function findLineInSource(source, regex) {
|
|
166
|
+
const match = regex.exec(source);
|
|
167
|
+
if (!match) return null;
|
|
168
|
+
return source.substring(0, match.index).split("\n").length;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function warnUnusedDeclarations(cx, compName, instance, script, fragment) {
|
|
172
|
+
if (!script && !fragment.querySelector("[bind\\:]")) return;
|
|
173
|
+
if (cx.unusedWarned.has(compName)) return;
|
|
174
|
+
cx.unusedWarned.add(compName);
|
|
175
|
+
|
|
176
|
+
const props = extractPropsDefaults(script).map(p => p.name);
|
|
177
|
+
const topVars = extractTopLevelVariables(script).map(v => v.name);
|
|
178
|
+
const topFuncs = extractTopLevelFunctions(script, RUNTIME_KW)
|
|
179
|
+
.map(src => src.match(/^(?:async\s+)?function\s+([a-zA-Z_$][0-9a-zA-Z_$]*)/)?.[1])
|
|
180
|
+
.filter(Boolean);
|
|
121
181
|
|
|
182
|
+
const bindings = [];
|
|
183
|
+
for (const el of fragment.querySelectorAll("*")) {
|
|
184
|
+
for (const attr of el.attributes) {
|
|
185
|
+
if (attr.name.startsWith("bind:")) bindings.push({ name: attr.value, element: el });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const counts = new Map();
|
|
190
|
+
if (script) countIdentifiers(script, counts);
|
|
191
|
+
|
|
192
|
+
const walk = (node) => {
|
|
193
|
+
if (node.nodeType === 3) {
|
|
194
|
+
for (const m of node.textContent.matchAll(/\{([^}]+)\}/g)) countIdentifiers(m[1], counts);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (node.attributes) {
|
|
198
|
+
for (const attr of node.attributes) {
|
|
199
|
+
if (attr.name.startsWith("bind:")) {
|
|
200
|
+
countIdentifiers(attr.value, counts);
|
|
201
|
+
} else {
|
|
202
|
+
for (const m of attr.value.matchAll(/\{([^}]+)\}/g)) countIdentifiers(m[1], counts);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
for (const child of node.childNodes) walk(child);
|
|
207
|
+
};
|
|
208
|
+
walk(fragment);
|
|
209
|
+
|
|
210
|
+
const isUnused = (name) => (counts.get(name) || 0) <= 1;
|
|
211
|
+
const warn = (kind, name, declRegex) => {
|
|
212
|
+
const lineNum = findLineInSource(instance, declRegex);
|
|
213
|
+
warnUnusedDeclaration(lineNum !== null ? `${compName}:${lineNum}` : compName, kind, name);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
for (const name of props) {
|
|
217
|
+
if (isUnused(name)) warn("prop", name, new RegExp("export\\s+let\\s+" + escapeNameForRegex(name) + "\\b"));
|
|
218
|
+
}
|
|
219
|
+
for (const name of topVars) {
|
|
220
|
+
if (isUnused(name)) warn("variable", name, new RegExp("(?:^|[^\\w])(?:let|const)\\s+" + escapeNameForRegex(name) + "\\b"));
|
|
221
|
+
}
|
|
222
|
+
for (const name of topFuncs) {
|
|
223
|
+
if (isUnused(name)) warn("function", name, new RegExp("(?:async\\s+)?function\\s+" + escapeNameForRegex(name) + "\\b"));
|
|
224
|
+
}
|
|
225
|
+
const seenBindings = new Set();
|
|
226
|
+
for (const { name, element } of bindings) {
|
|
227
|
+
if (seenBindings.has(name)) continue;
|
|
228
|
+
seenBindings.add(name);
|
|
229
|
+
if (isUnused(name)) {
|
|
230
|
+
const lineNum = findElementLine(instance, element.outerHTML);
|
|
231
|
+
warnUnusedDeclaration(lineNum !== null ? `${compName}:${lineNum}` : compName, "binding", name);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
122
235
|
|
|
123
236
|
export function processComponentElement(
|
|
124
237
|
element,
|
|
@@ -135,8 +248,8 @@ export function processComponentElement(
|
|
|
135
248
|
if (renderChain && renderChain.includes(compName)) return false;
|
|
136
249
|
|
|
137
250
|
instance = protectCurlyBraces(instance);
|
|
138
|
-
const dom =
|
|
139
|
-
const doc = dom.
|
|
251
|
+
const dom = parseHTML(instance);
|
|
252
|
+
const doc = dom.document;
|
|
140
253
|
let script = doc.querySelector("script")?.innerHTML;
|
|
141
254
|
let template = doc.querySelector("template")?.innerHTML;
|
|
142
255
|
let styles = doc.querySelector("style")?.innerHTML;
|
|
@@ -163,7 +276,7 @@ export function processComponentElement(
|
|
|
163
276
|
if (cx.staticCtxRegistry && cx.staticCtxRegistry.has(element)) {
|
|
164
277
|
ctx = cx.staticCtxRegistry.get(element);
|
|
165
278
|
} else {
|
|
166
|
-
ctx =
|
|
279
|
+
ctx = extractCtxFromEl(element);
|
|
167
280
|
cx.staticCtxRegistry && cx.staticCtxRegistry.set(element, ctx);
|
|
168
281
|
}
|
|
169
282
|
|
|
@@ -171,7 +284,7 @@ export function processComponentElement(
|
|
|
171
284
|
compProps.forEach(({ name, defaultValue }) => {
|
|
172
285
|
if (defaultValue !== undefined && !(name in ctx)) {
|
|
173
286
|
try {
|
|
174
|
-
ctx[name] =
|
|
287
|
+
ctx[name] = compileExpr(defaultValue, false)();
|
|
175
288
|
} catch {
|
|
176
289
|
ctx[name] = defaultValue;
|
|
177
290
|
}
|
|
@@ -180,6 +293,15 @@ export function processComponentElement(
|
|
|
180
293
|
}
|
|
181
294
|
|
|
182
295
|
const topFuncSrc = extractTopLevelFunctions(script || "", RUNTIME_KW);
|
|
296
|
+
const topVars = extractTopLevelVariables(script || "");
|
|
297
|
+
for (const { name, value } of topVars) {
|
|
298
|
+
if (name in ctx) continue;
|
|
299
|
+
if (value !== undefined) {
|
|
300
|
+
try {
|
|
301
|
+
ctx[name] = compileExpr(value, false)();
|
|
302
|
+
} catch {}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
183
305
|
for (const src of topFuncSrc) {
|
|
184
306
|
try {
|
|
185
307
|
const fn = (0, eval)("(" + src + ")");
|
|
@@ -195,9 +317,11 @@ export function processComponentElement(
|
|
|
195
317
|
get(target, key) { return target[key]; }
|
|
196
318
|
});
|
|
197
319
|
|
|
198
|
-
const fragment =
|
|
320
|
+
const fragment = parseFragment(template, doc);
|
|
321
|
+
|
|
322
|
+
warnUnusedDeclarations(cx, compName, instance, script, fragment);
|
|
199
323
|
|
|
200
|
-
const slotFragment =
|
|
324
|
+
const slotFragment = parseFragment(elInnerHtml, doc);
|
|
201
325
|
if (sourceFile) {
|
|
202
326
|
validateChainStructure(slotFragment, sourceFile, sourceContent, elInnerHtml);
|
|
203
327
|
}
|
|
@@ -215,6 +339,21 @@ export function processComponentElement(
|
|
|
215
339
|
const elBindIds = new Map();
|
|
216
340
|
let bindCounter = 0;
|
|
217
341
|
|
|
342
|
+
const conditionalLocations = new Map();
|
|
343
|
+
for (const { el } of childEntries) {
|
|
344
|
+
if (!el.hasAttribute("if") && !hasMountIf(el) && !el.hasAttribute("elif")) continue;
|
|
345
|
+
let location = sourceFile;
|
|
346
|
+
if (instance) {
|
|
347
|
+
const lineNum = findElementLine(instance, el.outerHTML);
|
|
348
|
+
if (lineNum !== null) location = `${compName}:${lineNum}`;
|
|
349
|
+
}
|
|
350
|
+
if (location === sourceFile && sourceContent) {
|
|
351
|
+
const lineNum = findElementLine(sourceContent, el.outerHTML);
|
|
352
|
+
if (lineNum !== null) location = `${sourceFile}:${lineNum}`;
|
|
353
|
+
}
|
|
354
|
+
conditionalLocations.set(el, location);
|
|
355
|
+
}
|
|
356
|
+
|
|
218
357
|
childEntries.forEach(({ el: child, parent }) => {
|
|
219
358
|
if (!condChains.has(parent)) {
|
|
220
359
|
condChains.set(parent, { active: false, rendered: false });
|
|
@@ -222,13 +361,26 @@ export function processComponentElement(
|
|
|
222
361
|
const condChain = condChains.get(parent);
|
|
223
362
|
|
|
224
363
|
const hasIf = child.hasAttribute("if");
|
|
225
|
-
const hasDelIf =
|
|
364
|
+
const hasDelIf = hasMountIf(child);
|
|
226
365
|
const hasElif = child.hasAttribute("elif");
|
|
227
366
|
const hasElse = child.hasAttribute("else");
|
|
228
367
|
|
|
368
|
+
if (hasIf || hasDelIf || hasElif) {
|
|
369
|
+
const location = conditionalLocations.get(child) || sourceFile;
|
|
370
|
+
const stripBraces = (raw) => raw.startsWith("{") ? raw.slice(1, -1) : raw;
|
|
371
|
+
const tag = child.tagName.toLowerCase();
|
|
372
|
+
const warnIfConstant = (expr, attr) => {
|
|
373
|
+
const { constant, value } = evaluateConstant(expr);
|
|
374
|
+
if (constant) warnConstantCondition(location, tag, attr, Boolean(value));
|
|
375
|
+
};
|
|
376
|
+
if (hasIf) warnIfConstant(stripBraces(child.getAttribute("if")), "if");
|
|
377
|
+
if (hasDelIf) warnIfConstant(stripBraces(getMountIf(child)), "mount:if");
|
|
378
|
+
if (hasElif) warnIfConstant(stripBraces(child.getAttribute("elif")), "elif");
|
|
379
|
+
}
|
|
380
|
+
|
|
229
381
|
if (hasElif || hasElse) {
|
|
230
382
|
if (!condChain.active) {
|
|
231
|
-
throwError(`${instance.__sourceFile || compName}: <${child.tagName.toLowerCase()}> has ${hasElif ? "elif" : "else"} without a preceding if/
|
|
383
|
+
throwError(`${instance.__sourceFile || compName}: <${child.tagName.toLowerCase()}> has ${hasElif ? "elif" : "else"} without a preceding if/mount:if sibling`);
|
|
232
384
|
}
|
|
233
385
|
if (condChain.rendered) {
|
|
234
386
|
child.remove();
|
|
@@ -243,7 +395,7 @@ export function processComponentElement(
|
|
|
243
395
|
if (hasElif || hasElse) {
|
|
244
396
|
if (hasElif) {
|
|
245
397
|
const expr = child.getAttribute("elif").slice(1, -1);
|
|
246
|
-
const fn =
|
|
398
|
+
const fn = compileExpr(expr, true);
|
|
247
399
|
if (!fn(ctxProxy)) {
|
|
248
400
|
child.remove();
|
|
249
401
|
return;
|
|
@@ -255,9 +407,9 @@ export function processComponentElement(
|
|
|
255
407
|
condChain.active = false;
|
|
256
408
|
}
|
|
257
409
|
} else if (hasIf || hasDelIf) {
|
|
258
|
-
const raw = hasIf ? child.getAttribute("if") :
|
|
410
|
+
const raw = hasIf ? child.getAttribute("if") : getMountIf(child);
|
|
259
411
|
const expr = raw.slice(1, -1);
|
|
260
|
-
const fn =
|
|
412
|
+
const fn = compileExpr(expr, true);
|
|
261
413
|
condChain.active = true;
|
|
262
414
|
if (fn(ctxProxy)) {
|
|
263
415
|
child.replaceWith(...child.children);
|
|
@@ -292,19 +444,28 @@ export function processComponentElement(
|
|
|
292
444
|
return;
|
|
293
445
|
}
|
|
294
446
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
(
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
447
|
+
child.setAttribute(
|
|
448
|
+
attribute.name,
|
|
449
|
+
attribute.value.replace(
|
|
450
|
+
/\{([^}]+)\}/g,
|
|
451
|
+
(_, expr) => {
|
|
452
|
+
try {
|
|
453
|
+
return compileExpr(expr, true)(ctxProxy);
|
|
454
|
+
} catch {
|
|
455
|
+
return "";
|
|
456
|
+
}
|
|
302
457
|
}
|
|
303
|
-
|
|
458
|
+
)
|
|
304
459
|
);
|
|
305
460
|
});
|
|
306
461
|
|
|
307
|
-
|
|
462
|
+
const condAttrs = {};
|
|
463
|
+
if (hasIf) condAttrs["if"] = child.getAttribute("if");
|
|
464
|
+
if (hasDelIf) condAttrs["mount:if"] = getMountIf(child);
|
|
465
|
+
if (hasElif) condAttrs["elif"] = child.getAttribute("elif");
|
|
466
|
+
if (hasElse) condAttrs["else"] = "";
|
|
467
|
+
|
|
468
|
+
const processed = processComponentElement(
|
|
308
469
|
child,
|
|
309
470
|
cx,
|
|
310
471
|
renderChain.concat(compName),
|
|
@@ -312,9 +473,17 @@ export function processComponentElement(
|
|
|
312
473
|
template
|
|
313
474
|
);
|
|
314
475
|
|
|
315
|
-
|
|
476
|
+
let condTarget = child;
|
|
477
|
+
if (processed && processed.nodeType === 1) {
|
|
478
|
+
condTarget = processed;
|
|
479
|
+
for (const [name, value] of Object.entries(condAttrs)) {
|
|
480
|
+
condTarget.setAttribute(name, value);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
applyConditionalToElement(condTarget, ctxProxy, condChain, hasIf, hasDelIf, hasElif, hasElse);
|
|
316
485
|
|
|
317
|
-
interpolateNode(
|
|
486
|
+
interpolateNode(condTarget, ctxProxy)
|
|
318
487
|
});
|
|
319
488
|
|
|
320
489
|
let csrRuntimeSource = null;
|
|
@@ -338,14 +507,14 @@ export function processComponentElement(
|
|
|
338
507
|
const ctxDefParts = [];
|
|
339
508
|
const declared = new Set();
|
|
340
509
|
for (const [key, value] of Object.entries(runtimeCtx)) {
|
|
341
|
-
ctxDefParts.push(`let ${key} = ctx.${key}
|
|
510
|
+
ctxDefParts.push(`let ${key} = ctx.${key}??${JSON.stringify(value)};\n`);
|
|
342
511
|
declared.add(key);
|
|
343
512
|
}
|
|
344
513
|
|
|
345
514
|
for (const { name, defaultValue } of compProps) {
|
|
346
515
|
if (declared.has(name)) continue;
|
|
347
516
|
if (defaultValue !== undefined) {
|
|
348
|
-
ctxDefParts.push(`let ${name} = ctx.${name}
|
|
517
|
+
ctxDefParts.push(`let ${name} = ctx.${name}??(${defaultValue});\n`);
|
|
349
518
|
} else {
|
|
350
519
|
ctxDefParts.push(`let ${name} = ctx.${name};\n`);
|
|
351
520
|
}
|
|
@@ -365,6 +534,14 @@ export function processComponentElement(
|
|
|
365
534
|
const topFuncs = topFuncSrc;
|
|
366
535
|
|
|
367
536
|
let injectCode = ctxDef;
|
|
537
|
+
for (const b of bindings) declared.add(b.varName);
|
|
538
|
+
const undeclaredTopVars = topVars.filter(v => !declared.has(v.name));
|
|
539
|
+
if (undeclaredTopVars.length > 0) {
|
|
540
|
+
injectCode += "\n" + undeclaredTopVars.map(v => v.value !== undefined
|
|
541
|
+
? `${v.keyword} ${v.name} = ctx.${v.name}??(${v.value});`
|
|
542
|
+
: `${v.keyword} ${v.name};`
|
|
543
|
+
).join("\n") + "\n";
|
|
544
|
+
}
|
|
368
545
|
if (bindings.length > 0) {
|
|
369
546
|
injectCode += "\n" + bindings.map(b => {
|
|
370
547
|
const accessor = b.prop === "self" ? "" : "." + b.prop;
|
|
@@ -406,8 +583,12 @@ export function processComponentElement(
|
|
|
406
583
|
generateCSRClass(compName, cx);
|
|
407
584
|
}
|
|
408
585
|
|
|
586
|
+
if (element.style.display === "none" && firstChild && firstChild.nodeType === 1) {
|
|
587
|
+
firstChild.style.display = "none";
|
|
588
|
+
}
|
|
589
|
+
|
|
409
590
|
element.replaceWith(fragment);
|
|
410
|
-
return true;
|
|
591
|
+
return firstChild && firstChild.nodeType === 1 ? firstChild : true;
|
|
411
592
|
}
|
|
412
593
|
|
|
413
594
|
export function processAllComponents(appElements, loadedComponents, pageSourceFile, pageSourceContent) {
|
package/compiler/config.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import path from "path";
|
|
2
|
-
import {
|
|
2
|
+
import { getConfig } from "../utils.js";
|
|
3
3
|
|
|
4
4
|
export async function loadConfig(rootDir) {
|
|
5
|
-
const config = await
|
|
5
|
+
const config = await getConfig(rootDir);
|
|
6
6
|
const bundleConfig = config.bundle || {};
|
|
7
7
|
|
|
8
8
|
const srcDir = bundleConfig.srcDir || "src";
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseHTML } from "linkedom";
|
|
2
2
|
import { promises as fs } from "fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { protectCurlyBraces, restoreCurlyBraces } from "../utils.js";
|
|
5
|
-
import { extractContextFromElement } from "../parser/context.js";
|
|
6
5
|
import { throwError } from "./utils.js";
|
|
7
6
|
import { readMyFile } from "./fs.js";
|
|
8
7
|
|
|
9
8
|
export function createDOM(srcIndexContent) {
|
|
10
|
-
|
|
9
|
+
const protectedContent = protectCurlyBraces(srcIndexContent);
|
|
10
|
+
const dom = parseHTML(protectedContent);
|
|
11
|
+
dom.protectedContent = protectedContent;
|
|
12
|
+
return dom;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export function validateAppContainer(doc) {
|
|
@@ -23,7 +25,7 @@ export function getAppElements(appContainer) {
|
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export async function serializeDOM(dom) {
|
|
26
|
-
return restoreCurlyBraces(dom.
|
|
28
|
+
return restoreCurlyBraces(dom.document.toString());
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
export async function writeHTMLOutput(html, outDirPath) {
|