chocola 2.0.0-next.4 → 2.0.0-next.6
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 +12 -8
- package/compiler/chalk.js +32 -0
- package/compiler/component-processor.js +110 -330
- package/compiler/dom-processor.js +8 -22
- package/compiler/index.js +84 -80
- package/compiler/pipeline.js +40 -13
- package/compiler/utils.js +52 -81
- package/dev/index.js +1 -1
- package/package.json +4 -9
- package/parser/component.js +129 -0
- package/parser/context.js +29 -0
- package/parser/css.js +96 -0
- package/parser/index.js +5 -0
- package/parser/template.js +121 -0
- package/parser/utils.js +13 -0
- package/utils.js +15 -0
package/README.md
CHANGED
|
@@ -2,31 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
Chocola is a JavaScript library for creating web user interfaces.
|
|
4
4
|
|
|
5
|
-
`chocola` only contains the functionality to build and serve web static builds. For production you may want to implement `chocola` to your workflow with other frameworks
|
|
5
|
+
`chocola` only contains the functionality to build and serve web static builds. For production you may want to implement `chocola` to your workflow with other frameworks until official workflows are provided.
|
|
6
6
|
|
|
7
7
|
## Usage
|
|
8
8
|
|
|
9
9
|
```html
|
|
10
|
-
<!--
|
|
10
|
+
<!-- Greeting.html -->
|
|
11
11
|
<script>
|
|
12
|
-
let self = new
|
|
12
|
+
let self = new HTMLDivElement;
|
|
13
|
+
let input = new HTMLInputElement;
|
|
13
14
|
|
|
14
|
-
export let title = "
|
|
15
|
+
export let title = "Hello";
|
|
15
16
|
|
|
16
17
|
function $runtime() {
|
|
17
|
-
|
|
18
|
+
input.focus();
|
|
18
19
|
}
|
|
19
20
|
</script>
|
|
20
21
|
|
|
21
22
|
<template>
|
|
22
|
-
<
|
|
23
|
+
<div>
|
|
24
|
+
<h1>{title}</h1>
|
|
25
|
+
<input bind:self="input" type="text" placeholder="Your name">
|
|
26
|
+
</div>
|
|
23
27
|
</template>
|
|
24
28
|
|
|
25
29
|
<style>
|
|
26
|
-
|
|
30
|
+
h1 { color: chocolate; }
|
|
27
31
|
</style>
|
|
28
32
|
```
|
|
29
33
|
|
|
30
34
|
## Documentation
|
|
31
35
|
|
|
32
|
-
See https://github.com/
|
|
36
|
+
See https://github.com/chocolajs/chocola/wiki/Overview
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const ansi = {
|
|
2
|
+
bold: "\x1b[1m",
|
|
3
|
+
underline: "\x1b[4m",
|
|
4
|
+
red: "\x1b[31m",
|
|
5
|
+
green: "\x1b[32m",
|
|
6
|
+
yellow: "\x1b[33m",
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const reset = "\x1b[0m";
|
|
10
|
+
|
|
11
|
+
function hexToAnsi(h) {
|
|
12
|
+
const r = parseInt(h.slice(1, 3), 16);
|
|
13
|
+
const g = parseInt(h.slice(3, 5), 16);
|
|
14
|
+
const b = parseInt(h.slice(5, 7), 16);
|
|
15
|
+
return `\x1b[38;2;${r};${g};${b}m`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function makeChalk(stack) {
|
|
19
|
+
const fn = (s) => {
|
|
20
|
+
if (stack.length === 0) return s;
|
|
21
|
+
return stack.map(n => ansi[n] || hexToAnsi(n)).join("") + s + reset;
|
|
22
|
+
};
|
|
23
|
+
return new Proxy(fn, {
|
|
24
|
+
get(_, prop) {
|
|
25
|
+
if (prop === "hex") return (h) => makeChalk([...stack, h]);
|
|
26
|
+
return makeChalk([...stack, prop]);
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const chalk = makeChalk([]);
|
|
32
|
+
export default chalk;
|
|
@@ -1,219 +1,42 @@
|
|
|
1
1
|
import { JSDOM } from "jsdom";
|
|
2
|
-
import {
|
|
3
|
-
import { genRandomId, incrementAlfabet, throwError,
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
import { protectCurlyBraces } from "../utils.js";
|
|
3
|
+
import { genRandomId, incrementAlfabet, throwError, deterministicHash } from "./utils.js";
|
|
4
|
+
import {
|
|
5
|
+
extractPropsDefaults, extractRuntime, extractTopLevelFunctions,
|
|
6
|
+
extractContextFromElement, hasDelIfAttr, getDelIfAttr, removeDelIfAttr,
|
|
7
|
+
reservedAttrs, validateChainStructure, applyConditionalToElement, interpolateNode,
|
|
8
|
+
scopeCss, compileExpression,
|
|
9
|
+
} from "../parser/index.js";
|
|
10
|
+
import chalk from "./chalk.js";
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ProcessContext {
|
|
15
|
+
constructor(loadedComponents, runtimeChunks, compIdColl, letterState, runtimeMap, cssScopes, cssScopesMap, scopedStyles, staticCtxRegistry) {
|
|
16
|
+
this.loadedComponents = loadedComponents;
|
|
17
|
+
this.runtimeChunks = runtimeChunks;
|
|
18
|
+
this.compIdColl = compIdColl;
|
|
19
|
+
this.letterState = letterState;
|
|
20
|
+
this.runtimeMap = runtimeMap;
|
|
21
|
+
this.cssScopes = cssScopes;
|
|
22
|
+
this.cssScopesMap = cssScopesMap;
|
|
23
|
+
this.scopedStyles = scopedStyles;
|
|
24
|
+
this.staticCtxRegistry = staticCtxRegistry;
|
|
13
25
|
}
|
|
14
|
-
return false;
|
|
15
26
|
}
|
|
16
27
|
|
|
17
|
-
function scopeSelector(sel, cssId) {
|
|
18
|
-
const s = sel.trim();
|
|
19
|
-
if (s.startsWith("." + cssId)) return s;
|
|
20
|
-
if (hasCombinator(s)) return `.${cssId} ${s}`;
|
|
21
|
-
return `.${cssId}${s}, .${cssId} ${s}`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function scopeCss(cssString, cssId) {
|
|
25
|
-
const keyframeRegex = /@(-webkit-)?keyframes\s+(\S+)/gi;
|
|
26
|
-
const animNames = new Set();
|
|
27
|
-
let match;
|
|
28
|
-
while ((match = keyframeRegex.exec(cssString))) {
|
|
29
|
-
animNames.add(match[2]);
|
|
30
|
-
}
|
|
31
28
|
|
|
32
|
-
if (animNames.size > 0) {
|
|
33
|
-
const sorted = [...animNames].sort((a, b) => b.length - a.length);
|
|
34
|
-
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
35
|
-
for (const name of sorted) {
|
|
36
|
-
cssString = cssString.replace(
|
|
37
|
-
new RegExp(`(@(?:-webkit-)?keyframes\\s+)${esc(name)}`, "gi"),
|
|
38
|
-
`$1${name}-${cssId}`
|
|
39
|
-
);
|
|
40
|
-
cssString = cssString.replace(
|
|
41
|
-
new RegExp(`((?:animation|animation-name)\\s*:\\s*[^;{]*?)\\b${esc(name)}\\b`, "gi"),
|
|
42
|
-
`$1${name}-${cssId}`
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function findMatchingBrace(s, openIndex) {
|
|
48
|
-
let depth = 0;
|
|
49
|
-
for (let i = openIndex; i < s.length; i++) {
|
|
50
|
-
const ch = s[i];
|
|
51
|
-
if (ch === '{') depth++;
|
|
52
|
-
else if (ch === '}') {
|
|
53
|
-
depth--;
|
|
54
|
-
if (depth === 0) return i;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return s.length - 1;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const scopeInnerAtRules = new Set(["@media", "@supports", "@container", "@layer"]);
|
|
61
|
-
|
|
62
|
-
function processBlock(str) {
|
|
63
|
-
str = str.replaceAll(":root", `.${cssId}`);
|
|
64
|
-
|
|
65
|
-
let out = "";
|
|
66
|
-
let i = 0;
|
|
67
|
-
while (i < str.length) {
|
|
68
|
-
const braceIndex = str.indexOf('{', i);
|
|
69
|
-
if (braceIndex === -1) {
|
|
70
|
-
out += str.slice(i);
|
|
71
|
-
break;
|
|
72
|
-
}
|
|
73
|
-
const header = str.substring(i, braceIndex);
|
|
74
|
-
const endBrace = findMatchingBrace(str, braceIndex);
|
|
75
|
-
const inner = str.substring(braceIndex + 1, endBrace);
|
|
76
|
-
|
|
77
|
-
if (header.trim().startsWith("@")) {
|
|
78
|
-
const atKeyword = header.trim().split(/\s+/)[0].toLowerCase();
|
|
79
|
-
if (scopeInnerAtRules.has(atKeyword)) {
|
|
80
|
-
out += header + "{" + processBlock(inner) + "}";
|
|
81
|
-
} else {
|
|
82
|
-
out += header + "{" + inner + "}";
|
|
83
|
-
}
|
|
84
|
-
} else {
|
|
85
|
-
const innerScoped = processBlock(inner);
|
|
86
|
-
const selectors = header
|
|
87
|
-
.split(",")
|
|
88
|
-
.map(s => s.trim())
|
|
89
|
-
.filter(Boolean);
|
|
90
|
-
const scopedHeader = selectors.length > 0
|
|
91
|
-
? selectors.map(sel => scopeSelector(sel, cssId)).join(", ")
|
|
92
|
-
: header;
|
|
93
|
-
out += scopedHeader + "{" + innerScoped + "}";
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
i = endBrace + 1;
|
|
97
|
-
}
|
|
98
|
-
return out;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return processBlock(cssString);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function hasDelIfAttr(el) {
|
|
105
|
-
return el.hasAttribute("del:if");
|
|
106
|
-
}
|
|
107
|
-
function getDelIfAttr(el) {
|
|
108
|
-
return el.getAttribute("del:if");
|
|
109
|
-
}
|
|
110
|
-
function removeDelIfAttr(el) {
|
|
111
|
-
el.removeAttribute("del:if");
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function extractPropsDefaults(script) {
|
|
115
|
-
if (!script) return [];
|
|
116
|
-
const propsRegex = /export\s+let\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*(?:=\s*([^;]+))?;/g;
|
|
117
|
-
let props = [];
|
|
118
|
-
let match;
|
|
119
|
-
while ((match = propsRegex.exec(script)) !== null) {
|
|
120
|
-
props.push({ name: match[1].trim(), defaultValue: match[2]?.trim() });
|
|
121
|
-
}
|
|
122
|
-
return props;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function interpolateNode(root, ctxProxy) {
|
|
126
|
-
const stack = [root];
|
|
127
|
-
while (stack.length) {
|
|
128
|
-
const node = stack.pop();
|
|
129
|
-
if (node.nodeType === 3) {
|
|
130
|
-
node.textContent = node.textContent.replace(/\{([^}]+)\}/g, (_, expr) => {
|
|
131
|
-
try {
|
|
132
|
-
return compileExpression(expr, true)(ctxProxy);
|
|
133
|
-
} catch {
|
|
134
|
-
return "";
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
} else {
|
|
138
|
-
for (let i = node.childNodes.length - 1; i >= 0; i--) {
|
|
139
|
-
stack.push(node.childNodes[i]);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function getLineNumber(sourceContent, parentContent, idxInParent) {
|
|
146
|
-
if (sourceContent === parentContent) {
|
|
147
|
-
return parentContent.substring(0, idxInParent).split("\n").length;
|
|
148
|
-
}
|
|
149
|
-
const templateStartMatch = sourceContent.match(/<template[^>]*>/);
|
|
150
|
-
if (templateStartMatch) {
|
|
151
|
-
const contentStart = templateStartMatch.index + templateStartMatch[0].length;
|
|
152
|
-
const beforeContent = sourceContent.substring(0, contentStart);
|
|
153
|
-
const linesInBefore = beforeContent.split("\n").length;
|
|
154
|
-
const linesInTemplate = parentContent.substring(0, idxInParent).split("\n").length;
|
|
155
|
-
return linesInBefore + linesInTemplate - 1;
|
|
156
|
-
}
|
|
157
|
-
return null;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function validateChainStructure(parent, sourceFile, sourceContent, parentContent) {
|
|
161
|
-
const children = [...parent.children];
|
|
162
|
-
let chainActive = false;
|
|
163
|
-
|
|
164
|
-
for (const child of children) {
|
|
165
|
-
const hasIf = child.hasAttribute("if");
|
|
166
|
-
const hasDelIf = hasDelIfAttr(child);
|
|
167
|
-
const hasElif = child.hasAttribute("elif");
|
|
168
|
-
const hasElse = child.hasAttribute("else");
|
|
169
|
-
|
|
170
|
-
if (hasElif || hasElse) {
|
|
171
|
-
if (!chainActive) {
|
|
172
|
-
const tag = child.tagName.toLowerCase();
|
|
173
|
-
const attr = hasElif ? "elif" : "else";
|
|
174
|
-
let loc = sourceFile;
|
|
175
|
-
if (sourceContent && parentContent) {
|
|
176
|
-
const idx = parentContent.indexOf(child.outerHTML);
|
|
177
|
-
if (idx !== -1) {
|
|
178
|
-
const lineNum = getLineNumber(sourceContent, parentContent, idx);
|
|
179
|
-
if (lineNum !== null) loc = `${sourceFile}:${lineNum}`;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
throwError(`${loc}\n <${tag}> has ${attr} without a preceding if/del-if sibling`);
|
|
183
|
-
}
|
|
184
|
-
if (hasElse) {
|
|
185
|
-
chainActive = false;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (hasIf || hasDelIf) {
|
|
190
|
-
chainActive = true;
|
|
191
|
-
} else if (!hasElif && !hasElse) {
|
|
192
|
-
chainActive = false;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
validateChainStructure(child, sourceFile, sourceContent, parentContent);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
29
|
|
|
199
30
|
export function processComponentElement(
|
|
200
31
|
element,
|
|
201
|
-
|
|
202
|
-
runtimeChunks,
|
|
203
|
-
compIdColl,
|
|
204
|
-
letterState,
|
|
205
|
-
runtimeMap,
|
|
206
|
-
cssScopes,
|
|
207
|
-
cssScopesMap,
|
|
208
|
-
scopedStyles,
|
|
32
|
+
cx,
|
|
209
33
|
renderChain = [],
|
|
210
|
-
staticCtxRegistry,
|
|
211
34
|
sourceFile,
|
|
212
35
|
sourceContent
|
|
213
36
|
) {
|
|
214
37
|
const tagName = element.tagName.toLowerCase();
|
|
215
38
|
const compName = tagName + ".html";
|
|
216
|
-
let instance = loadedComponents.get(compName);
|
|
39
|
+
let instance = cx.loadedComponents.get(compName);
|
|
217
40
|
|
|
218
41
|
if (!instance || instance === undefined) return false;
|
|
219
42
|
if (renderChain && renderChain.includes(compName)) return false;
|
|
@@ -233,11 +56,11 @@ export function processComponentElement(
|
|
|
233
56
|
const compProps = extractPropsDefaults(script);
|
|
234
57
|
|
|
235
58
|
let ctx;
|
|
236
|
-
if (staticCtxRegistry && staticCtxRegistry.has(element)) {
|
|
237
|
-
ctx = staticCtxRegistry.get(element);
|
|
59
|
+
if (cx.staticCtxRegistry && cx.staticCtxRegistry.has(element)) {
|
|
60
|
+
ctx = cx.staticCtxRegistry.get(element);
|
|
238
61
|
} else {
|
|
239
62
|
ctx = extractContextFromElement(element);
|
|
240
|
-
staticCtxRegistry && staticCtxRegistry.set(element, ctx);
|
|
63
|
+
cx.staticCtxRegistry && cx.staticCtxRegistry.set(element, ctx);
|
|
241
64
|
}
|
|
242
65
|
|
|
243
66
|
if (compProps.length > 0) {
|
|
@@ -252,6 +75,15 @@ export function processComponentElement(
|
|
|
252
75
|
});
|
|
253
76
|
}
|
|
254
77
|
|
|
78
|
+
const topFuncSrc = extractTopLevelFunctions(script || "", RUNTIME_KW);
|
|
79
|
+
for (const src of topFuncSrc) {
|
|
80
|
+
try {
|
|
81
|
+
const fn = (0, eval)("(" + src + ")");
|
|
82
|
+
const name = fn.name;
|
|
83
|
+
if (name && !(name in ctx)) ctx[name] = fn;
|
|
84
|
+
} catch {}
|
|
85
|
+
}
|
|
86
|
+
|
|
255
87
|
const elInnerHtml = element.innerHTML;
|
|
256
88
|
|
|
257
89
|
const ctxProxy = new Proxy(ctx, {
|
|
@@ -275,6 +107,9 @@ export function processComponentElement(
|
|
|
275
107
|
parent: el.parentNode
|
|
276
108
|
}));
|
|
277
109
|
const condChains = new Map();
|
|
110
|
+
const bindings = [];
|
|
111
|
+
const elBindIds = new Map();
|
|
112
|
+
let bindCounter = 0;
|
|
278
113
|
|
|
279
114
|
childEntries.forEach(({ el: child, parent }) => {
|
|
280
115
|
if (!condChains.has(parent)) {
|
|
@@ -335,11 +170,24 @@ export function processComponentElement(
|
|
|
335
170
|
return;
|
|
336
171
|
}
|
|
337
172
|
|
|
338
|
-
const reservedAttrs = ["if", "del:if", "elif", "else"];
|
|
339
|
-
|
|
340
173
|
Array.from(child.attributes).forEach(attribute => {
|
|
341
174
|
if (!attribute || attribute === undefined) return;
|
|
342
175
|
if (reservedAttrs.includes(attribute.name)) return;
|
|
176
|
+
|
|
177
|
+
if (attribute.name.startsWith("bind:")) {
|
|
178
|
+
const prop = attribute.name.slice(5);
|
|
179
|
+
const varName = attribute.value;
|
|
180
|
+
let bindId = elBindIds.get(child);
|
|
181
|
+
if (!bindId) {
|
|
182
|
+
bindId = "b" + (bindCounter++);
|
|
183
|
+
elBindIds.set(child, bindId);
|
|
184
|
+
child.setAttribute("data-chbind-" + bindId, "");
|
|
185
|
+
}
|
|
186
|
+
bindings.push({ prop, varName, bindId });
|
|
187
|
+
child.removeAttribute(attribute.name);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
343
191
|
attribute.value = attribute.value.replace(
|
|
344
192
|
/\{([^}]+)\}/g,
|
|
345
193
|
(_, expr) => {
|
|
@@ -354,59 +202,13 @@ export function processComponentElement(
|
|
|
354
202
|
|
|
355
203
|
processComponentElement(
|
|
356
204
|
child,
|
|
357
|
-
|
|
358
|
-
runtimeChunks,
|
|
359
|
-
compIdColl,
|
|
360
|
-
letterState,
|
|
361
|
-
runtimeMap,
|
|
362
|
-
cssScopes,
|
|
363
|
-
cssScopesMap,
|
|
364
|
-
scopedStyles,
|
|
205
|
+
cx,
|
|
365
206
|
renderChain.concat(compName),
|
|
366
|
-
staticCtxRegistry,
|
|
367
207
|
compName,
|
|
368
208
|
template
|
|
369
209
|
);
|
|
370
210
|
|
|
371
|
-
|
|
372
|
-
const expr = child.getAttribute("if").slice(1, -1);
|
|
373
|
-
const fn = compileExpression(expr, true);
|
|
374
|
-
condChain.active = true;
|
|
375
|
-
if (fn(ctxProxy)) {
|
|
376
|
-
condChain.rendered = true;
|
|
377
|
-
} else {
|
|
378
|
-
child.style.display = "none";
|
|
379
|
-
condChain.rendered = false;
|
|
380
|
-
}
|
|
381
|
-
child.removeAttribute("if");
|
|
382
|
-
} else if (hasDelIf) {
|
|
383
|
-
const expr = getDelIfAttr(child).slice(1, -1);
|
|
384
|
-
const fn = compileExpression(expr, true);
|
|
385
|
-
condChain.active = true;
|
|
386
|
-
if (fn(ctxProxy)) {
|
|
387
|
-
condChain.rendered = true;
|
|
388
|
-
} else {
|
|
389
|
-
child.remove();
|
|
390
|
-
condChain.rendered = false;
|
|
391
|
-
}
|
|
392
|
-
removeDelIfAttr(child);
|
|
393
|
-
} else if (hasElif) {
|
|
394
|
-
const expr = child.getAttribute("elif").slice(1, -1);
|
|
395
|
-
const fn = compileExpression(expr, true);
|
|
396
|
-
if (fn(ctxProxy)) {
|
|
397
|
-
condChain.rendered = true;
|
|
398
|
-
} else {
|
|
399
|
-
child.remove();
|
|
400
|
-
}
|
|
401
|
-
child.removeAttribute("elif");
|
|
402
|
-
} else if (hasElse) {
|
|
403
|
-
condChain.rendered = true;
|
|
404
|
-
condChain.active = false;
|
|
405
|
-
child.removeAttribute("else");
|
|
406
|
-
} else {
|
|
407
|
-
condChain.active = false;
|
|
408
|
-
condChain.rendered = false;
|
|
409
|
-
}
|
|
211
|
+
applyConditionalToElement(child, ctxProxy, condChain, hasIf, hasDelIf, hasElif, hasElse);
|
|
410
212
|
|
|
411
213
|
interpolateNode(child, ctxProxy)
|
|
412
214
|
});
|
|
@@ -415,7 +217,7 @@ export function processComponentElement(
|
|
|
415
217
|
|
|
416
218
|
if (firstChild && firstChild.nodeType === 1) {
|
|
417
219
|
if (script) {
|
|
418
|
-
const compId = "chid-" + genRandomId(compIdColl);
|
|
220
|
+
const compId = "chid-" + genRandomId(cx.compIdColl);
|
|
419
221
|
firstChild.setAttribute("chid", compId);
|
|
420
222
|
|
|
421
223
|
const ctxRegex = /ctx\s*=\s*({.*?})/;
|
|
@@ -428,91 +230,70 @@ export function processComponentElement(
|
|
|
428
230
|
runtimeCtx = {};
|
|
429
231
|
}
|
|
430
232
|
}
|
|
431
|
-
|
|
233
|
+
const ctxDefParts = [];
|
|
234
|
+
const declared = new Set();
|
|
432
235
|
for (const [key, value] of Object.entries(runtimeCtx)) {
|
|
433
|
-
|
|
236
|
+
ctxDefParts.push(`let ${key} = ctx.${key}||${JSON.stringify(value)};\n`);
|
|
237
|
+
declared.add(key);
|
|
434
238
|
}
|
|
435
239
|
|
|
436
240
|
for (const { name, defaultValue } of compProps) {
|
|
241
|
+
if (declared.has(name)) continue;
|
|
437
242
|
if (defaultValue !== undefined) {
|
|
438
|
-
|
|
243
|
+
ctxDefParts.push(`let ${name} = ctx.${name}||${defaultValue};\n`);
|
|
244
|
+
} else {
|
|
245
|
+
ctxDefParts.push(`let ${name} = ctx.${name};\n`);
|
|
439
246
|
}
|
|
440
247
|
}
|
|
441
248
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
function extractRuntime(script) {
|
|
445
|
-
const startRegex = /(?:async\s+)?function\s+\$runtime\(([^)]*)\)\s*\{/;
|
|
446
|
-
const match = script.match(startRegex);
|
|
447
|
-
|
|
448
|
-
if (!match) return null;
|
|
449
|
-
|
|
450
|
-
const startIndexBrace = match.index + match[0].length - 1;
|
|
451
|
-
|
|
452
|
-
let bracesCount = 0;
|
|
453
|
-
let closingBraceIndex = -1;
|
|
454
|
-
|
|
455
|
-
for (let i = startIndexBrace; i < script.length; i++) {
|
|
456
|
-
if (script[i] === "{") {
|
|
457
|
-
bracesCount++;
|
|
458
|
-
} else if (script[i] === "}") {
|
|
459
|
-
bracesCount--;
|
|
460
|
-
if (bracesCount === 0) {
|
|
461
|
-
closingBraceIndex = i;
|
|
462
|
-
break;
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
if (closingBraceIndex === -1) {
|
|
468
|
-
throw new Error(`${compName} $runtime function has unclosed curly braces.`);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
const fullMatch = script.substring(match.index, closingBraceIndex + 1);
|
|
249
|
+
const ctxDef = ctxDefParts.join("");
|
|
472
250
|
|
|
473
|
-
|
|
474
|
-
}
|
|
251
|
+
script = script.replace(ctxRegex, "ctx");
|
|
475
252
|
|
|
476
|
-
let runtime = extractRuntime(script);
|
|
253
|
+
let runtime = extractRuntime(script, compName);
|
|
477
254
|
|
|
478
255
|
if (runtime) {
|
|
479
|
-
|
|
480
|
-
runtime = runtime.replace(name, `ctx.${name}`);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
runtime = runtime.replace(/\$runtime\([^)]*\)\s*\{/, match => match + "\n" + ctxDef);
|
|
484
|
-
|
|
485
|
-
let letterEntry = runtimeMap && runtimeMap.get(compName);
|
|
256
|
+
let letterEntry = cx.runtimeMap && cx.runtimeMap.get(compName);
|
|
486
257
|
let letter;
|
|
487
258
|
if (!letterEntry) {
|
|
488
|
-
letter = getNextLetter(letterState);
|
|
259
|
+
letter = getNextLetter(cx.letterState);
|
|
260
|
+
const topFuncs = topFuncSrc;
|
|
261
|
+
|
|
262
|
+
let injectCode = ctxDef;
|
|
263
|
+
if (bindings.length > 0) {
|
|
264
|
+
injectCode += "\n" + bindings.map(b => {
|
|
265
|
+
const accessor = b.prop === "self" ? "" : "." + b.prop;
|
|
266
|
+
return "let " + b.varName + " = self.querySelector('[data-chbind-" + b.bindId + "]')" + accessor + ";";
|
|
267
|
+
}).join("\n") + "\n";
|
|
268
|
+
}
|
|
269
|
+
if (topFuncs.length > 0) {
|
|
270
|
+
injectCode += "\n" + topFuncs.join("\n\n") + "\n";
|
|
271
|
+
}
|
|
272
|
+
runtime = runtime.replace(/\$runtime\([^)]*\)\s*\{/, match => match + "\n" + injectCode);
|
|
273
|
+
|
|
489
274
|
runtime = runtime.replace(`${RUNTIME_KW}()`, `${letter}r(self, ctx)`);
|
|
490
|
-
runtimeChunks.push(runtime);
|
|
491
|
-
runtimeMap && runtimeMap.set(compName, { letter });
|
|
275
|
+
cx.runtimeChunks.push(runtime);
|
|
276
|
+
cx.runtimeMap && cx.runtimeMap.set(compName, { letter });
|
|
492
277
|
} else {
|
|
493
278
|
letter = letterEntry.letter;
|
|
494
279
|
}
|
|
495
280
|
|
|
496
|
-
runtimeChunks.push(`${letter}r(document.querySelector('[chid="${compId}"]'), ${JSON.stringify(ctx)});`);
|
|
281
|
+
cx.runtimeChunks.push(`${letter}r(document.querySelector('[chid="${compId}"]'), ${JSON.stringify(ctx)});`);
|
|
497
282
|
}
|
|
498
283
|
}
|
|
499
284
|
}
|
|
500
285
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
cssScopesMap.set(compName, cssId);
|
|
506
|
-
}
|
|
507
|
-
if (fragment.children.length === 1 && firstChild.nodeType === 1) {
|
|
508
|
-
firstChild.classList.add(cssId);
|
|
509
|
-
}
|
|
510
|
-
styles = scopeCss(styles, cssId);
|
|
511
|
-
scopedStyles.push(styles);
|
|
286
|
+
let cssId = cx.cssScopesMap && cx.cssScopesMap.get(compName);
|
|
287
|
+
if (!cssId) {
|
|
288
|
+
cssId = deterministicHash(compName, 8);
|
|
289
|
+
cx.cssScopesMap.set(compName, cssId);
|
|
512
290
|
}
|
|
513
|
-
|
|
514
291
|
if (fragment.children.length === 1 && firstChild.nodeType === 1) {
|
|
515
|
-
firstChild.
|
|
292
|
+
firstChild.classList.add(cssId);
|
|
293
|
+
}
|
|
294
|
+
if (styles) {
|
|
295
|
+
styles = scopeCss(styles, cssId);
|
|
296
|
+
cx.scopedStyles.push(styles);
|
|
516
297
|
}
|
|
517
298
|
|
|
518
299
|
element.replaceWith(fragment);
|
|
@@ -520,24 +301,24 @@ export function processComponentElement(
|
|
|
520
301
|
}
|
|
521
302
|
|
|
522
303
|
export function processAllComponents(appElements, loadedComponents, pageSourceFile, pageSourceContent) {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
let runtimeMap = new Map();
|
|
527
|
-
let cssScopes = [];
|
|
528
|
-
let cssScopesMap = new Map();
|
|
529
|
-
let scopedStyles = [];
|
|
530
|
-
let staticCtxRegistry = new Map();
|
|
304
|
+
const cx = new ProcessContext(
|
|
305
|
+
loadedComponents, [], [], { value: null }, new Map(), [], new Map(), [], new Map()
|
|
306
|
+
);
|
|
531
307
|
|
|
532
308
|
appElements.forEach(el => {
|
|
533
309
|
if (!el.isConnected) return;
|
|
534
|
-
processComponentElement(el,
|
|
310
|
+
processComponentElement(el, cx, [], pageSourceFile, pageSourceContent);
|
|
535
311
|
});
|
|
536
|
-
const runtimeScript = runtimeChunks.join("\n");
|
|
537
|
-
const hasComponents = runtimeChunks.length > 0;
|
|
538
|
-
const scopesCss =
|
|
312
|
+
const runtimeScript = cx.runtimeChunks.join("\n");
|
|
313
|
+
const hasComponents = cx.runtimeChunks.length > 0;
|
|
314
|
+
const scopesCss = cx.scopedStyles.join("\n");
|
|
315
|
+
|
|
316
|
+
const hashMap = {};
|
|
317
|
+
for (const [compName, hash] of cx.cssScopesMap) {
|
|
318
|
+
hashMap[compName] = hash;
|
|
319
|
+
}
|
|
539
320
|
|
|
540
|
-
return { runtimeScript, hasComponents, scopesCss };
|
|
321
|
+
return { runtimeScript, hasComponents, scopesCss, hashMap };
|
|
541
322
|
}
|
|
542
323
|
|
|
543
324
|
function getNextLetter(letterState) {
|
|
@@ -550,4 +331,3 @@ function getNextLetter(letterState) {
|
|
|
550
331
|
}
|
|
551
332
|
|
|
552
333
|
const RUNTIME_KW = "$runtime";
|
|
553
|
-
const RUNTIME_REGEX = new RegExp(RUNTIME_KW, "g");
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { JSDOM } from "jsdom";
|
|
2
2
|
import { promises as fs } from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import {
|
|
4
|
+
import { protectCurlyBraces, restoreCurlyBraces } from "../utils.js";
|
|
5
|
+
import { extractContextFromElement } from "../parser/context.js";
|
|
6
|
+
import { throwError } from "./utils.js";
|
|
5
7
|
import { readMyFile } from "./fs.js";
|
|
6
8
|
|
|
7
9
|
export function createDOM(srcIndexContent) {
|
|
@@ -20,28 +22,8 @@ export function getAppElements(appContainer) {
|
|
|
20
22
|
return Array.from(appContainer.querySelectorAll("*"));
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
export function extractContextFromElement(element) {
|
|
24
|
-
const ctx = {};
|
|
25
|
-
for (const attr of element.attributes) {
|
|
26
|
-
const key = attr.name;
|
|
27
|
-
const val = attr.value;
|
|
28
|
-
if (!val.includes("{")) { ctx[key] = val; continue; }
|
|
29
|
-
const matches = [...val.matchAll(/\{([^}]+)\}/g)];
|
|
30
|
-
if (matches.length === 1 && matches[0][0] === val) {
|
|
31
|
-
try {
|
|
32
|
-
ctx[key] = compileExpression(matches[0][1], false)();
|
|
33
|
-
continue;
|
|
34
|
-
} catch {}
|
|
35
|
-
}
|
|
36
|
-
ctx[key] = val;
|
|
37
|
-
}
|
|
38
|
-
return ctx;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
25
|
export async function serializeDOM(dom) {
|
|
42
|
-
|
|
43
|
-
const finalHtml = restoreCurlyBraces(dom.serialize());
|
|
44
|
-
return beautify.html(finalHtml, { indent_size: 2 });
|
|
26
|
+
return restoreCurlyBraces(dom.serialize());
|
|
45
27
|
}
|
|
46
28
|
|
|
47
29
|
export async function writeHTMLOutput(html, outDirPath) {
|
|
@@ -66,6 +48,10 @@ export function appendStylesheetLink(doc, filename) {
|
|
|
66
48
|
doc.head.appendChild(linkEl);
|
|
67
49
|
}
|
|
68
50
|
|
|
51
|
+
export function getScriptElements(doc) {
|
|
52
|
+
return Array.from(doc.querySelectorAll("script[src]"));
|
|
53
|
+
}
|
|
54
|
+
|
|
69
55
|
export function appendRuntimeScript(doc, filename) {
|
|
70
56
|
const runtimeScriptEl = doc.createElement("script");
|
|
71
57
|
runtimeScriptEl.type = "module";
|