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 +17 -10
- package/compiler/component-processor.js +4 -11
- package/compiler/config.js +40 -52
- package/compiler/index.js +61 -134
- package/compiler/module-graph.js +0 -9
- package/compiler/utils.js +102 -116
- package/package.json +56 -57
- package/utils.js +39 -122
- package/bin/chocola.js +0 -227
- package/dev/index.js +0 -210
- package/parser/script.test.js +0 -103
- package/server/index.js +0 -432
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
|
-
|
|
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
|
-
- [
|
|
50
|
+
- [Build API reference](documentation/07-reference/04-cli.md)
|
|
45
51
|
|
|
46
|
-
##
|
|
52
|
+
## Roadmap
|
|
47
53
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
301
|
-
|
|
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
|
-
|
|
678
|
-
|
|
679
|
-
reachInject = null;
|
|
671
|
+
if (reachInject.fallback) {
|
|
672
|
+
reachInject = null;
|
|
680
673
|
}
|
|
681
674
|
}
|
|
682
675
|
}
|
package/compiler/config.js
CHANGED
|
@@ -1,52 +1,40 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
import { promises as fsp } from "fs";
|
|
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
|
-
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
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
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
|
-
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
|
+
};
|
package/compiler/module-graph.js
CHANGED
|
@@ -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
|
-
|
|
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";
|