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/compiler/index.js CHANGED
@@ -1,11 +1,13 @@
1
1
  import { promises as fs } from "fs";
2
- import chalk from "chalk";
2
+ import path from "path";
3
+ import chalk from "./chalk.js";
3
4
  import { loadConfig, resolvePaths } from "./config.js";
4
5
  import {
5
6
  createDOM,
6
7
  validateAppContainer,
7
8
  getAppElements,
8
9
  getAssetLinks,
10
+ getScriptElements,
9
11
  appendRuntimeScript,
10
12
  appendStylesheetLink,
11
13
  serializeDOM,
@@ -14,12 +16,14 @@ import {
14
16
  } from "./dom-processor.js";
15
17
  import { processAllComponents } from "./component-processor.js";
16
18
  import { generateRuntimeScript } from "./runtime-generator.js";
17
- import { genRandomId, throwError, compileExpression } from "./utils.js";
19
+ import { genRandomId, throwError } from "./utils.js";
20
+ import { compileExpression, hasDelIfAttr, getDelIfAttr, removeDelIfAttr } from "../parser/index.js";
18
21
  import {
19
22
  copyStaticDir,
20
23
  getComponents,
21
24
  getSrcIndex,
22
25
  processIcons,
26
+ processScript,
23
27
  processStylesheet,
24
28
  } from "./pipeline.js";
25
29
 
@@ -91,6 +95,7 @@ async function loadAndDisplayComponents(srcComponentsPath) {
91
95
 
92
96
  async function processAssets(doc, rootDir, srcDir, outDirPath) {
93
97
  const { stylesheets, icons } = getAssetLinks(doc);
98
+ const scripts = getScriptElements(doc);
94
99
  const fileIds = [];
95
100
  let cssContents = [];
96
101
 
@@ -102,9 +107,81 @@ async function processAssets(doc, rootDir, srcDir, outDirPath) {
102
107
  for (const link of icons) {
103
108
  await processIcons(link, rootDir, srcDir, outDirPath);
104
109
  }
110
+
111
+ for (const script of scripts) {
112
+ await processScript(doc, script, rootDir, srcDir, outDirPath, fileIds);
113
+ }
105
114
  return cssContents
106
115
  }
107
116
 
117
+ function processPageConditionals(parent) {
118
+ const children = [...parent.children];
119
+ let chainActive = false;
120
+ let chainRendered = false;
121
+
122
+ for (const child of children) {
123
+ const hasIf = child.hasAttribute("if");
124
+ const hasDelIf = hasDelIfAttr(child);
125
+ const hasElif = child.hasAttribute("elif");
126
+ const hasElse = child.hasAttribute("else");
127
+
128
+ if (hasElif || hasElse) {
129
+ if (!chainActive) continue;
130
+ if (chainRendered) { child.remove(); continue; }
131
+ }
132
+
133
+ if (hasIf) {
134
+ const raw = child.getAttribute("if");
135
+ const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
136
+ const fn = compileExpression(expr, false);
137
+ const result = fn();
138
+ chainActive = true;
139
+ if (result) {
140
+ chainRendered = true;
141
+ } else {
142
+ child.remove();
143
+ chainRendered = false;
144
+ }
145
+ child.removeAttribute("if");
146
+ } else if (hasDelIf) {
147
+ const raw = getDelIfAttr(child);
148
+ const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
149
+ const fn = compileExpression(expr, false);
150
+ const result = fn();
151
+ chainActive = true;
152
+ if (result) {
153
+ chainRendered = true;
154
+ } else {
155
+ child.remove();
156
+ chainRendered = false;
157
+ }
158
+ removeDelIfAttr(child);
159
+ } else if (hasElif) {
160
+ const raw = child.getAttribute("elif");
161
+ const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
162
+ const fn = compileExpression(expr, false);
163
+ const result = fn();
164
+ if (result) {
165
+ chainRendered = true;
166
+ } else {
167
+ child.remove();
168
+ }
169
+ child.removeAttribute("elif");
170
+ } else if (hasElse) {
171
+ chainRendered = true;
172
+ chainActive = false;
173
+ child.removeAttribute("else");
174
+ } else {
175
+ chainActive = false;
176
+ chainRendered = false;
177
+ }
178
+
179
+ if (child.parentNode) {
180
+ processPageConditionals(child);
181
+ }
182
+ }
183
+ }
184
+
108
185
  export default async function compile(rootDir, buildConfig) {
109
186
  const isHotReload = buildConfig?.isHotReload || null;
110
187
  !isHotReload && logBanner();
@@ -129,87 +206,10 @@ export default async function compile(rootDir, buildConfig) {
129
206
  const doc = dom.window.document;
130
207
  const appContainer = validateAppContainer(doc);
131
208
 
132
- function hasDelIfAttr(el) {
133
- return el.hasAttribute("del:if");
134
- }
135
- function getDelIfAttr(el) {
136
- return el.getAttribute("del:if");
137
- }
138
- function removeDelIfAttr(el) {
139
- el.removeAttribute("del:if");
140
- }
141
-
142
- function processPageConditionals(parent) {
143
- const children = [...parent.children];
144
- let chainActive = false;
145
- let chainRendered = false;
146
-
147
- for (const child of children) {
148
- const hasIf = child.hasAttribute("if");
149
- const hasDelIf = hasDelIfAttr(child);
150
- const hasElif = child.hasAttribute("elif");
151
- const hasElse = child.hasAttribute("else");
152
-
153
- if (hasElif || hasElse) {
154
- if (!chainActive) continue;
155
- if (chainRendered) { child.remove(); continue; }
156
- }
157
-
158
- if (hasIf) {
159
- const raw = child.getAttribute("if");
160
- const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
161
- const fn = compileExpression(expr, false);
162
- const result = fn();
163
- chainActive = true;
164
- if (result) {
165
- chainRendered = true;
166
- } else {
167
- child.remove();
168
- chainRendered = false;
169
- }
170
- child.removeAttribute("if");
171
- } else if (hasDelIf) {
172
- const raw = getDelIfAttr(child);
173
- const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
174
- const fn = compileExpression(expr, false);
175
- const result = fn();
176
- chainActive = true;
177
- if (result) {
178
- chainRendered = true;
179
- } else {
180
- child.remove();
181
- chainRendered = false;
182
- }
183
- removeDelIfAttr(child);
184
- } else if (hasElif) {
185
- const raw = child.getAttribute("elif");
186
- const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
187
- const fn = compileExpression(expr, false);
188
- const result = fn();
189
- if (result) {
190
- chainRendered = true;
191
- } else {
192
- child.remove();
193
- }
194
- child.removeAttribute("elif");
195
- } else if (hasElse) {
196
- chainRendered = true;
197
- chainActive = false;
198
- child.removeAttribute("else");
199
- } else {
200
- chainActive = false;
201
- chainRendered = false;
202
- }
203
-
204
- if (child.parentNode) {
205
- processPageConditionals(child);
206
- }
207
- }
208
- }
209
209
  processPageConditionals(appContainer);
210
210
 
211
211
  const appElements = getAppElements(appContainer);
212
- const { runtimeScript, scopesCss } = processAllComponents(appElements, loadedComponents, pageSourcePath, srcIndexContent);
212
+ const { runtimeScript, scopesCss, hashMap } = processAllComponents(appElements, loadedComponents, pageSourcePath, srcIndexContent);
213
213
  const runtimeFilename = await generateRuntimeScript(runtimeScript, paths.outDir);
214
214
  await processAssets(doc, rootDir, config.srcDir, paths.outDir);
215
215
 
@@ -229,6 +229,10 @@ export default async function compile(rootDir, buildConfig) {
229
229
  throwError(err.message || err);
230
230
  }
231
231
 
232
+ const chocolaDir = path.join(rootDir, ".chocola");
233
+ await fs.mkdir(chocolaDir, { recursive: true });
234
+ await fs.writeFile(path.join(chocolaDir, "hashes.json"), JSON.stringify(hashMap, null, 2) + "\n");
235
+
232
236
  !isHotReload && logSuccess(paths.outDir);
233
237
  isHotReload && console.log("Dev server updated");
234
238
  }
@@ -15,22 +15,23 @@ export async function getComponents(libDir) {
15
15
  throw Error(`The specified components folder ${libDir} could not be found.`);
16
16
  }
17
17
 
18
- for (const comp of components) {
19
- if (!comp.endsWith(".html")) continue;
18
+ const reads = components
19
+ .filter(comp => comp.endsWith(".html"))
20
+ .map(async (comp) => {
21
+ try {
22
+ const compPath = path.join(libDir, comp);
23
+ const instance = await fs.readFile(compPath, "utf-8");
20
24
 
21
- componentsLib.push(comp);
25
+ if (instance === "" || instance.trim().length === 0) emptyComps.push(comp);
22
26
 
23
- try {
24
- const compPath = path.join(libDir, comp);
25
- const instance = await fs.readFile(compPath, "utf-8");
27
+ loadedComponents.set(comp.toLowerCase(), instance);
28
+ componentsLib.push(comp);
29
+ } catch (err) {
30
+ throwError(`Failed to load component "${comp}": ${err.message || err}`);
31
+ }
32
+ });
26
33
 
27
- if (instance === "" || instance.trim().length === 0) emptyComps.push(comp);
28
-
29
- loadedComponents.set(comp.toLowerCase(), instance);
30
- } catch (err) {
31
- throwError(`Failed to load component "${comp}": ${err.message || err}`);
32
- }
33
- }
34
+ await Promise.all(reads);
34
35
 
35
36
  return { componentsLib, loadedComponents, emptyComps };
36
37
  } catch (err) {
@@ -83,6 +84,32 @@ export async function processIcons(link, rootDir, srcDir, outDirPath) {
83
84
  }
84
85
  }
85
86
 
87
+ export async function processScript(doc, script, rootDir, srcDir, outDirPath, fileIds) {
88
+ try {
89
+ const src = script.getAttribute("src");
90
+ if (src.startsWith("http://") || src.startsWith("https://")) return;
91
+ const scriptPath = path.join(rootDir, srcDir, src);
92
+ const content = await fs.readFile(scriptPath, { encoding: "utf8" });
93
+ const jsFileName = "js-" + genRandomId(fileIds, 6) + ".js";
94
+ await fs.writeFile(path.join(outDirPath, jsFileName), content);
95
+
96
+ const newScript = doc.createElement("script");
97
+ for (const attr of script.attributes) {
98
+ if (attr.name === "src") {
99
+ newScript.setAttribute("src", "./" + jsFileName);
100
+ } else {
101
+ newScript.setAttribute(attr.name, attr.value);
102
+ }
103
+ }
104
+ if (script.textContent.trim()) {
105
+ newScript.textContent = script.textContent;
106
+ }
107
+ script.parentNode.replaceChild(newScript, script);
108
+ } catch (err) {
109
+ throwError(`Failed to process script: ${err}`);
110
+ }
111
+ }
112
+
86
113
  export async function copyStaticDir(srcPath, outDirPath) {
87
114
  const staticSrc = path.join(srcPath, "static");
88
115
  const staticDest = path.join(outDirPath, "static");
package/compiler/utils.js CHANGED
@@ -1,81 +1,52 @@
1
- import fs from "fs/promises";
2
- import path from "path";
3
- import { pathToFileURL } from "url";
4
- import chalk from "chalk";
5
-
6
- export function throwError(err) {
7
- console.log(chalk.red.bold("Error!"), "A fatal error has occurred:\n");
8
- throw new Error(err);
9
- }
10
-
11
- export function genRandomId(collection = null, length = 10, lettersOnly = false) {
12
- let id;
13
- if (lettersOnly) {
14
- id = Array.from({ length }, () => ID_LETTERS[Math.floor(Math.random() * ID_LETTERS.length)]).join("");
15
- } else {
16
- id = Math.random().toString(36).substring(2, length + 2);
17
- }
18
- if (!collection) return id;
19
- if (collection.includes(id)) {
20
- return genRandomId(collection, length, lettersOnly);
21
- } else {
22
- collection.push(id);
23
- return id;
24
- }
25
- }
26
-
27
- export function incrementAlfabet(letters) {
28
- let arr = letters.split("");
29
- let i = arr.length - 1;
30
-
31
- while (i >= 0) {
32
- let pos = arr[i].charCodeAt(0) - 97;
33
- if (pos < 25) {
34
- arr[i] = String.fromCharCode(pos + 97 + 1);
35
- return arr.join("");
36
- } else {
37
- arr[i] = "a";
38
- i--;
39
- }
40
- }
41
-
42
- return "a" + arr.join("");
43
- }
44
-
45
- export function isWebLink(str) {
46
- try {
47
- const url = new URL(str);
48
- return url.protocol === "http:" || url.protocol === "https:";
49
- } catch (e) {
50
- return false;
51
- }
52
- }
53
-
54
- const LBRACE_PH = "_%%CHOCOLA-LBRACE%%_";
55
- const RBRACE_PH = "_%%CHOCOLA-RBRACE%%_";
56
-
57
- export function protectCurlyBraces(html) {
58
- return html
59
- .replace(/&(?:lbrace|#123|#x7B);/gi, LBRACE_PH)
60
- .replace(/&(?:rcub|#125|#x7D);/gi, RBRACE_PH);
61
- }
62
-
63
- export function restoreCurlyBraces(html) {
64
- return html
65
- .replace(/_%%CHOCOLA-LBRACE%%_/g, "{")
66
- .replace(/_%%CHOCOLA-RBRACE%%_/g, "}");
67
- }
68
-
69
- const ID_LETTERS = "abcdefghijklmnopqrstuvwxyz";
70
- const compiledCache = new Map();
71
- export function compileExpression(expr, useCtx) {
72
- const key = useCtx ? `ctx:${expr}` : `raw:${expr}`;
73
- let fn = compiledCache.get(key);
74
- if (!fn) {
75
- fn = useCtx
76
- ? new Function("ctx", `with(ctx) { return (${expr}); }`)
77
- : new Function(`"use strict"; return (${expr})`);
78
- compiledCache.set(key, fn);
79
- }
80
- return fn;
81
- }
1
+ import { createHash } from "crypto";
2
+ import chalk from "./chalk.js";
3
+
4
+ export function throwError(err) {
5
+ console.log(chalk.red.bold("Error!"), "A fatal error has occurred:\n");
6
+ throw new Error(err);
7
+ }
8
+
9
+ export function genRandomId(collection = null, length = 10, lettersOnly = false) {
10
+ let id;
11
+ if (lettersOnly) {
12
+ id = Array.from({ length }, () => ID_LETTERS[Math.floor(Math.random() * ID_LETTERS.length)]).join("");
13
+ } else {
14
+ id = Math.random().toString(36).substring(2, length + 2);
15
+ }
16
+ if (!collection) return id;
17
+ if (collection.includes(id)) {
18
+ return genRandomId(collection, length, lettersOnly);
19
+ } else {
20
+ collection.push(id);
21
+ return id;
22
+ }
23
+ }
24
+
25
+ export function incrementAlfabet(letters) {
26
+ let arr = letters.split("");
27
+ let i = arr.length - 1;
28
+
29
+ while (i >= 0) {
30
+ let pos = arr[i].charCodeAt(0) - 97;
31
+ if (pos < 25) {
32
+ arr[i] = String.fromCharCode(pos + 97 + 1);
33
+ return arr.join("");
34
+ } else {
35
+ arr[i] = "a";
36
+ i--;
37
+ }
38
+ }
39
+
40
+ return "a" + arr.join("");
41
+ }
42
+
43
+ export function deterministicHash(content, length = 8) {
44
+ const hash = createHash("sha256").update(content).digest();
45
+ let result = "";
46
+ for (let i = 0; i < length; i++) {
47
+ result += String.fromCharCode(97 + (hash[i % hash.length] % 26));
48
+ }
49
+ return result;
50
+ }
51
+
52
+ const ID_LETTERS = "abcdefghijklmnopqrstuvwxyz";
package/dev/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import http from "http";
2
2
  import fs from "fs";
3
3
  import path from "path";
4
- import chalk from "chalk";
4
+ import chalk from "../compiler/chalk.js";
5
5
  import compile from "../compiler/index.js";
6
6
  import { loadConfig, resolvePaths } from "../compiler/config.js";
7
7
  import { getChocolaConfig } from "../utils.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chocola",
3
- "version": "2.0.0-next.4",
3
+ "version": "2.0.0-next.6",
4
4
  "description": "The sweetest way to build the web",
5
5
  "keywords": [
6
6
  "web",
@@ -13,11 +13,11 @@
13
13
  ],
14
14
  "homepage": "https://chocolaframework.web.app/",
15
15
  "bugs": {
16
- "url": "https://github.com/sad-gabi/chocola/issues"
16
+ "url": "https://github.com/chocolajs/chocola/issues"
17
17
  },
18
18
  "repository": {
19
19
  "type": "git",
20
- "url": "git+https://github.com/sad-gabi/chocola.git"
20
+ "url": "git+https://github.com/chocolajs/chocola.git"
21
21
  },
22
22
  "license": "MIT",
23
23
  "author": "SadGabi",
@@ -25,7 +25,7 @@
25
25
  "main": "index.js",
26
26
  "funding": {
27
27
  "type": "github",
28
- "url": "https://github.com/sponsors/sad-gabi"
28
+ "url": "https://github.com/sponsors/chocolajs"
29
29
  },
30
30
  "scripts": {
31
31
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -35,11 +35,6 @@
35
35
  "./dev": "./dev/index.js"
36
36
  },
37
37
  "dependencies": {
38
- "chalk": "^5.6.2",
39
- "js-beautify": "^1.15.4",
40
38
  "jsdom": "^27.4.0"
41
- },
42
- "devDependencies": {
43
- "js-beautify": "^1.15.4"
44
39
  }
45
40
  }
@@ -0,0 +1,129 @@
1
+ export function extractPropsDefaults(script) {
2
+ if (!script) return [];
3
+ const propsRegex = /export\s+let\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*(?:=\s*([^;]+))?;/g;
4
+ let props = [];
5
+ let match;
6
+ while ((match = propsRegex.exec(script)) !== null) {
7
+ props.push({ name: match[1].trim(), defaultValue: match[2]?.trim() });
8
+ }
9
+ return props;
10
+ }
11
+
12
+ export function extractRuntime(script, compName) {
13
+ const startRegex = /(?:async\s+)?function\s+\$runtime\(([^)]*)\)\s*\{/;
14
+ const match = script.match(startRegex);
15
+ if (!match) return null;
16
+
17
+ const startIndexBrace = match.index + match[0].length - 1;
18
+ let bracesCount = 0;
19
+ let inString = false;
20
+ let stringChar = null;
21
+
22
+ for (let i = startIndexBrace; i < script.length; i++) {
23
+ const ch = script[i];
24
+
25
+ if (inString) {
26
+ if (ch === '\\') { i++; continue; }
27
+ if (ch === stringChar) inString = false;
28
+ continue;
29
+ }
30
+
31
+ if (ch === "'" || ch === '"' || ch === '`') {
32
+ inString = true;
33
+ stringChar = ch;
34
+ continue;
35
+ }
36
+
37
+ if (ch === '{') {
38
+ bracesCount++;
39
+ } else if (ch === '}') {
40
+ bracesCount--;
41
+ if (bracesCount === 0) {
42
+ return script.substring(match.index, i + 1);
43
+ }
44
+ }
45
+ }
46
+
47
+ throw new Error(`${compName} $runtime function has unclosed curly braces.`);
48
+ }
49
+
50
+ export function extractTopLevelFunctions(script, excludeName) {
51
+ const funcs = [];
52
+ let i = 0;
53
+ let depth = 0;
54
+ let inStr = false;
55
+ let strChar = null;
56
+
57
+ while (i < script.length) {
58
+ const ch = script[i];
59
+
60
+ if (inStr) {
61
+ if (ch === '\\') i++;
62
+ else if (ch === strChar) inStr = false;
63
+ i++;
64
+ continue;
65
+ }
66
+
67
+ if (ch === '"' || ch === "'" || ch === '`') {
68
+ inStr = true;
69
+ strChar = ch;
70
+ i++;
71
+ continue;
72
+ }
73
+
74
+ if (ch === '{') { depth++; i++; continue; }
75
+ if (ch === '}') { depth--; i++; continue; }
76
+
77
+ if (depth === 0) {
78
+ const rest = script.slice(i);
79
+ const m = rest.match(/^(?:async\s+)?function\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*\(/);
80
+ if (m) {
81
+ const name = m[1];
82
+ const pastParen = i + m[0].length;
83
+
84
+ let parenDepth = 1;
85
+ let p = pastParen;
86
+ while (p < script.length && parenDepth > 0) {
87
+ if (script[p] === '(') parenDepth++;
88
+ if (script[p] === ')') parenDepth--;
89
+ p++;
90
+ }
91
+
92
+ while (p < script.length && script[p] !== '{') p++;
93
+ if (p >= script.length) { i++; continue; }
94
+
95
+ let bDepth = 1;
96
+ let bodyStr = false;
97
+ let bodyStrChar = null;
98
+ let end = p + 1;
99
+ while (end < script.length && bDepth > 0) {
100
+ const c = script[end];
101
+ if (bodyStr) {
102
+ if (c === '\\') end++;
103
+ else if (c === bodyStrChar) bodyStr = false;
104
+ end++;
105
+ continue;
106
+ }
107
+ if (c === '"' || c === "'" || c === '`') {
108
+ bodyStr = true;
109
+ bodyStrChar = c;
110
+ end++;
111
+ continue;
112
+ }
113
+ if (c === '{') bDepth++;
114
+ if (c === '}') bDepth--;
115
+ end++;
116
+ }
117
+
118
+ if (name !== excludeName) {
119
+ funcs.push(script.slice(i, end));
120
+ }
121
+ i = end;
122
+ continue;
123
+ }
124
+ }
125
+ i++;
126
+ }
127
+
128
+ return funcs;
129
+ }
@@ -0,0 +1,29 @@
1
+ import { compileExpression } from "./utils.js";
2
+
3
+ export function extractContextFromElement(element) {
4
+ const ctx = {};
5
+ for (const attr of element.attributes) {
6
+ const key = attr.name;
7
+ const val = attr.value;
8
+ if (!val.includes("{")) { ctx[key] = val; continue; }
9
+ const matches = [...val.matchAll(/\{([^}]+)\}/g)];
10
+ if (matches.length === 1 && matches[0][0] === val) {
11
+ try {
12
+ ctx[key] = compileExpression(matches[0][1], false)();
13
+ continue;
14
+ } catch {}
15
+ }
16
+ ctx[key] = val;
17
+ }
18
+ return ctx;
19
+ }
20
+
21
+ export function hasDelIfAttr(el) {
22
+ return el.hasAttribute("del:if");
23
+ }
24
+ export function getDelIfAttr(el) {
25
+ return el.getAttribute("del:if");
26
+ }
27
+ export function removeDelIfAttr(el) {
28
+ el.removeAttribute("del:if");
29
+ }