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/README.md CHANGED
@@ -2,31 +2,35 @@
2
2
 
3
3
  Chocola is a JavaScript library for creating web user interfaces.
4
4
 
5
- `chocola` only contains the functionality to build and serve web static builds. For production you may want to implement `chocola` to your workflow with other frameworks as Vite until official workflows are provided.
5
+ `chocola` only contains the functionality to build and serve web static builds. For production you may want to implement `chocola` to your workflow with other frameworks until official workflows are provided.
6
6
 
7
7
  ## Usage
8
8
 
9
9
  ```html
10
- <!-- MyTitle.html -->
10
+ <!-- Greeting.html -->
11
11
  <script>
12
- let self = new HTMLElement;
12
+ let self = new HTMLDivElement;
13
+ let input = new HTMLInputElement;
13
14
 
14
- export let title = "Default title";
15
+ export let title = "Hello";
15
16
 
16
17
  function $runtime() {
17
- console.log("Component mounted:", self)
18
+ input.focus();
18
19
  }
19
20
  </script>
20
21
 
21
22
  <template>
22
- <h1>{title}</h1>
23
+ <div>
24
+ <h1>{title}</h1>
25
+ <input bind:self="input" type="text" placeholder="Your name">
26
+ </div>
23
27
  </template>
24
28
 
25
29
  <style>
26
- button { color: chocolate; }
30
+ h1 { color: chocolate; }
27
31
  </style>
28
32
  ```
29
33
 
30
34
  ## Documentation
31
35
 
32
- See https://github.com/sad-gabi/chocola/wiki
36
+ See https://github.com/chocolajs/chocola/wiki/Overview
@@ -0,0 +1,32 @@
1
+ const ansi = {
2
+ bold: "\x1b[1m",
3
+ underline: "\x1b[4m",
4
+ red: "\x1b[31m",
5
+ green: "\x1b[32m",
6
+ yellow: "\x1b[33m",
7
+ };
8
+
9
+ const reset = "\x1b[0m";
10
+
11
+ function hexToAnsi(h) {
12
+ const r = parseInt(h.slice(1, 3), 16);
13
+ const g = parseInt(h.slice(3, 5), 16);
14
+ const b = parseInt(h.slice(5, 7), 16);
15
+ return `\x1b[38;2;${r};${g};${b}m`;
16
+ }
17
+
18
+ function makeChalk(stack) {
19
+ const fn = (s) => {
20
+ if (stack.length === 0) return s;
21
+ return stack.map(n => ansi[n] || hexToAnsi(n)).join("") + s + reset;
22
+ };
23
+ return new Proxy(fn, {
24
+ get(_, prop) {
25
+ if (prop === "hex") return (h) => makeChalk([...stack, h]);
26
+ return makeChalk([...stack, prop]);
27
+ },
28
+ });
29
+ }
30
+
31
+ const chalk = makeChalk([]);
32
+ export default chalk;
@@ -1,219 +1,42 @@
1
1
  import { JSDOM } from "jsdom";
2
- import { extractContextFromElement } from "./dom-processor.js";
3
- import { genRandomId, incrementAlfabet, throwError, protectCurlyBraces, compileExpression } from "./utils.js";
4
- import chalk from "chalk";
5
- import beautify from "js-beautify";
6
-
7
- function hasCombinator(sel) {
8
- let depth = 0;
9
- for (const ch of sel) {
10
- if (ch === '[') depth++;
11
- else if (ch === ']') depth--;
12
- else if (depth === 0 && (ch === ' ' || ch === '>' || ch === '+' || ch === '~')) return true;
2
+ import { protectCurlyBraces } from "../utils.js";
3
+ import { genRandomId, incrementAlfabet, throwError, deterministicHash } from "./utils.js";
4
+ import {
5
+ extractPropsDefaults, extractRuntime, extractTopLevelFunctions,
6
+ extractContextFromElement, hasDelIfAttr, getDelIfAttr, removeDelIfAttr,
7
+ reservedAttrs, validateChainStructure, applyConditionalToElement, interpolateNode,
8
+ scopeCss, compileExpression,
9
+ } from "../parser/index.js";
10
+ import chalk from "./chalk.js";
11
+
12
+
13
+
14
+ class ProcessContext {
15
+ constructor(loadedComponents, runtimeChunks, compIdColl, letterState, runtimeMap, cssScopes, cssScopesMap, scopedStyles, staticCtxRegistry) {
16
+ this.loadedComponents = loadedComponents;
17
+ this.runtimeChunks = runtimeChunks;
18
+ this.compIdColl = compIdColl;
19
+ this.letterState = letterState;
20
+ this.runtimeMap = runtimeMap;
21
+ this.cssScopes = cssScopes;
22
+ this.cssScopesMap = cssScopesMap;
23
+ this.scopedStyles = scopedStyles;
24
+ this.staticCtxRegistry = staticCtxRegistry;
13
25
  }
14
- return false;
15
26
  }
16
27
 
17
- function scopeSelector(sel, cssId) {
18
- const s = sel.trim();
19
- if (s.startsWith("." + cssId)) return s;
20
- if (hasCombinator(s)) return `.${cssId} ${s}`;
21
- return `.${cssId}${s}, .${cssId} ${s}`;
22
- }
23
-
24
- function scopeCss(cssString, cssId) {
25
- const keyframeRegex = /@(-webkit-)?keyframes\s+(\S+)/gi;
26
- const animNames = new Set();
27
- let match;
28
- while ((match = keyframeRegex.exec(cssString))) {
29
- animNames.add(match[2]);
30
- }
31
28
 
32
- if (animNames.size > 0) {
33
- const sorted = [...animNames].sort((a, b) => b.length - a.length);
34
- const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
35
- for (const name of sorted) {
36
- cssString = cssString.replace(
37
- new RegExp(`(@(?:-webkit-)?keyframes\\s+)${esc(name)}`, "gi"),
38
- `$1${name}-${cssId}`
39
- );
40
- cssString = cssString.replace(
41
- new RegExp(`((?:animation|animation-name)\\s*:\\s*[^;{]*?)\\b${esc(name)}\\b`, "gi"),
42
- `$1${name}-${cssId}`
43
- );
44
- }
45
- }
46
-
47
- function findMatchingBrace(s, openIndex) {
48
- let depth = 0;
49
- for (let i = openIndex; i < s.length; i++) {
50
- const ch = s[i];
51
- if (ch === '{') depth++;
52
- else if (ch === '}') {
53
- depth--;
54
- if (depth === 0) return i;
55
- }
56
- }
57
- return s.length - 1;
58
- }
59
-
60
- const scopeInnerAtRules = new Set(["@media", "@supports", "@container", "@layer"]);
61
-
62
- function processBlock(str) {
63
- str = str.replaceAll(":root", `.${cssId}`);
64
-
65
- let out = "";
66
- let i = 0;
67
- while (i < str.length) {
68
- const braceIndex = str.indexOf('{', i);
69
- if (braceIndex === -1) {
70
- out += str.slice(i);
71
- break;
72
- }
73
- const header = str.substring(i, braceIndex);
74
- const endBrace = findMatchingBrace(str, braceIndex);
75
- const inner = str.substring(braceIndex + 1, endBrace);
76
-
77
- if (header.trim().startsWith("@")) {
78
- const atKeyword = header.trim().split(/\s+/)[0].toLowerCase();
79
- if (scopeInnerAtRules.has(atKeyword)) {
80
- out += header + "{" + processBlock(inner) + "}";
81
- } else {
82
- out += header + "{" + inner + "}";
83
- }
84
- } else {
85
- const innerScoped = processBlock(inner);
86
- const selectors = header
87
- .split(",")
88
- .map(s => s.trim())
89
- .filter(Boolean);
90
- const scopedHeader = selectors.length > 0
91
- ? selectors.map(sel => scopeSelector(sel, cssId)).join(", ")
92
- : header;
93
- out += scopedHeader + "{" + innerScoped + "}";
94
- }
95
-
96
- i = endBrace + 1;
97
- }
98
- return out;
99
- }
100
-
101
- return processBlock(cssString);
102
- }
103
-
104
- function hasDelIfAttr(el) {
105
- return el.hasAttribute("del:if");
106
- }
107
- function getDelIfAttr(el) {
108
- return el.getAttribute("del:if");
109
- }
110
- function removeDelIfAttr(el) {
111
- el.removeAttribute("del:if");
112
- }
113
-
114
- function extractPropsDefaults(script) {
115
- if (!script) return [];
116
- const propsRegex = /export\s+let\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*(?:=\s*([^;]+))?;/g;
117
- let props = [];
118
- let match;
119
- while ((match = propsRegex.exec(script)) !== null) {
120
- props.push({ name: match[1].trim(), defaultValue: match[2]?.trim() });
121
- }
122
- return props;
123
- }
124
-
125
- function interpolateNode(root, ctxProxy) {
126
- const stack = [root];
127
- while (stack.length) {
128
- const node = stack.pop();
129
- if (node.nodeType === 3) {
130
- node.textContent = node.textContent.replace(/\{([^}]+)\}/g, (_, expr) => {
131
- try {
132
- return compileExpression(expr, true)(ctxProxy);
133
- } catch {
134
- return "";
135
- }
136
- });
137
- } else {
138
- for (let i = node.childNodes.length - 1; i >= 0; i--) {
139
- stack.push(node.childNodes[i]);
140
- }
141
- }
142
- }
143
- }
144
-
145
- function getLineNumber(sourceContent, parentContent, idxInParent) {
146
- if (sourceContent === parentContent) {
147
- return parentContent.substring(0, idxInParent).split("\n").length;
148
- }
149
- const templateStartMatch = sourceContent.match(/<template[^>]*>/);
150
- if (templateStartMatch) {
151
- const contentStart = templateStartMatch.index + templateStartMatch[0].length;
152
- const beforeContent = sourceContent.substring(0, contentStart);
153
- const linesInBefore = beforeContent.split("\n").length;
154
- const linesInTemplate = parentContent.substring(0, idxInParent).split("\n").length;
155
- return linesInBefore + linesInTemplate - 1;
156
- }
157
- return null;
158
- }
159
-
160
- function validateChainStructure(parent, sourceFile, sourceContent, parentContent) {
161
- const children = [...parent.children];
162
- let chainActive = false;
163
-
164
- for (const child of children) {
165
- const hasIf = child.hasAttribute("if");
166
- const hasDelIf = hasDelIfAttr(child);
167
- const hasElif = child.hasAttribute("elif");
168
- const hasElse = child.hasAttribute("else");
169
-
170
- if (hasElif || hasElse) {
171
- if (!chainActive) {
172
- const tag = child.tagName.toLowerCase();
173
- const attr = hasElif ? "elif" : "else";
174
- let loc = sourceFile;
175
- if (sourceContent && parentContent) {
176
- const idx = parentContent.indexOf(child.outerHTML);
177
- if (idx !== -1) {
178
- const lineNum = getLineNumber(sourceContent, parentContent, idx);
179
- if (lineNum !== null) loc = `${sourceFile}:${lineNum}`;
180
- }
181
- }
182
- throwError(`${loc}\n <${tag}> has ${attr} without a preceding if/del-if sibling`);
183
- }
184
- if (hasElse) {
185
- chainActive = false;
186
- }
187
- }
188
-
189
- if (hasIf || hasDelIf) {
190
- chainActive = true;
191
- } else if (!hasElif && !hasElse) {
192
- chainActive = false;
193
- }
194
-
195
- validateChainStructure(child, sourceFile, sourceContent, parentContent);
196
- }
197
- }
198
29
 
199
30
  export function processComponentElement(
200
31
  element,
201
- loadedComponents,
202
- runtimeChunks,
203
- compIdColl,
204
- letterState,
205
- runtimeMap,
206
- cssScopes,
207
- cssScopesMap,
208
- scopedStyles,
32
+ cx,
209
33
  renderChain = [],
210
- staticCtxRegistry,
211
34
  sourceFile,
212
35
  sourceContent
213
36
  ) {
214
37
  const tagName = element.tagName.toLowerCase();
215
38
  const compName = tagName + ".html";
216
- let instance = loadedComponents.get(compName);
39
+ let instance = cx.loadedComponents.get(compName);
217
40
 
218
41
  if (!instance || instance === undefined) return false;
219
42
  if (renderChain && renderChain.includes(compName)) return false;
@@ -233,11 +56,11 @@ export function processComponentElement(
233
56
  const compProps = extractPropsDefaults(script);
234
57
 
235
58
  let ctx;
236
- if (staticCtxRegistry && staticCtxRegistry.has(element)) {
237
- ctx = staticCtxRegistry.get(element);
59
+ if (cx.staticCtxRegistry && cx.staticCtxRegistry.has(element)) {
60
+ ctx = cx.staticCtxRegistry.get(element);
238
61
  } else {
239
62
  ctx = extractContextFromElement(element);
240
- staticCtxRegistry && staticCtxRegistry.set(element, ctx);
63
+ cx.staticCtxRegistry && cx.staticCtxRegistry.set(element, ctx);
241
64
  }
242
65
 
243
66
  if (compProps.length > 0) {
@@ -252,6 +75,15 @@ export function processComponentElement(
252
75
  });
253
76
  }
254
77
 
78
+ const topFuncSrc = extractTopLevelFunctions(script || "", RUNTIME_KW);
79
+ for (const src of topFuncSrc) {
80
+ try {
81
+ const fn = (0, eval)("(" + src + ")");
82
+ const name = fn.name;
83
+ if (name && !(name in ctx)) ctx[name] = fn;
84
+ } catch {}
85
+ }
86
+
255
87
  const elInnerHtml = element.innerHTML;
256
88
 
257
89
  const ctxProxy = new Proxy(ctx, {
@@ -275,6 +107,9 @@ export function processComponentElement(
275
107
  parent: el.parentNode
276
108
  }));
277
109
  const condChains = new Map();
110
+ const bindings = [];
111
+ const elBindIds = new Map();
112
+ let bindCounter = 0;
278
113
 
279
114
  childEntries.forEach(({ el: child, parent }) => {
280
115
  if (!condChains.has(parent)) {
@@ -335,11 +170,24 @@ export function processComponentElement(
335
170
  return;
336
171
  }
337
172
 
338
- const reservedAttrs = ["if", "del:if", "elif", "else"];
339
-
340
173
  Array.from(child.attributes).forEach(attribute => {
341
174
  if (!attribute || attribute === undefined) return;
342
175
  if (reservedAttrs.includes(attribute.name)) return;
176
+
177
+ if (attribute.name.startsWith("bind:")) {
178
+ const prop = attribute.name.slice(5);
179
+ const varName = attribute.value;
180
+ let bindId = elBindIds.get(child);
181
+ if (!bindId) {
182
+ bindId = "b" + (bindCounter++);
183
+ elBindIds.set(child, bindId);
184
+ child.setAttribute("data-chbind-" + bindId, "");
185
+ }
186
+ bindings.push({ prop, varName, bindId });
187
+ child.removeAttribute(attribute.name);
188
+ return;
189
+ }
190
+
343
191
  attribute.value = attribute.value.replace(
344
192
  /\{([^}]+)\}/g,
345
193
  (_, expr) => {
@@ -354,59 +202,13 @@ export function processComponentElement(
354
202
 
355
203
  processComponentElement(
356
204
  child,
357
- loadedComponents,
358
- runtimeChunks,
359
- compIdColl,
360
- letterState,
361
- runtimeMap,
362
- cssScopes,
363
- cssScopesMap,
364
- scopedStyles,
205
+ cx,
365
206
  renderChain.concat(compName),
366
- staticCtxRegistry,
367
207
  compName,
368
208
  template
369
209
  );
370
210
 
371
- if (hasIf) {
372
- const expr = child.getAttribute("if").slice(1, -1);
373
- const fn = compileExpression(expr, true);
374
- condChain.active = true;
375
- if (fn(ctxProxy)) {
376
- condChain.rendered = true;
377
- } else {
378
- child.style.display = "none";
379
- condChain.rendered = false;
380
- }
381
- child.removeAttribute("if");
382
- } else if (hasDelIf) {
383
- const expr = getDelIfAttr(child).slice(1, -1);
384
- const fn = compileExpression(expr, true);
385
- condChain.active = true;
386
- if (fn(ctxProxy)) {
387
- condChain.rendered = true;
388
- } else {
389
- child.remove();
390
- condChain.rendered = false;
391
- }
392
- removeDelIfAttr(child);
393
- } else if (hasElif) {
394
- const expr = child.getAttribute("elif").slice(1, -1);
395
- const fn = compileExpression(expr, true);
396
- if (fn(ctxProxy)) {
397
- condChain.rendered = true;
398
- } else {
399
- child.remove();
400
- }
401
- child.removeAttribute("elif");
402
- } else if (hasElse) {
403
- condChain.rendered = true;
404
- condChain.active = false;
405
- child.removeAttribute("else");
406
- } else {
407
- condChain.active = false;
408
- condChain.rendered = false;
409
- }
211
+ applyConditionalToElement(child, ctxProxy, condChain, hasIf, hasDelIf, hasElif, hasElse);
410
212
 
411
213
  interpolateNode(child, ctxProxy)
412
214
  });
@@ -415,7 +217,7 @@ export function processComponentElement(
415
217
 
416
218
  if (firstChild && firstChild.nodeType === 1) {
417
219
  if (script) {
418
- const compId = "chid-" + genRandomId(compIdColl);
220
+ const compId = "chid-" + genRandomId(cx.compIdColl);
419
221
  firstChild.setAttribute("chid", compId);
420
222
 
421
223
  const ctxRegex = /ctx\s*=\s*({.*?})/;
@@ -428,91 +230,70 @@ export function processComponentElement(
428
230
  runtimeCtx = {};
429
231
  }
430
232
  }
431
- let ctxDef = "";
233
+ const ctxDefParts = [];
234
+ const declared = new Set();
432
235
  for (const [key, value] of Object.entries(runtimeCtx)) {
433
- ctxDef += `ctx.${key} = ctx.${key}||${JSON.stringify(value)};\n`;
236
+ ctxDefParts.push(`let ${key} = ctx.${key}||${JSON.stringify(value)};\n`);
237
+ declared.add(key);
434
238
  }
435
239
 
436
240
  for (const { name, defaultValue } of compProps) {
241
+ if (declared.has(name)) continue;
437
242
  if (defaultValue !== undefined) {
438
- ctxDef += `ctx.${name} = ctx.${name}||${defaultValue};\n`;
243
+ ctxDefParts.push(`let ${name} = ctx.${name}||${defaultValue};\n`);
244
+ } else {
245
+ ctxDefParts.push(`let ${name} = ctx.${name};\n`);
439
246
  }
440
247
  }
441
248
 
442
- script = script.replace(ctxRegex, "ctx");
443
-
444
- function extractRuntime(script) {
445
- const startRegex = /(?:async\s+)?function\s+\$runtime\(([^)]*)\)\s*\{/;
446
- const match = script.match(startRegex);
447
-
448
- if (!match) return null;
449
-
450
- const startIndexBrace = match.index + match[0].length - 1;
451
-
452
- let bracesCount = 0;
453
- let closingBraceIndex = -1;
454
-
455
- for (let i = startIndexBrace; i < script.length; i++) {
456
- if (script[i] === "{") {
457
- bracesCount++;
458
- } else if (script[i] === "}") {
459
- bracesCount--;
460
- if (bracesCount === 0) {
461
- closingBraceIndex = i;
462
- break;
463
- }
464
- }
465
- }
466
-
467
- if (closingBraceIndex === -1) {
468
- throw new Error(`${compName} $runtime function has unclosed curly braces.`);
469
- }
470
-
471
- const fullMatch = script.substring(match.index, closingBraceIndex + 1);
249
+ const ctxDef = ctxDefParts.join("");
472
250
 
473
- return fullMatch;
474
- }
251
+ script = script.replace(ctxRegex, "ctx");
475
252
 
476
- let runtime = extractRuntime(script);
253
+ let runtime = extractRuntime(script, compName);
477
254
 
478
255
  if (runtime) {
479
- for (const { name } of compProps) {
480
- runtime = runtime.replace(name, `ctx.${name}`);
481
- }
482
-
483
- runtime = runtime.replace(/\$runtime\([^)]*\)\s*\{/, match => match + "\n" + ctxDef);
484
-
485
- let letterEntry = runtimeMap && runtimeMap.get(compName);
256
+ let letterEntry = cx.runtimeMap && cx.runtimeMap.get(compName);
486
257
  let letter;
487
258
  if (!letterEntry) {
488
- letter = getNextLetter(letterState);
259
+ letter = getNextLetter(cx.letterState);
260
+ const topFuncs = topFuncSrc;
261
+
262
+ let injectCode = ctxDef;
263
+ if (bindings.length > 0) {
264
+ injectCode += "\n" + bindings.map(b => {
265
+ const accessor = b.prop === "self" ? "" : "." + b.prop;
266
+ return "let " + b.varName + " = self.querySelector('[data-chbind-" + b.bindId + "]')" + accessor + ";";
267
+ }).join("\n") + "\n";
268
+ }
269
+ if (topFuncs.length > 0) {
270
+ injectCode += "\n" + topFuncs.join("\n\n") + "\n";
271
+ }
272
+ runtime = runtime.replace(/\$runtime\([^)]*\)\s*\{/, match => match + "\n" + injectCode);
273
+
489
274
  runtime = runtime.replace(`${RUNTIME_KW}()`, `${letter}r(self, ctx)`);
490
- runtimeChunks.push(runtime);
491
- runtimeMap && runtimeMap.set(compName, { letter });
275
+ cx.runtimeChunks.push(runtime);
276
+ cx.runtimeMap && cx.runtimeMap.set(compName, { letter });
492
277
  } else {
493
278
  letter = letterEntry.letter;
494
279
  }
495
280
 
496
- runtimeChunks.push(`${letter}r(document.querySelector('[chid="${compId}"]'), ${JSON.stringify(ctx)});`);
281
+ cx.runtimeChunks.push(`${letter}r(document.querySelector('[chid="${compId}"]'), ${JSON.stringify(ctx)});`);
497
282
  }
498
283
  }
499
284
  }
500
285
 
501
- if (styles) {
502
- let cssId = cssScopesMap && cssScopesMap.get(compName);
503
- if (!cssId) {
504
- cssId = genRandomId(cssScopes, 8, true);
505
- cssScopesMap.set(compName, cssId);
506
- }
507
- if (fragment.children.length === 1 && firstChild.nodeType === 1) {
508
- firstChild.classList.add(cssId);
509
- }
510
- styles = scopeCss(styles, cssId);
511
- scopedStyles.push(styles);
286
+ let cssId = cx.cssScopesMap && cx.cssScopesMap.get(compName);
287
+ if (!cssId) {
288
+ cssId = deterministicHash(compName, 8);
289
+ cx.cssScopesMap.set(compName, cssId);
512
290
  }
513
-
514
291
  if (fragment.children.length === 1 && firstChild.nodeType === 1) {
515
- firstChild.setAttribute("data-ch-source", compName);
292
+ firstChild.classList.add(cssId);
293
+ }
294
+ if (styles) {
295
+ styles = scopeCss(styles, cssId);
296
+ cx.scopedStyles.push(styles);
516
297
  }
517
298
 
518
299
  element.replaceWith(fragment);
@@ -520,24 +301,24 @@ export function processComponentElement(
520
301
  }
521
302
 
522
303
  export function processAllComponents(appElements, loadedComponents, pageSourceFile, pageSourceContent) {
523
- let runtimeChunks = [];
524
- let compIdColl = [];
525
- let letterState = { value: null };
526
- let runtimeMap = new Map();
527
- let cssScopes = [];
528
- let cssScopesMap = new Map();
529
- let scopedStyles = [];
530
- let staticCtxRegistry = new Map();
304
+ const cx = new ProcessContext(
305
+ loadedComponents, [], [], { value: null }, new Map(), [], new Map(), [], new Map()
306
+ );
531
307
 
532
308
  appElements.forEach(el => {
533
309
  if (!el.isConnected) return;
534
- processComponentElement(el, loadedComponents, runtimeChunks, compIdColl, letterState, runtimeMap, cssScopes, cssScopesMap, scopedStyles, [], staticCtxRegistry, pageSourceFile, pageSourceContent);
310
+ processComponentElement(el, cx, [], pageSourceFile, pageSourceContent);
535
311
  });
536
- const runtimeScript = runtimeChunks.join("\n");
537
- const hasComponents = runtimeChunks.length > 0;
538
- const scopesCss = beautify.css(scopedStyles.join("\n"));
312
+ const runtimeScript = cx.runtimeChunks.join("\n");
313
+ const hasComponents = cx.runtimeChunks.length > 0;
314
+ const scopesCss = cx.scopedStyles.join("\n");
315
+
316
+ const hashMap = {};
317
+ for (const [compName, hash] of cx.cssScopesMap) {
318
+ hashMap[compName] = hash;
319
+ }
539
320
 
540
- return { runtimeScript, hasComponents, scopesCss };
321
+ return { runtimeScript, hasComponents, scopesCss, hashMap };
541
322
  }
542
323
 
543
324
  function getNextLetter(letterState) {
@@ -550,4 +331,3 @@ function getNextLetter(letterState) {
550
331
  }
551
332
 
552
333
  const RUNTIME_KW = "$runtime";
553
- const RUNTIME_REGEX = new RegExp(RUNTIME_KW, "g");
@@ -1,7 +1,9 @@
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, compileExpression } from "./utils.js";
4
+ import { protectCurlyBraces, restoreCurlyBraces } from "../utils.js";
5
+ import { extractContextFromElement } from "../parser/context.js";
6
+ import { throwError } from "./utils.js";
5
7
  import { readMyFile } from "./fs.js";
6
8
 
7
9
  export function createDOM(srcIndexContent) {
@@ -20,28 +22,8 @@ export function getAppElements(appContainer) {
20
22
  return Array.from(appContainer.querySelectorAll("*"));
21
23
  }
22
24
 
23
- export function extractContextFromElement(element) {
24
- const ctx = {};
25
- for (const attr of element.attributes) {
26
- const key = attr.name;
27
- const val = attr.value;
28
- if (!val.includes("{")) { ctx[key] = val; continue; }
29
- const matches = [...val.matchAll(/\{([^}]+)\}/g)];
30
- if (matches.length === 1 && matches[0][0] === val) {
31
- try {
32
- ctx[key] = compileExpression(matches[0][1], false)();
33
- continue;
34
- } catch {}
35
- }
36
- ctx[key] = val;
37
- }
38
- return ctx;
39
- }
40
-
41
25
  export async function serializeDOM(dom) {
42
- const beautify = (await import("js-beautify")).default;
43
- const finalHtml = restoreCurlyBraces(dom.serialize());
44
- return beautify.html(finalHtml, { indent_size: 2 });
26
+ return restoreCurlyBraces(dom.serialize());
45
27
  }
46
28
 
47
29
  export async function writeHTMLOutput(html, outDirPath) {
@@ -66,6 +48,10 @@ export function appendStylesheetLink(doc, filename) {
66
48
  doc.head.appendChild(linkEl);
67
49
  }
68
50
 
51
+ export function getScriptElements(doc) {
52
+ return Array.from(doc.querySelectorAll("script[src]"));
53
+ }
54
+
69
55
  export function appendRuntimeScript(doc, filename) {
70
56
  const runtimeScriptEl = doc.createElement("script");
71
57
  runtimeScriptEl.type = "module";