chocola 2.0.0-next.7 → 2.0.0-next.9

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,5 +1,6 @@
1
1
  import { promises as fs } from "fs";
2
2
  import path from "path";
3
+ import { performance } from "perf_hooks";
3
4
  import chalk from "./chalk.js";
4
5
  import { loadConfig, resolvePaths } from "./config.js";
5
6
  import {
@@ -16,8 +17,8 @@ import {
16
17
  } from "./dom-processor.js";
17
18
  import { processAllComponents } from "./component-processor.js";
18
19
  import { generateRuntimeScript } from "./runtime-generator.js";
19
- import { genRandomId, throwError } from "./utils.js";
20
- import { compileExpression, hasDelIfAttr, getDelIfAttr, removeDelIfAttr } from "../parser/index.js";
20
+ import { genRandomId, throwError, warnConstantCondition, findElementLine } from "./utils.js";
21
+ import { compileExpr, evaluateConstant, hasMountIf, getMountIf, removeMountIf } from "../parser/index.js";
21
22
  import {
22
23
  copyStaticDir,
23
24
  getComponents,
@@ -26,48 +27,47 @@ import {
26
27
  processScript,
27
28
  processStylesheet,
28
29
  } from "./pipeline.js";
30
+ import { text } from "stream/consumers";
29
31
 
30
-
31
- const logSeparation = chalk.yellow(`
32
- ________________________________________________________________________
33
- ========================================================================
34
- `);
32
+ const GOLD_COLOR = "#D87416";
33
+ const WHITE_COLOR = "#FAFAF8";
34
+ const TEXT_FAINT = "#E7EBE1";
35
35
 
36
36
  function logBanner() {
37
- console.log(chalk.bold.hex("#945e33")(`\n RUNNING CHOCOLA BUNDLER`));
38
- console.log(logSeparation);
39
37
  console.log(
40
- chalk.hex("#945e33")(`
41
-
42
-
43
- ▄████▄ ██░ ██ ▒█████ ▄████▄ ▒█████ ██▓ ▄▄▄
44
- ▒██▀ ▀█ ▓██░ ██▒▒██▒ ██▒▒██▀ ▀█ ▒██▒ ██▒▓██▒ ▒████▄
45
- ▒▓█ ▄ ▒██▀▀██░▒██░ ██▒▒▓█ ▄ ▒██░ ██▒▒██░ ▒██ ▀█▄
46
- ▒▓▓▄ ▄██▒░▓█ ░██ ▒██ ██░▒▓▓▄ ▄██▒▒██ ██░▒██░ ░██▄▄▄▄██
47
- ▒ ▓███▀ ░░▓█▒░██▓░ ████▓▒░▒ ▓███▀ ░░ ████▓▒░░██████▒▓█ ▓██▒
48
- ░ ░▒ ▒ ░ ▒ ░░▒░▒░ ▒░▒░▒░ ░ ░▒ ▒ ░░ ▒░▒░▒░ ░ ▒░▓ ░▒▒ ▓▒█░
49
- ░ ▒ ▒ ░▒░ ░ ░ ░ ▒ ▒░ ░ ▒ ░ ░ ▒░ ░ ░ ▒ ░ ░ ▒▒ ░
50
- ░ ░ ░░ ░░ ░ ░ ░ ▒ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ▒
51
- ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
52
- ░ ░
53
-
54
-
55
- `)
38
+ chalk.hex(GOLD_COLOR)(`
39
+ ┌─────────────────────────────────────────────┐
40
+ │┌-------------------------------------------┐│
41
+ ││ ││`)
42
+ );
43
+ console.log(
44
+ chalk.hex(GOLD_COLOR)(` ││ `) +
45
+ chalk.bold.hex(WHITE_COLOR)(`{`) +
46
+ chalk.bold.hex(GOLD_COLOR)(` C H O C O L A `) +
47
+ chalk.bold.hex(WHITE_COLOR)(`}`) +
48
+ chalk.hex(GOLD_COLOR)(` ││\n`) +
49
+ chalk.hex(GOLD_COLOR)(` ││ ││
50
+ ││ `) +
51
+ chalk.hex(TEXT_FAINT)(`THE SWEETEST WAY TO BUILD THE WEB`) +
52
+ chalk.hex(GOLD_COLOR)(` ││
53
+ ││ ││
54
+ │└-------------------------------------------┘│
55
+ └─────────────────────────────────────────────┘
56
+ `)
56
57
  );
57
58
  }
58
59
 
59
- function logSuccess(outDirPath) {
60
- console.log(`
61
- ▄▄ ▄▄▄ ▄▄▄▄ ▄▄▄▄ ▄▄▄ ▄▄ ▄▄ ▄▄▄▄▄ ██
62
- ██ ██▀██ ██▄██ ██▀██ ██▀██ ███▄██ ██▄▄ ██
63
- ▄▄█▀ ▀███▀ ██▄█▀ ████▀ ▀███▀ ██ ▀██ ██▄▄▄ ▄▄
64
-
65
- `);
60
+ function logSuccess(outDirPath, durationMs) {
66
61
  console.log(
67
62
  chalk.bold.green(">"),
68
63
  "Project bundled succesfully at",
69
- chalk.green.underline(outDirPath) + "\n\n"
70
- );
64
+ chalk.green.underline(outDirPath));
65
+ console.log(chalk.bold.green(`\nJOB DONE!`) + chalk.hex(TEXT_FAINT)(` (${formatDuration(durationMs)})\n`));
66
+ }
67
+
68
+ function formatDuration(ms) {
69
+ if (ms >= 1000) return (ms / 1000).toFixed(2) + "s";
70
+ return Math.round(ms) + "ms";
71
71
  }
72
72
 
73
73
  async function setupOutputDirectory(outDirPath, emptyOutDir) {
@@ -81,9 +81,8 @@ async function loadAndDisplayComponents(srcComponentsPath) {
81
81
  const foundComponents = await getComponents(srcComponentsPath);
82
82
  const { loadedComponents, notDefComps: emptyComps, componentsLib } = foundComponents;
83
83
 
84
- console.log(` LOADING COMPONENTS`);
85
84
  console.log(chalk.bold.green(">"), "Components found in", chalk.green.underline(srcComponentsPath) + ":");
86
- console.log(" ", componentsLib, "\n");
85
+ console.log(" ", componentsLib, "\n\n");
87
86
 
88
87
  if (emptyComps?.length > 0) {
89
88
  console.warn(chalk.bold.yellow("WARNING!"), "The following component files are empty:");
@@ -114,39 +113,62 @@ async function processAssets(doc, rootDir, srcDir, outDirPath) {
114
113
  return cssContents
115
114
  }
116
115
 
117
- function processPageConditionals(parent) {
116
+ function processPageConditionals(parent, sourceFile, sourceContent) {
118
117
  const children = [...parent.children];
119
118
  let chainActive = false;
120
119
  let chainRendered = false;
121
120
 
121
+ const getLocation = (child) => {
122
+ if (!sourceContent) return sourceFile;
123
+ const lineNum = findElementLine(sourceContent, child.outerHTML);
124
+ return lineNum !== null ? `${sourceFile}:${lineNum}` : sourceFile;
125
+ };
126
+
127
+ const warnIfConstant = (expr, child, attr) => {
128
+ const { constant, value } = evaluateConstant(expr);
129
+ if (!constant) return;
130
+ warnConstantCondition(getLocation(child), child.tagName.toLowerCase(), attr, Boolean(value));
131
+ };
132
+
122
133
  for (const child of children) {
123
134
  const hasIf = child.hasAttribute("if");
124
- const hasDelIf = hasDelIfAttr(child);
135
+ const hasDelIf = hasMountIf(child);
125
136
  const hasElif = child.hasAttribute("elif");
126
137
  const hasElse = child.hasAttribute("else");
127
138
 
139
+ if (hasIf || hasDelIf || hasElif) {
140
+ const stripBraces = (raw) => raw.startsWith("{") ? raw.slice(1, -1) : raw;
141
+ if (hasIf) warnIfConstant(stripBraces(child.getAttribute("if")), child, "if");
142
+ if (hasDelIf) warnIfConstant(stripBraces(getMountIf(child)), child, "mount:if");
143
+ if (hasElif) warnIfConstant(stripBraces(child.getAttribute("elif")), child, "elif");
144
+ }
145
+
128
146
  if (hasElif || hasElse) {
129
- if (!chainActive) continue;
147
+ if (!chainActive) {
148
+ const tag = child.tagName.toLowerCase();
149
+ const attr = hasElif ? "elif" : "else";
150
+ throwError(`${getLocation(child)}\n <${tag}> has ${attr} without a preceding if/mount:if sibling`);
151
+ }
130
152
  if (chainRendered) { child.remove(); continue; }
131
153
  }
132
154
 
133
155
  if (hasIf) {
134
156
  const raw = child.getAttribute("if");
135
157
  const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
136
- const fn = compileExpression(expr, false);
158
+ const fn = compileExpr(expr, false);
137
159
  const result = fn();
138
160
  chainActive = true;
139
161
  if (result) {
140
162
  chainRendered = true;
141
163
  } else {
142
- child.remove();
164
+ child.style.display = "none";
143
165
  chainRendered = false;
144
166
  }
145
167
  child.removeAttribute("if");
146
168
  } else if (hasDelIf) {
147
- const raw = getDelIfAttr(child);
169
+ const raw = getMountIf(child);
148
170
  const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
149
- const fn = compileExpression(expr, false);
171
+ const fn = compileExpr(expr, false);
150
172
  const result = fn();
151
173
  chainActive = true;
152
174
  if (result) {
@@ -155,11 +177,11 @@ function processPageConditionals(parent) {
155
177
  child.remove();
156
178
  chainRendered = false;
157
179
  }
158
- removeDelIfAttr(child);
180
+ removeMountIf(child);
159
181
  } else if (hasElif) {
160
182
  const raw = child.getAttribute("elif");
161
183
  const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
162
- const fn = compileExpression(expr, false);
184
+ const fn = compileExpr(expr, false);
163
185
  const result = fn();
164
186
  if (result) {
165
187
  chainRendered = true;
@@ -177,18 +199,18 @@ function processPageConditionals(parent) {
177
199
  }
178
200
 
179
201
  if (child.parentNode) {
180
- processPageConditionals(child);
202
+ processPageConditionals(child, sourceFile, sourceContent);
181
203
  }
182
204
  }
183
205
  }
184
206
 
185
207
  export default async function compile(rootDir, buildConfig) {
186
208
  const isHotReload = buildConfig?.isHotReload || null;
209
+ const startTime = performance.now();
187
210
  !isHotReload && logBanner();
188
211
 
189
212
  const config = await loadConfig(rootDir);
190
213
  const paths = resolvePaths(rootDir, config);
191
- !isHotReload && console.log(logSeparation);
192
214
 
193
215
  await setupOutputDirectory(paths.outDir, config.emptyOutDir);
194
216
 
@@ -197,16 +219,13 @@ export default async function compile(rootDir, buildConfig) {
197
219
  const pageSourcePath = indexFiles.srcPath;
198
220
 
199
221
  const loadedComponents = await loadAndDisplayComponents(paths.components);
200
- !isHotReload && console.log(logSeparation);
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);
222
+ !isHotReload && console.log(chalk.bold.green(">"), "Creating Chocola static build in directory", chalk.green.underline(paths.outDir));
204
223
 
205
224
  const dom = createDOM(srcIndexContent);
206
- const doc = dom.window.document;
225
+ const doc = dom.document;
207
226
  const appContainer = validateAppContainer(doc);
208
227
 
209
- processPageConditionals(appContainer);
228
+ processPageConditionals(appContainer, pageSourcePath, dom.protectedContent);
210
229
 
211
230
  const appElements = getAppElements(appContainer);
212
231
  const { runtimeScript, scopesCss, hashMap, csrClasses } = processAllComponents(appElements, loadedComponents, pageSourcePath, srcIndexContent);
@@ -236,8 +255,10 @@ export default async function compile(rootDir, buildConfig) {
236
255
  await fs.mkdir(chocolaDir, { recursive: true });
237
256
  await fs.writeFile(path.join(chocolaDir, "hashes.json"), JSON.stringify(hashMap, null, 2) + "\n");
238
257
 
239
- !isHotReload && logSuccess(paths.outDir);
240
- isHotReload && console.log("Dev server updated");
258
+ const durationMs = performance.now() - startTime;
259
+
260
+ !isHotReload && logSuccess(paths.outDir, durationMs);
261
+ isHotReload && console.log("Dev server updated " + chalk.hex(TEXT_FAINT)(`(${formatDuration(durationMs)})`));
241
262
  }
242
263
 
243
264
  /**
package/compiler/utils.js CHANGED
@@ -6,6 +6,84 @@ export function throwError(err) {
6
6
  throw new Error(err);
7
7
  }
8
8
 
9
+ export function warnConstantCondition(location, tag, attr, truthy) {
10
+ const article = /^[aeiou]/.test(attr) ? "an" : "a";
11
+ console.warn(
12
+ chalk.bold.yellow("WARNING!"),
13
+ `${location}: <${tag}> has ${article} \`${attr}\` condition that is always ${truthy ? "truthy" : "falsy"}`
14
+ );
15
+ }
16
+
17
+ export function warnUnusedDeclaration(location, kind, name) {
18
+ console.warn(
19
+ chalk.bold.yellow("WARNING!"),
20
+ `${location}: ${kind} \`${name}\` is never used`
21
+ );
22
+ }
23
+
24
+ function normalizeAttributeQuotes(html) {
25
+ return html
26
+ .replace(/(\s[\w:.-]+)=(['"])([\s\S]*?)\2/g, '$1="$3"')
27
+ .replace(/(\s[\w:.-]+)=([^\s"'<>=]+)/g, '$1="$2"');
28
+ }
29
+
30
+ function getStartTag(outerHTML) {
31
+ let inQuote = null;
32
+ for (let i = 0; i < outerHTML.length; i++) {
33
+ const c = outerHTML[i];
34
+ if (inQuote) {
35
+ if (c === inQuote) inQuote = null;
36
+ } else if (c === '"' || c === "'") {
37
+ inQuote = c;
38
+ } else if (c === ">") {
39
+ return outerHTML.slice(0, i + 1);
40
+ }
41
+ }
42
+ return outerHTML;
43
+ }
44
+
45
+ function escapeRegex(str) {
46
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
47
+ }
48
+
49
+ function flexibleStartTagRegex(startTag) {
50
+ return new RegExp(
51
+ startTag
52
+ .split(/\s+/)
53
+ .filter(Boolean)
54
+ .map(escapeRegex)
55
+ .join("\\s+")
56
+ );
57
+ }
58
+
59
+ export function findElementLine(sourceContent, outerHTML) {
60
+ const source = normalizeAttributeQuotes(sourceContent).toLowerCase().replace(/\/\s*>/g, ">");
61
+ const startTags = [
62
+ normalizeAttributeQuotes(getStartTag(outerHTML)),
63
+ normalizeAttributeQuotes(
64
+ getStartTag(
65
+ outerHTML
66
+ .replace(/&quot;/g, '"')
67
+ .replace(/&amp;/g, "&")
68
+ .replace(/&lt;/g, "<")
69
+ .replace(/&gt;/g, ">")
70
+ )
71
+ ),
72
+ ].map(tag => tag.toLowerCase());
73
+ const candidates = [
74
+ flexibleStartTagRegex(startTags[0]),
75
+ flexibleStartTagRegex(startTags[0].replace(/=""/g, "")),
76
+ flexibleStartTagRegex(startTags[1]),
77
+ ];
78
+ for (const re of candidates) {
79
+ const match = re.exec(source);
80
+ if (match) {
81
+ return source.substring(0, match.index).split("\n").length;
82
+ }
83
+ }
84
+ return null;
85
+ }
86
+
9
87
  export function genRandomId(collection = null, length = 10, lettersOnly = false) {
10
88
  let id;
11
89
  if (lettersOnly) {
package/dev/index.js CHANGED
@@ -4,7 +4,7 @@ import path from "path";
4
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
- import { getChocolaConfig } from "../utils.js";
7
+ import { getConfig } from "../utils.js";
8
8
 
9
9
  export async function serve(__rootdir) {
10
10
  await compile(__rootdir);
@@ -17,7 +17,7 @@ export async function serve(__rootdir) {
17
17
 
18
18
  let lastBuildTime = Date.now();
19
19
 
20
- const fullConfig = await getChocolaConfig(__rootdir);
20
+ const fullConfig = await getConfig(__rootdir);
21
21
  const config = await loadConfig(__rootdir);
22
22
  const paths = resolvePaths(__rootdir, config);
23
23
  const devConfig = fullConfig.dev;
@@ -144,7 +144,7 @@ export async function serve(__rootdir) {
144
144
  });
145
145
 
146
146
  server.listen(__config.port, __config.hostname, () => {
147
- console.log(`Chocola App running at http://${__config.hostname}:${__config.port}/`);
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.7",
3
+ "version": "2.0.0-next.9",
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
- "jsdom": "^27.4.0"
38
+ "linkedom": "^0.18.13"
39
39
  }
40
40
  }
@@ -44,7 +44,146 @@ export function extractRuntime(script, compName) {
44
44
  }
45
45
  }
46
46
 
47
- throw new Error(`${compName} $runtime function has unclosed curly braces.`);
47
+ throw new Error(`${compName} $runtime function has unclosed curly braces`);
48
+ }
49
+
50
+ export function extractTopLevelVariables(script) {
51
+ if (!script) return [];
52
+ const vars = [];
53
+ let i = 0;
54
+ let depth = 0;
55
+ let parenDepth = 0;
56
+ let bracketDepth = 0;
57
+ let inStr = false;
58
+ let strChar = null;
59
+
60
+ while (i < script.length) {
61
+ const ch = script[i];
62
+
63
+ if (inStr) {
64
+ if (ch === "\\") i++;
65
+ else if (ch === strChar) inStr = false;
66
+ i++;
67
+ continue;
68
+ }
69
+
70
+ if (ch === '"' || ch === "'" || ch === "`") {
71
+ inStr = true;
72
+ strChar = ch;
73
+ i++;
74
+ continue;
75
+ }
76
+
77
+ if (ch === "/" && script[i + 1] === "/") {
78
+ while (i < script.length && script[i] !== "\n") i++;
79
+ continue;
80
+ }
81
+ if (ch === "/" && script[i + 1] === "*") {
82
+ i += 2;
83
+ while (i < script.length && !(script[i] === "*" && script[i + 1] === "/")) i++;
84
+ i += 2;
85
+ continue;
86
+ }
87
+
88
+ if (ch === "{") { depth++; i++; continue; }
89
+ if (ch === "}") { depth--; i++; continue; }
90
+ if (ch === "(") { parenDepth++; i++; continue; }
91
+ if (ch === ")") { parenDepth--; i++; continue; }
92
+ if (ch === "[") { bracketDepth++; i++; continue; }
93
+ if (ch === "]") { bracketDepth--; i++; continue; }
94
+
95
+ if (depth === 0 && parenDepth === 0 && bracketDepth === 0) {
96
+ const rest = script.slice(i);
97
+ const m = rest.match(/^(let|const)\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*/);
98
+ if (m) {
99
+ const keyword = m[1];
100
+ const before = script.slice(0, i).trimEnd();
101
+ if (!before.endsWith("export")) {
102
+ let name = m[2];
103
+ let p = i + m[0].length;
104
+ while (p < script.length && /\s/.test(script[p])) p++;
105
+ i = p;
106
+
107
+ while (p < script.length) {
108
+ i = p;
109
+ let q = p;
110
+
111
+ if (script[q] === "=") {
112
+ q++;
113
+ while (q < script.length && /\s/.test(script[q])) q++;
114
+ const delim = findDeclaratorEnd(script, q);
115
+ if (name !== "self" && name !== "ctx") {
116
+ vars.push({ keyword, name, value: script.slice(q, delim).trim() });
117
+ }
118
+ if (script[delim] === ";") { i = delim + 1; break; }
119
+ p = delim + 1;
120
+ while (p < script.length && /\s/.test(script[p])) p++;
121
+ } else {
122
+ if (name !== "self" && name !== "ctx") {
123
+ vars.push({ keyword, name, value: undefined });
124
+ }
125
+ if (script[q] === ";") { i = q + 1; break; }
126
+ if (script[q] !== ",") { i = q; break; }
127
+ p = q + 1;
128
+ while (p < script.length && /\s/.test(script[p])) p++;
129
+ }
130
+
131
+ const nameMatch = script.slice(p).match(/^([a-zA-Z_$][0-9a-zA-Z_$]*)\s*/);
132
+ if (!nameMatch) { i = p; break; }
133
+ name = nameMatch[1];
134
+ p += nameMatch[0].length;
135
+ }
136
+ continue;
137
+ }
138
+ }
139
+ }
140
+ i++;
141
+ }
142
+
143
+ return vars;
144
+ }
145
+
146
+ function findDeclaratorEnd(script, start) {
147
+ let i = start;
148
+ let parens = 0;
149
+ let brackets = 0;
150
+ let braces = 0;
151
+ let inStr = false;
152
+ let strChar = null;
153
+
154
+ while (i < script.length) {
155
+ const c = script[i];
156
+
157
+ if (inStr) {
158
+ if (c === "\\") i++;
159
+ else if (c === strChar) inStr = false;
160
+ i++;
161
+ continue;
162
+ }
163
+
164
+ if (c === "/" && script[i + 1] === "/") {
165
+ while (i < script.length && script[i] !== "\n") i++;
166
+ continue;
167
+ }
168
+ if (c === "/" && script[i + 1] === "*") {
169
+ i += 2;
170
+ while (i < script.length && !(script[i] === "*" && script[i + 1] === "/")) i++;
171
+ i += 2;
172
+ continue;
173
+ }
174
+
175
+ if (c === '"' || c === "'" || c === "`") { inStr = true; strChar = c; i++; continue; }
176
+ if (c === "(") { parens++; i++; continue; }
177
+ if (c === ")") { parens--; i++; continue; }
178
+ if (c === "[") { brackets++; i++; continue; }
179
+ if (c === "]") { brackets--; i++; continue; }
180
+ if (c === "{") { braces++; i++; continue; }
181
+ if (c === "}") { braces--; i++; continue; }
182
+ if ((c === ";" || c === ",") && parens === 0 && brackets === 0 && braces === 0) return i;
183
+ i++;
184
+ }
185
+
186
+ return i;
48
187
  }
49
188
 
50
189
  export function extractTopLevelFunctions(script, excludeName) {
package/parser/context.js CHANGED
@@ -1,15 +1,16 @@
1
- import { compileExpression } from "./utils.js";
1
+ import { compileExpr } from "./utils.js";
2
+ import { restoreTemplateChars } from "../utils.js";
2
3
 
3
- export function extractContextFromElement(element) {
4
+ export function extractCtxFromEl(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) {
11
12
  try {
12
- ctx[key] = compileExpression(matches[0][1], false)();
13
+ ctx[key] = compileExpr(matches[0][1], false)();
13
14
  continue;
14
15
  } catch {}
15
16
  }
@@ -18,12 +19,12 @@ export function extractContextFromElement(element) {
18
19
  return ctx;
19
20
  }
20
21
 
21
- export function hasDelIfAttr(el) {
22
- return el.hasAttribute("del:if");
22
+ export function hasMountIf(el) {
23
+ return el.hasAttribute("mount:if");
23
24
  }
24
- export function getDelIfAttr(el) {
25
- return el.getAttribute("del:if");
25
+ export function getMountIf(el) {
26
+ return el.getAttribute("mount:if");
26
27
  }
27
- export function removeDelIfAttr(el) {
28
- el.removeAttribute("del:if");
28
+ export function removeMountIf(el) {
29
+ el.removeAttribute("mount:if");
29
30
  }
package/parser/css.js CHANGED
@@ -38,7 +38,7 @@ export function scopeCss(cssString, cssId) {
38
38
  }
39
39
  }
40
40
 
41
- function findMatchingBrace(s, openIndex) {
41
+ function findClosure(s, openIndex) {
42
42
  let depth = 0;
43
43
  for (let i = openIndex; i < s.length; i++) {
44
44
  const ch = s[i];
@@ -65,7 +65,7 @@ export function scopeCss(cssString, cssId) {
65
65
  break;
66
66
  }
67
67
  const header = str.substring(i, braceIndex);
68
- const endBrace = findMatchingBrace(str, braceIndex);
68
+ const endBrace = findClosure(str, braceIndex);
69
69
  const inner = str.substring(braceIndex + 1, endBrace);
70
70
 
71
71
  if (header.trim().startsWith("@")) {
package/parser/index.js CHANGED
@@ -1,5 +1,5 @@
1
- export { extractPropsDefaults, extractRuntime, extractTopLevelFunctions } from "./component.js";
2
- export { extractContextFromElement, hasDelIfAttr, getDelIfAttr, removeDelIfAttr } from "./context.js";
1
+ export { extractPropsDefaults, extractRuntime, extractTopLevelFunctions, extractTopLevelVariables } from "./component.js";
2
+ export { extractCtxFromEl, hasMountIf, getMountIf, removeMountIf } from "./context.js";
3
3
  export { reservedAttrs, getLineNumber, validateChainStructure, applyConditionalToElement, interpolateNode } from "./template.js";
4
4
  export { scopeCss } from "./css.js";
5
- export { compileExpression } from "./utils.js";
5
+ export { compileExpr, evaluateConstant } from "./utils.js";
@@ -1,16 +1,16 @@
1
- import { compileExpression } from "./utils.js";
2
- import { hasDelIfAttr, getDelIfAttr, removeDelIfAttr } from "./context.js";
1
+ import { compileExpr } from "./utils.js";
2
+ import { hasMountIf, getMountIf, removeMountIf } from "./context.js";
3
3
 
4
- export const reservedAttrs = ["if", "del:if", "elif", "else"];
4
+ export const reservedAttrs = ["if", "mount:if", "elif", "else"];
5
5
 
6
- export function getLineNumber(sourceContent, parentContent, idxInParent) {
7
- if (sourceContent === parentContent) {
6
+ export function getLineNumber(srcContent, parentContent, idxInParent) {
7
+ if (srcContent === parentContent) {
8
8
  return parentContent.substring(0, idxInParent).split("\n").length;
9
9
  }
10
- const templateStartMatch = sourceContent.match(/<template[^>]*>/);
10
+ const templateStartMatch = srcContent.match(/<template[^>]*>/);
11
11
  if (templateStartMatch) {
12
12
  const contentStart = templateStartMatch.index + templateStartMatch[0].length;
13
- const beforeContent = sourceContent.substring(0, contentStart);
13
+ const beforeContent = srcContent.substring(0, contentStart);
14
14
  const linesInBefore = beforeContent.split("\n").length;
15
15
  const linesInTemplate = parentContent.substring(0, idxInParent).split("\n").length;
16
16
  return linesInBefore + linesInTemplate - 1;
@@ -24,7 +24,7 @@ export function validateChainStructure(parent, sourceFile, sourceContent, parent
24
24
 
25
25
  for (const child of children) {
26
26
  const hasIf = child.hasAttribute("if");
27
- const hasDelIf = hasDelIfAttr(child);
27
+ const hasDelIf = hasMountIf(child);
28
28
  const hasElif = child.hasAttribute("elif");
29
29
  const hasElse = child.hasAttribute("else");
30
30
 
@@ -40,7 +40,7 @@ export function validateChainStructure(parent, sourceFile, sourceContent, parent
40
40
  if (lineNum !== null) loc = `${sourceFile}:${lineNum}`;
41
41
  }
42
42
  }
43
- throw new Error(`${loc}\n <${tag}> has ${attr} without a preceding if/del-if sibling`);
43
+ throw new Error(`${loc}\n <${tag}> has ${attr} without a preceding if/mount:if sibling`);
44
44
  }
45
45
  if (hasElse) {
46
46
  chainActive = false;
@@ -60,7 +60,7 @@ export function validateChainStructure(parent, sourceFile, sourceContent, parent
60
60
  export function applyConditionalToElement(child, ctxProxy, condChain, hasIf, hasDelIf, hasElif, hasElse) {
61
61
  if (hasIf) {
62
62
  const expr = child.getAttribute("if").slice(1, -1);
63
- const fn = compileExpression(expr, true);
63
+ const fn = compileExpr(expr, true);
64
64
  condChain.active = true;
65
65
  if (fn(ctxProxy)) {
66
66
  condChain.rendered = true;
@@ -70,8 +70,8 @@ export function applyConditionalToElement(child, ctxProxy, condChain, hasIf, has
70
70
  }
71
71
  child.removeAttribute("if");
72
72
  } else if (hasDelIf) {
73
- const expr = getDelIfAttr(child).slice(1, -1);
74
- const fn = compileExpression(expr, true);
73
+ const expr = getMountIf(child).slice(1, -1);
74
+ const fn = compileExpr(expr, true);
75
75
  condChain.active = true;
76
76
  if (fn(ctxProxy)) {
77
77
  condChain.rendered = true;
@@ -79,10 +79,10 @@ export function applyConditionalToElement(child, ctxProxy, condChain, hasIf, has
79
79
  child.remove();
80
80
  condChain.rendered = false;
81
81
  }
82
- removeDelIfAttr(child);
82
+ removeMountIf(child);
83
83
  } else if (hasElif) {
84
84
  const expr = child.getAttribute("elif").slice(1, -1);
85
- const fn = compileExpression(expr, true);
85
+ const fn = compileExpr(expr, true);
86
86
  if (fn(ctxProxy)) {
87
87
  condChain.rendered = true;
88
88
  } else {
@@ -106,7 +106,7 @@ export function interpolateNode(root, ctxProxy) {
106
106
  if (node.nodeType === 3) {
107
107
  node.textContent = node.textContent.replace(/\{([^}]+)\}/g, (_, expr) => {
108
108
  try {
109
- return compileExpression(expr, true)(ctxProxy);
109
+ return compileExpr(expr, true)(ctxProxy);
110
110
  } catch {
111
111
  return "";
112
112
  }