chocola 2.0.0-next.7 → 2.0.0-next.8
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 +39 -35
- package/compiler/component-processor.js +55 -23
- package/compiler/dom-processor.js +3 -3
- package/compiler/index.js +29 -40
- package/dev/index.js +1 -1
- package/package.json +2 -2
- package/parser/context.js +5 -4
- package/parser/template.js +1 -1
- package/parser/utils.js +3 -0
- package/runtime/index.js +10 -7
- package/utils.js +27 -2
package/README.md
CHANGED
|
@@ -1,35 +1,39 @@
|
|
|
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
|
+

|
|
2
|
+
|
|
3
|
+
## What is Chocola
|
|
4
|
+
|
|
5
|
+
Chocola is a new and sweeter way to build your web apps.
|
|
6
|
+
|
|
7
|
+
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.
|
|
8
|
+
|
|
9
|
+
Import components. Instantiate them. Mount, update, remove. All client-side. Same file, zero overhead.
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<script>
|
|
13
|
+
let self;
|
|
14
|
+
let input;
|
|
15
|
+
|
|
16
|
+
export let title = "Hello";
|
|
17
|
+
|
|
18
|
+
function $runtime() {
|
|
19
|
+
input.focus();
|
|
20
|
+
}
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<div>
|
|
25
|
+
<h1>{title}</h1>
|
|
26
|
+
<input bind:self="input" type="text" placeholder="Your name">
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<style>
|
|
31
|
+
h1 { color: chocolate; }
|
|
32
|
+
</style>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Chocola 2 is in current development and will add CSR SPA and much more features!
|
|
36
|
+
|
|
37
|
+
## Documentation
|
|
38
|
+
|
|
39
|
+
https://github.com/chocolajs/chocola/wiki/Overview
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "path";
|
|
2
|
-
import {
|
|
2
|
+
import { parseHTML } from "linkedom";
|
|
3
3
|
import { protectCurlyBraces } from "../utils.js";
|
|
4
4
|
import { genRandomId, incrementAlfabet, throwError, deterministicHash } from "./utils.js";
|
|
5
5
|
import {
|
|
@@ -31,6 +31,17 @@ function escapeForTemplateLiteral(str) {
|
|
|
31
31
|
return str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\${/g, "\\${");
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
function parseFragment(html, doc) {
|
|
35
|
+
const fragment = doc.createDocumentFragment();
|
|
36
|
+
const temp = doc.createElement("div");
|
|
37
|
+
temp.innerHTML = html;
|
|
38
|
+
const children = [...temp.childNodes];
|
|
39
|
+
for (const child of children) {
|
|
40
|
+
fragment.appendChild(child);
|
|
41
|
+
}
|
|
42
|
+
return fragment;
|
|
43
|
+
}
|
|
44
|
+
|
|
34
45
|
function generateCSRClass(compName, cx, explicitClassName) {
|
|
35
46
|
if (cx.csrClasses.has(compName)) return;
|
|
36
47
|
|
|
@@ -38,8 +49,8 @@ function generateCSRClass(compName, cx, explicitClassName) {
|
|
|
38
49
|
if (!instance) return;
|
|
39
50
|
|
|
40
51
|
instance = protectCurlyBraces(instance);
|
|
41
|
-
const dom =
|
|
42
|
-
const doc = dom.
|
|
52
|
+
const dom = parseHTML(instance);
|
|
53
|
+
const doc = dom.document;
|
|
43
54
|
const script = doc.querySelector("script")?.innerHTML;
|
|
44
55
|
const template = doc.querySelector("template")?.innerHTML;
|
|
45
56
|
const styles = doc.querySelector("style")?.innerHTML;
|
|
@@ -61,7 +72,7 @@ function generateCSRClass(compName, cx, explicitClassName) {
|
|
|
61
72
|
|
|
62
73
|
const childMappings = [];
|
|
63
74
|
const bindVarNames = new Set();
|
|
64
|
-
const templateDoc =
|
|
75
|
+
const templateDoc = parseFragment(template, doc);
|
|
65
76
|
const seenTags = new Set();
|
|
66
77
|
for (const el of templateDoc.querySelectorAll("*")) {
|
|
67
78
|
const tag = el.tagName.toLowerCase();
|
|
@@ -86,7 +97,7 @@ function generateCSRClass(compName, cx, explicitClassName) {
|
|
|
86
97
|
let injectCode = "";
|
|
87
98
|
for (const { name, defaultValue } of compProps) {
|
|
88
99
|
if (defaultValue !== undefined) {
|
|
89
|
-
injectCode += `let ${name} = ctx.${name}
|
|
100
|
+
injectCode += `let ${name} = ctx.${name}??${defaultValue};\n`;
|
|
90
101
|
} else {
|
|
91
102
|
injectCode += `let ${name} = ctx.${name};\n`;
|
|
92
103
|
}
|
|
@@ -135,8 +146,8 @@ export function processComponentElement(
|
|
|
135
146
|
if (renderChain && renderChain.includes(compName)) return false;
|
|
136
147
|
|
|
137
148
|
instance = protectCurlyBraces(instance);
|
|
138
|
-
const dom =
|
|
139
|
-
const doc = dom.
|
|
149
|
+
const dom = parseHTML(instance);
|
|
150
|
+
const doc = dom.document;
|
|
140
151
|
let script = doc.querySelector("script")?.innerHTML;
|
|
141
152
|
let template = doc.querySelector("template")?.innerHTML;
|
|
142
153
|
let styles = doc.querySelector("style")?.innerHTML;
|
|
@@ -195,9 +206,9 @@ export function processComponentElement(
|
|
|
195
206
|
get(target, key) { return target[key]; }
|
|
196
207
|
});
|
|
197
208
|
|
|
198
|
-
const fragment =
|
|
209
|
+
const fragment = parseFragment(template, doc);
|
|
199
210
|
|
|
200
|
-
const slotFragment =
|
|
211
|
+
const slotFragment = parseFragment(elInnerHtml, doc);
|
|
201
212
|
if (sourceFile) {
|
|
202
213
|
validateChainStructure(slotFragment, sourceFile, sourceContent, elInnerHtml);
|
|
203
214
|
}
|
|
@@ -292,19 +303,28 @@ export function processComponentElement(
|
|
|
292
303
|
return;
|
|
293
304
|
}
|
|
294
305
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
(
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
306
|
+
child.setAttribute(
|
|
307
|
+
attribute.name,
|
|
308
|
+
attribute.value.replace(
|
|
309
|
+
/\{([^}]+)\}/g,
|
|
310
|
+
(_, expr) => {
|
|
311
|
+
try {
|
|
312
|
+
return compileExpression(expr, true)(ctxProxy);
|
|
313
|
+
} catch {
|
|
314
|
+
return "";
|
|
315
|
+
}
|
|
302
316
|
}
|
|
303
|
-
|
|
317
|
+
)
|
|
304
318
|
);
|
|
305
319
|
});
|
|
306
320
|
|
|
307
|
-
|
|
321
|
+
const condAttrs = {};
|
|
322
|
+
if (hasIf) condAttrs["if"] = child.getAttribute("if");
|
|
323
|
+
if (hasDelIf) condAttrs["mount:if"] = getDelIfAttr(child);
|
|
324
|
+
if (hasElif) condAttrs["elif"] = child.getAttribute("elif");
|
|
325
|
+
if (hasElse) condAttrs["else"] = "";
|
|
326
|
+
|
|
327
|
+
const processed = processComponentElement(
|
|
308
328
|
child,
|
|
309
329
|
cx,
|
|
310
330
|
renderChain.concat(compName),
|
|
@@ -312,9 +332,17 @@ export function processComponentElement(
|
|
|
312
332
|
template
|
|
313
333
|
);
|
|
314
334
|
|
|
315
|
-
|
|
335
|
+
let condTarget = child;
|
|
336
|
+
if (processed && processed.nodeType === 1) {
|
|
337
|
+
condTarget = processed;
|
|
338
|
+
for (const [name, value] of Object.entries(condAttrs)) {
|
|
339
|
+
condTarget.setAttribute(name, value);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
applyConditionalToElement(condTarget, ctxProxy, condChain, hasIf, hasDelIf, hasElif, hasElse);
|
|
316
344
|
|
|
317
|
-
interpolateNode(
|
|
345
|
+
interpolateNode(condTarget, ctxProxy)
|
|
318
346
|
});
|
|
319
347
|
|
|
320
348
|
let csrRuntimeSource = null;
|
|
@@ -338,14 +366,14 @@ export function processComponentElement(
|
|
|
338
366
|
const ctxDefParts = [];
|
|
339
367
|
const declared = new Set();
|
|
340
368
|
for (const [key, value] of Object.entries(runtimeCtx)) {
|
|
341
|
-
ctxDefParts.push(`let ${key} = ctx.${key}
|
|
369
|
+
ctxDefParts.push(`let ${key} = ctx.${key}??${JSON.stringify(value)};\n`);
|
|
342
370
|
declared.add(key);
|
|
343
371
|
}
|
|
344
372
|
|
|
345
373
|
for (const { name, defaultValue } of compProps) {
|
|
346
374
|
if (declared.has(name)) continue;
|
|
347
375
|
if (defaultValue !== undefined) {
|
|
348
|
-
ctxDefParts.push(`let ${name} = ctx.${name}
|
|
376
|
+
ctxDefParts.push(`let ${name} = ctx.${name}??${defaultValue};\n`);
|
|
349
377
|
} else {
|
|
350
378
|
ctxDefParts.push(`let ${name} = ctx.${name};\n`);
|
|
351
379
|
}
|
|
@@ -406,8 +434,12 @@ export function processComponentElement(
|
|
|
406
434
|
generateCSRClass(compName, cx);
|
|
407
435
|
}
|
|
408
436
|
|
|
437
|
+
if (element.style.display === "none" && firstChild && firstChild.nodeType === 1) {
|
|
438
|
+
firstChild.style.display = "none";
|
|
439
|
+
}
|
|
440
|
+
|
|
409
441
|
element.replaceWith(fragment);
|
|
410
|
-
return true;
|
|
442
|
+
return firstChild && firstChild.nodeType === 1 ? firstChild : true;
|
|
411
443
|
}
|
|
412
444
|
|
|
413
445
|
export function processAllComponents(appElements, loadedComponents, pageSourceFile, pageSourceContent) {
|
|
@@ -1,4 +1,4 @@
|
|
|
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";
|
|
@@ -7,7 +7,7 @@ import { throwError } from "./utils.js";
|
|
|
7
7
|
import { readMyFile } from "./fs.js";
|
|
8
8
|
|
|
9
9
|
export function createDOM(srcIndexContent) {
|
|
10
|
-
return
|
|
10
|
+
return parseHTML(protectCurlyBraces(srcIndexContent));
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function validateAppContainer(doc) {
|
|
@@ -23,7 +23,7 @@ export function getAppElements(appContainer) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export async function serializeDOM(dom) {
|
|
26
|
-
return restoreCurlyBraces(dom.
|
|
26
|
+
return restoreCurlyBraces(dom.document.toString());
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export async function writeHTMLOutput(html, outDirPath) {
|
package/compiler/index.js
CHANGED
|
@@ -26,48 +26,42 @@ import {
|
|
|
26
26
|
processScript,
|
|
27
27
|
processStylesheet,
|
|
28
28
|
} from "./pipeline.js";
|
|
29
|
+
import { text } from "stream/consumers";
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
========================================================================
|
|
34
|
-
`);
|
|
31
|
+
const GOLD_COLOR = "#D87416";
|
|
32
|
+
const WHITE_COLOR = "#FAFAF8";
|
|
33
|
+
const TEXT_FAINT = "#E7EBE1";
|
|
35
34
|
|
|
36
35
|
function logBanner() {
|
|
37
|
-
console.log(chalk.bold.hex("#945e33")(`\n RUNNING CHOCOLA BUNDLER`));
|
|
38
|
-
console.log(logSeparation);
|
|
39
36
|
console.log(
|
|
40
|
-
chalk.hex(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
37
|
+
chalk.hex(GOLD_COLOR)(`
|
|
38
|
+
┌─────────────────────────────────────────────┐
|
|
39
|
+
│┌-------------------------------------------┐│
|
|
40
|
+
││ ││`)
|
|
41
|
+
);
|
|
42
|
+
console.log(
|
|
43
|
+
chalk.hex(GOLD_COLOR)(` ││ `) +
|
|
44
|
+
chalk.bold.hex(WHITE_COLOR)(`{`) +
|
|
45
|
+
chalk.bold.hex(GOLD_COLOR)(` C H O C O L A `) +
|
|
46
|
+
chalk.bold.hex(WHITE_COLOR)(`}`) +
|
|
47
|
+
chalk.hex(GOLD_COLOR)(` ││\n`) +
|
|
48
|
+
chalk.hex(GOLD_COLOR)(` ││ ││
|
|
49
|
+
││ `) +
|
|
50
|
+
chalk.hex(TEXT_FAINT)(`THE SWEETEST WAY TO BUILD THE WEB`) +
|
|
51
|
+
chalk.hex(GOLD_COLOR)(` ││
|
|
52
|
+
││ ││
|
|
53
|
+
│└-------------------------------------------┘│
|
|
54
|
+
└─────────────────────────────────────────────┘
|
|
55
|
+
`)
|
|
56
56
|
);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
function logSuccess(outDirPath) {
|
|
60
|
-
console.log(`
|
|
61
|
-
▄▄ ▄▄▄ ▄▄▄▄ ▄▄▄▄ ▄▄▄ ▄▄ ▄▄ ▄▄▄▄▄ ██
|
|
62
|
-
██ ██▀██ ██▄██ ██▀██ ██▀██ ███▄██ ██▄▄ ██
|
|
63
|
-
▄▄█▀ ▀███▀ ██▄█▀ ████▀ ▀███▀ ██ ▀██ ██▄▄▄ ▄▄
|
|
64
|
-
|
|
65
|
-
`);
|
|
66
60
|
console.log(
|
|
67
61
|
chalk.bold.green(">"),
|
|
68
62
|
"Project bundled succesfully at",
|
|
69
|
-
chalk.green.underline(outDirPath)
|
|
70
|
-
|
|
63
|
+
chalk.green.underline(outDirPath));
|
|
64
|
+
console.log(chalk.bold.green(`\nJOB DONE!\n`));
|
|
71
65
|
}
|
|
72
66
|
|
|
73
67
|
async function setupOutputDirectory(outDirPath, emptyOutDir) {
|
|
@@ -81,9 +75,8 @@ async function loadAndDisplayComponents(srcComponentsPath) {
|
|
|
81
75
|
const foundComponents = await getComponents(srcComponentsPath);
|
|
82
76
|
const { loadedComponents, notDefComps: emptyComps, componentsLib } = foundComponents;
|
|
83
77
|
|
|
84
|
-
console.log(` LOADING COMPONENTS`);
|
|
85
78
|
console.log(chalk.bold.green(">"), "Components found in", chalk.green.underline(srcComponentsPath) + ":");
|
|
86
|
-
console.log(" ", componentsLib, "\n");
|
|
79
|
+
console.log(" ", componentsLib, "\n\n");
|
|
87
80
|
|
|
88
81
|
if (emptyComps?.length > 0) {
|
|
89
82
|
console.warn(chalk.bold.yellow("WARNING!"), "The following component files are empty:");
|
|
@@ -139,7 +132,7 @@ function processPageConditionals(parent) {
|
|
|
139
132
|
if (result) {
|
|
140
133
|
chainRendered = true;
|
|
141
134
|
} else {
|
|
142
|
-
child.
|
|
135
|
+
child.style.display = "none";
|
|
143
136
|
chainRendered = false;
|
|
144
137
|
}
|
|
145
138
|
child.removeAttribute("if");
|
|
@@ -188,7 +181,6 @@ export default async function compile(rootDir, buildConfig) {
|
|
|
188
181
|
|
|
189
182
|
const config = await loadConfig(rootDir);
|
|
190
183
|
const paths = resolvePaths(rootDir, config);
|
|
191
|
-
!isHotReload && console.log(logSeparation);
|
|
192
184
|
|
|
193
185
|
await setupOutputDirectory(paths.outDir, config.emptyOutDir);
|
|
194
186
|
|
|
@@ -197,13 +189,10 @@ export default async function compile(rootDir, buildConfig) {
|
|
|
197
189
|
const pageSourcePath = indexFiles.srcPath;
|
|
198
190
|
|
|
199
191
|
const loadedComponents = await loadAndDisplayComponents(paths.components);
|
|
200
|
-
!isHotReload && console.log(
|
|
201
|
-
!isHotReload && console.log(` BUNDLING STATIC BUILD`);
|
|
202
|
-
!isHotReload && console.log(chalk.bold.green(">"), "Creating Chocola static build in directory", chalk.green.underline(paths.outDir) + "\n");
|
|
203
|
-
!isHotReload && console.log(logSeparation);
|
|
192
|
+
!isHotReload && console.log(chalk.bold.green(">"), "Creating Chocola static build in directory", chalk.green.underline(paths.outDir));
|
|
204
193
|
|
|
205
194
|
const dom = createDOM(srcIndexContent);
|
|
206
|
-
const doc = dom.
|
|
195
|
+
const doc = dom.document;
|
|
207
196
|
const appContainer = validateAppContainer(doc);
|
|
208
197
|
|
|
209
198
|
processPageConditionals(appContainer);
|
package/dev/index.js
CHANGED
|
@@ -144,7 +144,7 @@ export async function serve(__rootdir) {
|
|
|
144
144
|
});
|
|
145
145
|
|
|
146
146
|
server.listen(__config.port, __config.hostname, () => {
|
|
147
|
-
console.log(
|
|
147
|
+
console.log('Live server running at', chalk.hex("#68C4EE").underline(`http://${__config.hostname}:${__config.port}/`));
|
|
148
148
|
});
|
|
149
149
|
}
|
|
150
150
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chocola",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.8",
|
|
4
4
|
"description": "The sweetest way to build the web",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"web",
|
|
@@ -35,6 +35,6 @@
|
|
|
35
35
|
"./dev": "./dev/index.js"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"
|
|
38
|
+
"linkedom": "^0.18.13"
|
|
39
39
|
}
|
|
40
40
|
}
|
package/parser/context.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { compileExpression } from "./utils.js";
|
|
2
|
+
import { restoreTemplateChars } from "../utils.js";
|
|
2
3
|
|
|
3
4
|
export function extractContextFromElement(element) {
|
|
4
5
|
const ctx = {};
|
|
5
6
|
for (const attr of element.attributes) {
|
|
6
7
|
const key = attr.name;
|
|
7
|
-
const val = attr.value;
|
|
8
|
+
const val = restoreTemplateChars(attr.value);
|
|
8
9
|
if (!val.includes("{")) { ctx[key] = val; continue; }
|
|
9
10
|
const matches = [...val.matchAll(/\{([^}]+)\}/g)];
|
|
10
11
|
if (matches.length === 1 && matches[0][0] === val) {
|
|
@@ -19,11 +20,11 @@ export function extractContextFromElement(element) {
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export function hasDelIfAttr(el) {
|
|
22
|
-
return el.hasAttribute("
|
|
23
|
+
return el.hasAttribute("mount:if");
|
|
23
24
|
}
|
|
24
25
|
export function getDelIfAttr(el) {
|
|
25
|
-
return el.getAttribute("
|
|
26
|
+
return el.getAttribute("mount:if");
|
|
26
27
|
}
|
|
27
28
|
export function removeDelIfAttr(el) {
|
|
28
|
-
el.removeAttribute("
|
|
29
|
+
el.removeAttribute("mount:if");
|
|
29
30
|
}
|
package/parser/template.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { compileExpression } from "./utils.js";
|
|
2
2
|
import { hasDelIfAttr, getDelIfAttr, removeDelIfAttr } from "./context.js";
|
|
3
3
|
|
|
4
|
-
export const reservedAttrs = ["if", "
|
|
4
|
+
export const reservedAttrs = ["if", "mount:if", "elif", "else"];
|
|
5
5
|
|
|
6
6
|
export function getLineNumber(sourceContent, parentContent, idxInParent) {
|
|
7
7
|
if (sourceContent === parentContent) {
|
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
5
|
export function compileExpression(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) {
|
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
6
|
function compileExpression(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) {
|
|
@@ -47,7 +53,7 @@ function applyConditionalToElement(child, ctx, chain, hasIf, hasDelIf, hasElif,
|
|
|
47
53
|
}
|
|
48
54
|
child.removeAttribute("if");
|
|
49
55
|
} else if (hasDelIf) {
|
|
50
|
-
const expr = child.getAttribute("
|
|
56
|
+
const expr = child.getAttribute("mount:if").slice(1, -1);
|
|
51
57
|
chain.active = true;
|
|
52
58
|
if (compileExpression(expr, true)(ctx)) {
|
|
53
59
|
chain.rendered = true;
|
|
@@ -55,7 +61,7 @@ function applyConditionalToElement(child, ctx, chain, hasIf, hasDelIf, hasElif,
|
|
|
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
67
|
if (compileExpression(expr, true)(ctx)) {
|
|
@@ -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]) {
|
|
@@ -99,7 +102,7 @@ function interpolateAttributes(element, ctx) {
|
|
|
99
102
|
function processConditionals(element, ctx, chain) {
|
|
100
103
|
for (const child of [...element.children]) {
|
|
101
104
|
const hasIf = child.hasAttribute("if");
|
|
102
|
-
const hasDelIf = child.hasAttribute("
|
|
105
|
+
const hasDelIf = child.hasAttribute("mount:if");
|
|
103
106
|
const hasElif = child.hasAttribute("elif");
|
|
104
107
|
const hasElse = child.hasAttribute("else");
|
|
105
108
|
|
|
@@ -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
|
@@ -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
|
}
|