chocola 2.0.0-next.6 → 2.0.0-next.8

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
@@ -1,36 +1,39 @@
1
- # chocola
2
-
3
- Chocola is a JavaScript library for creating web user interfaces.
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 until official workflows are provided.
6
-
7
- ## Usage
8
-
9
- ```html
10
- <!-- Greeting.html -->
11
- <script>
12
- let self = new HTMLDivElement;
13
- let input = new HTMLInputElement;
14
-
15
- export let title = "Hello";
16
-
17
- function $runtime() {
18
- input.focus();
19
- }
20
- </script>
21
-
22
- <template>
23
- <div>
24
- <h1>{title}</h1>
25
- <input bind:self="input" type="text" placeholder="Your name">
26
- </div>
27
- </template>
28
-
29
- <style>
30
- h1 { color: chocolate; }
31
- </style>
32
- ```
33
-
34
- ## Documentation
35
-
36
- See https://github.com/chocolajs/chocola/wiki/Overview
1
+ ![Chochola • The sweetest way to build the web](https://chocolaframework.web.app/static/img/banner.jpg)
2
+
3
+ ## What is Chocola
4
+
5
+ Chocola is a new and sweeter way to build your web apps.
6
+
7
+ No bundler config. No virtual DOM. No hydration ceremony. Just `.html` files with `<template>`, `<script>`, and `<style>` — compiled to static HTML with scoped CSS, and optional runtime when you need it.
8
+
9
+ Import components. Instantiate them. Mount, update, remove. All client-side. Same file, zero overhead.
10
+
11
+ ```html
12
+ <script>
13
+ let self;
14
+ let input;
15
+
16
+ export let title = "Hello";
17
+
18
+ function $runtime() {
19
+ input.focus();
20
+ }
21
+ </script>
22
+
23
+ <template>
24
+ <div>
25
+ <h1>{title}</h1>
26
+ <input bind:self="input" type="text" placeholder="Your name">
27
+ </div>
28
+ </template>
29
+
30
+ <style>
31
+ h1 { color: chocolate; }
32
+ </style>
33
+ ```
34
+
35
+ Chocola 2 is in current development and will add CSR SPA and much more features!
36
+
37
+ ## Documentation
38
+
39
+ https://github.com/chocolajs/chocola/wiki/Overview
package/compiler/chalk.js CHANGED
@@ -1,32 +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
+ 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,4 +1,5 @@
1
- import { JSDOM } from "jsdom";
1
+ import path from "path";
2
+ import { parseHTML } from "linkedom";
2
3
  import { protectCurlyBraces } from "../utils.js";
3
4
  import { genRandomId, incrementAlfabet, throwError, deterministicHash } from "./utils.js";
4
5
  import {
@@ -12,7 +13,7 @@ import chalk from "./chalk.js";
12
13
 
13
14
 
14
15
  class ProcessContext {
15
- constructor(loadedComponents, runtimeChunks, compIdColl, letterState, runtimeMap, cssScopes, cssScopesMap, scopedStyles, staticCtxRegistry) {
16
+ constructor(loadedComponents, runtimeChunks, compIdColl, letterState, runtimeMap, cssScopes, cssScopesMap, scopedStyles, staticCtxRegistry, csrClasses) {
16
17
  this.loadedComponents = loadedComponents;
17
18
  this.runtimeChunks = runtimeChunks;
18
19
  this.compIdColl = compIdColl;
@@ -22,9 +23,112 @@ class ProcessContext {
22
23
  this.cssScopesMap = cssScopesMap;
23
24
  this.scopedStyles = scopedStyles;
24
25
  this.staticCtxRegistry = staticCtxRegistry;
26
+ this.csrClasses = csrClasses;
25
27
  }
26
28
  }
27
29
 
30
+ function escapeForTemplateLiteral(str) {
31
+ return str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\${/g, "\\${");
32
+ }
33
+
34
+ function parseFragment(html, doc) {
35
+ const fragment = doc.createDocumentFragment();
36
+ const temp = doc.createElement("div");
37
+ temp.innerHTML = html;
38
+ const children = [...temp.childNodes];
39
+ for (const child of children) {
40
+ fragment.appendChild(child);
41
+ }
42
+ return fragment;
43
+ }
44
+
45
+ function generateCSRClass(compName, cx, explicitClassName) {
46
+ if (cx.csrClasses.has(compName)) return;
47
+
48
+ let instance = cx.loadedComponents.get(compName);
49
+ if (!instance) return;
50
+
51
+ instance = protectCurlyBraces(instance);
52
+ const dom = parseHTML(instance);
53
+ const doc = dom.document;
54
+ const script = doc.querySelector("script")?.innerHTML;
55
+ const template = doc.querySelector("template")?.innerHTML;
56
+ const styles = doc.querySelector("style")?.innerHTML;
57
+
58
+ if (!template) return;
59
+
60
+ const compProps = extractPropsDefaults(script);
61
+ const topFuncSrc = extractTopLevelFunctions(script || "", RUNTIME_KW);
62
+ let runtime = extractRuntime(script || "", compName);
63
+
64
+ if (!cx.cssScopesMap.has(compName)) {
65
+ cx.cssScopesMap.set(compName, deterministicHash(compName, 8));
66
+ }
67
+ const cssId = cx.cssScopesMap.get(compName);
68
+
69
+ if (styles) {
70
+ cx.scopedStyles.push(scopeCss(styles, cssId));
71
+ }
72
+
73
+ const childMappings = [];
74
+ const bindVarNames = new Set();
75
+ const templateDoc = parseFragment(template, doc);
76
+ const seenTags = new Set();
77
+ for (const el of templateDoc.querySelectorAll("*")) {
78
+ const tag = el.tagName.toLowerCase();
79
+ if (!seenTags.has(tag)) {
80
+ seenTags.add(tag);
81
+ const childCompName = tag + ".html";
82
+ if (cx.loadedComponents.has(childCompName)) {
83
+ generateCSRClass(childCompName, cx);
84
+ const childClassName = childCompName.replace(".html", "").replace(/^\w/, c => c.toUpperCase());
85
+ childMappings.push({ tag, compClass: childClassName });
86
+ }
87
+ }
88
+ for (const attr of el.attributes) {
89
+ if (attr.name.startsWith("bind:")) {
90
+ bindVarNames.add(attr.value);
91
+ }
92
+ }
93
+ }
94
+
95
+ let csrRuntimeSource = null;
96
+ if (runtime) {
97
+ let injectCode = "";
98
+ for (const { name, defaultValue } of compProps) {
99
+ if (defaultValue !== undefined) {
100
+ injectCode += `let ${name} = ctx.${name}??${defaultValue};\n`;
101
+ } else {
102
+ injectCode += `let ${name} = ctx.${name};\n`;
103
+ }
104
+ }
105
+ for (const varName of bindVarNames) {
106
+ if (varName !== "self") {
107
+ injectCode += `let ${varName} = ctx.${varName};\n`;
108
+ }
109
+ }
110
+ if (topFuncSrc.length > 0) {
111
+ injectCode += "\n" + topFuncSrc.join("\n\n") + "\n";
112
+ }
113
+ runtime = runtime.replace(/\$runtime\([^)]*\)\s*\{/, match => match + "\n" + injectCode);
114
+ runtime = runtime.replace(`${RUNTIME_KW}()`, `function(self, ctx)`);
115
+ csrRuntimeSource = runtime.replace(/^(async\s+)?function\s+\w+/, "$1function");
116
+ }
117
+
118
+ const className = explicitClassName || compName.replace(".html", "").replace(/^\w/, c => c.toUpperCase());
119
+ const propsObj = {};
120
+ for (const { name, defaultValue } of compProps) {
121
+ propsObj[name] = defaultValue !== undefined ? defaultValue : null;
122
+ }
123
+
124
+ const childrenPart = childMappings.length > 0
125
+ ? ",\n children: [" + childMappings.map(m => `{tag:"${m.tag}",compClass:${m.compClass}}`).join(",") + "]"
126
+ : "";
127
+ const runtimePart = csrRuntimeSource ? `,\n runtime: ${csrRuntimeSource}` : "";
128
+ const classDef = `class ${className} extends ChocolaComponent {\n constructor() {\n super({\n template: \`${escapeForTemplateLiteral(template)}\`,\n hash: "${cssId}",\n props: ${JSON.stringify(propsObj)}${runtimePart}${childrenPart}\n });\n }\n}`;
129
+ cx.csrClasses.set(compName, classDef);
130
+ }
131
+
28
132
 
29
133
 
30
134
  export function processComponentElement(
@@ -42,8 +146,8 @@ export function processComponentElement(
42
146
  if (renderChain && renderChain.includes(compName)) return false;
43
147
 
44
148
  instance = protectCurlyBraces(instance);
45
- const dom = new JSDOM(instance);
46
- const doc = dom.window.document;
149
+ const dom = parseHTML(instance);
150
+ const doc = dom.document;
47
151
  let script = doc.querySelector("script")?.innerHTML;
48
152
  let template = doc.querySelector("template")?.innerHTML;
49
153
  let styles = doc.querySelector("style")?.innerHTML;
@@ -53,6 +157,17 @@ export function processComponentElement(
53
157
  return false;
54
158
  }
55
159
 
160
+ if (script) {
161
+ const importRegex = /import\s+(\w+)\s+from\s+['"]([^'"]+)['"]\s*;?\s*/g;
162
+ script = script.replace(importRegex, (_, importedName, importPath) => {
163
+ const importedCompName = path.basename(importPath).toLowerCase();
164
+ if (cx.loadedComponents.has(importedCompName)) {
165
+ generateCSRClass(importedCompName, cx, importedName);
166
+ }
167
+ return "";
168
+ });
169
+ }
170
+
56
171
  const compProps = extractPropsDefaults(script);
57
172
 
58
173
  let ctx;
@@ -91,9 +206,9 @@ export function processComponentElement(
91
206
  get(target, key) { return target[key]; }
92
207
  });
93
208
 
94
- const fragment = JSDOM.fragment(template);
209
+ const fragment = parseFragment(template, doc);
95
210
 
96
- const slotFragment = JSDOM.fragment(elInnerHtml);
211
+ const slotFragment = parseFragment(elInnerHtml, doc);
97
212
  if (sourceFile) {
98
213
  validateChainStructure(slotFragment, sourceFile, sourceContent, elInnerHtml);
99
214
  }
@@ -188,19 +303,28 @@ export function processComponentElement(
188
303
  return;
189
304
  }
190
305
 
191
- attribute.value = attribute.value.replace(
192
- /\{([^}]+)\}/g,
193
- (_, expr) => {
194
- try {
195
- return compileExpression(expr, true)(ctxProxy);
196
- } catch {
197
- return "";
306
+ child.setAttribute(
307
+ attribute.name,
308
+ attribute.value.replace(
309
+ /\{([^}]+)\}/g,
310
+ (_, expr) => {
311
+ try {
312
+ return compileExpression(expr, true)(ctxProxy);
313
+ } catch {
314
+ return "";
315
+ }
198
316
  }
199
- }
317
+ )
200
318
  );
201
319
  });
202
320
 
203
- processComponentElement(
321
+ const condAttrs = {};
322
+ if (hasIf) condAttrs["if"] = child.getAttribute("if");
323
+ if (hasDelIf) condAttrs["mount:if"] = getDelIfAttr(child);
324
+ if (hasElif) condAttrs["elif"] = child.getAttribute("elif");
325
+ if (hasElse) condAttrs["else"] = "";
326
+
327
+ const processed = processComponentElement(
204
328
  child,
205
329
  cx,
206
330
  renderChain.concat(compName),
@@ -208,11 +332,20 @@ export function processComponentElement(
208
332
  template
209
333
  );
210
334
 
211
- applyConditionalToElement(child, ctxProxy, condChain, hasIf, hasDelIf, hasElif, hasElse);
335
+ let condTarget = child;
336
+ if (processed && processed.nodeType === 1) {
337
+ condTarget = processed;
338
+ for (const [name, value] of Object.entries(condAttrs)) {
339
+ condTarget.setAttribute(name, value);
340
+ }
341
+ }
342
+
343
+ applyConditionalToElement(condTarget, ctxProxy, condChain, hasIf, hasDelIf, hasElif, hasElse);
212
344
 
213
- interpolateNode(child, ctxProxy)
345
+ interpolateNode(condTarget, ctxProxy)
214
346
  });
215
347
 
348
+ let csrRuntimeSource = null;
216
349
  const firstChild = fragment.children[0];
217
350
 
218
351
  if (firstChild && firstChild.nodeType === 1) {
@@ -233,14 +366,14 @@ export function processComponentElement(
233
366
  const ctxDefParts = [];
234
367
  const declared = new Set();
235
368
  for (const [key, value] of Object.entries(runtimeCtx)) {
236
- ctxDefParts.push(`let ${key} = ctx.${key}||${JSON.stringify(value)};\n`);
369
+ ctxDefParts.push(`let ${key} = ctx.${key}??${JSON.stringify(value)};\n`);
237
370
  declared.add(key);
238
371
  }
239
372
 
240
373
  for (const { name, defaultValue } of compProps) {
241
374
  if (declared.has(name)) continue;
242
375
  if (defaultValue !== undefined) {
243
- ctxDefParts.push(`let ${name} = ctx.${name}||${defaultValue};\n`);
376
+ ctxDefParts.push(`let ${name} = ctx.${name}??${defaultValue};\n`);
244
377
  } else {
245
378
  ctxDefParts.push(`let ${name} = ctx.${name};\n`);
246
379
  }
@@ -272,6 +405,7 @@ export function processComponentElement(
272
405
  runtime = runtime.replace(/\$runtime\([^)]*\)\s*\{/, match => match + "\n" + injectCode);
273
406
 
274
407
  runtime = runtime.replace(`${RUNTIME_KW}()`, `${letter}r(self, ctx)`);
408
+ csrRuntimeSource = runtime.replace(/^function\s+\w+/, "function");
275
409
  cx.runtimeChunks.push(runtime);
276
410
  cx.runtimeMap && cx.runtimeMap.set(compName, { letter });
277
411
  } else {
@@ -296,15 +430,38 @@ export function processComponentElement(
296
430
  cx.scopedStyles.push(styles);
297
431
  }
298
432
 
433
+ if (csrRuntimeSource && !cx.csrClasses.has(compName)) {
434
+ generateCSRClass(compName, cx);
435
+ }
436
+
437
+ if (element.style.display === "none" && firstChild && firstChild.nodeType === 1) {
438
+ firstChild.style.display = "none";
439
+ }
440
+
299
441
  element.replaceWith(fragment);
300
- return true;
442
+ return firstChild && firstChild.nodeType === 1 ? firstChild : true;
301
443
  }
302
444
 
303
445
  export function processAllComponents(appElements, loadedComponents, pageSourceFile, pageSourceContent) {
304
446
  const cx = new ProcessContext(
305
- loadedComponents, [], [], { value: null }, new Map(), [], new Map(), [], new Map()
447
+ loadedComponents, [], [], { value: null }, new Map(), [], new Map(), [], new Map(), new Map()
306
448
  );
307
449
 
450
+ for (const [compName, instance] of cx.loadedComponents) {
451
+ const script = instance.match(/<script>([\s\S]*?)<\/script>/i);
452
+ if (script) {
453
+ const importRegex = /import\s+(\w+)\s+from\s+['"]([^'"]+)['"]\s*;?\s*/g;
454
+ let match;
455
+ while ((match = importRegex.exec(script[1])) !== null) {
456
+ const importedName = match[1];
457
+ const importedCompName = path.basename(match[2]).toLowerCase();
458
+ if (cx.loadedComponents.has(importedCompName)) {
459
+ generateCSRClass(importedCompName, cx, importedName);
460
+ }
461
+ }
462
+ }
463
+ }
464
+
308
465
  appElements.forEach(el => {
309
466
  if (!el.isConnected) return;
310
467
  processComponentElement(el, cx, [], pageSourceFile, pageSourceContent);
@@ -312,13 +469,14 @@ export function processAllComponents(appElements, loadedComponents, pageSourceFi
312
469
  const runtimeScript = cx.runtimeChunks.join("\n");
313
470
  const hasComponents = cx.runtimeChunks.length > 0;
314
471
  const scopesCss = cx.scopedStyles.join("\n");
472
+ const csrClasses = [...cx.csrClasses.values()].join("\n\n");
315
473
 
316
474
  const hashMap = {};
317
475
  for (const [compName, hash] of cx.cssScopesMap) {
318
476
  hashMap[compName] = hash;
319
477
  }
320
478
 
321
- return { runtimeScript, hasComponents, scopesCss, hashMap };
479
+ return { runtimeScript, hasComponents, scopesCss, hashMap, csrClasses };
322
480
  }
323
481
 
324
482
  function getNextLetter(letterState) {
@@ -1,4 +1,4 @@
1
- import { JSDOM } from "jsdom";
1
+ import { parseHTML } from "linkedom";
2
2
  import { promises as fs } from "fs";
3
3
  import path from "path";
4
4
  import { protectCurlyBraces, restoreCurlyBraces } from "../utils.js";
@@ -7,7 +7,7 @@ import { throwError } from "./utils.js";
7
7
  import { readMyFile } from "./fs.js";
8
8
 
9
9
  export function createDOM(srcIndexContent) {
10
- return new JSDOM(protectCurlyBraces(srcIndexContent));
10
+ return parseHTML(protectCurlyBraces(srcIndexContent));
11
11
  }
12
12
 
13
13
  export function validateAppContainer(doc) {
@@ -23,7 +23,7 @@ export function getAppElements(appContainer) {
23
23
  }
24
24
 
25
25
  export async function serializeDOM(dom) {
26
- return restoreCurlyBraces(dom.serialize());
26
+ return restoreCurlyBraces(dom.document.toString());
27
27
  }
28
28
 
29
29
  export async function writeHTMLOutput(html, outDirPath) {
@@ -54,7 +54,6 @@ export function getScriptElements(doc) {
54
54
 
55
55
  export function appendRuntimeScript(doc, filename) {
56
56
  const runtimeScriptEl = doc.createElement("script");
57
- runtimeScriptEl.type = "module";
58
57
  runtimeScriptEl.src = "./" + filename;
59
58
  doc.body.appendChild(runtimeScriptEl);
60
59
  }
package/compiler/index.js CHANGED
@@ -26,48 +26,42 @@ import {
26
26
  processScript,
27
27
  processStylesheet,
28
28
  } from "./pipeline.js";
29
+ import { text } from "stream/consumers";
29
30
 
30
-
31
- const logSeparation = chalk.yellow(`
32
- ________________________________________________________________________
33
- ========================================================================
34
- `);
31
+ const GOLD_COLOR = "#D87416";
32
+ const WHITE_COLOR = "#FAFAF8";
33
+ const TEXT_FAINT = "#E7EBE1";
35
34
 
36
35
  function logBanner() {
37
- console.log(chalk.bold.hex("#945e33")(`\n RUNNING CHOCOLA BUNDLER`));
38
- console.log(logSeparation);
39
36
  console.log(
40
- chalk.hex("#945e33")(`
41
-
42
-
43
- ▄████▄ ██░ ██ ▒█████ ▄████▄ ▒█████ ██▓ ▄▄▄
44
- ▒██▀ ▀█ ▓██░ ██▒▒██▒ ██▒▒██▀ ▀█ ▒██▒ ██▒▓██▒ ▒████▄
45
- ▒▓█ ▄ ▒██▀▀██░▒██░ ██▒▒▓█ ▄ ▒██░ ██▒▒██░ ▒██ ▀█▄
46
- ▒▓▓▄ ▄██▒░▓█ ░██ ▒██ ██░▒▓▓▄ ▄██▒▒██ ██░▒██░ ░██▄▄▄▄██
47
- ▒ ▓███▀ ░░▓█▒░██▓░ ████▓▒░▒ ▓███▀ ░░ ████▓▒░░██████▒▓█ ▓██▒
48
- ░ ░▒ ▒ ░ ▒ ░░▒░▒░ ▒░▒░▒░ ░ ░▒ ▒ ░░ ▒░▒░▒░ ░ ▒░▓ ░▒▒ ▓▒█░
49
- ░ ▒ ▒ ░▒░ ░ ░ ░ ▒ ▒░ ░ ▒ ░ ░ ▒░ ░ ░ ▒ ░ ░ ▒▒ ░
50
- ░ ░ ░░ ░░ ░ ░ ░ ▒ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ▒
51
- ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
52
- ░ ░
53
-
54
-
55
- `)
37
+ chalk.hex(GOLD_COLOR)(`
38
+ ┌─────────────────────────────────────────────┐
39
+ │┌-------------------------------------------┐│
40
+ ││ ││`)
41
+ );
42
+ console.log(
43
+ chalk.hex(GOLD_COLOR)(` ││ `) +
44
+ chalk.bold.hex(WHITE_COLOR)(`{`) +
45
+ chalk.bold.hex(GOLD_COLOR)(` C H O C O L A `) +
46
+ chalk.bold.hex(WHITE_COLOR)(`}`) +
47
+ chalk.hex(GOLD_COLOR)(` ││\n`) +
48
+ chalk.hex(GOLD_COLOR)(` ││ ││
49
+ ││ `) +
50
+ chalk.hex(TEXT_FAINT)(`THE SWEETEST WAY TO BUILD THE WEB`) +
51
+ chalk.hex(GOLD_COLOR)(` ││
52
+ ││ ││
53
+ │└-------------------------------------------┘│
54
+ └─────────────────────────────────────────────┘
55
+ `)
56
56
  );
57
57
  }
58
58
 
59
59
  function logSuccess(outDirPath) {
60
- console.log(`
61
- ▄▄ ▄▄▄ ▄▄▄▄ ▄▄▄▄ ▄▄▄ ▄▄ ▄▄ ▄▄▄▄▄ ██
62
- ██ ██▀██ ██▄██ ██▀██ ██▀██ ███▄██ ██▄▄ ██
63
- ▄▄█▀ ▀███▀ ██▄█▀ ████▀ ▀███▀ ██ ▀██ ██▄▄▄ ▄▄
64
-
65
- `);
66
60
  console.log(
67
61
  chalk.bold.green(">"),
68
62
  "Project bundled succesfully at",
69
- chalk.green.underline(outDirPath) + "\n\n"
70
- );
63
+ chalk.green.underline(outDirPath));
64
+ console.log(chalk.bold.green(`\nJOB DONE!\n`));
71
65
  }
72
66
 
73
67
  async function setupOutputDirectory(outDirPath, emptyOutDir) {
@@ -81,9 +75,8 @@ async function loadAndDisplayComponents(srcComponentsPath) {
81
75
  const foundComponents = await getComponents(srcComponentsPath);
82
76
  const { loadedComponents, notDefComps: emptyComps, componentsLib } = foundComponents;
83
77
 
84
- console.log(` LOADING COMPONENTS`);
85
78
  console.log(chalk.bold.green(">"), "Components found in", chalk.green.underline(srcComponentsPath) + ":");
86
- console.log(" ", componentsLib, "\n");
79
+ console.log(" ", componentsLib, "\n\n");
87
80
 
88
81
  if (emptyComps?.length > 0) {
89
82
  console.warn(chalk.bold.yellow("WARNING!"), "The following component files are empty:");
@@ -139,7 +132,7 @@ function processPageConditionals(parent) {
139
132
  if (result) {
140
133
  chainRendered = true;
141
134
  } else {
142
- child.remove();
135
+ child.style.display = "none";
143
136
  chainRendered = false;
144
137
  }
145
138
  child.removeAttribute("if");
@@ -188,7 +181,6 @@ export default async function compile(rootDir, buildConfig) {
188
181
 
189
182
  const config = await loadConfig(rootDir);
190
183
  const paths = resolvePaths(rootDir, config);
191
- !isHotReload && console.log(logSeparation);
192
184
 
193
185
  await setupOutputDirectory(paths.outDir, config.emptyOutDir);
194
186
 
@@ -197,20 +189,18 @@ export default async function compile(rootDir, buildConfig) {
197
189
  const pageSourcePath = indexFiles.srcPath;
198
190
 
199
191
  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);
192
+ !isHotReload && console.log(chalk.bold.green(">"), "Creating Chocola static build in directory", chalk.green.underline(paths.outDir));
204
193
 
205
194
  const dom = createDOM(srcIndexContent);
206
- const doc = dom.window.document;
195
+ const doc = dom.document;
207
196
  const appContainer = validateAppContainer(doc);
208
197
 
209
198
  processPageConditionals(appContainer);
210
199
 
211
200
  const appElements = getAppElements(appContainer);
212
- const { runtimeScript, scopesCss, hashMap } = processAllComponents(appElements, loadedComponents, pageSourcePath, srcIndexContent);
213
- const runtimeFilename = await generateRuntimeScript(runtimeScript, paths.outDir);
201
+ const { runtimeScript, scopesCss, hashMap, csrClasses } = processAllComponents(appElements, loadedComponents, pageSourcePath, srcIndexContent);
202
+ const csrSource = await fs.readFile(new URL("../runtime/index.js", import.meta.url), "utf-8");
203
+ const runtimeFilenames = await generateRuntimeScript(runtimeScript, paths.outDir, csrSource, csrClasses);
214
204
  await processAssets(doc, rootDir, config.srcDir, paths.outDir);
215
205
 
216
206
  if (scopesCss) {
@@ -219,7 +209,9 @@ export default async function compile(rootDir, buildConfig) {
219
209
  appendStylesheetLink(doc, fileName);
220
210
  };
221
211
 
222
- appendRuntimeScript(doc, runtimeFilename);
212
+ for (const name of runtimeFilenames) {
213
+ appendRuntimeScript(doc, name);
214
+ }
223
215
  const html = await serializeDOM(dom);
224
216
  await writeHTMLOutput(html, paths.outDir);
225
217
 
@@ -2,12 +2,29 @@ import { promises as fs } from "fs";
2
2
  import path from "path";
3
3
  import { genRandomId } from "./utils.js";
4
4
 
5
- export async function generateRuntimeScript(runtimeScript, outDirPath) {
5
+ export async function generateRuntimeScript(runtimeScript, outDirPath, csrSource, csrClasses) {
6
6
  const fileIds = [];
7
- const runtimeFilename = "run-" + genRandomId(fileIds, 6) + ".js";
8
- const runtimeFileContents = `document.addEventListener("DOMContentLoaded", () => {${runtimeScript}})`;
7
+ const filenames = [];
9
8
 
10
- await fs.writeFile(path.join(outDirPath, runtimeFilename), runtimeFileContents);
9
+ if (csrSource) {
10
+ const name = "run-" + genRandomId(fileIds, 6) + ".js";
11
+ const source = csrSource.replace(/^export\s+\{?\s*[^}]+\s*}?\s*;?\s*$/m, "");
12
+ await fs.writeFile(path.join(outDirPath, name), source);
13
+ filenames.push(name);
14
+ }
11
15
 
12
- return runtimeFilename;
16
+ if (csrClasses) {
17
+ const name = "run-" + genRandomId(fileIds, 6) + ".js";
18
+ await fs.writeFile(path.join(outDirPath, name), csrClasses);
19
+ filenames.push(name);
20
+ }
21
+
22
+ if (runtimeScript) {
23
+ const name = "run-" + genRandomId(fileIds, 6) + ".js";
24
+ const content = `document.addEventListener("DOMContentLoaded", () => {${runtimeScript}})`;
25
+ await fs.writeFile(path.join(outDirPath, name), content);
26
+ filenames.push(name);
27
+ }
28
+
29
+ return filenames;
13
30
  }