chocola 2.0.0-next.11 → 2.0.0-next.12

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
@@ -7,7 +7,7 @@
7
7
 
8
8
  Chocola is a new and sweeter way to build your web apps.
9
9
 
10
- No bundler config. No virtual DOM. No hydration ceremony. Just `.html` files with `<template>`, `<script>`, and `<style>` compiled to HTML with scoped CSS the browser already understands, and optional runtime when you need it.
10
+ Just `.html` files with `<template>`, `<script>`, and `<style>` compiled to HTML with scoped CSS the browser already understands, and optional runtime when you need it.
11
11
 
12
12
  Import components. Instantiate them. Mount, update, remove. Client-side or server-side. Same file, minimal overhead.
13
13
 
@@ -37,19 +37,26 @@ Import components. Instantiate them. Mount, update, remove. Client-side or serve
37
37
  </style>
38
38
  ```
39
39
 
40
+ ## Supporting Chocola
41
+
42
+ As of now, Chocola is a project driven by one developer (me, [@sadgabi20](https://github.com/sadgabi20)), but made to be community driven in the future. Therefore, the only current way to support Chocola is via my [GitHub Sponsors](https://github.com/sponsors/sadgabi20).
43
+
44
+ If this project achieves a wider support, it will have its own official support channels.
45
+
40
46
  ## Documentation
41
47
 
42
48
  - [Getting started](documentation/01-introduction/02-getting-started.md)
43
49
  - [Project structure](documentation/01-introduction/03-project-structure.md)
44
- - [CLI reference](documentation/07-reference/04-cli.md)
50
+ - [Build API reference](documentation/07-reference/04-cli.md)
45
51
 
46
- ## Quick start
52
+ ## Roadmap
47
53
 
48
- ```sh
49
- npm install chocola
50
- npx chocola dev # dev server
51
- npx chocola build # production build
52
- npx chocola serve # SSR production server
53
- ```
54
+ If you want to know what is being worked on right now, see the [Roadmap](ROADMAP.md).
55
+
56
+ ## Contributing
57
+
58
+ Please follow the [Contribution Guidelines](CONTRIBUTING.md) to know how to contribute to Chocola.
59
+
60
+ ## License
54
61
 
55
- No init scripts required: `chocola.config.json` is optional. See [Getting started](documentation/01-introduction/02-getting-started.md) for more.
62
+ [MIT](LICENSE)
@@ -8,9 +8,6 @@ import {
8
8
  reservedAttrs, validateChainStructure, applyConditionalToElement, interpolateNode,
9
9
  scopeCss, compileExpr, evaluateConstant,
10
10
  } from "../parser/index.js";
11
- import chalk from "./chalk.js";
12
-
13
-
14
11
 
15
12
  class ProcessContext {
16
13
  constructor(loadedComponents, runtimeChunks, compIdColl, runtimeMap, cssScopes, cssScopesMap, scopedStyles, staticCtxRegistry, csrClasses, treeShakeRuntime = true, originalNames = null) {
@@ -112,7 +109,6 @@ function generateCSRClass(compName, cx, explicitClassName) {
112
109
  reach = computeReachable(parsedForReach, { bindings: [...bindVarNames] });
113
110
  if (reach.fallback) {
114
111
  // conservative: include all and warn
115
- console.warn(chalk.yellow(`WARN ${displayName} — dynamic $runtime, including all declarations`));
116
112
  }
117
113
  }
118
114
  const injectedNames = new Set(compProps.map(p => p.name));
@@ -297,9 +293,8 @@ export function processComponentElement(
297
293
  let template = doc.querySelector("template")?.innerHTML;
298
294
  let styles = doc.querySelector("style")?.innerHTML;
299
295
 
300
- if (!template) {
301
- console.warn(chalk.yellow(`${displayName} — component is missing a <template>`));
302
- return false;
296
+ if (!template) {
297
+ return false;
303
298
  }
304
299
 
305
300
  // Store original script for reachability (before stripping)
@@ -324,7 +319,6 @@ export function processComponentElement(
324
319
  for (const imp of importsToGenerate) {
325
320
  const isComponent = imp.source.toLowerCase().endsWith(".html");
326
321
  if (!isComponent) {
327
- console.warn(chalk.yellow(`WARN ${displayName} — JS import "${imp.source}" is client-reachable but not bundled (Phase 1: dropping)`));
328
322
  continue;
329
323
  }
330
324
  const importedCompName = path.basename(imp.source).toLowerCase();
@@ -674,9 +668,8 @@ export function processComponentElement(
674
668
  if (parsedReach.ast) {
675
669
  const bindNames = bindings.map(b => b.varName);
676
670
  reachInject = computeReachable(parsedReach, { bindings: bindNames });
677
- if (reachInject.fallback) {
678
- console.warn(chalk.yellow(`WARN ${displayName} — dynamic $runtime, including all declarations`));
679
- reachInject = null;
671
+ if (reachInject.fallback) {
672
+ reachInject = null;
680
673
  }
681
674
  }
682
675
  }
@@ -1,52 +1,40 @@
1
- import path from "path";
2
- import { promises as fsp } from "fs";
3
- import chalk from "./chalk.js";
4
- import { getConfig, isMissingConfigFile, queueConfigWarning } from "../utils.js";
5
-
6
- const warnedBundleFields = new Set();
7
-
8
- export async function loadConfig(rootDir, { silent, customPath, overrides } = {}) {
9
- const config = customPath
10
- ? await fsp.readFile(customPath, "utf-8").then(r => JSON.parse(r))
11
- : await getConfig(rootDir, { silent });
12
-
13
- let srcDir = "src", outDir = "dist", libDir = "lib", emptyOutDir = true, treeShakeRuntime = true;
14
-
15
- if (!customPath || !isMissingConfigFile(config)) {
16
- const hasBundle = (config.bundle !== undefined && config.bundle !== null) || (config.build !== undefined && config.build !== null);
17
- const bundleConfig = config.bundle || config.build || {};
18
- const compilerConfig = config.compiler || {};
19
- srcDir = bundleConfig.srcDir || "src";
20
- outDir = bundleConfig.outDir || "dist";
21
- libDir = bundleConfig.libDir || "lib";
22
- emptyOutDir = bundleConfig.emptyOutDir !== false;
23
- treeShakeRuntime = compilerConfig.treeShakeRuntime !== false;
24
-
25
- if (!silent && hasBundle) {
26
- if (bundleConfig.srcDir == null && !warnedBundleFields.has(rootDir + ":srcDir")) {
27
- warnedBundleFields.add(rootDir + ":srcDir");
28
- queueConfigWarning(rootDir, chalk.bold.yellow("WARNING!"), `bundle.srcDir not defined in chocola.config.json file: using default "src" bundle.srcDir.`);
29
- }
30
- if (bundleConfig.outDir == null && !warnedBundleFields.has(rootDir + ":outDir")) {
31
- warnedBundleFields.add(rootDir + ":outDir");
32
- queueConfigWarning(rootDir, chalk.bold.yellow("WARNING!"), `bundle.outDir not defined in chocola.config.json file: using default "dist" bundle.outDir.`);
33
- }
34
- if (bundleConfig.libDir == null && !warnedBundleFields.has(rootDir + ":libDir")) {
35
- warnedBundleFields.add(rootDir + ":libDir");
36
- queueConfigWarning(rootDir, chalk.bold.yellow("WARNING!"), `bundle.libDir not defined in chocola.config.json file: using default "lib" bundle.libDir.`);
37
- }
38
- }
39
- }
40
-
41
- const result = { srcDir, outDir, libDir, emptyOutDir, treeShakeRuntime };
42
- if (overrides) { Object.assign(result, overrides); }
43
- return result;
44
- }
45
-
46
- export function resolvePaths(rootDir, config) {
47
- return {
48
- outDir: path.isAbsolute(config.outDir) ? config.outDir : path.join(rootDir, config.outDir),
49
- src: path.isAbsolute(config.srcDir) ? config.srcDir : path.join(rootDir, config.srcDir),
50
- components: path.isAbsolute(config.srcDir) ? path.join(config.srcDir, config.libDir) : path.join(rootDir, config.srcDir, config.libDir),
51
- };
52
- }
1
+ import path from "path";
2
+ import { promises as fsp } from "fs";
3
+
4
+ export async function loadConfig(rootDir, { silent, customPath, overrides } = {}) {
5
+ let config;
6
+ try {
7
+ const raw = customPath
8
+ ? await fsp.readFile(customPath, "utf-8").then(r => JSON.parse(r))
9
+ : await fsp.readFile(path.join(rootDir, "chocola.config.json"), "utf-8").then(r => JSON.parse(r));
10
+ config = raw;
11
+ } catch {
12
+ if (!silent) {
13
+ // Config file missing — silently use defaults
14
+ }
15
+ config = {};
16
+ }
17
+
18
+ let srcDir = "src", outDir = "dist", libDir = "lib", emptyOutDir = true, treeShakeRuntime = true;
19
+
20
+ const hasBundle = (config.bundle !== undefined && config.bundle !== null) || (config.build !== undefined && config.build !== null);
21
+ const bundleConfig = config.bundle || config.build || {};
22
+ const compilerConfig = config.compiler || {};
23
+ srcDir = bundleConfig.srcDir || "src";
24
+ outDir = bundleConfig.outDir || "dist";
25
+ libDir = bundleConfig.libDir || "lib";
26
+ emptyOutDir = bundleConfig.emptyOutDir !== false;
27
+ treeShakeRuntime = compilerConfig.treeShakeRuntime !== false;
28
+
29
+ const result = { srcDir, outDir, libDir, emptyOutDir, treeShakeRuntime };
30
+ if (overrides) { Object.assign(result, overrides); }
31
+ return result;
32
+ }
33
+
34
+ export function resolvePaths(rootDir, config) {
35
+ return {
36
+ outDir: path.isAbsolute(config.outDir) ? config.outDir : path.join(rootDir, config.outDir),
37
+ src: path.isAbsolute(config.srcDir) ? config.srcDir : path.join(rootDir, config.srcDir),
38
+ components: path.isAbsolute(config.srcDir) ? path.join(config.srcDir, config.libDir) : path.join(rootDir, config.srcDir, config.libDir),
39
+ };
40
+ }
package/compiler/index.js CHANGED
@@ -1,134 +1,61 @@
1
- import { promises as fs } from "fs";
2
- import path from "path";
3
- import { performance } from "perf_hooks";
4
- import chalk from "./chalk.js";
5
- import { buildModuleGraph } from "./module-graph.js";
6
- import { renderPage } from "./render.js";
7
- import { writeHTMLOutput } from "./dom-processor.js";
8
- import { flushConfigWarnings } from "../utils.js";
9
-
10
- export { buildModuleGraph } from "./module-graph.js";
11
- export { renderPage } from "./render.js";
12
- export { ChocolaModule, ModuleGraph } from "./module-graph.js";
13
-
14
- const GOLD_COLOR = "#D87416";
15
- const WHITE_COLOR = "#FAFAF8";
16
- const TEXT_FAINT = "#E7EBE1";
17
-
18
- export function logBanner() {
19
- const plain = process.env.NO_COLOR === "1";
20
- const ch = plain ? (() => (s) => s) : chalk;
21
- console.log(
22
- ch.hex(GOLD_COLOR)(`
23
- ┌─────────────────────────────────────────────┐
24
- │┌-------------------------------------------┐│
25
- ││ ││`)
26
- );
27
- console.log(
28
- ch.hex(GOLD_COLOR)(` ││ `) +
29
- ch.bold.hex(WHITE_COLOR)(`{`) +
30
- ch.bold.hex(GOLD_COLOR)(` C H O C O L A `) +
31
- ch.bold.hex(WHITE_COLOR)(`}`) +
32
- ch.hex(GOLD_COLOR)(` ││\n`) +
33
- ch.hex(GOLD_COLOR)(` ││ ││
34
- ││ `) +
35
- ch.hex(TEXT_FAINT)(`THE SWEETEST WAY TO BUILD THE WEB`) +
36
- ch.hex(GOLD_COLOR)(` ││
37
- ││ ││
38
- │└-------------------------------------------┘│
39
- └─────────────────────────────────────────────┘
40
- `)
41
- );
42
- }
43
-
44
- function logSuccess(outDirPath, durationMs) {
45
- const plain = process.env.NO_COLOR === "1";
46
- const ch = plain ? (() => (s) => s) : chalk;
47
- console.log(ch.bold.green(`\nJOB DONE!`) + ch.hex(TEXT_FAINT)(` (${formatDuration(durationMs)})\n`));
48
- }
49
-
50
- function formatDuration(ms) {
51
- if (ms >= 1000) return (ms / 1000).toFixed(2) + "s";
52
- return Math.round(ms) + "ms";
53
- }
54
-
55
- async function setupOutputDirectory(outDirPath, emptyOutDir) {
56
- if (emptyOutDir) {
57
- await fs.rm(outDirPath, { recursive: true, force: true });
58
- await fs.mkdir(outDirPath);
59
- }
60
- }
61
-
62
- export async function emit(graph, options = {}) {
63
- const outDir = graph.paths.outDir;
64
- await setupOutputDirectory(outDir, graph.config.emptyOutDir);
65
-
66
- const result = await renderPage(graph, options.ctx);
67
-
68
- for (const file of result.files) {
69
- await fs.writeFile(path.join(outDir, file.path), file.content);
70
- }
71
-
72
- for (const copy of result.copies) {
73
- if (copy.recursive) {
74
- await fs.cp(copy.from, copy.to, { recursive: true, force: true });
75
- } else {
76
- await fs.copyFile(copy.from, copy.to);
77
- }
78
- }
79
-
80
- await writeHTMLOutput(result.html, outDir);
81
-
82
- const chocolaDir = path.join(graph.rootDir, ".chocola");
83
- await fs.mkdir(chocolaDir, { recursive: true });
84
- await fs.writeFile(path.join(chocolaDir, "hashes.json"), JSON.stringify(result.hashMap, null, 2) + "\n");
85
-
86
- return result;
87
- }
88
-
89
- export default async function compile(rootDir, buildConfig) {
90
- const isHotReload = buildConfig?.isHotReload || null;
91
- const overrides = buildConfig?.overrides || null;
92
- const startTime = performance.now();
93
- !isHotReload && logBanner();
94
-
95
- const graph = await buildModuleGraph(rootDir, { overrides });
96
-
97
- !isHotReload && console.log(chalk.bold.green(">"), "Creating Chocola static build in directory", chalk.green.underline(graph.paths.outDir));
98
-
99
- await emit(graph);
100
-
101
- const durationMs = performance.now() - startTime;
102
-
103
- if (!isHotReload) {
104
- flushConfigWarnings(rootDir);
105
- logSuccess(graph.paths.outDir, durationMs);
106
- }
107
- if (isHotReload) {
108
- flushConfigWarnings(rootDir);
109
- console.log("Dev server updated " + chalk.hex(TEXT_FAINT)(`(${formatDuration(durationMs)})`));
110
- }
111
- }
112
-
113
- /**
114
- * An intrinsic object that contains the Chocola App methods.
115
- */
116
- export const app = {
117
- /**
118
- * Initializes your Chocola App using a root directory.
119
- *
120
- * ```js
121
- * import { app } from "chocola/compiler"
122
- import path from "path";
123
- import { fileURLToPath } from "url";
124
-
125
- const __filename = fileURLToPath(import.meta.url);
126
- const __dirname = path.dirname(__filename);
127
-
128
- app.build(__dirname);
129
- ```
130
- * @example
131
- * @param {PathLike} __rootdir the directory where your Chocola App is
132
- */
133
- async build(__rootdir) { return compile(__rootdir) }
134
- };
1
+ import { promises as fs } from "fs";
2
+ import path from "path";
3
+ import { performance } from "perf_hooks";
4
+ import { buildModuleGraph } from "./module-graph.js";
5
+ import { renderPage } from "./render.js";
6
+ import { writeHTMLOutput } from "./dom-processor.js";
7
+
8
+ export { buildModuleGraph } from "./module-graph.js";
9
+ export { renderPage } from "./render.js";
10
+ export { ChocolaModule, ModuleGraph } from "./module-graph.js";
11
+
12
+ async function setupOutputDirectory(outDirPath, emptyOutDir) {
13
+ if (emptyOutDir) {
14
+ await fs.rm(outDirPath, { recursive: true, force: true });
15
+ await fs.mkdir(outDirPath);
16
+ }
17
+ }
18
+
19
+ export async function emit(graph, options = {}) {
20
+ const outDir = graph.paths.outDir;
21
+ await setupOutputDirectory(outDir, graph.config.emptyOutDir);
22
+
23
+ const result = await renderPage(graph, options.ctx);
24
+
25
+ for (const file of result.files) {
26
+ await fs.writeFile(path.join(outDir, file.path), file.content);
27
+ }
28
+
29
+ for (const copy of result.copies) {
30
+ if (copy.recursive) {
31
+ await fs.cp(copy.from, copy.to, { recursive: true, force: true });
32
+ } else {
33
+ await fs.copyFile(copy.from, copy.to);
34
+ }
35
+ }
36
+
37
+ await writeHTMLOutput(result.html, outDir);
38
+
39
+ const chocolaDir = path.join(graph.rootDir, ".chocola");
40
+ await fs.mkdir(chocolaDir, { recursive: true });
41
+ await fs.writeFile(path.join(chocolaDir, "hashes.json"), JSON.stringify(result.hashMap, null, 2) + "\n");
42
+
43
+ return result;
44
+ }
45
+
46
+ export default async function compile(rootDir, buildConfig = {}) {
47
+ const isHotReload = buildConfig?.isHotReload || null;
48
+ const overrides = buildConfig?.overrides || null;
49
+ const silent = buildConfig?.silent || false;
50
+ const startTime = performance.now();
51
+
52
+ const graph = await buildModuleGraph(rootDir, { overrides });
53
+
54
+ await emit(graph);
55
+
56
+ const durationMs = performance.now() - startTime;
57
+ }
58
+
59
+ export const app = {
60
+ async build(__rootdir) { return compile(__rootdir) }
61
+ };
@@ -1,7 +1,6 @@
1
1
  import path from "path";
2
2
  import { promises as fs } from "fs";
3
3
  import { parseHTML } from "linkedom";
4
- import chalk from "./chalk.js";
5
4
  import { loadConfig, resolvePaths } from "./config.js";
6
5
  import { getComponents, getSrcIndex } from "./pipeline.js";
7
6
  import { protectCurlyBraces } from "../utils.js";
@@ -216,14 +215,6 @@ export async function buildModuleGraph(rootDir, { overrides } = {}) {
216
215
  const foundComponents = await getComponents(paths.components);
217
216
  const { loadedComponents, originalNames, componentsLib, emptyComps } = foundComponents;
218
217
 
219
- console.log(chalk.bold.green(">"), "Components found in", chalk.green.underline(paths.components) + ":");
220
- console.log(" ", componentsLib, "\n\n");
221
-
222
- if (emptyComps?.length > 0) {
223
- console.warn(chalk.bold.yellow("WARNING!"), "The following component files are empty:");
224
- console.log(" ", emptyComps);
225
- }
226
-
227
218
  for (const filename of componentsLib) {
228
219
  const compName = filename.toLowerCase();
229
220
  const source = loadedComponents.get(compName);
package/compiler/utils.js CHANGED
@@ -1,116 +1,102 @@
1
- import { createHash } from "crypto";
2
- import chalk from "./chalk.js";
3
-
4
- export function throwError(err) {
5
- console.log(chalk.red.bold("Error!"), "A fatal error has occurred:\n");
6
- throw new Error(err);
7
- }
8
-
9
- export function warnConstantCondition(location, tag, attr, expr, truthy) {
10
- const article = /^[aeiou]/.test(attr) ? "an" : "a";
11
- console.warn(
12
- chalk.bold.yellow("WARN "),
13
- `${chalk.bold.green(location)} ${chalk.dim('—')} ${chalk.blue(`<${tag}>`)} \`${chalk.bold(attr + "={" + expr + "}")}\` is always ${chalk.green(truthy ? "truthy" : "falsy")}`
14
- );
15
- }
16
-
17
- export function warnUnusedDeclaration(location, kind, name) {
18
- console.warn(
19
- chalk.bold.yellow("WARN "),
20
- `${chalk.bold.green(location)} ${chalk.dim('—')} ${kind} \`${chalk.blue(name)}\` is never used`
21
- );
22
- }
23
-
24
- function normalizeAttributeQuotes(html) {
25
- return html
26
- .replace(/(\s[\w:.-]+)=(['"])([\s\S]*?)\2/g, '$1="$3"')
27
- .replace(/(\s[\w:.-]+)=([^\s"'<>=]+)/g, '$1="$2"');
28
- }
29
-
30
- function getStartTag(outerHTML) {
31
- let inQuote = null;
32
- for (let i = 0; i < outerHTML.length; i++) {
33
- const c = outerHTML[i];
34
- if (inQuote) {
35
- if (c === inQuote) inQuote = null;
36
- } else if (c === '"' || c === "'") {
37
- inQuote = c;
38
- } else if (c === ">") {
39
- return outerHTML.slice(0, i + 1);
40
- }
41
- }
42
- return outerHTML;
43
- }
44
-
45
- function escapeRegex(str) {
46
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
47
- }
48
-
49
- function flexibleStartTagRegex(startTag) {
50
- return new RegExp(
51
- startTag
52
- .split(/\s+/)
53
- .filter(Boolean)
54
- .map(escapeRegex)
55
- .join("\\s+")
56
- );
57
- }
58
-
59
- export function findElementLine(sourceContent, outerHTML) {
60
- const source = normalizeAttributeQuotes(sourceContent).toLowerCase().replace(/\/\s*>/g, ">");
61
- const startTags = [
62
- normalizeAttributeQuotes(getStartTag(outerHTML)),
63
- normalizeAttributeQuotes(
64
- getStartTag(
65
- outerHTML
66
- .replace(/&quot;/g, '"')
67
- .replace(/&amp;/g, "&")
68
- .replace(/&lt;/g, "<")
69
- .replace(/&gt;/g, ">")
70
- )
71
- ),
72
- ].map(tag => tag.toLowerCase());
73
- const candidates = [
74
- flexibleStartTagRegex(startTags[0]),
75
- flexibleStartTagRegex(startTags[0].replace(/=""/g, "")),
76
- flexibleStartTagRegex(startTags[1]),
77
- ];
78
- for (const re of candidates) {
79
- const match = re.exec(source);
80
- if (match) {
81
- return source.substring(0, match.index).split("\n").length;
82
- }
83
- }
84
- return null;
85
- }
86
-
87
- export function genRandomId(collection = null, length = 10, lettersOnly = false) {
88
- let id;
89
- if (lettersOnly) {
90
- id = Array.from({ length }, () => ID_LETTERS[Math.floor(Math.random() * ID_LETTERS.length)]).join("");
91
- } else {
92
- id = Math.random().toString(36).substring(2, length + 2);
93
- }
94
- if (!collection) return id;
95
- if (collection.includes(id)) {
96
- return genRandomId(collection, length, lettersOnly);
97
- } else {
98
- collection.push(id);
99
- return id;
100
- }
101
- }
102
-
103
- export function deterministicHash(content, length = 8) {
104
- const hash = createHash("sha256").update(content).digest();
105
- let result = "";
106
- for (let i = 0; i < length; i++) {
107
- result += String.fromCharCode(97 + (hash[i % hash.length] % 26));
108
- }
109
- return result;
110
- }
111
-
112
- export function runtimeFunctionId(compName) {
113
- return "r_" + deterministicHash(compName, 8);
114
- }
115
-
116
- const ID_LETTERS = "abcdefghijklmnopqrstuvwxyz";
1
+ import { createHash } from "crypto";
2
+
3
+ export function throwError(err) {
4
+ throw new Error(err);
5
+ }
6
+
7
+ export function warnConstantCondition() {}
8
+ export function warnUnusedDeclaration() {}
9
+
10
+ function normalizeAttributeQuotes(html) {
11
+ return html
12
+ .replace(/(\s[\w:.-]+)=(['"])([\s\S]*?)\2/g, '$1="$3"')
13
+ .replace(/(\s[\w:.-]+)=([^\s"'<>=]+)/g, '$1="$2"');
14
+ }
15
+
16
+ function getStartTag(outerHTML) {
17
+ let inQuote = null;
18
+ for (let i = 0; i < outerHTML.length; i++) {
19
+ const c = outerHTML[i];
20
+ if (inQuote) {
21
+ if (c === inQuote) inQuote = null;
22
+ } else if (c === '"' || c === "'") {
23
+ inQuote = c;
24
+ } else if (c === ">") {
25
+ return outerHTML.slice(0, i + 1);
26
+ }
27
+ }
28
+ return outerHTML;
29
+ }
30
+
31
+ function escapeRegex(str) {
32
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
33
+ }
34
+
35
+ function flexibleStartTagRegex(startTag) {
36
+ return new RegExp(
37
+ startTag
38
+ .split(/\s+/)
39
+ .filter(Boolean)
40
+ .map(escapeRegex)
41
+ .join("\\s+")
42
+ );
43
+ }
44
+
45
+ export function findElementLine(sourceContent, outerHTML) {
46
+ const source = normalizeAttributeQuotes(sourceContent).toLowerCase().replace(/\/\s*>/g, ">");
47
+ const startTags = [
48
+ normalizeAttributeQuotes(getStartTag(outerHTML)),
49
+ normalizeAttributeQuotes(
50
+ getStartTag(
51
+ outerHTML
52
+ .replace(/&quot;/g, '"')
53
+ .replace(/&amp;/g, "&")
54
+ .replace(/&lt;/g, "<")
55
+ .replace(/&gt;/g, ">")
56
+ )
57
+ ),
58
+ ].map(tag => tag.toLowerCase());
59
+ const candidates = [
60
+ flexibleStartTagRegex(startTags[0]),
61
+ flexibleStartTagRegex(startTags[0].replace(/=""/g, "")),
62
+ flexibleStartTagRegex(startTags[1]),
63
+ ];
64
+ for (const re of candidates) {
65
+ const match = re.exec(source);
66
+ if (match) {
67
+ return source.substring(0, match.index).split("\n").length;
68
+ }
69
+ }
70
+ return null;
71
+ }
72
+
73
+ export function genRandomId(collection = null, length = 10, lettersOnly = false) {
74
+ let id;
75
+ if (lettersOnly) {
76
+ id = Array.from({ length }, () => ID_LETTERS[Math.floor(Math.random) * ID_LETTERS.length]).join("");
77
+ } else {
78
+ id = Math.random().toString(36).substring(2, length + 2);
79
+ }
80
+ if (!collection) return id;
81
+ if (collection.includes(id)) {
82
+ return genRandomId(collection, length, lettersOnly);
83
+ } else {
84
+ collection.push(id);
85
+ return id;
86
+ }
87
+ }
88
+
89
+ export function deterministicHash(content, length = 8) {
90
+ const hash = createHash("sha256").update(content).digest();
91
+ let result = "";
92
+ for (let i = 0; i < length; i++) {
93
+ result += String.fromCharCode(97 + (hash[i % hash.length] % 26));
94
+ }
95
+ return result;
96
+ }
97
+
98
+ export function runtimeFunctionId(compName) {
99
+ return "r_" + deterministicHash(compName, 8);
100
+ }
101
+
102
+ const ID_LETTERS = "abcdefghijklmnopqrstuvwxyz";