chocola 2.0.0-next.5 → 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.
@@ -1,667 +1,333 @@
1
- import { JSDOM } from "jsdom";
2
- import { extractContextFromElement } from "./dom-processor.js";
3
- import { genRandomId, incrementAlfabet, throwError, protectCurlyBraces, compileExpression, hasDelIfAttr, getDelIfAttr, removeDelIfAttr } from "./utils.js";
4
- import chalk from "./chalk.js";
5
-
6
- function hasCombinator(sel) {
7
- let depth = 0;
8
- for (const ch of sel) {
9
- if (ch === '[') depth++;
10
- else if (ch === ']') depth--;
11
- else if (depth === 0 && (ch === ' ' || ch === '>' || ch === '+' || ch === '~')) return true;
12
- }
13
- return false;
14
- }
15
-
16
- function scopeSelector(sel, cssId) {
17
- const s = sel.trim();
18
- if (s.startsWith("." + cssId)) return s;
19
- if (hasCombinator(s)) return `.${cssId} ${s}`;
20
- return `.${cssId}${s}, .${cssId} ${s}`;
21
- }
22
-
23
- function scopeCss(cssString, cssId) {
24
- const keyframeRegex = /@(-webkit-)?keyframes\s+(\S+)/gi;
25
- const animNames = new Set();
26
- let match;
27
- while ((match = keyframeRegex.exec(cssString))) {
28
- animNames.add(match[2]);
29
- }
30
-
31
- if (animNames.size > 0) {
32
- const sorted = [...animNames].sort((a, b) => b.length - a.length);
33
- const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
34
- for (const name of sorted) {
35
- cssString = cssString.replace(
36
- new RegExp(`(@(?:-webkit-)?keyframes\\s+)${esc(name)}`, "gi"),
37
- `$1${name}-${cssId}`
38
- );
39
- cssString = cssString.replace(
40
- new RegExp(`((?:animation|animation-name)\\s*:\\s*[^;{]*?)\\b${esc(name)}\\b`, "gi"),
41
- `$1${name}-${cssId}`
42
- );
43
- }
44
- }
45
-
46
- function findMatchingBrace(s, openIndex) {
47
- let depth = 0;
48
- for (let i = openIndex; i < s.length; i++) {
49
- const ch = s[i];
50
- if (ch === '{') depth++;
51
- else if (ch === '}') {
52
- depth--;
53
- if (depth === 0) return i;
54
- }
55
- }
56
- return s.length - 1;
57
- }
58
-
59
- const scopeInnerAtRules = new Set(["@media", "@supports", "@container", "@layer"]);
60
-
61
- function processBlock(str) {
62
- str = str.replaceAll(":root", `.${cssId}`);
63
-
64
- let out = "";
65
- let i = 0;
66
- while (i < str.length) {
67
- const braceIndex = str.indexOf('{', i);
68
- if (braceIndex === -1) {
69
- out += str.slice(i);
70
- break;
71
- }
72
- const header = str.substring(i, braceIndex);
73
- const endBrace = findMatchingBrace(str, braceIndex);
74
- const inner = str.substring(braceIndex + 1, endBrace);
75
-
76
- if (header.trim().startsWith("@")) {
77
- const atKeyword = header.trim().split(/\s+/)[0].toLowerCase();
78
- if (scopeInnerAtRules.has(atKeyword)) {
79
- out += header + "{" + processBlock(inner) + "}";
80
- } else {
81
- out += header + "{" + inner + "}";
82
- }
83
- } else {
84
- const innerScoped = processBlock(inner);
85
- const selectors = header
86
- .split(",")
87
- .map(s => s.trim())
88
- .filter(Boolean);
89
- const scopedHeader = selectors.length > 0
90
- ? selectors.map(sel => scopeSelector(sel, cssId)).join(", ")
91
- : header;
92
- out += scopedHeader + "{" + innerScoped + "}";
93
- }
94
-
95
- i = endBrace + 1;
96
- }
97
- return out;
98
- }
99
-
100
- return processBlock(cssString);
101
- }
102
-
103
- function extractPropsDefaults(script) {
104
- if (!script) return [];
105
- const propsRegex = /export\s+let\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*(?:=\s*([^;]+))?;/g;
106
- let props = [];
107
- let match;
108
- while ((match = propsRegex.exec(script)) !== null) {
109
- props.push({ name: match[1].trim(), defaultValue: match[2]?.trim() });
110
- }
111
- return props;
112
- }
113
-
114
- function interpolateNode(root, ctxProxy) {
115
- const stack = [root];
116
- while (stack.length) {
117
- const node = stack.pop();
118
- if (node.nodeType === 3) {
119
- node.textContent = node.textContent.replace(/\{([^}]+)\}/g, (_, expr) => {
120
- try {
121
- return compileExpression(expr, true)(ctxProxy);
122
- } catch {
123
- return "";
124
- }
125
- });
126
- } else {
127
- for (let i = node.childNodes.length - 1; i >= 0; i--) {
128
- stack.push(node.childNodes[i]);
129
- }
130
- }
131
- }
132
- }
133
-
134
- function getLineNumber(sourceContent, parentContent, idxInParent) {
135
- if (sourceContent === parentContent) {
136
- return parentContent.substring(0, idxInParent).split("\n").length;
137
- }
138
- const templateStartMatch = sourceContent.match(/<template[^>]*>/);
139
- if (templateStartMatch) {
140
- const contentStart = templateStartMatch.index + templateStartMatch[0].length;
141
- const beforeContent = sourceContent.substring(0, contentStart);
142
- const linesInBefore = beforeContent.split("\n").length;
143
- const linesInTemplate = parentContent.substring(0, idxInParent).split("\n").length;
144
- return linesInBefore + linesInTemplate - 1;
145
- }
146
- return null;
147
- }
148
-
149
- function validateChainStructure(parent, sourceFile, sourceContent, parentContent) {
150
- const children = [...parent.children];
151
- let chainActive = false;
152
-
153
- for (const child of children) {
154
- const hasIf = child.hasAttribute("if");
155
- const hasDelIf = hasDelIfAttr(child);
156
- const hasElif = child.hasAttribute("elif");
157
- const hasElse = child.hasAttribute("else");
158
-
159
- if (hasElif || hasElse) {
160
- if (!chainActive) {
161
- const tag = child.tagName.toLowerCase();
162
- const attr = hasElif ? "elif" : "else";
163
- let loc = sourceFile;
164
- if (sourceContent && parentContent) {
165
- const idx = parentContent.indexOf(child.outerHTML);
166
- if (idx !== -1) {
167
- const lineNum = getLineNumber(sourceContent, parentContent, idx);
168
- if (lineNum !== null) loc = `${sourceFile}:${lineNum}`;
169
- }
170
- }
171
- throwError(`${loc}\n <${tag}> has ${attr} without a preceding if/del-if sibling`);
172
- }
173
- if (hasElse) {
174
- chainActive = false;
175
- }
176
- }
177
-
178
- if (hasIf || hasDelIf) {
179
- chainActive = true;
180
- } else if (!hasElif && !hasElse) {
181
- chainActive = false;
182
- }
183
-
184
- validateChainStructure(child, sourceFile, sourceContent, parentContent);
185
- }
186
- }
187
-
188
- class ProcessContext {
189
- constructor(loadedComponents, runtimeChunks, compIdColl, letterState, runtimeMap, cssScopes, cssScopesMap, scopedStyles, staticCtxRegistry) {
190
- this.loadedComponents = loadedComponents;
191
- this.runtimeChunks = runtimeChunks;
192
- this.compIdColl = compIdColl;
193
- this.letterState = letterState;
194
- this.runtimeMap = runtimeMap;
195
- this.cssScopes = cssScopes;
196
- this.cssScopesMap = cssScopesMap;
197
- this.scopedStyles = scopedStyles;
198
- this.staticCtxRegistry = staticCtxRegistry;
199
- }
200
- }
201
-
202
- const reservedAttrs = ["if", "del:if", "elif", "else"];
203
-
204
- function extractTopLevelFunctions(script, excludeName) {
205
- const funcs = [];
206
- let i = 0;
207
- let depth = 0;
208
- let inStr = false;
209
- let strChar = null;
210
-
211
- while (i < script.length) {
212
- const ch = script[i];
213
-
214
- if (inStr) {
215
- if (ch === '\\') i++;
216
- else if (ch === strChar) inStr = false;
217
- i++;
218
- continue;
219
- }
220
-
221
- if (ch === '"' || ch === "'" || ch === '`') {
222
- inStr = true;
223
- strChar = ch;
224
- i++;
225
- continue;
226
- }
227
-
228
- if (ch === '{') { depth++; i++; continue; }
229
- if (ch === '}') { depth--; i++; continue; }
230
-
231
- if (depth === 0) {
232
- const rest = script.slice(i);
233
- const m = rest.match(/^(?:async\s+)?function\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*\(/);
234
- if (m) {
235
- const name = m[1];
236
- const pastParen = i + m[0].length;
237
-
238
- let parenDepth = 1;
239
- let p = pastParen;
240
- while (p < script.length && parenDepth > 0) {
241
- if (script[p] === '(') parenDepth++;
242
- if (script[p] === ')') parenDepth--;
243
- p++;
244
- }
245
-
246
- while (p < script.length && script[p] !== '{') p++;
247
- if (p >= script.length) { i++; continue; }
248
-
249
- let bDepth = 1;
250
- let bodyStr = false;
251
- let bodyStrChar = null;
252
- let end = p + 1;
253
- while (end < script.length && bDepth > 0) {
254
- const c = script[end];
255
- if (bodyStr) {
256
- if (c === '\\') end++;
257
- else if (c === bodyStrChar) bodyStr = false;
258
- end++;
259
- continue;
260
- }
261
- if (c === '"' || c === "'" || c === '`') {
262
- bodyStr = true;
263
- bodyStrChar = c;
264
- end++;
265
- continue;
266
- }
267
- if (c === '{') bDepth++;
268
- if (c === '}') bDepth--;
269
- end++;
270
- }
271
-
272
- if (name !== excludeName) {
273
- funcs.push(script.slice(i, end));
274
- }
275
- i = end;
276
- continue;
277
- }
278
- }
279
- i++;
280
- }
281
-
282
- return funcs;
283
- }
284
-
285
- function extractRuntime(script, compName) {
286
- const startRegex = /(?:async\s+)?function\s+\$runtime\(([^)]*)\)\s*\{/;
287
- const match = script.match(startRegex);
288
- if (!match) return null;
289
-
290
- const startIndexBrace = match.index + match[0].length - 1;
291
- let bracesCount = 0;
292
- let inString = false;
293
- let stringChar = null;
294
-
295
- for (let i = startIndexBrace; i < script.length; i++) {
296
- const ch = script[i];
297
-
298
- if (inString) {
299
- if (ch === '\\') { i++; continue; }
300
- if (ch === stringChar) inString = false;
301
- continue;
302
- }
303
-
304
- if (ch === "'" || ch === '"' || ch === '`') {
305
- inString = true;
306
- stringChar = ch;
307
- continue;
308
- }
309
-
310
- if (ch === '{') {
311
- bracesCount++;
312
- } else if (ch === '}') {
313
- bracesCount--;
314
- if (bracesCount === 0) {
315
- return script.substring(match.index, i + 1);
316
- }
317
- }
318
- }
319
-
320
- throw new Error(`${compName} $runtime function has unclosed curly braces.`);
321
- }
322
-
323
- function applyConditionalToElement(child, ctxProxy, condChain, hasIf, hasDelIf, hasElif, hasElse) {
324
- if (hasIf) {
325
- const expr = child.getAttribute("if").slice(1, -1);
326
- const fn = compileExpression(expr, true);
327
- condChain.active = true;
328
- if (fn(ctxProxy)) {
329
- condChain.rendered = true;
330
- } else {
331
- child.style.display = "none";
332
- condChain.rendered = false;
333
- }
334
- child.removeAttribute("if");
335
- } else if (hasDelIf) {
336
- const expr = getDelIfAttr(child).slice(1, -1);
337
- const fn = compileExpression(expr, true);
338
- condChain.active = true;
339
- if (fn(ctxProxy)) {
340
- condChain.rendered = true;
341
- } else {
342
- child.remove();
343
- condChain.rendered = false;
344
- }
345
- removeDelIfAttr(child);
346
- } else if (hasElif) {
347
- const expr = child.getAttribute("elif").slice(1, -1);
348
- const fn = compileExpression(expr, true);
349
- if (fn(ctxProxy)) {
350
- condChain.rendered = true;
351
- } else {
352
- child.remove();
353
- }
354
- child.removeAttribute("elif");
355
- } else if (hasElse) {
356
- condChain.rendered = true;
357
- condChain.active = false;
358
- child.removeAttribute("else");
359
- } else {
360
- condChain.active = false;
361
- condChain.rendered = false;
362
- }
363
- }
364
-
365
- export function processComponentElement(
366
- element,
367
- cx,
368
- renderChain = [],
369
- sourceFile,
370
- sourceContent
371
- ) {
372
- const tagName = element.tagName.toLowerCase();
373
- const compName = tagName + ".html";
374
- let instance = cx.loadedComponents.get(compName);
375
-
376
- if (!instance || instance === undefined) return false;
377
- if (renderChain && renderChain.includes(compName)) return false;
378
-
379
- instance = protectCurlyBraces(instance);
380
- const dom = new JSDOM(instance);
381
- const doc = dom.window.document;
382
- let script = doc.querySelector("script")?.innerHTML;
383
- let template = doc.querySelector("template")?.innerHTML;
384
- let styles = doc.querySelector("style")?.innerHTML;
385
-
386
- if (!template) {
387
- console.warn(chalk.yellow(`${compName} — component is missing a <template>`));
388
- return false;
389
- }
390
-
391
- const compProps = extractPropsDefaults(script);
392
-
393
- let ctx;
394
- if (cx.staticCtxRegistry && cx.staticCtxRegistry.has(element)) {
395
- ctx = cx.staticCtxRegistry.get(element);
396
- } else {
397
- ctx = extractContextFromElement(element);
398
- cx.staticCtxRegistry && cx.staticCtxRegistry.set(element, ctx);
399
- }
400
-
401
- if (compProps.length > 0) {
402
- compProps.forEach(({ name, defaultValue }) => {
403
- if (defaultValue !== undefined && !(name in ctx)) {
404
- try {
405
- ctx[name] = compileExpression(defaultValue, false)();
406
- } catch {
407
- ctx[name] = defaultValue;
408
- }
409
- }
410
- });
411
- }
412
-
413
- const topFuncSrc = extractTopLevelFunctions(script || "", RUNTIME_KW);
414
- for (const src of topFuncSrc) {
415
- try {
416
- const fn = (0, eval)("(" + src + ")");
417
- const name = fn.name;
418
- if (name && !(name in ctx)) ctx[name] = fn;
419
- } catch {}
420
- }
421
-
422
- const elInnerHtml = element.innerHTML;
423
-
424
- const ctxProxy = new Proxy(ctx, {
425
- has() { return true; },
426
- get(target, key) { return target[key]; }
427
- });
428
-
429
- const fragment = JSDOM.fragment(template);
430
-
431
- const slotFragment = JSDOM.fragment(elInnerHtml);
432
- if (sourceFile) {
433
- validateChainStructure(slotFragment, sourceFile, sourceContent, elInnerHtml);
434
- }
435
- validateChainStructure(fragment, instance.__sourceFile || compName, instance, template);
436
- Array.from(fragment.querySelectorAll("slot")).forEach(slot => {
437
- slot.replaceWith(slotFragment);
438
- });
439
-
440
- const childEntries = Array.from(fragment.querySelectorAll("*")).map(el => ({
441
- el,
442
- parent: el.parentNode
443
- }));
444
- const condChains = new Map();
445
- const bindings = [];
446
- const elBindIds = new Map();
447
- let bindCounter = 0;
448
-
449
- childEntries.forEach(({ el: child, parent }) => {
450
- if (!condChains.has(parent)) {
451
- condChains.set(parent, { active: false, rendered: false });
452
- }
453
- const condChain = condChains.get(parent);
454
-
455
- const hasIf = child.hasAttribute("if");
456
- const hasDelIf = hasDelIfAttr(child);
457
- const hasElif = child.hasAttribute("elif");
458
- const hasElse = child.hasAttribute("else");
459
-
460
- if (hasElif || hasElse) {
461
- if (!condChain.active) {
462
- throwError(`${instance.__sourceFile || compName}: <${child.tagName.toLowerCase()}> has ${hasElif ? "elif" : "else"} without a preceding if/del-if sibling`);
463
- }
464
- if (condChain.rendered) {
465
- child.remove();
466
- if (hasElse) {
467
- condChain.active = false;
468
- }
469
- return;
470
- }
471
- }
472
-
473
- if (child.tagName.toLowerCase() === "void") {
474
- if (hasElif || hasElse) {
475
- if (hasElif) {
476
- const expr = child.getAttribute("elif").slice(1, -1);
477
- const fn = compileExpression(expr, true);
478
- if (!fn(ctxProxy)) {
479
- child.remove();
480
- return;
481
- }
482
- }
483
- child.replaceWith(...child.children);
484
- condChain.rendered = true;
485
- if (hasElse) {
486
- condChain.active = false;
487
- }
488
- } else if (hasIf || hasDelIf) {
489
- const raw = hasIf ? child.getAttribute("if") : getDelIfAttr(child);
490
- const expr = raw.slice(1, -1);
491
- const fn = compileExpression(expr, true);
492
- condChain.active = true;
493
- if (fn(ctxProxy)) {
494
- child.replaceWith(...child.children);
495
- condChain.rendered = true;
496
- } else {
497
- child.remove();
498
- condChain.rendered = false;
499
- }
500
- } else {
501
- child.replaceWith(...child.children);
502
- condChain.active = false;
503
- condChain.rendered = false;
504
- }
505
- return;
506
- }
507
-
508
- Array.from(child.attributes).forEach(attribute => {
509
- if (!attribute || attribute === undefined) return;
510
- if (reservedAttrs.includes(attribute.name)) return;
511
-
512
- if (attribute.name.startsWith("bind:")) {
513
- const prop = attribute.name.slice(5);
514
- const varName = attribute.value;
515
- let bindId = elBindIds.get(child);
516
- if (!bindId) {
517
- bindId = "b" + (bindCounter++);
518
- elBindIds.set(child, bindId);
519
- child.setAttribute("data-chbind-" + bindId, "");
520
- }
521
- bindings.push({ prop, varName, bindId });
522
- child.removeAttribute(attribute.name);
523
- return;
524
- }
525
-
526
- attribute.value = attribute.value.replace(
527
- /\{([^}]+)\}/g,
528
- (_, expr) => {
529
- try {
530
- return compileExpression(expr, true)(ctxProxy);
531
- } catch {
532
- return "";
533
- }
534
- }
535
- );
536
- });
537
-
538
- processComponentElement(
539
- child,
540
- cx,
541
- renderChain.concat(compName),
542
- compName,
543
- template
544
- );
545
-
546
- applyConditionalToElement(child, ctxProxy, condChain, hasIf, hasDelIf, hasElif, hasElse);
547
-
548
- interpolateNode(child, ctxProxy)
549
- });
550
-
551
- const firstChild = fragment.children[0];
552
-
553
- if (firstChild && firstChild.nodeType === 1) {
554
- if (script) {
555
- const compId = "chid-" + genRandomId(cx.compIdColl);
556
- firstChild.setAttribute("chid", compId);
557
-
558
- const ctxRegex = /ctx\s*=\s*({.*?})/;
559
- const ctxMatch = script.match(ctxRegex);
560
- let runtimeCtx = {};
561
- if (ctxMatch) {
562
- try {
563
- runtimeCtx = JSON.parse(ctxMatch[1].replace(/(\w+):/g, '"$1":'));
564
- } catch (e) {
565
- runtimeCtx = {};
566
- }
567
- }
568
- const ctxDefParts = [];
569
- const declared = new Set();
570
- for (const [key, value] of Object.entries(runtimeCtx)) {
571
- ctxDefParts.push(`let ${key} = ctx.${key}||${JSON.stringify(value)};\n`);
572
- declared.add(key);
573
- }
574
-
575
- for (const { name, defaultValue } of compProps) {
576
- if (declared.has(name)) continue;
577
- if (defaultValue !== undefined) {
578
- ctxDefParts.push(`let ${name} = ctx.${name}||${defaultValue};\n`);
579
- } else {
580
- ctxDefParts.push(`let ${name} = ctx.${name};\n`);
581
- }
582
- }
583
-
584
- const ctxDef = ctxDefParts.join("");
585
-
586
- script = script.replace(ctxRegex, "ctx");
587
-
588
- let runtime = extractRuntime(script, compName);
589
-
590
- if (runtime) {
591
- let letterEntry = cx.runtimeMap && cx.runtimeMap.get(compName);
592
- let letter;
593
- if (!letterEntry) {
594
- letter = getNextLetter(cx.letterState);
595
- const topFuncs = topFuncSrc;
596
-
597
- let injectCode = ctxDef;
598
- if (bindings.length > 0) {
599
- injectCode += "\n" + bindings.map(b => {
600
- const accessor = b.prop === "self" ? "" : "." + b.prop;
601
- return "let " + b.varName + " = self.querySelector('[data-chbind-" + b.bindId + "]')" + accessor + ";";
602
- }).join("\n") + "\n";
603
- }
604
- if (topFuncs.length > 0) {
605
- injectCode += "\n" + topFuncs.join("\n\n") + "\n";
606
- }
607
- runtime = runtime.replace(/\$runtime\([^)]*\)\s*\{/, match => match + "\n" + injectCode);
608
-
609
- runtime = runtime.replace(`${RUNTIME_KW}()`, `${letter}r(self, ctx)`);
610
- cx.runtimeChunks.push(runtime);
611
- cx.runtimeMap && cx.runtimeMap.set(compName, { letter });
612
- } else {
613
- letter = letterEntry.letter;
614
- }
615
-
616
- cx.runtimeChunks.push(`${letter}r(document.querySelector('[chid="${compId}"]'), ${JSON.stringify(ctx)});`);
617
- }
618
- }
619
- }
620
-
621
- if (styles) {
622
- let cssId = cx.cssScopesMap && cx.cssScopesMap.get(compName);
623
- if (!cssId) {
624
- cssId = genRandomId(cx.cssScopes, 8, true);
625
- cx.cssScopesMap.set(compName, cssId);
626
- }
627
- if (fragment.children.length === 1 && firstChild.nodeType === 1) {
628
- firstChild.classList.add(cssId);
629
- }
630
- styles = scopeCss(styles, cssId);
631
- cx.scopedStyles.push(styles);
632
- }
633
-
634
- if (fragment.children.length === 1 && firstChild.nodeType === 1) {
635
- firstChild.setAttribute("data-ch-source", compName);
636
- }
637
-
638
- element.replaceWith(fragment);
639
- return true;
640
- }
641
-
642
- export function processAllComponents(appElements, loadedComponents, pageSourceFile, pageSourceContent) {
643
- const cx = new ProcessContext(
644
- loadedComponents, [], [], { value: null }, new Map(), [], new Map(), [], new Map()
645
- );
646
-
647
- appElements.forEach(el => {
648
- if (!el.isConnected) return;
649
- processComponentElement(el, cx, [], pageSourceFile, pageSourceContent);
650
- });
651
- const runtimeScript = cx.runtimeChunks.join("\n");
652
- const hasComponents = cx.runtimeChunks.length > 0;
653
- const scopesCss = cx.scopedStyles.join("\n");
654
-
655
- return { runtimeScript, hasComponents, scopesCss };
656
- }
657
-
658
- function getNextLetter(letterState) {
659
- if (!letterState.value) {
660
- letterState.value = "a";
661
- } else {
662
- letterState.value = incrementAlfabet(letterState.value);
663
- }
664
- return letterState.value;
665
- }
666
-
667
- const RUNTIME_KW = "$runtime";
1
+ import { JSDOM } from "jsdom";
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;
25
+ }
26
+ }
27
+
28
+
29
+
30
+ export function processComponentElement(
31
+ element,
32
+ cx,
33
+ renderChain = [],
34
+ sourceFile,
35
+ sourceContent
36
+ ) {
37
+ const tagName = element.tagName.toLowerCase();
38
+ const compName = tagName + ".html";
39
+ let instance = cx.loadedComponents.get(compName);
40
+
41
+ if (!instance || instance === undefined) return false;
42
+ if (renderChain && renderChain.includes(compName)) return false;
43
+
44
+ instance = protectCurlyBraces(instance);
45
+ const dom = new JSDOM(instance);
46
+ const doc = dom.window.document;
47
+ let script = doc.querySelector("script")?.innerHTML;
48
+ let template = doc.querySelector("template")?.innerHTML;
49
+ let styles = doc.querySelector("style")?.innerHTML;
50
+
51
+ if (!template) {
52
+ console.warn(chalk.yellow(`${compName} — component is missing a <template>`));
53
+ return false;
54
+ }
55
+
56
+ const compProps = extractPropsDefaults(script);
57
+
58
+ let ctx;
59
+ if (cx.staticCtxRegistry && cx.staticCtxRegistry.has(element)) {
60
+ ctx = cx.staticCtxRegistry.get(element);
61
+ } else {
62
+ ctx = extractContextFromElement(element);
63
+ cx.staticCtxRegistry && cx.staticCtxRegistry.set(element, ctx);
64
+ }
65
+
66
+ if (compProps.length > 0) {
67
+ compProps.forEach(({ name, defaultValue }) => {
68
+ if (defaultValue !== undefined && !(name in ctx)) {
69
+ try {
70
+ ctx[name] = compileExpression(defaultValue, false)();
71
+ } catch {
72
+ ctx[name] = defaultValue;
73
+ }
74
+ }
75
+ });
76
+ }
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
+
87
+ const elInnerHtml = element.innerHTML;
88
+
89
+ const ctxProxy = new Proxy(ctx, {
90
+ has() { return true; },
91
+ get(target, key) { return target[key]; }
92
+ });
93
+
94
+ const fragment = JSDOM.fragment(template);
95
+
96
+ const slotFragment = JSDOM.fragment(elInnerHtml);
97
+ if (sourceFile) {
98
+ validateChainStructure(slotFragment, sourceFile, sourceContent, elInnerHtml);
99
+ }
100
+ validateChainStructure(fragment, instance.__sourceFile || compName, instance, template);
101
+ Array.from(fragment.querySelectorAll("slot")).forEach(slot => {
102
+ slot.replaceWith(slotFragment);
103
+ });
104
+
105
+ const childEntries = Array.from(fragment.querySelectorAll("*")).map(el => ({
106
+ el,
107
+ parent: el.parentNode
108
+ }));
109
+ const condChains = new Map();
110
+ const bindings = [];
111
+ const elBindIds = new Map();
112
+ let bindCounter = 0;
113
+
114
+ childEntries.forEach(({ el: child, parent }) => {
115
+ if (!condChains.has(parent)) {
116
+ condChains.set(parent, { active: false, rendered: false });
117
+ }
118
+ const condChain = condChains.get(parent);
119
+
120
+ const hasIf = child.hasAttribute("if");
121
+ const hasDelIf = hasDelIfAttr(child);
122
+ const hasElif = child.hasAttribute("elif");
123
+ const hasElse = child.hasAttribute("else");
124
+
125
+ if (hasElif || hasElse) {
126
+ if (!condChain.active) {
127
+ throwError(`${instance.__sourceFile || compName}: <${child.tagName.toLowerCase()}> has ${hasElif ? "elif" : "else"} without a preceding if/del-if sibling`);
128
+ }
129
+ if (condChain.rendered) {
130
+ child.remove();
131
+ if (hasElse) {
132
+ condChain.active = false;
133
+ }
134
+ return;
135
+ }
136
+ }
137
+
138
+ if (child.tagName.toLowerCase() === "void") {
139
+ if (hasElif || hasElse) {
140
+ if (hasElif) {
141
+ const expr = child.getAttribute("elif").slice(1, -1);
142
+ const fn = compileExpression(expr, true);
143
+ if (!fn(ctxProxy)) {
144
+ child.remove();
145
+ return;
146
+ }
147
+ }
148
+ child.replaceWith(...child.children);
149
+ condChain.rendered = true;
150
+ if (hasElse) {
151
+ condChain.active = false;
152
+ }
153
+ } else if (hasIf || hasDelIf) {
154
+ const raw = hasIf ? child.getAttribute("if") : getDelIfAttr(child);
155
+ const expr = raw.slice(1, -1);
156
+ const fn = compileExpression(expr, true);
157
+ condChain.active = true;
158
+ if (fn(ctxProxy)) {
159
+ child.replaceWith(...child.children);
160
+ condChain.rendered = true;
161
+ } else {
162
+ child.remove();
163
+ condChain.rendered = false;
164
+ }
165
+ } else {
166
+ child.replaceWith(...child.children);
167
+ condChain.active = false;
168
+ condChain.rendered = false;
169
+ }
170
+ return;
171
+ }
172
+
173
+ Array.from(child.attributes).forEach(attribute => {
174
+ if (!attribute || attribute === undefined) return;
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
+
191
+ attribute.value = attribute.value.replace(
192
+ /\{([^}]+)\}/g,
193
+ (_, expr) => {
194
+ try {
195
+ return compileExpression(expr, true)(ctxProxy);
196
+ } catch {
197
+ return "";
198
+ }
199
+ }
200
+ );
201
+ });
202
+
203
+ processComponentElement(
204
+ child,
205
+ cx,
206
+ renderChain.concat(compName),
207
+ compName,
208
+ template
209
+ );
210
+
211
+ applyConditionalToElement(child, ctxProxy, condChain, hasIf, hasDelIf, hasElif, hasElse);
212
+
213
+ interpolateNode(child, ctxProxy)
214
+ });
215
+
216
+ const firstChild = fragment.children[0];
217
+
218
+ if (firstChild && firstChild.nodeType === 1) {
219
+ if (script) {
220
+ const compId = "chid-" + genRandomId(cx.compIdColl);
221
+ firstChild.setAttribute("chid", compId);
222
+
223
+ const ctxRegex = /ctx\s*=\s*({.*?})/;
224
+ const ctxMatch = script.match(ctxRegex);
225
+ let runtimeCtx = {};
226
+ if (ctxMatch) {
227
+ try {
228
+ runtimeCtx = JSON.parse(ctxMatch[1].replace(/(\w+):/g, '"$1":'));
229
+ } catch (e) {
230
+ runtimeCtx = {};
231
+ }
232
+ }
233
+ const ctxDefParts = [];
234
+ const declared = new Set();
235
+ for (const [key, value] of Object.entries(runtimeCtx)) {
236
+ ctxDefParts.push(`let ${key} = ctx.${key}||${JSON.stringify(value)};\n`);
237
+ declared.add(key);
238
+ }
239
+
240
+ for (const { name, defaultValue } of compProps) {
241
+ if (declared.has(name)) continue;
242
+ if (defaultValue !== undefined) {
243
+ ctxDefParts.push(`let ${name} = ctx.${name}||${defaultValue};\n`);
244
+ } else {
245
+ ctxDefParts.push(`let ${name} = ctx.${name};\n`);
246
+ }
247
+ }
248
+
249
+ const ctxDef = ctxDefParts.join("");
250
+
251
+ script = script.replace(ctxRegex, "ctx");
252
+
253
+ let runtime = extractRuntime(script, compName);
254
+
255
+ if (runtime) {
256
+ let letterEntry = cx.runtimeMap && cx.runtimeMap.get(compName);
257
+ let letter;
258
+ if (!letterEntry) {
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
+
274
+ runtime = runtime.replace(`${RUNTIME_KW}()`, `${letter}r(self, ctx)`);
275
+ cx.runtimeChunks.push(runtime);
276
+ cx.runtimeMap && cx.runtimeMap.set(compName, { letter });
277
+ } else {
278
+ letter = letterEntry.letter;
279
+ }
280
+
281
+ cx.runtimeChunks.push(`${letter}r(document.querySelector('[chid="${compId}"]'), ${JSON.stringify(ctx)});`);
282
+ }
283
+ }
284
+ }
285
+
286
+ let cssId = cx.cssScopesMap && cx.cssScopesMap.get(compName);
287
+ if (!cssId) {
288
+ cssId = deterministicHash(compName, 8);
289
+ cx.cssScopesMap.set(compName, cssId);
290
+ }
291
+ if (fragment.children.length === 1 && firstChild.nodeType === 1) {
292
+ firstChild.classList.add(cssId);
293
+ }
294
+ if (styles) {
295
+ styles = scopeCss(styles, cssId);
296
+ cx.scopedStyles.push(styles);
297
+ }
298
+
299
+ element.replaceWith(fragment);
300
+ return true;
301
+ }
302
+
303
+ export function processAllComponents(appElements, loadedComponents, pageSourceFile, pageSourceContent) {
304
+ const cx = new ProcessContext(
305
+ loadedComponents, [], [], { value: null }, new Map(), [], new Map(), [], new Map()
306
+ );
307
+
308
+ appElements.forEach(el => {
309
+ if (!el.isConnected) return;
310
+ processComponentElement(el, cx, [], pageSourceFile, pageSourceContent);
311
+ });
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
+ }
320
+
321
+ return { runtimeScript, hasComponents, scopesCss, hashMap };
322
+ }
323
+
324
+ function getNextLetter(letterState) {
325
+ if (!letterState.value) {
326
+ letterState.value = "a";
327
+ } else {
328
+ letterState.value = incrementAlfabet(letterState.value);
329
+ }
330
+ return letterState.value;
331
+ }
332
+
333
+ const RUNTIME_KW = "$runtime";