chocola 2.0.0-next.1 → 2.0.0-next.2
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/compiler/component-processor.js +28 -49
- package/compiler/dom-processor.js +3 -2
- package/compiler/index.js +5 -5
- package/compiler/utils.js +17 -5
- package/dev/index.js +1 -7
- package/package.json +2 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { JSDOM } from "jsdom";
|
|
2
2
|
import { extractContextFromElement } from "./dom-processor.js";
|
|
3
|
-
import { genRandomId, incrementAlfabet, throwError, protectCurlyBraces } from "./utils.js";
|
|
3
|
+
import { genRandomId, incrementAlfabet, throwError, protectCurlyBraces, compileExpression } from "./utils.js";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import beautify from "js-beautify";
|
|
6
6
|
|
|
@@ -118,24 +118,29 @@ function removeDelIfAttr(el) {
|
|
|
118
118
|
el.removeAttribute("del:if");
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
function interpolateNode(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
121
|
+
function interpolateNode(root, ctxProxy) {
|
|
122
|
+
const stack = [root];
|
|
123
|
+
while (stack.length) {
|
|
124
|
+
const node = stack.pop();
|
|
125
|
+
if (node.nodeType === 3) {
|
|
126
|
+
node.textContent = node.textContent.replace(/\{([^}]+)\}/g, (_, expr) => {
|
|
127
|
+
try {
|
|
128
|
+
return compileExpression(expr, true)(ctxProxy);
|
|
129
|
+
} catch {
|
|
130
|
+
return "";
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
} else {
|
|
134
|
+
for (let i = node.childNodes.length - 1; i >= 0; i--) {
|
|
135
|
+
stack.push(node.childNodes[i]);
|
|
128
136
|
}
|
|
129
|
-
}
|
|
130
|
-
} else {
|
|
131
|
-
node.childNodes.forEach(child => interpolateNode(child, ctxProxy));
|
|
137
|
+
}
|
|
132
138
|
}
|
|
133
139
|
}
|
|
134
140
|
|
|
135
141
|
function validateChainStructure(parent, sourceFile, sourceContent, parentContent) {
|
|
136
142
|
const children = [...parent.children];
|
|
137
143
|
let chainActive = false;
|
|
138
|
-
const outerCounts = {};
|
|
139
144
|
|
|
140
145
|
for (const child of children) {
|
|
141
146
|
const hasIf = child.hasAttribute("if");
|
|
@@ -148,37 +153,10 @@ function validateChainStructure(parent, sourceFile, sourceContent, parentContent
|
|
|
148
153
|
const tag = child.tagName.toLowerCase();
|
|
149
154
|
const attr = hasElif ? "elif" : "else";
|
|
150
155
|
let loc = sourceFile;
|
|
151
|
-
if (sourceContent && parentContent) {
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
let hits = 0;
|
|
156
|
-
while ((searchIdx = parentContent.indexOf(outer, searchIdx)) !== -1) {
|
|
157
|
-
hits++;
|
|
158
|
-
if (hits === outerCounts[outer]) {
|
|
159
|
-
let line = parentContent.substring(0, searchIdx).split("\n").length;
|
|
160
|
-
if (sourceContent !== parentContent) {
|
|
161
|
-
const anchor = parentContent.trim().slice(0, 80);
|
|
162
|
-
const vars = [
|
|
163
|
-
anchor,
|
|
164
|
-
anchor.replace(/=""/g, ' '),
|
|
165
|
-
anchor.replace(/=""/g, ''),
|
|
166
|
-
anchor.replace(/="/g, "='").replace(/"/g, "'"),
|
|
167
|
-
];
|
|
168
|
-
let anchorIdx = -1;
|
|
169
|
-
for (const v of vars) {
|
|
170
|
-
anchorIdx = sourceContent.indexOf(v);
|
|
171
|
-
if (anchorIdx !== -1) break;
|
|
172
|
-
}
|
|
173
|
-
if (anchorIdx !== -1) {
|
|
174
|
-
const offset = sourceContent.substring(0, anchorIdx).split("\n").length;
|
|
175
|
-
line = offset + line - 1;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
loc = `${sourceFile}:${line}`;
|
|
179
|
-
break;
|
|
180
|
-
}
|
|
181
|
-
searchIdx += outer.length;
|
|
156
|
+
if (sourceContent && sourceContent === parentContent) {
|
|
157
|
+
const idx = parentContent.indexOf(child.outerHTML);
|
|
158
|
+
if (idx !== -1) {
|
|
159
|
+
loc = `${sourceFile}:${parentContent.substring(0, idx).split("\n").length}`;
|
|
182
160
|
}
|
|
183
161
|
}
|
|
184
162
|
throwError(`${loc}\n <${tag}> has ${attr} without a preceding if/del-if sibling`);
|
|
@@ -291,7 +269,7 @@ export function processComponentElement(
|
|
|
291
269
|
if (hasElif || hasElse) {
|
|
292
270
|
if (hasElif) {
|
|
293
271
|
const expr = child.getAttribute("elif").slice(1, -1);
|
|
294
|
-
const fn =
|
|
272
|
+
const fn = compileExpression(expr, true);
|
|
295
273
|
if (!fn(ctxProxy)) {
|
|
296
274
|
child.remove();
|
|
297
275
|
return;
|
|
@@ -305,7 +283,7 @@ export function processComponentElement(
|
|
|
305
283
|
} else if (hasIf || hasDelIf) {
|
|
306
284
|
const raw = hasIf ? child.getAttribute("if") : getDelIfAttr(child);
|
|
307
285
|
const expr = raw.slice(1, -1);
|
|
308
|
-
const fn =
|
|
286
|
+
const fn = compileExpression(expr, true);
|
|
309
287
|
condChain.active = true;
|
|
310
288
|
if (fn(ctxProxy)) {
|
|
311
289
|
child.replaceWith(...child.children);
|
|
@@ -331,7 +309,7 @@ export function processComponentElement(
|
|
|
331
309
|
/\{([^}]+)\}/g,
|
|
332
310
|
(_, expr) => {
|
|
333
311
|
try {
|
|
334
|
-
return
|
|
312
|
+
return compileExpression(expr, true)(ctxProxy);
|
|
335
313
|
} catch {
|
|
336
314
|
return "";
|
|
337
315
|
}
|
|
@@ -357,7 +335,7 @@ export function processComponentElement(
|
|
|
357
335
|
|
|
358
336
|
if (hasIf) {
|
|
359
337
|
const expr = child.getAttribute("if").slice(1, -1);
|
|
360
|
-
const fn =
|
|
338
|
+
const fn = compileExpression(expr, true);
|
|
361
339
|
condChain.active = true;
|
|
362
340
|
if (fn(ctxProxy)) {
|
|
363
341
|
condChain.rendered = true;
|
|
@@ -368,7 +346,7 @@ export function processComponentElement(
|
|
|
368
346
|
child.removeAttribute("if");
|
|
369
347
|
} else if (hasDelIf) {
|
|
370
348
|
const expr = getDelIfAttr(child).slice(1, -1);
|
|
371
|
-
const fn =
|
|
349
|
+
const fn = compileExpression(expr, true);
|
|
372
350
|
condChain.active = true;
|
|
373
351
|
if (fn(ctxProxy)) {
|
|
374
352
|
condChain.rendered = true;
|
|
@@ -379,7 +357,7 @@ export function processComponentElement(
|
|
|
379
357
|
removeDelIfAttr(child);
|
|
380
358
|
} else if (hasElif) {
|
|
381
359
|
const expr = child.getAttribute("elif").slice(1, -1);
|
|
382
|
-
const fn =
|
|
360
|
+
const fn = compileExpression(expr, true);
|
|
383
361
|
if (fn(ctxProxy)) {
|
|
384
362
|
condChain.rendered = true;
|
|
385
363
|
} else {
|
|
@@ -486,6 +464,7 @@ export function processAllComponents(appElements, loadedComponents, pageSourceFi
|
|
|
486
464
|
let staticCtxRegistry = new Map();
|
|
487
465
|
|
|
488
466
|
appElements.forEach(el => {
|
|
467
|
+
if (!el.isConnected) return;
|
|
489
468
|
processComponentElement(el, loadedComponents, runtimeChunks, compIdColl, letterState, runtimeMap, cssScopes, cssScopesMap, scopedStyles, [], staticCtxRegistry, pageSourceFile, pageSourceContent);
|
|
490
469
|
});
|
|
491
470
|
const runtimeScript = runtimeChunks.join("\n");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { JSDOM } from "jsdom";
|
|
2
2
|
import { promises as fs } from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import { throwError, protectCurlyBraces, restoreCurlyBraces } from "./utils.js";
|
|
4
|
+
import { throwError, protectCurlyBraces, restoreCurlyBraces, compileExpression } from "./utils.js";
|
|
5
5
|
import { readMyFile } from "./fs.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -45,10 +45,11 @@ export function extractContextFromElement(element) {
|
|
|
45
45
|
for (const attr of element.attributes) {
|
|
46
46
|
const key = attr.name;
|
|
47
47
|
const val = attr.value;
|
|
48
|
+
if (!val.includes("{")) { ctx[key] = val; continue; }
|
|
48
49
|
const matches = [...val.matchAll(/\{([^}]+)\}/g)];
|
|
49
50
|
if (matches.length === 1 && matches[0][0] === val) {
|
|
50
51
|
try {
|
|
51
|
-
ctx[key] =
|
|
52
|
+
ctx[key] = compileExpression(matches[0][1], false)();
|
|
52
53
|
continue;
|
|
53
54
|
} catch {}
|
|
54
55
|
}
|
package/compiler/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "./dom-processor.js";
|
|
15
15
|
import { processAllComponents } from "./component-processor.js";
|
|
16
16
|
import { generateRuntimeScript } from "./runtime-generator.js";
|
|
17
|
-
import { genRandomId, throwError } from "./utils.js";
|
|
17
|
+
import { genRandomId, throwError, compileExpression } from "./utils.js";
|
|
18
18
|
import {
|
|
19
19
|
copyResources,
|
|
20
20
|
copyStaticDir,
|
|
@@ -174,7 +174,7 @@ export default async function runtime(rootDir, buildConfig) {
|
|
|
174
174
|
if (hasIf) {
|
|
175
175
|
const raw = child.getAttribute("if");
|
|
176
176
|
const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
|
|
177
|
-
const fn =
|
|
177
|
+
const fn = compileExpression(expr, false);
|
|
178
178
|
const result = fn();
|
|
179
179
|
chainActive = true;
|
|
180
180
|
if (result) {
|
|
@@ -187,7 +187,7 @@ export default async function runtime(rootDir, buildConfig) {
|
|
|
187
187
|
} else if (hasDelIf) {
|
|
188
188
|
const raw = getDelIfAttr(child);
|
|
189
189
|
const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
|
|
190
|
-
const fn =
|
|
190
|
+
const fn = compileExpression(expr, false);
|
|
191
191
|
const result = fn();
|
|
192
192
|
chainActive = true;
|
|
193
193
|
if (result) {
|
|
@@ -200,7 +200,7 @@ export default async function runtime(rootDir, buildConfig) {
|
|
|
200
200
|
} else if (hasElif) {
|
|
201
201
|
const raw = child.getAttribute("elif");
|
|
202
202
|
const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
|
|
203
|
-
const fn =
|
|
203
|
+
const fn = compileExpression(expr, false);
|
|
204
204
|
const result = fn();
|
|
205
205
|
if (result) {
|
|
206
206
|
chainRendered = true;
|
|
@@ -273,5 +273,5 @@ export const app = {
|
|
|
273
273
|
* @example
|
|
274
274
|
* @param {PathLike} __rootdir the directory where your Chocola App is
|
|
275
275
|
*/
|
|
276
|
-
build(__rootdir) { runtime(__rootdir) }
|
|
276
|
+
async build(__rootdir) { return runtime(__rootdir) }
|
|
277
277
|
};
|
package/compiler/utils.js
CHANGED
|
@@ -80,8 +80,7 @@ export async function loadWithAssets(filePath) {
|
|
|
80
80
|
export function genRandomId(collection = null, length = 10, lettersOnly = false) {
|
|
81
81
|
let id;
|
|
82
82
|
if (lettersOnly) {
|
|
83
|
-
|
|
84
|
-
id = Array.from({ length }, () => letters[Math.floor(Math.random() * letters.length)]).join("");
|
|
83
|
+
id = Array.from({ length }, () => ID_LETTERS[Math.floor(Math.random() * ID_LETTERS.length)]).join("");
|
|
85
84
|
} else {
|
|
86
85
|
id = Math.random().toString(36).substring(2, length + 2);
|
|
87
86
|
}
|
|
@@ -103,14 +102,13 @@ export function genRandomId(collection = null, length = 10, lettersOnly = false)
|
|
|
103
102
|
* incrementAlfabet("z") // "aa"
|
|
104
103
|
*/
|
|
105
104
|
export function incrementAlfabet(letters) {
|
|
106
|
-
const alfabet = "abcdefghijklmnopqrstuvwxyz";
|
|
107
105
|
let arr = letters.split("");
|
|
108
106
|
let i = arr.length - 1;
|
|
109
107
|
|
|
110
108
|
while (i >= 0) {
|
|
111
|
-
let pos =
|
|
109
|
+
let pos = arr[i].charCodeAt(0) - 97;
|
|
112
110
|
if (pos < 25) {
|
|
113
|
-
arr[i] =
|
|
111
|
+
arr[i] = String.fromCharCode(pos + 97 + 1);
|
|
114
112
|
return arr.join("");
|
|
115
113
|
} else {
|
|
116
114
|
arr[i] = "a";
|
|
@@ -161,3 +159,17 @@ export function restoreCurlyBraces(html) {
|
|
|
161
159
|
.replace(/_%%CHOCOLA-LBRACE%%_/g, "{")
|
|
162
160
|
.replace(/_%%CHOCOLA-RBRACE%%_/g, "}");
|
|
163
161
|
}
|
|
162
|
+
|
|
163
|
+
const ID_LETTERS = "abcdefghijklmnopqrstuvwxyz";
|
|
164
|
+
const compiledCache = new Map();
|
|
165
|
+
export function compileExpression(expr, useCtx) {
|
|
166
|
+
const key = useCtx ? `ctx:${expr}` : `raw:${expr}`;
|
|
167
|
+
let fn = compiledCache.get(key);
|
|
168
|
+
if (!fn) {
|
|
169
|
+
fn = useCtx
|
|
170
|
+
? new Function("ctx", `with(ctx) { return (${expr}); }`)
|
|
171
|
+
: new Function(`"use strict"; return (${expr})`);
|
|
172
|
+
compiledCache.set(key, fn);
|
|
173
|
+
}
|
|
174
|
+
return fn;
|
|
175
|
+
}
|
package/dev/index.js
CHANGED
|
@@ -29,7 +29,6 @@ export async function serve(__rootdir) {
|
|
|
29
29
|
else { console.warn(chalk.bold.yellow("WARNING!"), `port not defined in chocola.config.json file: using default ${__config.port} port.`) }
|
|
30
30
|
|
|
31
31
|
const srcDir = paths.src;
|
|
32
|
-
const componentsDir = paths.components;
|
|
33
32
|
|
|
34
33
|
let compileTimeout;
|
|
35
34
|
const scheduleRecompile = () => {
|
|
@@ -51,11 +50,6 @@ export async function serve(__rootdir) {
|
|
|
51
50
|
scheduleRecompile();
|
|
52
51
|
}
|
|
53
52
|
});
|
|
54
|
-
fs.watch(componentsDir, { recursive: true }, (eventType, filename) => {
|
|
55
|
-
if (filename && !filename.includes("node_modules")) {
|
|
56
|
-
scheduleRecompile();
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
53
|
|
|
60
54
|
const server = http.createServer((req, res) => {
|
|
61
55
|
if (req.url === "/api/hot-reload") {
|
|
@@ -174,5 +168,5 @@ app.build(__dirname);
|
|
|
174
168
|
* @example
|
|
175
169
|
* @param {PathLike} __rootdir the directory where your Chocola App is
|
|
176
170
|
*/
|
|
177
|
-
server(__rootdir) { serve(__rootdir) }
|
|
171
|
+
async server(__rootdir) { return serve(__rootdir) }
|
|
178
172
|
};
|
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.2",
|
|
4
4
|
"description": "The sweetest way to build the web",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"web",
|
|
@@ -32,8 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"exports": {
|
|
34
34
|
"./compiler": "./compiler/index.js",
|
|
35
|
-
"./dev": "./dev/index.js"
|
|
36
|
-
"./types": "./types/index.js"
|
|
35
|
+
"./dev": "./dev/index.js"
|
|
37
36
|
},
|
|
38
37
|
"dependencies": {
|
|
39
38
|
"chalk": "^5.6.2",
|