chocola 2.0.0-next.6 → 2.0.0-next.7
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 +4 -5
- package/compiler/chalk.js +32 -32
- package/compiler/component-processor.js +129 -3
- package/compiler/dom-processor.js +0 -1
- package/compiler/index.js +6 -3
- package/compiler/runtime-generator.js +22 -5
- package/compiler/utils.js +52 -52
- package/package.json +1 -1
- package/parser/component.js +129 -129
- package/parser/context.js +29 -29
- package/parser/css.js +96 -96
- package/parser/index.js +5 -5
- package/parser/template.js +120 -121
- package/parser/utils.js +13 -13
- package/runtime/index.js +309 -0
package/README.md
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
# chocola
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**The sweetest way to build the web.**
|
|
4
4
|
|
|
5
|
-
|
|
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
6
|
|
|
7
|
-
|
|
7
|
+
Import components. Instantiate them. Mount, update, remove. All client-side. Same file, zero overhead.
|
|
8
8
|
|
|
9
9
|
```html
|
|
10
|
-
<!-- Greeting.html -->
|
|
11
10
|
<script>
|
|
12
11
|
let self = new HTMLDivElement;
|
|
13
12
|
let input = new HTMLInputElement;
|
|
@@ -33,4 +32,4 @@ Chocola is a JavaScript library for creating web user interfaces.
|
|
|
33
32
|
|
|
34
33
|
## Documentation
|
|
35
34
|
|
|
36
|
-
|
|
35
|
+
https://github.com/chocolajs/chocola/wiki/Overview
|
package/compiler/chalk.js
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
const ansi = {
|
|
2
|
-
bold: "\x1b[1m",
|
|
3
|
-
underline: "\x1b[4m",
|
|
4
|
-
red: "\x1b[31m",
|
|
5
|
-
green: "\x1b[32m",
|
|
6
|
-
yellow: "\x1b[33m",
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
const reset = "\x1b[0m";
|
|
10
|
-
|
|
11
|
-
function hexToAnsi(h) {
|
|
12
|
-
const r = parseInt(h.slice(1, 3), 16);
|
|
13
|
-
const g = parseInt(h.slice(3, 5), 16);
|
|
14
|
-
const b = parseInt(h.slice(5, 7), 16);
|
|
15
|
-
return `\x1b[38;2;${r};${g};${b}m`;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function makeChalk(stack) {
|
|
19
|
-
const fn = (s) => {
|
|
20
|
-
if (stack.length === 0) return s;
|
|
21
|
-
return stack.map(n => ansi[n] || hexToAnsi(n)).join("") + s + reset;
|
|
22
|
-
};
|
|
23
|
-
return new Proxy(fn, {
|
|
24
|
-
get(_, prop) {
|
|
25
|
-
if (prop === "hex") return (h) => makeChalk([...stack, h]);
|
|
26
|
-
return makeChalk([...stack, prop]);
|
|
27
|
-
},
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const chalk = makeChalk([]);
|
|
32
|
-
export default chalk;
|
|
1
|
+
const ansi = {
|
|
2
|
+
bold: "\x1b[1m",
|
|
3
|
+
underline: "\x1b[4m",
|
|
4
|
+
red: "\x1b[31m",
|
|
5
|
+
green: "\x1b[32m",
|
|
6
|
+
yellow: "\x1b[33m",
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const reset = "\x1b[0m";
|
|
10
|
+
|
|
11
|
+
function hexToAnsi(h) {
|
|
12
|
+
const r = parseInt(h.slice(1, 3), 16);
|
|
13
|
+
const g = parseInt(h.slice(3, 5), 16);
|
|
14
|
+
const b = parseInt(h.slice(5, 7), 16);
|
|
15
|
+
return `\x1b[38;2;${r};${g};${b}m`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function makeChalk(stack) {
|
|
19
|
+
const fn = (s) => {
|
|
20
|
+
if (stack.length === 0) return s;
|
|
21
|
+
return stack.map(n => ansi[n] || hexToAnsi(n)).join("") + s + reset;
|
|
22
|
+
};
|
|
23
|
+
return new Proxy(fn, {
|
|
24
|
+
get(_, prop) {
|
|
25
|
+
if (prop === "hex") return (h) => makeChalk([...stack, h]);
|
|
26
|
+
return makeChalk([...stack, prop]);
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const chalk = makeChalk([]);
|
|
32
|
+
export default chalk;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from "path";
|
|
1
2
|
import { JSDOM } from "jsdom";
|
|
2
3
|
import { protectCurlyBraces } from "../utils.js";
|
|
3
4
|
import { genRandomId, incrementAlfabet, throwError, deterministicHash } from "./utils.js";
|
|
@@ -12,7 +13,7 @@ import chalk from "./chalk.js";
|
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class ProcessContext {
|
|
15
|
-
constructor(loadedComponents, runtimeChunks, compIdColl, letterState, runtimeMap, cssScopes, cssScopesMap, scopedStyles, staticCtxRegistry) {
|
|
16
|
+
constructor(loadedComponents, runtimeChunks, compIdColl, letterState, runtimeMap, cssScopes, cssScopesMap, scopedStyles, staticCtxRegistry, csrClasses) {
|
|
16
17
|
this.loadedComponents = loadedComponents;
|
|
17
18
|
this.runtimeChunks = runtimeChunks;
|
|
18
19
|
this.compIdColl = compIdColl;
|
|
@@ -22,9 +23,101 @@ class ProcessContext {
|
|
|
22
23
|
this.cssScopesMap = cssScopesMap;
|
|
23
24
|
this.scopedStyles = scopedStyles;
|
|
24
25
|
this.staticCtxRegistry = staticCtxRegistry;
|
|
26
|
+
this.csrClasses = csrClasses;
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
function escapeForTemplateLiteral(str) {
|
|
31
|
+
return str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\${/g, "\\${");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function generateCSRClass(compName, cx, explicitClassName) {
|
|
35
|
+
if (cx.csrClasses.has(compName)) return;
|
|
36
|
+
|
|
37
|
+
let instance = cx.loadedComponents.get(compName);
|
|
38
|
+
if (!instance) return;
|
|
39
|
+
|
|
40
|
+
instance = protectCurlyBraces(instance);
|
|
41
|
+
const dom = new JSDOM(instance);
|
|
42
|
+
const doc = dom.window.document;
|
|
43
|
+
const script = doc.querySelector("script")?.innerHTML;
|
|
44
|
+
const template = doc.querySelector("template")?.innerHTML;
|
|
45
|
+
const styles = doc.querySelector("style")?.innerHTML;
|
|
46
|
+
|
|
47
|
+
if (!template) return;
|
|
48
|
+
|
|
49
|
+
const compProps = extractPropsDefaults(script);
|
|
50
|
+
const topFuncSrc = extractTopLevelFunctions(script || "", RUNTIME_KW);
|
|
51
|
+
let runtime = extractRuntime(script || "", compName);
|
|
52
|
+
|
|
53
|
+
if (!cx.cssScopesMap.has(compName)) {
|
|
54
|
+
cx.cssScopesMap.set(compName, deterministicHash(compName, 8));
|
|
55
|
+
}
|
|
56
|
+
const cssId = cx.cssScopesMap.get(compName);
|
|
57
|
+
|
|
58
|
+
if (styles) {
|
|
59
|
+
cx.scopedStyles.push(scopeCss(styles, cssId));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const childMappings = [];
|
|
63
|
+
const bindVarNames = new Set();
|
|
64
|
+
const templateDoc = JSDOM.fragment(template);
|
|
65
|
+
const seenTags = new Set();
|
|
66
|
+
for (const el of templateDoc.querySelectorAll("*")) {
|
|
67
|
+
const tag = el.tagName.toLowerCase();
|
|
68
|
+
if (!seenTags.has(tag)) {
|
|
69
|
+
seenTags.add(tag);
|
|
70
|
+
const childCompName = tag + ".html";
|
|
71
|
+
if (cx.loadedComponents.has(childCompName)) {
|
|
72
|
+
generateCSRClass(childCompName, cx);
|
|
73
|
+
const childClassName = childCompName.replace(".html", "").replace(/^\w/, c => c.toUpperCase());
|
|
74
|
+
childMappings.push({ tag, compClass: childClassName });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const attr of el.attributes) {
|
|
78
|
+
if (attr.name.startsWith("bind:")) {
|
|
79
|
+
bindVarNames.add(attr.value);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let csrRuntimeSource = null;
|
|
85
|
+
if (runtime) {
|
|
86
|
+
let injectCode = "";
|
|
87
|
+
for (const { name, defaultValue } of compProps) {
|
|
88
|
+
if (defaultValue !== undefined) {
|
|
89
|
+
injectCode += `let ${name} = ctx.${name}||${defaultValue};\n`;
|
|
90
|
+
} else {
|
|
91
|
+
injectCode += `let ${name} = ctx.${name};\n`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
for (const varName of bindVarNames) {
|
|
95
|
+
if (varName !== "self") {
|
|
96
|
+
injectCode += `let ${varName} = ctx.${varName};\n`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (topFuncSrc.length > 0) {
|
|
100
|
+
injectCode += "\n" + topFuncSrc.join("\n\n") + "\n";
|
|
101
|
+
}
|
|
102
|
+
runtime = runtime.replace(/\$runtime\([^)]*\)\s*\{/, match => match + "\n" + injectCode);
|
|
103
|
+
runtime = runtime.replace(`${RUNTIME_KW}()`, `function(self, ctx)`);
|
|
104
|
+
csrRuntimeSource = runtime.replace(/^(async\s+)?function\s+\w+/, "$1function");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const className = explicitClassName || compName.replace(".html", "").replace(/^\w/, c => c.toUpperCase());
|
|
108
|
+
const propsObj = {};
|
|
109
|
+
for (const { name, defaultValue } of compProps) {
|
|
110
|
+
propsObj[name] = defaultValue !== undefined ? defaultValue : null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const childrenPart = childMappings.length > 0
|
|
114
|
+
? ",\n children: [" + childMappings.map(m => `{tag:"${m.tag}",compClass:${m.compClass}}`).join(",") + "]"
|
|
115
|
+
: "";
|
|
116
|
+
const runtimePart = csrRuntimeSource ? `,\n runtime: ${csrRuntimeSource}` : "";
|
|
117
|
+
const classDef = `class ${className} extends ChocolaComponent {\n constructor() {\n super({\n template: \`${escapeForTemplateLiteral(template)}\`,\n hash: "${cssId}",\n props: ${JSON.stringify(propsObj)}${runtimePart}${childrenPart}\n });\n }\n}`;
|
|
118
|
+
cx.csrClasses.set(compName, classDef);
|
|
119
|
+
}
|
|
120
|
+
|
|
28
121
|
|
|
29
122
|
|
|
30
123
|
export function processComponentElement(
|
|
@@ -53,6 +146,17 @@ export function processComponentElement(
|
|
|
53
146
|
return false;
|
|
54
147
|
}
|
|
55
148
|
|
|
149
|
+
if (script) {
|
|
150
|
+
const importRegex = /import\s+(\w+)\s+from\s+['"]([^'"]+)['"]\s*;?\s*/g;
|
|
151
|
+
script = script.replace(importRegex, (_, importedName, importPath) => {
|
|
152
|
+
const importedCompName = path.basename(importPath).toLowerCase();
|
|
153
|
+
if (cx.loadedComponents.has(importedCompName)) {
|
|
154
|
+
generateCSRClass(importedCompName, cx, importedName);
|
|
155
|
+
}
|
|
156
|
+
return "";
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
56
160
|
const compProps = extractPropsDefaults(script);
|
|
57
161
|
|
|
58
162
|
let ctx;
|
|
@@ -213,6 +317,7 @@ export function processComponentElement(
|
|
|
213
317
|
interpolateNode(child, ctxProxy)
|
|
214
318
|
});
|
|
215
319
|
|
|
320
|
+
let csrRuntimeSource = null;
|
|
216
321
|
const firstChild = fragment.children[0];
|
|
217
322
|
|
|
218
323
|
if (firstChild && firstChild.nodeType === 1) {
|
|
@@ -272,6 +377,7 @@ export function processComponentElement(
|
|
|
272
377
|
runtime = runtime.replace(/\$runtime\([^)]*\)\s*\{/, match => match + "\n" + injectCode);
|
|
273
378
|
|
|
274
379
|
runtime = runtime.replace(`${RUNTIME_KW}()`, `${letter}r(self, ctx)`);
|
|
380
|
+
csrRuntimeSource = runtime.replace(/^function\s+\w+/, "function");
|
|
275
381
|
cx.runtimeChunks.push(runtime);
|
|
276
382
|
cx.runtimeMap && cx.runtimeMap.set(compName, { letter });
|
|
277
383
|
} else {
|
|
@@ -296,15 +402,34 @@ export function processComponentElement(
|
|
|
296
402
|
cx.scopedStyles.push(styles);
|
|
297
403
|
}
|
|
298
404
|
|
|
405
|
+
if (csrRuntimeSource && !cx.csrClasses.has(compName)) {
|
|
406
|
+
generateCSRClass(compName, cx);
|
|
407
|
+
}
|
|
408
|
+
|
|
299
409
|
element.replaceWith(fragment);
|
|
300
410
|
return true;
|
|
301
411
|
}
|
|
302
412
|
|
|
303
413
|
export function processAllComponents(appElements, loadedComponents, pageSourceFile, pageSourceContent) {
|
|
304
414
|
const cx = new ProcessContext(
|
|
305
|
-
loadedComponents, [], [], { value: null }, new Map(), [], new Map(), [], new Map()
|
|
415
|
+
loadedComponents, [], [], { value: null }, new Map(), [], new Map(), [], new Map(), new Map()
|
|
306
416
|
);
|
|
307
417
|
|
|
418
|
+
for (const [compName, instance] of cx.loadedComponents) {
|
|
419
|
+
const script = instance.match(/<script>([\s\S]*?)<\/script>/i);
|
|
420
|
+
if (script) {
|
|
421
|
+
const importRegex = /import\s+(\w+)\s+from\s+['"]([^'"]+)['"]\s*;?\s*/g;
|
|
422
|
+
let match;
|
|
423
|
+
while ((match = importRegex.exec(script[1])) !== null) {
|
|
424
|
+
const importedName = match[1];
|
|
425
|
+
const importedCompName = path.basename(match[2]).toLowerCase();
|
|
426
|
+
if (cx.loadedComponents.has(importedCompName)) {
|
|
427
|
+
generateCSRClass(importedCompName, cx, importedName);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
308
433
|
appElements.forEach(el => {
|
|
309
434
|
if (!el.isConnected) return;
|
|
310
435
|
processComponentElement(el, cx, [], pageSourceFile, pageSourceContent);
|
|
@@ -312,13 +437,14 @@ export function processAllComponents(appElements, loadedComponents, pageSourceFi
|
|
|
312
437
|
const runtimeScript = cx.runtimeChunks.join("\n");
|
|
313
438
|
const hasComponents = cx.runtimeChunks.length > 0;
|
|
314
439
|
const scopesCss = cx.scopedStyles.join("\n");
|
|
440
|
+
const csrClasses = [...cx.csrClasses.values()].join("\n\n");
|
|
315
441
|
|
|
316
442
|
const hashMap = {};
|
|
317
443
|
for (const [compName, hash] of cx.cssScopesMap) {
|
|
318
444
|
hashMap[compName] = hash;
|
|
319
445
|
}
|
|
320
446
|
|
|
321
|
-
return { runtimeScript, hasComponents, scopesCss, hashMap };
|
|
447
|
+
return { runtimeScript, hasComponents, scopesCss, hashMap, csrClasses };
|
|
322
448
|
}
|
|
323
449
|
|
|
324
450
|
function getNextLetter(letterState) {
|
|
@@ -54,7 +54,6 @@ export function getScriptElements(doc) {
|
|
|
54
54
|
|
|
55
55
|
export function appendRuntimeScript(doc, filename) {
|
|
56
56
|
const runtimeScriptEl = doc.createElement("script");
|
|
57
|
-
runtimeScriptEl.type = "module";
|
|
58
57
|
runtimeScriptEl.src = "./" + filename;
|
|
59
58
|
doc.body.appendChild(runtimeScriptEl);
|
|
60
59
|
}
|
package/compiler/index.js
CHANGED
|
@@ -209,8 +209,9 @@ export default async function compile(rootDir, buildConfig) {
|
|
|
209
209
|
processPageConditionals(appContainer);
|
|
210
210
|
|
|
211
211
|
const appElements = getAppElements(appContainer);
|
|
212
|
-
const { runtimeScript, scopesCss, hashMap } = processAllComponents(appElements, loadedComponents, pageSourcePath, srcIndexContent);
|
|
213
|
-
const
|
|
212
|
+
const { runtimeScript, scopesCss, hashMap, csrClasses } = processAllComponents(appElements, loadedComponents, pageSourcePath, srcIndexContent);
|
|
213
|
+
const csrSource = await fs.readFile(new URL("../runtime/index.js", import.meta.url), "utf-8");
|
|
214
|
+
const runtimeFilenames = await generateRuntimeScript(runtimeScript, paths.outDir, csrSource, csrClasses);
|
|
214
215
|
await processAssets(doc, rootDir, config.srcDir, paths.outDir);
|
|
215
216
|
|
|
216
217
|
if (scopesCss) {
|
|
@@ -219,7 +220,9 @@ export default async function compile(rootDir, buildConfig) {
|
|
|
219
220
|
appendStylesheetLink(doc, fileName);
|
|
220
221
|
};
|
|
221
222
|
|
|
222
|
-
|
|
223
|
+
for (const name of runtimeFilenames) {
|
|
224
|
+
appendRuntimeScript(doc, name);
|
|
225
|
+
}
|
|
223
226
|
const html = await serializeDOM(dom);
|
|
224
227
|
await writeHTMLOutput(html, paths.outDir);
|
|
225
228
|
|
|
@@ -2,12 +2,29 @@ import { promises as fs } from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { genRandomId } from "./utils.js";
|
|
4
4
|
|
|
5
|
-
export async function generateRuntimeScript(runtimeScript, outDirPath) {
|
|
5
|
+
export async function generateRuntimeScript(runtimeScript, outDirPath, csrSource, csrClasses) {
|
|
6
6
|
const fileIds = [];
|
|
7
|
-
const
|
|
8
|
-
const runtimeFileContents = `document.addEventListener("DOMContentLoaded", () => {${runtimeScript}})`;
|
|
7
|
+
const filenames = [];
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
if (csrSource) {
|
|
10
|
+
const name = "run-" + genRandomId(fileIds, 6) + ".js";
|
|
11
|
+
const source = csrSource.replace(/^export\s+\{?\s*[^}]+\s*}?\s*;?\s*$/m, "");
|
|
12
|
+
await fs.writeFile(path.join(outDirPath, name), source);
|
|
13
|
+
filenames.push(name);
|
|
14
|
+
}
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
if (csrClasses) {
|
|
17
|
+
const name = "run-" + genRandomId(fileIds, 6) + ".js";
|
|
18
|
+
await fs.writeFile(path.join(outDirPath, name), csrClasses);
|
|
19
|
+
filenames.push(name);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (runtimeScript) {
|
|
23
|
+
const name = "run-" + genRandomId(fileIds, 6) + ".js";
|
|
24
|
+
const content = `document.addEventListener("DOMContentLoaded", () => {${runtimeScript}})`;
|
|
25
|
+
await fs.writeFile(path.join(outDirPath, name), content);
|
|
26
|
+
filenames.push(name);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return filenames;
|
|
13
30
|
}
|
package/compiler/utils.js
CHANGED
|
@@ -1,52 +1,52 @@
|
|
|
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 genRandomId(collection = null, length = 10, lettersOnly = false) {
|
|
10
|
-
let id;
|
|
11
|
-
if (lettersOnly) {
|
|
12
|
-
id = Array.from({ length }, () => ID_LETTERS[Math.floor(Math.random() * ID_LETTERS.length)]).join("");
|
|
13
|
-
} else {
|
|
14
|
-
id = Math.random().toString(36).substring(2, length + 2);
|
|
15
|
-
}
|
|
16
|
-
if (!collection) return id;
|
|
17
|
-
if (collection.includes(id)) {
|
|
18
|
-
return genRandomId(collection, length, lettersOnly);
|
|
19
|
-
} else {
|
|
20
|
-
collection.push(id);
|
|
21
|
-
return id;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function incrementAlfabet(letters) {
|
|
26
|
-
let arr = letters.split("");
|
|
27
|
-
let i = arr.length - 1;
|
|
28
|
-
|
|
29
|
-
while (i >= 0) {
|
|
30
|
-
let pos = arr[i].charCodeAt(0) - 97;
|
|
31
|
-
if (pos < 25) {
|
|
32
|
-
arr[i] = String.fromCharCode(pos + 97 + 1);
|
|
33
|
-
return arr.join("");
|
|
34
|
-
} else {
|
|
35
|
-
arr[i] = "a";
|
|
36
|
-
i--;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return "a" + arr.join("");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function deterministicHash(content, length = 8) {
|
|
44
|
-
const hash = createHash("sha256").update(content).digest();
|
|
45
|
-
let result = "";
|
|
46
|
-
for (let i = 0; i < length; i++) {
|
|
47
|
-
result += String.fromCharCode(97 + (hash[i % hash.length] % 26));
|
|
48
|
-
}
|
|
49
|
-
return result;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const ID_LETTERS = "abcdefghijklmnopqrstuvwxyz";
|
|
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 genRandomId(collection = null, length = 10, lettersOnly = false) {
|
|
10
|
+
let id;
|
|
11
|
+
if (lettersOnly) {
|
|
12
|
+
id = Array.from({ length }, () => ID_LETTERS[Math.floor(Math.random() * ID_LETTERS.length)]).join("");
|
|
13
|
+
} else {
|
|
14
|
+
id = Math.random().toString(36).substring(2, length + 2);
|
|
15
|
+
}
|
|
16
|
+
if (!collection) return id;
|
|
17
|
+
if (collection.includes(id)) {
|
|
18
|
+
return genRandomId(collection, length, lettersOnly);
|
|
19
|
+
} else {
|
|
20
|
+
collection.push(id);
|
|
21
|
+
return id;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function incrementAlfabet(letters) {
|
|
26
|
+
let arr = letters.split("");
|
|
27
|
+
let i = arr.length - 1;
|
|
28
|
+
|
|
29
|
+
while (i >= 0) {
|
|
30
|
+
let pos = arr[i].charCodeAt(0) - 97;
|
|
31
|
+
if (pos < 25) {
|
|
32
|
+
arr[i] = String.fromCharCode(pos + 97 + 1);
|
|
33
|
+
return arr.join("");
|
|
34
|
+
} else {
|
|
35
|
+
arr[i] = "a";
|
|
36
|
+
i--;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return "a" + arr.join("");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function deterministicHash(content, length = 8) {
|
|
44
|
+
const hash = createHash("sha256").update(content).digest();
|
|
45
|
+
let result = "";
|
|
46
|
+
for (let i = 0; i < length; i++) {
|
|
47
|
+
result += String.fromCharCode(97 + (hash[i % hash.length] % 26));
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const ID_LETTERS = "abcdefghijklmnopqrstuvwxyz";
|