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.
- package/LICENSE +1 -1
- package/README.md +24 -5
- package/compiler/component-processor.js +306 -72
- package/compiler/config.js +40 -22
- package/compiler/index.js +61 -126
- package/compiler/module-graph.js +57 -19
- package/compiler/pipeline.js +3 -1
- package/compiler/render.js +3 -1
- package/compiler/utils.js +102 -116
- package/package.json +56 -43
- package/parser/component.js +77 -5
- package/parser/index.js +1 -0
- package/parser/script.js +485 -0
- package/utils.js +39 -52
- package/dev/index.js +0 -172
- package/index.js +0 -0
- package/server/index.js +0 -402
package/compiler/config.js
CHANGED
|
@@ -1,22 +1,40 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
export async function loadConfig(rootDir) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export {
|
|
10
|
-
export {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
+
};
|
package/compiler/module-graph.js
CHANGED
|
@@ -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
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
if (
|
|
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
|
}
|
package/compiler/pipeline.js
CHANGED
|
@@ -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
|
}
|
package/compiler/render.js
CHANGED
|
@@ -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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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(/"/g, '"')
|
|
53
|
+
.replace(/&/g, "&")
|
|
54
|
+
.replace(/</g, "<")
|
|
55
|
+
.replace(/>/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";
|