chocola 2.0.0-next.7 → 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,35 +1,39 @@
1
- # chocola
2
-
3
- **The sweetest way to build the web.**
4
-
5
- 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.
6
-
7
- Import components. Instantiate them. Mount, update, remove. All client-side. Same file, zero overhead.
8
-
9
- ```html
10
- <script>
11
- let self = new HTMLDivElement;
12
- let input = new HTMLInputElement;
13
-
14
- export let title = "Hello";
15
-
16
- function $runtime() {
17
- input.focus();
18
- }
19
- </script>
20
-
21
- <template>
22
- <div>
23
- <h1>{title}</h1>
24
- <input bind:self="input" type="text" placeholder="Your name">
25
- </div>
26
- </template>
27
-
28
- <style>
29
- h1 { color: chocolate; }
30
- </style>
31
- ```
32
-
33
- ## Documentation
34
-
35
- 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
@@ -1,5 +1,5 @@
1
1
  import path from "path";
2
- import { JSDOM } from "jsdom";
2
+ import { parseHTML } from "linkedom";
3
3
  import { protectCurlyBraces } from "../utils.js";
4
4
  import { genRandomId, incrementAlfabet, throwError, deterministicHash } from "./utils.js";
5
5
  import {
@@ -31,6 +31,17 @@ function escapeForTemplateLiteral(str) {
31
31
  return str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\${/g, "\\${");
32
32
  }
33
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
+
34
45
  function generateCSRClass(compName, cx, explicitClassName) {
35
46
  if (cx.csrClasses.has(compName)) return;
36
47
 
@@ -38,8 +49,8 @@ function generateCSRClass(compName, cx, explicitClassName) {
38
49
  if (!instance) return;
39
50
 
40
51
  instance = protectCurlyBraces(instance);
41
- const dom = new JSDOM(instance);
42
- const doc = dom.window.document;
52
+ const dom = parseHTML(instance);
53
+ const doc = dom.document;
43
54
  const script = doc.querySelector("script")?.innerHTML;
44
55
  const template = doc.querySelector("template")?.innerHTML;
45
56
  const styles = doc.querySelector("style")?.innerHTML;
@@ -61,7 +72,7 @@ function generateCSRClass(compName, cx, explicitClassName) {
61
72
 
62
73
  const childMappings = [];
63
74
  const bindVarNames = new Set();
64
- const templateDoc = JSDOM.fragment(template);
75
+ const templateDoc = parseFragment(template, doc);
65
76
  const seenTags = new Set();
66
77
  for (const el of templateDoc.querySelectorAll("*")) {
67
78
  const tag = el.tagName.toLowerCase();
@@ -86,7 +97,7 @@ function generateCSRClass(compName, cx, explicitClassName) {
86
97
  let injectCode = "";
87
98
  for (const { name, defaultValue } of compProps) {
88
99
  if (defaultValue !== undefined) {
89
- injectCode += `let ${name} = ctx.${name}||${defaultValue};\n`;
100
+ injectCode += `let ${name} = ctx.${name}??${defaultValue};\n`;
90
101
  } else {
91
102
  injectCode += `let ${name} = ctx.${name};\n`;
92
103
  }
@@ -135,8 +146,8 @@ export function processComponentElement(
135
146
  if (renderChain && renderChain.includes(compName)) return false;
136
147
 
137
148
  instance = protectCurlyBraces(instance);
138
- const dom = new JSDOM(instance);
139
- const doc = dom.window.document;
149
+ const dom = parseHTML(instance);
150
+ const doc = dom.document;
140
151
  let script = doc.querySelector("script")?.innerHTML;
141
152
  let template = doc.querySelector("template")?.innerHTML;
142
153
  let styles = doc.querySelector("style")?.innerHTML;
@@ -195,9 +206,9 @@ export function processComponentElement(
195
206
  get(target, key) { return target[key]; }
196
207
  });
197
208
 
198
- const fragment = JSDOM.fragment(template);
209
+ const fragment = parseFragment(template, doc);
199
210
 
200
- const slotFragment = JSDOM.fragment(elInnerHtml);
211
+ const slotFragment = parseFragment(elInnerHtml, doc);
201
212
  if (sourceFile) {
202
213
  validateChainStructure(slotFragment, sourceFile, sourceContent, elInnerHtml);
203
214
  }
@@ -292,19 +303,28 @@ export function processComponentElement(
292
303
  return;
293
304
  }
294
305
 
295
- attribute.value = attribute.value.replace(
296
- /\{([^}]+)\}/g,
297
- (_, expr) => {
298
- try {
299
- return compileExpression(expr, true)(ctxProxy);
300
- } catch {
301
- 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
+ }
302
316
  }
303
- }
317
+ )
304
318
  );
305
319
  });
306
320
 
307
- 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(
308
328
  child,
309
329
  cx,
310
330
  renderChain.concat(compName),
@@ -312,9 +332,17 @@ export function processComponentElement(
312
332
  template
313
333
  );
314
334
 
315
- 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);
316
344
 
317
- interpolateNode(child, ctxProxy)
345
+ interpolateNode(condTarget, ctxProxy)
318
346
  });
319
347
 
320
348
  let csrRuntimeSource = null;
@@ -338,14 +366,14 @@ export function processComponentElement(
338
366
  const ctxDefParts = [];
339
367
  const declared = new Set();
340
368
  for (const [key, value] of Object.entries(runtimeCtx)) {
341
- ctxDefParts.push(`let ${key} = ctx.${key}||${JSON.stringify(value)};\n`);
369
+ ctxDefParts.push(`let ${key} = ctx.${key}??${JSON.stringify(value)};\n`);
342
370
  declared.add(key);
343
371
  }
344
372
 
345
373
  for (const { name, defaultValue } of compProps) {
346
374
  if (declared.has(name)) continue;
347
375
  if (defaultValue !== undefined) {
348
- ctxDefParts.push(`let ${name} = ctx.${name}||${defaultValue};\n`);
376
+ ctxDefParts.push(`let ${name} = ctx.${name}??${defaultValue};\n`);
349
377
  } else {
350
378
  ctxDefParts.push(`let ${name} = ctx.${name};\n`);
351
379
  }
@@ -406,8 +434,12 @@ export function processComponentElement(
406
434
  generateCSRClass(compName, cx);
407
435
  }
408
436
 
437
+ if (element.style.display === "none" && firstChild && firstChild.nodeType === 1) {
438
+ firstChild.style.display = "none";
439
+ }
440
+
409
441
  element.replaceWith(fragment);
410
- return true;
442
+ return firstChild && firstChild.nodeType === 1 ? firstChild : true;
411
443
  }
412
444
 
413
445
  export function processAllComponents(appElements, loadedComponents, pageSourceFile, pageSourceContent) {
@@ -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) {
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,13 +189,10 @@ 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);
package/dev/index.js CHANGED
@@ -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.8",
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
  }
package/parser/context.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { compileExpression } from "./utils.js";
2
+ import { restoreTemplateChars } from "../utils.js";
2
3
 
3
4
  export function extractContextFromElement(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) {
@@ -19,11 +20,11 @@ export function extractContextFromElement(element) {
19
20
  }
20
21
 
21
22
  export function hasDelIfAttr(el) {
22
- return el.hasAttribute("del:if");
23
+ return el.hasAttribute("mount:if");
23
24
  }
24
25
  export function getDelIfAttr(el) {
25
- return el.getAttribute("del:if");
26
+ return el.getAttribute("mount:if");
26
27
  }
27
28
  export function removeDelIfAttr(el) {
28
- el.removeAttribute("del:if");
29
+ el.removeAttribute("mount:if");
29
30
  }
@@ -1,7 +1,7 @@
1
1
  import { compileExpression } from "./utils.js";
2
2
  import { hasDelIfAttr, getDelIfAttr, removeDelIfAttr } 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
6
  export function getLineNumber(sourceContent, parentContent, idxInParent) {
7
7
  if (sourceContent === parentContent) {
package/parser/utils.js CHANGED
@@ -1,6 +1,9 @@
1
+ import { restoreTemplateChars } from "../utils.js";
2
+
1
3
  const compiledCache = new Map();
2
4
 
3
5
  export function compileExpression(expr, useCtx) {
6
+ expr = restoreTemplateChars(expr);
4
7
  const key = useCtx ? `ctx:${expr}` : `raw:${expr}`;
5
8
  let fn = compiledCache.get(key);
6
9
  if (!fn) {
package/runtime/index.js CHANGED
@@ -1,6 +1,12 @@
1
+ const LBRACE_PH = "_%%CHOCOLA-LBRACE%%_";
2
+ const RBRACE_PH = "_%%CHOCOLA-RBRACE%%_";
3
+
1
4
  const exprCache = new Map();
2
5
 
3
6
  function compileExpression(expr, useCtx) {
7
+ expr = expr.replace(/_%%CHOCOLA-(?:AMP|LT|GT)\d+%%_/g, char =>
8
+ char.includes("AMP") ? "&" : char.includes("LT") ? "<" : ">"
9
+ );
4
10
  const key = useCtx ? "ctx:" + expr : "raw:" + expr;
5
11
  let fn = exprCache.get(key);
6
12
  if (!fn) {
@@ -47,7 +53,7 @@ function applyConditionalToElement(child, ctx, chain, hasIf, hasDelIf, hasElif,
47
53
  }
48
54
  child.removeAttribute("if");
49
55
  } else if (hasDelIf) {
50
- const expr = child.getAttribute("del:if").slice(1, -1);
56
+ const expr = child.getAttribute("mount:if").slice(1, -1);
51
57
  chain.active = true;
52
58
  if (compileExpression(expr, true)(ctx)) {
53
59
  chain.rendered = true;
@@ -55,7 +61,7 @@ function applyConditionalToElement(child, ctx, chain, hasIf, hasDelIf, hasElif,
55
61
  child.remove();
56
62
  chain.rendered = false;
57
63
  }
58
- child.removeAttribute("del:if");
64
+ child.removeAttribute("mount:if");
59
65
  } else if (hasElif) {
60
66
  const expr = child.getAttribute("elif").slice(1, -1);
61
67
  if (compileExpression(expr, true)(ctx)) {
@@ -74,9 +80,6 @@ function applyConditionalToElement(child, ctx, chain, hasIf, hasDelIf, hasElif,
74
80
  }
75
81
  }
76
82
 
77
- const LBRACE_PH = "_%%CHOCOLA-LBRACE%%_";
78
- const RBRACE_PH = "_%%CHOCOLA-RBRACE%%_";
79
-
80
83
  function interpolateAttributes(element, ctx) {
81
84
  for (const el of [element, ...element.querySelectorAll("*")]) {
82
85
  for (const attr of [...el.attributes]) {
@@ -99,7 +102,7 @@ function interpolateAttributes(element, ctx) {
99
102
  function processConditionals(element, ctx, chain) {
100
103
  for (const child of [...element.children]) {
101
104
  const hasIf = child.hasAttribute("if");
102
- const hasDelIf = child.hasAttribute("del:if");
105
+ const hasDelIf = child.hasAttribute("mount:if");
103
106
  const hasElif = child.hasAttribute("elif");
104
107
  const hasElse = child.hasAttribute("else");
105
108
 
@@ -255,7 +258,7 @@ class ChocolaComponent {
255
258
  for (const el of els) {
256
259
  const props = {};
257
260
  for (const attr of [...el.attributes]) {
258
- if (attr.name.startsWith("data-ch") || attr.name === "if" || attr.name === "del:if" || attr.name === "elif" || attr.name === "else") continue;
261
+ if (attr.name.startsWith("data-ch") || attr.name === "if" || attr.name === "mount:if" || attr.name === "elif" || attr.name === "else") continue;
259
262
  props[attr.name] = attr.value;
260
263
  }
261
264
  const instance = new compClass();
package/utils.js CHANGED
@@ -13,15 +13,40 @@ export async function getChocolaConfig(__rootdir) {
13
13
 
14
14
  const LBRACE_PH = "_%%CHOCOLA-LBRACE%%_";
15
15
  const RBRACE_PH = "_%%CHOCOLA-RBRACE%%_";
16
+ const PROTECTED_REGEX = /_%%CHOCOLA-(?:AMP|LT|GT)\d+%%_/g;
17
+
18
+ let protectSeq = 0;
16
19
 
17
20
  export function protectCurlyBraces(html) {
18
21
  return html
19
22
  .replace(/&(?:lbrace|#123|#x7B);/gi, LBRACE_PH)
20
- .replace(/&(?:rcub|#125|#x7D);/gi, RBRACE_PH);
23
+ .replace(/&(?:rcub|#125|#x7D);/gi, RBRACE_PH)
24
+ .split(/(<script\b[^>]*>[\s\S]*?<\/script>|<style\b[^>]*>[\s\S]*?<\/style>)/gi)
25
+ .map((part, i) => i % 2 === 0
26
+ ? part.replace(/\{([^{}]*)\}/g, (m, inner) =>
27
+ `{${inner
28
+ .replace(/&/g, () => `_%%CHOCOLA-AMP${protectSeq++}%%_`)
29
+ .replace(/</g, () => `_%%CHOCOLA-LT${protectSeq++}%%_`)
30
+ .replace(/>/g, () => `_%%CHOCOLA-GT${protectSeq++}%%_`)}}`)
31
+ : part)
32
+ .join("");
21
33
  }
22
34
 
23
35
  export function restoreCurlyBraces(html) {
24
36
  return html
25
37
  .replace(/_%%CHOCOLA-LBRACE%%_/g, "{")
26
- .replace(/_%%CHOCOLA-RBRACE%%_/g, "}");
38
+ .replace(/_%%CHOCOLA-RBRACE%%_/g, "}")
39
+ .replace(PROTECTED_REGEX, char => {
40
+ if (char.includes("AMP")) return "&";
41
+ if (char.includes("LT")) return "<";
42
+ return ">";
43
+ });
44
+ }
45
+
46
+ export function restoreTemplateChars(str) {
47
+ return str.replace(PROTECTED_REGEX, char => {
48
+ if (char.includes("AMP")) return "&";
49
+ if (char.includes("LT")) return "<";
50
+ return ">";
51
+ });
27
52
  }