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.
@@ -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(node, ctxProxy) {
122
- if (node.nodeType === 3) {
123
- node.textContent = node.textContent.replace(/\{([^}]+)\}/g, (_, expr) => {
124
- try {
125
- return Function("ctx", `with(ctx) { return (${expr}); }`)(ctxProxy);
126
- } catch {
127
- return "";
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 outer = child.outerHTML;
153
- outerCounts[outer] = (outerCounts[outer] || 0) + 1;
154
- let searchIdx = 0;
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 = new Function("ctx", `with(ctx) { return (${expr}); }`);
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 = new Function("ctx", `with(ctx) { return (${expr}); }`);
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 Function("ctx", `with(ctx) { return (${expr}); }`)(ctxProxy);
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 = new Function("ctx", `with(ctx) { return (${expr}); }`);
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 = new Function("ctx", `with(ctx) { return (${expr}); }`);
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 = new Function("ctx", `with(ctx) { return (${expr}); }`);
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] = Function(`"use strict"; return (${matches[0][1]})`)();
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 = new Function(`"use strict"; return (${expr})`);
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 = new Function(`"use strict"; return (${expr})`);
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 = new Function(`"use strict"; return (${expr})`);
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
- const letters = "abcdefghijklmnopqrstuvwxyz";
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 = alfabet.indexOf(arr[i]);
109
+ let pos = arr[i].charCodeAt(0) - 97;
112
110
  if (pos < 25) {
113
- arr[i] = alfabet[pos + 1];
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.1",
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",