chocola 1.6.0 → 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
@@ -1,254 +1,277 @@
1
- import { promises as fs } from "fs";
2
- import chalk from "chalk";
3
- import { loadConfig, resolvePaths } from "./config.js";
4
- import {
5
- createDOM,
6
- validateAppContainer,
7
- getAppElements,
8
- getAssetLinks,
9
- appendRuntimeScript,
10
- appendStylesheetLink,
11
- serializeDOM,
12
- writeHTMLOutput,
13
- writeCSSOutput,
14
- } from "./dom-processor.js";
15
- import { processAllComponents } from "./component-processor.js";
16
- import { generateRuntimeScript } from "./runtime-generator.js";
17
- import { genRandomId, throwError } from "./utils.js";
18
- import {
19
- copyResources,
20
- copyStaticDir,
21
- getComponents,
22
- getSrcIndex,
23
- processIcons,
24
- processStylesheet,
25
- } from "./pipeline.js";
26
-
27
-
28
- const logSeparation = chalk.yellow(`
29
- ________________________________________________________________________
30
- ========================================================================
31
- `);
32
-
33
- function logBanner() {
34
- console.log(chalk.bold.hex("#945e33")(`\n RUNNING CHOCOLA BUNDLER`));
35
- console.log(logSeparation);
36
- console.log(
37
- chalk.hex("#945e33")(`
38
-
39
-
40
- ▄████▄ ██░ ██ ▒█████ ▄████▄ ▒█████ ██▓ ▄▄▄
41
- ▒██▀ ▀█ ▓██░ ██▒▒██▒ ██▒▒██▀ ▀█ ▒██▒ ██▒▓██▒ ▒████▄
42
- ▒▓█ ▄ ▒██▀▀██░▒██░ ██▒▒▓█ ▄ ▒██░ ██▒▒██░ ▒██ ▀█▄
43
- ▒▓▓▄ ▄██▒░▓█ ░██ ▒██ ██░▒▓▓▄ ▄██▒▒██ ██░▒██░ ░██▄▄▄▄██
44
- ▒ ▓███▀ ░░▓█▒░██▓░ ████▓▒░▒ ▓███▀ ░░ ████▓▒░░██████▒▓█ ▓██▒
45
- ░ ░▒ ▒ ░ ▒ ░░▒░▒░ ▒░▒░▒░ ░ ░▒ ▒ ░░ ▒░▒░▒░ ░ ▒░▓ ░▒▒ ▓▒█░
46
- ░ ▒ ▒ ░▒░ ░ ░ ░ ▒ ▒░ ░ ▒ ░ ░ ▒░ ░ ░ ▒ ░ ░ ▒▒ ░
47
- ░ ░ ░░ ░░ ░ ░ ░ ▒ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ▒
48
- ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
49
- ░ ░
50
-
51
-
52
- `)
53
- );
54
- }
55
-
56
- function logSuccess(outDirPath) {
57
- console.log(`
58
- ▄▄ ▄▄▄ ▄▄▄▄ ▄▄▄▄ ▄▄▄ ▄▄ ▄▄ ▄▄▄▄▄ ██
59
- ██ ██▀██ ██▄██ ██▀██ ██▀██ ███▄██ ██▄▄ ██
60
- ▄▄█▀ ▀███▀ ██▄█▀ ████▀ ▀███▀ ██ ▀██ ██▄▄▄ ▄▄
61
-
62
- `);
63
- console.log(
64
- chalk.bold.green(">"),
65
- "Project bundled succesfully at",
66
- chalk.green.underline(outDirPath) + "\n\n"
67
- );
68
- }
69
-
70
- async function setupOutputDirectory(outDirPath, emptyOutDir) {
71
- if (emptyOutDir) {
72
- await fs.rm(outDirPath, { recursive: true, force: true });
73
- await fs.mkdir(outDirPath);
74
- }
75
- }
76
-
77
- async function loadAndDisplayComponents(srcComponentsPath) {
78
- const foundComponents = await getComponents(srcComponentsPath);
79
- const { loadedComponents, notDefComps, componentsLib } = foundComponents;
80
-
81
- console.log(` LOADING COMPONENTS`);
82
- console.log(chalk.bold.green(">"), "Components found in", chalk.green.underline(srcComponentsPath) + ":");
83
- console.log(" ", componentsLib, "\n");
84
-
85
- if (notDefComps.length > 0) {
86
- console.warn(chalk.bold.yellow("WARNING!"), "The following components don't include a default export:");
87
- console.log(" ", notDefComps);
88
- }
89
-
90
- return loadedComponents;
91
- }
92
-
93
- async function processAssets(doc, rootDir, srcDir, outDirPath) {
94
- const { stylesheets, icons } = getAssetLinks(doc);
95
- const fileIds = [];
96
- let cssContents = [];
97
-
98
- for (const link of stylesheets) {
99
- const css = await processStylesheet(link, rootDir, srcDir, outDirPath, fileIds);
100
- cssContents.push(css);
101
- }
102
-
103
- for (const link of icons) {
104
- await processIcons(link, rootDir, srcDir, outDirPath);
105
- }
106
- return cssContents
107
- }
108
-
109
- /**
110
- * Compiles a static build of your Chocola project from the directory provided.
111
- * @param {import("fs").PathLike} rootDir
112
- * @param {Object<{
113
- * isHotReload?: boolean
114
- * }
115
- * >} config
116
- */
117
- export default async function runtime(rootDir, buildConfig) {
118
- const isHotReload = buildConfig?.isHotReload || null;
119
- !isHotReload && logBanner();
120
-
121
- const config = await loadConfig(rootDir);
122
- const paths = resolvePaths(rootDir, config);
123
- !isHotReload && console.log(logSeparation);
124
-
125
- await setupOutputDirectory(paths.outDir, config.emptyOutDir);
126
-
127
- const indexFiles = await getSrcIndex(paths.src);
128
- const srcIndexContent = indexFiles.srcHtmlFile;
129
- const pageSourcePath = indexFiles.srcPath;
130
-
131
- const loadedComponents = await loadAndDisplayComponents(paths.components);
132
- !isHotReload && console.log(logSeparation);
133
- !isHotReload && console.log(` BUNDLING STATIC BUILD`);
134
- !isHotReload && console.log(chalk.bold.green(">"), "Creating Chocola static build in directory", chalk.green.underline(paths.outDir) + "\n");
135
- !isHotReload && console.log(logSeparation);
136
-
137
- const dom = createDOM(srcIndexContent);
138
- const doc = dom.window.document;
139
- const appContainer = validateAppContainer(doc);
140
-
141
- const warnedDeprecated = new Set();
142
- function hasDelIfAttr(el, sourceLoc) {
143
- if (el.hasAttribute("del-if") && !el.hasAttribute("del:if") && !warnedDeprecated.has(sourceLoc)) {
144
- warnedDeprecated.add(sourceLoc);
145
- const name = sourceLoc ? sourceLoc.split(/[\\/]/).pop() : "unknown";
146
- console.warn(chalk.yellow(`[deprecation] "del-if" is deprecated in ${chalk.cyan(name)} and will be removed in Chocola 2. Use "del:if" instead.`));
147
- }
148
- return el.hasAttribute("del-if") || el.hasAttribute("del:if");
149
- }
150
- function getDelIfAttr(el) {
151
- return el.getAttribute("del:if") ?? el.getAttribute("del-if");
152
- }
153
- function removeDelIfAttr(el) {
154
- el.removeAttribute("del-if");
155
- el.removeAttribute("del:if");
156
- }
157
-
158
- function processPageConditionals(parent) {
159
- const children = [...parent.children];
160
- let chainActive = false;
161
- let chainRendered = false;
162
-
163
- for (const child of children) {
164
- const hasIf = child.hasAttribute("if");
165
- const hasDelIf = hasDelIfAttr(child, pageSourcePath);
166
- const hasElif = child.hasAttribute("elif");
167
- const hasElse = child.hasAttribute("else");
168
-
169
- if (hasElif || hasElse) {
170
- if (!chainActive) continue;
171
- if (chainRendered) { child.remove(); continue; }
172
- }
173
-
174
- if (hasIf) {
175
- const raw = child.getAttribute("if");
176
- const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
177
- const fn = new Function(`"use strict"; return (${expr})`);
178
- const result = fn();
179
- chainActive = true;
180
- if (result) {
181
- chainRendered = true;
182
- } else {
183
- child.remove();
184
- chainRendered = false;
185
- }
186
- child.removeAttribute("if");
187
- } else if (hasDelIf) {
188
- const raw = getDelIfAttr(child);
189
- const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
190
- const fn = new Function(`"use strict"; return (${expr})`);
191
- const result = fn();
192
- chainActive = true;
193
- if (result) {
194
- chainRendered = true;
195
- } else {
196
- child.remove();
197
- chainRendered = false;
198
- }
199
- removeDelIfAttr(child);
200
- } else if (hasElif) {
201
- const raw = child.getAttribute("elif");
202
- const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
203
- const fn = new Function(`"use strict"; return (${expr})`);
204
- const result = fn();
205
- if (result) {
206
- chainRendered = true;
207
- } else {
208
- child.remove();
209
- }
210
- child.removeAttribute("elif");
211
- } else if (hasElse) {
212
- chainRendered = true;
213
- chainActive = false;
214
- child.removeAttribute("else");
215
- } else {
216
- chainActive = false;
217
- chainRendered = false;
218
- }
219
-
220
- if (child.parentNode) {
221
- processPageConditionals(child);
222
- }
223
- }
224
- }
225
- processPageConditionals(appContainer);
226
-
227
- const appElements = getAppElements(appContainer);
228
- const { runtimeScript, scopesCss } = processAllComponents(appElements, loadedComponents, pageSourcePath, srcIndexContent);
229
- const runtimeFilename = await generateRuntimeScript(runtimeScript, paths.outDir);
230
- const globalCss = (await processAssets(doc, rootDir, config.srcDir, paths.outDir)).join("\n");
231
-
232
- if (scopesCss) {
233
- const fileName = "sc-" + genRandomId(null, 6) + ".css";
234
- await writeCSSOutput(scopesCss, paths.outDir, fileName);
235
- appendStylesheetLink(doc, fileName);
236
- };
237
-
238
- appendRuntimeScript(doc, runtimeFilename);
239
- const html = await serializeDOM(dom);
240
- await writeHTMLOutput(html, paths.outDir);
241
-
242
- try {
243
- if (config.assetImport === "static") {
244
- await copyStaticDir(paths.src, paths.outDir);
245
- } else {
246
- await copyResources(rootDir, scopesCss, globalCss, config.srcDir, paths.outDir);
247
- }
248
- } catch (err) {
249
- throwError(err.message || err);
250
- }
251
-
252
- !isHotReload && logSuccess(paths.outDir);
253
- isHotReload && console.log("Dev server updated");
254
- }
1
+ import { promises as fs } from "fs";
2
+ import chalk from "chalk";
3
+ import { loadConfig, resolvePaths } from "./config.js";
4
+ import {
5
+ createDOM,
6
+ validateAppContainer,
7
+ getAppElements,
8
+ getAssetLinks,
9
+ appendRuntimeScript,
10
+ appendStylesheetLink,
11
+ serializeDOM,
12
+ writeHTMLOutput,
13
+ writeCSSOutput,
14
+ } from "./dom-processor.js";
15
+ import { processAllComponents } from "./component-processor.js";
16
+ import { generateRuntimeScript } from "./runtime-generator.js";
17
+ import { genRandomId, throwError, compileExpression } from "./utils.js";
18
+ import {
19
+ copyResources,
20
+ copyStaticDir,
21
+ getComponents,
22
+ getSrcIndex,
23
+ processIcons,
24
+ processStylesheet,
25
+ } from "./pipeline.js";
26
+
27
+
28
+ const logSeparation = chalk.yellow(`
29
+ ________________________________________________________________________
30
+ ========================================================================
31
+ `);
32
+
33
+ function logBanner() {
34
+ console.log(chalk.bold.hex("#945e33")(`\n RUNNING CHOCOLA BUNDLER`));
35
+ console.log(logSeparation);
36
+ console.log(
37
+ chalk.hex("#945e33")(`
38
+
39
+
40
+ ▄████▄ ██░ ██ ▒█████ ▄████▄ ▒█████ ██▓ ▄▄▄
41
+ ▒██▀ ▀█ ▓██░ ██▒▒██▒ ██▒▒██▀ ▀█ ▒██▒ ██▒▓██▒ ▒████▄
42
+ ▒▓█ ▄ ▒██▀▀██░▒██░ ██▒▒▓█ ▄ ▒██░ ██▒▒██░ ▒██ ▀█▄
43
+ ▒▓▓▄ ▄██▒░▓█ ░██ ▒██ ██░▒▓▓▄ ▄██▒▒██ ██░▒██░ ░██▄▄▄▄██
44
+ ▒ ▓███▀ ░░▓█▒░██▓░ ████▓▒░▒ ▓███▀ ░░ ████▓▒░░██████▒▓█ ▓██▒
45
+ ░ ░▒ ▒ ░ ▒ ░░▒░▒░ ▒░▒░▒░ ░ ░▒ ▒ ░░ ▒░▒░▒░ ░ ▒░▓ ░▒▒ ▓▒█░
46
+ ░ ▒ ▒ ░▒░ ░ ░ ░ ▒ ▒░ ░ ▒ ░ ░ ▒░ ░ ░ ▒ ░ ░ ▒▒ ░
47
+ ░ ░ ░░ ░░ ░ ░ ░ ▒ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ▒
48
+ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
49
+ ░ ░
50
+
51
+
52
+ `)
53
+ );
54
+ }
55
+
56
+ function logSuccess(outDirPath) {
57
+ console.log(`
58
+ ▄▄ ▄▄▄ ▄▄▄▄ ▄▄▄▄ ▄▄▄ ▄▄ ▄▄ ▄▄▄▄▄ ██
59
+ ██ ██▀██ ██▄██ ██▀██ ██▀██ ███▄██ ██▄▄ ██
60
+ ▄▄█▀ ▀███▀ ██▄█▀ ████▀ ▀███▀ ██ ▀██ ██▄▄▄ ▄▄
61
+
62
+ `);
63
+ console.log(
64
+ chalk.bold.green(">"),
65
+ "Project bundled succesfully at",
66
+ chalk.green.underline(outDirPath) + "\n\n"
67
+ );
68
+ }
69
+
70
+ async function setupOutputDirectory(outDirPath, emptyOutDir) {
71
+ if (emptyOutDir) {
72
+ await fs.rm(outDirPath, { recursive: true, force: true });
73
+ await fs.mkdir(outDirPath);
74
+ }
75
+ }
76
+
77
+ async function loadAndDisplayComponents(srcComponentsPath) {
78
+ const foundComponents = await getComponents(srcComponentsPath);
79
+ const { loadedComponents, notDefComps, componentsLib } = foundComponents;
80
+
81
+ console.log(` LOADING COMPONENTS`);
82
+ console.log(chalk.bold.green(">"), "Components found in", chalk.green.underline(srcComponentsPath) + ":");
83
+ console.log(" ", componentsLib, "\n");
84
+
85
+ if (notDefComps.length > 0) {
86
+ console.warn(chalk.bold.yellow("WARNING!"), "The following components don't include a default export:");
87
+ console.log(" ", notDefComps);
88
+ }
89
+
90
+ return loadedComponents;
91
+ }
92
+
93
+ async function processAssets(doc, rootDir, srcDir, outDirPath) {
94
+ const { stylesheets, icons } = getAssetLinks(doc);
95
+ const fileIds = [];
96
+ let cssContents = [];
97
+
98
+ for (const link of stylesheets) {
99
+ const css = await processStylesheet(link, rootDir, srcDir, outDirPath, fileIds);
100
+ cssContents.push(css);
101
+ }
102
+
103
+ for (const link of icons) {
104
+ await processIcons(link, rootDir, srcDir, outDirPath);
105
+ }
106
+ return cssContents
107
+ }
108
+
109
+ /**
110
+ * Compiles a static build of your Chocola project from the directory provided.
111
+ * @param {import("fs").PathLike} rootDir
112
+ * @param {Object<{
113
+ * isHotReload?: boolean
114
+ * }
115
+ * >} config
116
+ */
117
+ export default async function runtime(rootDir, buildConfig) {
118
+ const isHotReload = buildConfig?.isHotReload || null;
119
+ !isHotReload && logBanner();
120
+
121
+ const config = await loadConfig(rootDir);
122
+ const paths = resolvePaths(rootDir, config);
123
+ !isHotReload && console.log(logSeparation);
124
+
125
+ await setupOutputDirectory(paths.outDir, config.emptyOutDir);
126
+
127
+ const indexFiles = await getSrcIndex(paths.src);
128
+ const srcIndexContent = indexFiles.srcHtmlFile;
129
+ const pageSourcePath = indexFiles.srcPath;
130
+
131
+ const loadedComponents = await loadAndDisplayComponents(paths.components);
132
+ !isHotReload && console.log(logSeparation);
133
+ !isHotReload && console.log(` BUNDLING STATIC BUILD`);
134
+ !isHotReload && console.log(chalk.bold.green(">"), "Creating Chocola static build in directory", chalk.green.underline(paths.outDir) + "\n");
135
+ !isHotReload && console.log(logSeparation);
136
+
137
+ const dom = createDOM(srcIndexContent);
138
+ const doc = dom.window.document;
139
+ const appContainer = validateAppContainer(doc);
140
+
141
+ const warnedDeprecated = new Set();
142
+ function hasDelIfAttr(el, sourceLoc) {
143
+ if (el.hasAttribute("del-if") && !el.hasAttribute("del:if") && !warnedDeprecated.has(sourceLoc)) {
144
+ warnedDeprecated.add(sourceLoc);
145
+ const name = sourceLoc ? sourceLoc.split(/[\\/]/).pop() : "unknown";
146
+ console.warn(chalk.yellow(`[deprecation] "del-if" is deprecated in ${chalk.cyan(name)} and will be removed in Chocola 2. Use "del:if" instead.`));
147
+ }
148
+ return el.hasAttribute("del-if") || el.hasAttribute("del:if");
149
+ }
150
+ function getDelIfAttr(el) {
151
+ return el.getAttribute("del:if") ?? el.getAttribute("del-if");
152
+ }
153
+ function removeDelIfAttr(el) {
154
+ el.removeAttribute("del-if");
155
+ el.removeAttribute("del:if");
156
+ }
157
+
158
+ function processPageConditionals(parent) {
159
+ const children = [...parent.children];
160
+ let chainActive = false;
161
+ let chainRendered = false;
162
+
163
+ for (const child of children) {
164
+ const hasIf = child.hasAttribute("if");
165
+ const hasDelIf = hasDelIfAttr(child, pageSourcePath);
166
+ const hasElif = child.hasAttribute("elif");
167
+ const hasElse = child.hasAttribute("else");
168
+
169
+ if (hasElif || hasElse) {
170
+ if (!chainActive) continue;
171
+ if (chainRendered) { child.remove(); continue; }
172
+ }
173
+
174
+ if (hasIf) {
175
+ const raw = child.getAttribute("if");
176
+ const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
177
+ const fn = compileExpression(expr, false);
178
+ const result = fn();
179
+ chainActive = true;
180
+ if (result) {
181
+ chainRendered = true;
182
+ } else {
183
+ child.remove();
184
+ chainRendered = false;
185
+ }
186
+ child.removeAttribute("if");
187
+ } else if (hasDelIf) {
188
+ const raw = getDelIfAttr(child);
189
+ const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
190
+ const fn = compileExpression(expr, false);
191
+ const result = fn();
192
+ chainActive = true;
193
+ if (result) {
194
+ chainRendered = true;
195
+ } else {
196
+ child.remove();
197
+ chainRendered = false;
198
+ }
199
+ removeDelIfAttr(child);
200
+ } else if (hasElif) {
201
+ const raw = child.getAttribute("elif");
202
+ const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
203
+ const fn = compileExpression(expr, false);
204
+ const result = fn();
205
+ if (result) {
206
+ chainRendered = true;
207
+ } else {
208
+ child.remove();
209
+ }
210
+ child.removeAttribute("elif");
211
+ } else if (hasElse) {
212
+ chainRendered = true;
213
+ chainActive = false;
214
+ child.removeAttribute("else");
215
+ } else {
216
+ chainActive = false;
217
+ chainRendered = false;
218
+ }
219
+
220
+ if (child.parentNode) {
221
+ processPageConditionals(child);
222
+ }
223
+ }
224
+ }
225
+ processPageConditionals(appContainer);
226
+
227
+ const appElements = getAppElements(appContainer);
228
+ const { runtimeScript, scopesCss } = processAllComponents(appElements, loadedComponents, pageSourcePath, srcIndexContent);
229
+ const runtimeFilename = await generateRuntimeScript(runtimeScript, paths.outDir);
230
+ const globalCss = (await processAssets(doc, rootDir, config.srcDir, paths.outDir)).join("\n");
231
+
232
+ if (scopesCss) {
233
+ const fileName = "sc-" + genRandomId(null, 6) + ".css";
234
+ await writeCSSOutput(scopesCss, paths.outDir, fileName);
235
+ appendStylesheetLink(doc, fileName);
236
+ };
237
+
238
+ appendRuntimeScript(doc, runtimeFilename);
239
+ const html = await serializeDOM(dom);
240
+ await writeHTMLOutput(html, paths.outDir);
241
+
242
+ try {
243
+ if (config.assetImport === "static") {
244
+ await copyStaticDir(paths.src, paths.outDir);
245
+ } else {
246
+ await copyResources(rootDir, scopesCss, globalCss, config.srcDir, paths.outDir);
247
+ }
248
+ } catch (err) {
249
+ throwError(err.message || err);
250
+ }
251
+
252
+ !isHotReload && logSuccess(paths.outDir);
253
+ isHotReload && console.log("Dev server updated");
254
+ }
255
+
256
+ /**
257
+ * An intrinsic object that contains the Chocola App methods.
258
+ */
259
+ export const app = {
260
+ /**
261
+ * Initializes your Chocola App using a root directory.
262
+ *
263
+ * ```js
264
+ * import { app } from "chocola/compiler"
265
+ import path from "path";
266
+ import { fileURLToPath } from "url";
267
+
268
+ const __filename = fileURLToPath(import.meta.url);
269
+ const __dirname = path.dirname(__filename);
270
+
271
+ app.build(__dirname);
272
+ ```
273
+ * @example
274
+ * @param {PathLike} __rootdir the directory where your Chocola App is
275
+ */
276
+ async build(__rootdir) { return runtime(__rootdir) }
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") {
@@ -152,4 +146,27 @@ export async function serve(__rootdir) {
152
146
  server.listen(__config.port, __config.hostname, () => {
153
147
  console.log(`Chocola App running at http://${__config.hostname}:${__config.port}/`);
154
148
  });
155
- }
149
+ }
150
+
151
+ /**
152
+ * An intrinsic object that contains the Chocola Dev methods.
153
+ */
154
+ export const dev = {
155
+ /**
156
+ * Initializes your Chocola dev server using a root directory.
157
+ *
158
+ * ```js
159
+ * import { app } from "chocola/compiler"
160
+ import path from "path";
161
+ import { fileURLToPath } from "url";
162
+
163
+ const __filename = fileURLToPath(import.meta.url);
164
+ const __dirname = path.dirname(__filename);
165
+
166
+ app.build(__dirname);
167
+ ```
168
+ * @example
169
+ * @param {PathLike} __rootdir the directory where your Chocola App is
170
+ */
171
+ async server(__rootdir) { return serve(__rootdir) }
172
+ };
package/index.js CHANGED
@@ -1,31 +0,0 @@
1
- import compile from "./compiler/index.js";
2
- import * as development from "./dev/index.js";
3
-
4
- /**
5
- * An intrinsic object that contains the Chocola App methods.
6
- */
7
- export const app = {
8
- /**
9
- * Initializes your Chocola App using a root directory and a source directory.
10
- *
11
- * ```js
12
- * import { app } from "chocola"
13
- import path from "path";
14
- import { fileURLToPath } from "url";
15
-
16
- const __filename = fileURLToPath(import.meta.url);
17
- const __dirname = path.dirname(__filename);
18
-
19
- app.build(__dirname, "src");
20
- ```
21
- * @example
22
- * @param {PathLike} __rootdir the directory where your Chocola App is
23
- * @param {PathLike} __srcdir the directory where your app sources files are
24
- * @returns {Object}
25
- */
26
- build(__rootdir, __srcdir) { compile(__rootdir, __srcdir) }
27
- }
28
-
29
- export const dev = {
30
- server(__rootdir, __srcdir, port) { development.serve(__rootdir, __srcdir, port) }
31
- };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chocola",
3
- "version": "1.6.0",
3
+ "version": "2.0.0-next.2",
4
4
  "description": "The sweetest way to build the web",
5
5
  "keywords": [
6
6
  "web",
@@ -31,8 +31,8 @@
31
31
  "test": "echo \"Error: no test specified\" && exit 1"
32
32
  },
33
33
  "exports": {
34
- ".": "./index.js",
35
- "./types": "./types/index.js"
34
+ "./compiler": "./compiler/index.js",
35
+ "./dev": "./dev/index.js"
36
36
  },
37
37
  "dependencies": {
38
38
  "chalk": "^5.6.2",