chocola 2.0.0-next.10 → 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.
@@ -1,22 +1,40 @@
1
- import path from "path";
2
- import { getConfig } from "../utils.js";
3
-
4
- export async function loadConfig(rootDir) {
5
- const config = await getConfig(rootDir);
6
- const bundleConfig = config.bundle || {};
7
-
8
- const srcDir = bundleConfig.srcDir || "src";
9
- const outDir = bundleConfig.outDir || "dist";
10
- const libDir = bundleConfig.libDir || "lib";
11
- const emptyOutDir = bundleConfig.emptyOutDir !== false;
12
-
13
- return { srcDir, outDir, libDir, emptyOutDir };
14
- }
15
-
16
- export function resolvePaths(rootDir, config) {
17
- return {
18
- outDir: path.join(rootDir, config.outDir),
19
- src: path.join(rootDir, config.srcDir),
20
- components: path.join(rootDir, config.srcDir, config.libDir),
21
- };
22
- }
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,126 +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
-
9
- export { buildModuleGraph } from "./module-graph.js";
10
- export { renderPage } from "./render.js";
11
- export { ChocolaModule, ModuleGraph } from "./module-graph.js";
12
-
13
- const GOLD_COLOR = "#D87416";
14
- const WHITE_COLOR = "#FAFAF8";
15
- const TEXT_FAINT = "#E7EBE1";
16
-
17
- function logBanner() {
18
- console.log(
19
- chalk.hex(GOLD_COLOR)(`
20
- ┌─────────────────────────────────────────────┐
21
- │┌-------------------------------------------┐│
22
- ││ ││`)
23
- );
24
- console.log(
25
- chalk.hex(GOLD_COLOR)(` ││ `) +
26
- chalk.bold.hex(WHITE_COLOR)(`{`) +
27
- chalk.bold.hex(GOLD_COLOR)(` C H O C O L A `) +
28
- chalk.bold.hex(WHITE_COLOR)(`}`) +
29
- chalk.hex(GOLD_COLOR)(` ││\n`) +
30
- chalk.hex(GOLD_COLOR)(` ││ ││
31
- ││ `) +
32
- chalk.hex(TEXT_FAINT)(`THE SWEETEST WAY TO BUILD THE WEB`) +
33
- chalk.hex(GOLD_COLOR)(` ││
34
- ││ ││
35
- │└-------------------------------------------┘│
36
- └─────────────────────────────────────────────┘
37
- `)
38
- );
39
- }
40
-
41
- function logSuccess(outDirPath, durationMs) {
42
- console.log(
43
- chalk.bold.green(">"),
44
- "Project bundled succesfully at",
45
- chalk.green.underline(outDirPath));
46
- console.log(chalk.bold.green(`\nJOB DONE!`) + chalk.hex(TEXT_FAINT)(` (${formatDuration(durationMs)})\n`));
47
- }
48
-
49
- function formatDuration(ms) {
50
- if (ms >= 1000) return (ms / 1000).toFixed(2) + "s";
51
- return Math.round(ms) + "ms";
52
- }
53
-
54
- async function setupOutputDirectory(outDirPath, emptyOutDir) {
55
- if (emptyOutDir) {
56
- await fs.rm(outDirPath, { recursive: true, force: true });
57
- await fs.mkdir(outDirPath);
58
- }
59
- }
60
-
61
- export async function emit(graph, options = {}) {
62
- const outDir = graph.paths.outDir;
63
- await setupOutputDirectory(outDir, graph.config.emptyOutDir);
64
-
65
- const result = await renderPage(graph, options.ctx);
66
-
67
- for (const file of result.files) {
68
- await fs.writeFile(path.join(outDir, file.path), file.content);
69
- }
70
-
71
- for (const copy of result.copies) {
72
- if (copy.recursive) {
73
- await fs.cp(copy.from, copy.to, { recursive: true, force: true });
74
- } else {
75
- await fs.copyFile(copy.from, copy.to);
76
- }
77
- }
78
-
79
- await writeHTMLOutput(result.html, outDir);
80
-
81
- const chocolaDir = path.join(graph.rootDir, ".chocola");
82
- await fs.mkdir(chocolaDir, { recursive: true });
83
- await fs.writeFile(path.join(chocolaDir, "hashes.json"), JSON.stringify(result.hashMap, null, 2) + "\n");
84
-
85
- return result;
86
- }
87
-
88
- export default async function compile(rootDir, buildConfig) {
89
- const isHotReload = buildConfig?.isHotReload || null;
90
- const startTime = performance.now();
91
- !isHotReload && logBanner();
92
-
93
- const graph = await buildModuleGraph(rootDir);
94
-
95
- !isHotReload && console.log(chalk.bold.green(">"), "Creating Chocola static build in directory", chalk.green.underline(graph.paths.outDir));
96
-
97
- await emit(graph);
98
-
99
- const durationMs = performance.now() - startTime;
100
-
101
- !isHotReload && logSuccess(graph.paths.outDir, durationMs);
102
- isHotReload && console.log("Dev server updated " + chalk.hex(TEXT_FAINT)(`(${formatDuration(durationMs)})`));
103
- }
104
-
105
- /**
106
- * An intrinsic object that contains the Chocola App methods.
107
- */
108
- export const app = {
109
- /**
110
- * Initializes your Chocola App using a root directory.
111
- *
112
- * ```js
113
- * import { app } from "chocola/compiler"
114
- import path from "path";
115
- import { fileURLToPath } from "url";
116
-
117
- const __filename = fileURLToPath(import.meta.url);
118
- const __dirname = path.dirname(__filename);
119
-
120
- app.build(__dirname);
121
- ```
122
- * @example
123
- * @param {PathLike} __rootdir the directory where your Chocola App is
124
- */
125
- async build(__rootdir) { return compile(__rootdir) }
126
- };
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,14 +1,13 @@
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";
8
7
  import { deterministicHash, runtimeFunctionId } from "./utils.js";
9
8
  import { createDOM, getAssetLinks, getScriptElements } from "./dom-processor.js";
10
9
  import {
11
- extractPropsDefaults, extractTopLevelFunctions, extractTopLevelVariables,
10
+ extractPropsDefaults, extractTopLevelFunctions, extractTopLevelVariables, parseScript, computeReachable,
12
11
  } from "../parser/index.js";
13
12
 
14
13
  const RUNTIME_KW = "$runtime";
@@ -31,6 +30,7 @@ export class ChocolaModule {
31
30
  this.topFuncs = [];
32
31
  this.topVars = [];
33
32
  this.imports = [];
33
+ this.neededClientImports = [];
34
34
  this.cssId = null;
35
35
  this.fnId = null;
36
36
  }
@@ -44,6 +44,7 @@ export class ModuleGraph {
44
44
  this.modules = new Map();
45
45
  this.components = new Map();
46
46
  this.loadedComponents = new Map();
47
+ this.originalComponentNames = new Map();
47
48
  this.page = null;
48
49
  }
49
50
 
@@ -92,13 +93,55 @@ function compileComponentModule(module, graph) {
92
93
 
93
94
  const deps = new Set();
94
95
  if (module.script) {
95
- const importRegex = /import\s+(\w+)\s+from\s+['"]([^'"]+)['"]\s*;?\s*/g;
96
- let match;
97
- while ((match = importRegex.exec(module.script)) !== null) {
98
- const importedCompName = path.basename(match[2]).toLowerCase();
99
- const importedModule = graph.component(importedCompName);
100
- if (importedModule) deps.add(importedModule.id);
96
+ const parsed = parseScript(module.script);
97
+ if (parsed.ast) {
98
+ module.imports = parsed.imports;
99
+ let importsForDeps = parsed.imports;
100
+ const treeShake = graph.config.treeShakeRuntime !== false;
101
+ if (treeShake && parsed.runtimeNode) {
102
+ const reach = computeReachable(parsed, { bindings: [] });
103
+ if (!reach.fallback) {
104
+ const neededSet = new Set(reach.neededImports);
105
+ importsForDeps = parsed.imports.filter(imp => neededSet.has(imp));
106
+ }
107
+ module.neededClientImports = importsForDeps;
108
+ } else if (!treeShake) {
109
+ // Feature flag disabled: include all imports for deps
110
+ importsForDeps = parsed.imports;
111
+ module.neededClientImports = importsForDeps;
112
+ } else {
113
+ importsForDeps = [];
114
+ module.neededClientImports = [];
115
+ }
116
+ for (const imp of importsForDeps) {
117
+ const isComponent = imp.source.toLowerCase().endsWith(".html");
118
+ if (!isComponent) {
119
+ // Phase 1: JS imports that are client-reachable are warned in component-processor (render-time)
120
+ // to avoid duplicate warnings at graph build time. Silently skip for deps.
121
+ continue;
122
+ }
123
+ const importedCompName = path.basename(imp.source).toLowerCase();
124
+ const importedModule = graph.component(importedCompName);
125
+ if (importedModule) deps.add(importedModule.id);
126
+ }
127
+ // Template deps added below will cover component usage in template even when
128
+ // script import is not client-reachable (server-only).
129
+ // Keep full import list in module.imports for reference.
130
+ } else {
131
+ // Fallback to regex for scripts that failed to parse (e.g., syntax errors)
132
+ module.imports = [];
133
+ module.neededClientImports = [];
134
+ const importRegex = /import\s+(\w+)\s+from\s+['"]([^'"]+)['"]\s*;?\s*/g;
135
+ let match;
136
+ while ((match = importRegex.exec(module.script)) !== null) {
137
+ const importedCompName = path.basename(match[2]).toLowerCase();
138
+ const importedModule = graph.component(importedCompName);
139
+ if (importedModule) deps.add(importedModule.id);
140
+ }
101
141
  }
142
+ } else {
143
+ module.imports = [];
144
+ module.neededClientImports = [];
102
145
  }
103
146
  if (module.template) {
104
147
  const frag = parseHTML(module.template).document;
@@ -152,8 +195,8 @@ function scanAssetModules(doc, graph) {
152
195
  return deps;
153
196
  }
154
197
 
155
- export async function buildModuleGraph(rootDir) {
156
- const config = await loadConfig(rootDir);
198
+ export async function buildModuleGraph(rootDir, { overrides } = {}) {
199
+ const config = overrides ? await loadConfig(rootDir, { silent: true, overrides }) : await loadConfig(rootDir);
157
200
  const paths = resolvePaths(rootDir, config);
158
201
 
159
202
  const graph = new ModuleGraph(rootDir, config, paths);
@@ -170,15 +213,7 @@ export async function buildModuleGraph(rootDir) {
170
213
  graph.page = pageModule;
171
214
 
172
215
  const foundComponents = await getComponents(paths.components);
173
- const { loadedComponents, componentsLib, emptyComps } = foundComponents;
174
-
175
- console.log(chalk.bold.green(">"), "Components found in", chalk.green.underline(paths.components) + ":");
176
- console.log(" ", componentsLib, "\n\n");
177
-
178
- if (emptyComps?.length > 0) {
179
- console.warn(chalk.bold.yellow("WARNING!"), "The following component files are empty:");
180
- console.log(" ", emptyComps);
181
- }
216
+ const { loadedComponents, originalNames, componentsLib, emptyComps } = foundComponents;
182
217
 
183
218
  for (const filename of componentsLib) {
184
219
  const compName = filename.toLowerCase();
@@ -195,6 +230,9 @@ export async function buildModuleGraph(rootDir) {
195
230
  graph.addModule(module);
196
231
  }
197
232
 
233
+ // Track original filename casing for log display (fallback to lowercase tag)
234
+ graph.originalComponentNames = originalNames;
235
+
198
236
  for (const module of graph.modules.values()) {
199
237
  if (module.kind === "component") compileComponentModule(module, graph);
200
238
  }
@@ -7,6 +7,7 @@ export async function getComponents(libDir) {
7
7
  try {
8
8
  let componentsLib = [];
9
9
  let loadedComponents = new Map();
10
+ let originalNames = new Map();
10
11
  let emptyComps = [];
11
12
 
12
13
  const components = await fs.readdir(libDir);
@@ -25,6 +26,7 @@ export async function getComponents(libDir) {
25
26
  if (instance === "" || instance.trim().length === 0) emptyComps.push(comp);
26
27
 
27
28
  loadedComponents.set(comp.toLowerCase(), instance);
29
+ originalNames.set(comp.toLowerCase(), comp);
28
30
  componentsLib.push(comp);
29
31
  } catch (err) {
30
32
  throwError(`Failed to load component "${comp}": ${err.message || err}`);
@@ -33,7 +35,7 @@ export async function getComponents(libDir) {
33
35
 
34
36
  await Promise.all(reads);
35
37
 
36
- return { componentsLib, loadedComponents, emptyComps };
38
+ return { componentsLib, loadedComponents, originalNames, emptyComps };
37
39
  } catch (err) {
38
40
  throwError(`Failed to load components from ${libDir}: ${err.message}`);
39
41
  }
@@ -154,7 +154,9 @@ export async function renderPage(graph, ctx = {}) {
154
154
  graph.loadedComponents,
155
155
  page.sourcePath,
156
156
  page.source,
157
- ctx
157
+ ctx,
158
+ graph.config.treeShakeRuntime,
159
+ graph.originalComponentNames
158
160
  );
159
161
 
160
162
  const csrSource = await fs.readFile(new URL("../runtime/index.js", import.meta.url), "utf-8");
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";