chocola 1.6.0 → 2.0.0-next.10
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 +34 -36
- package/compiler/chalk.js +34 -0
- package/compiler/component-processor.js +526 -398
- package/compiler/config.js +3 -56
- package/compiler/dom-processor.js +12 -75
- package/compiler/fs.js +0 -11
- package/compiler/index.js +126 -254
- package/compiler/module-graph.js +210 -0
- package/compiler/pipeline.js +54 -196
- package/compiler/render.js +181 -0
- package/compiler/runtime-generator.js +20 -15
- package/compiler/utils.js +79 -126
- package/dev/index.js +28 -11
- package/index.js +0 -31
- package/package.json +11 -13
- package/parser/component.js +268 -0
- package/parser/context.js +59 -0
- package/parser/css.js +96 -0
- package/parser/index.js +5 -0
- package/parser/template.js +120 -0
- package/parser/utils.js +32 -0
- package/runtime/index.js +312 -0
- package/server/index.js +402 -0
- package/utils.js +41 -6
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { promises as fs } from "fs";
|
|
3
|
+
import {
|
|
4
|
+
createDOM,
|
|
5
|
+
validateAppContainer,
|
|
6
|
+
getAppElements,
|
|
7
|
+
getAssetLinks,
|
|
8
|
+
getScriptElements,
|
|
9
|
+
appendRuntimeScript,
|
|
10
|
+
appendStylesheetLink,
|
|
11
|
+
serializeDOM,
|
|
12
|
+
} from "./dom-processor.js";
|
|
13
|
+
import { processAllComponents } from "./component-processor.js";
|
|
14
|
+
import { generateRuntimeScript } from "./runtime-generator.js";
|
|
15
|
+
import { deterministicHash, throwError, warnConstantCondition, findElementLine } from "./utils.js";
|
|
16
|
+
import { compileExpr, evaluateConstant, hasMountIf, getMountIf, removeMountIf } from "../parser/index.js";
|
|
17
|
+
import {
|
|
18
|
+
processStylesheet,
|
|
19
|
+
processIcons,
|
|
20
|
+
processScript,
|
|
21
|
+
copyStaticDir,
|
|
22
|
+
} from "./pipeline.js";
|
|
23
|
+
|
|
24
|
+
function processPageConditionals(parent, sourceFile, sourceContent, ctx = {}) {
|
|
25
|
+
const ctxProxy = new Proxy(ctx, {
|
|
26
|
+
has() { return true; },
|
|
27
|
+
get(target, key) { return target[key]; },
|
|
28
|
+
});
|
|
29
|
+
const children = [...parent.children];
|
|
30
|
+
let chainActive = false;
|
|
31
|
+
let chainRendered = false;
|
|
32
|
+
|
|
33
|
+
const getLocation = (child) => {
|
|
34
|
+
if (!sourceContent) return sourceFile;
|
|
35
|
+
const lineNum = findElementLine(sourceContent, child.outerHTML);
|
|
36
|
+
return lineNum !== null ? `${sourceFile}:${lineNum}` : sourceFile;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const warnIfConstant = (expr, child, attr) => {
|
|
40
|
+
const { constant, value } = evaluateConstant(expr);
|
|
41
|
+
if (!constant) return;
|
|
42
|
+
warnConstantCondition(getLocation(child), child.tagName.toLowerCase(), attr, Boolean(value));
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
for (const child of children) {
|
|
46
|
+
const hasIf = child.hasAttribute("if");
|
|
47
|
+
const hasDelIf = hasMountIf(child);
|
|
48
|
+
const hasElif = child.hasAttribute("elif");
|
|
49
|
+
const hasElse = child.hasAttribute("else");
|
|
50
|
+
|
|
51
|
+
if (hasIf || hasDelIf || hasElif) {
|
|
52
|
+
const stripBraces = (raw) => raw.startsWith("{") ? raw.slice(1, -1) : raw;
|
|
53
|
+
if (hasIf) warnIfConstant(stripBraces(child.getAttribute("if")), child, "if");
|
|
54
|
+
if (hasDelIf) warnIfConstant(stripBraces(getMountIf(child)), child, "mount:if");
|
|
55
|
+
if (hasElif) warnIfConstant(stripBraces(child.getAttribute("elif")), child, "elif");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (hasElif || hasElse) {
|
|
59
|
+
if (!chainActive) {
|
|
60
|
+
const tag = child.tagName.toLowerCase();
|
|
61
|
+
const attr = hasElif ? "elif" : "else";
|
|
62
|
+
throwError(`${getLocation(child)}\n <${tag}> has ${attr} without a preceding if/mount:if sibling`);
|
|
63
|
+
}
|
|
64
|
+
if (chainRendered) { child.remove(); continue; }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (hasIf) {
|
|
68
|
+
const raw = child.getAttribute("if");
|
|
69
|
+
const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
|
|
70
|
+
const fn = compileExpr(expr, true);
|
|
71
|
+
const result = fn(ctxProxy);
|
|
72
|
+
chainActive = true;
|
|
73
|
+
if (result) {
|
|
74
|
+
chainRendered = true;
|
|
75
|
+
} else {
|
|
76
|
+
child.style.display = "none";
|
|
77
|
+
chainRendered = false;
|
|
78
|
+
}
|
|
79
|
+
child.removeAttribute("if");
|
|
80
|
+
} else if (hasDelIf) {
|
|
81
|
+
const raw = getMountIf(child);
|
|
82
|
+
const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
|
|
83
|
+
const fn = compileExpr(expr, true);
|
|
84
|
+
const result = fn(ctxProxy);
|
|
85
|
+
chainActive = true;
|
|
86
|
+
if (result) {
|
|
87
|
+
chainRendered = true;
|
|
88
|
+
} else {
|
|
89
|
+
child.remove();
|
|
90
|
+
chainRendered = false;
|
|
91
|
+
}
|
|
92
|
+
removeMountIf(child);
|
|
93
|
+
} else if (hasElif) {
|
|
94
|
+
const raw = child.getAttribute("elif");
|
|
95
|
+
const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
|
|
96
|
+
const fn = compileExpr(expr, true);
|
|
97
|
+
const result = fn(ctxProxy);
|
|
98
|
+
if (result) {
|
|
99
|
+
chainRendered = true;
|
|
100
|
+
} else {
|
|
101
|
+
child.remove();
|
|
102
|
+
}
|
|
103
|
+
child.removeAttribute("elif");
|
|
104
|
+
} else if (hasElse) {
|
|
105
|
+
chainRendered = true;
|
|
106
|
+
chainActive = false;
|
|
107
|
+
child.removeAttribute("else");
|
|
108
|
+
} else {
|
|
109
|
+
chainActive = false;
|
|
110
|
+
chainRendered = false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (child.parentNode) {
|
|
114
|
+
processPageConditionals(child, sourceFile, sourceContent, ctx);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function processAssets(doc, graph, out) {
|
|
120
|
+
const { stylesheets, icons } = getAssetLinks(doc);
|
|
121
|
+
const scripts = getScriptElements(doc);
|
|
122
|
+
|
|
123
|
+
for (const link of stylesheets) {
|
|
124
|
+
await processStylesheet(link, graph.rootDir, graph.config.srcDir, out);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const link of icons) {
|
|
128
|
+
await processIcons(link, graph.rootDir, graph.config.srcDir, out);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
for (const script of scripts) {
|
|
132
|
+
await processScript(doc, script, graph.rootDir, graph.config.srcDir, out);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function renderPage(graph, ctx = {}) {
|
|
137
|
+
const out = {
|
|
138
|
+
files: [],
|
|
139
|
+
copies: [],
|
|
140
|
+
ids: [],
|
|
141
|
+
outDir: graph.paths.outDir,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const page = graph.page;
|
|
145
|
+
const dom = createDOM(page.source);
|
|
146
|
+
const doc = dom.document;
|
|
147
|
+
const appContainer = validateAppContainer(doc);
|
|
148
|
+
|
|
149
|
+
processPageConditionals(appContainer, page.sourcePath, dom.protectedContent, ctx);
|
|
150
|
+
|
|
151
|
+
const appElements = getAppElements(appContainer);
|
|
152
|
+
const { runtimeScript, scopesCss, hashMap, csrClasses } = processAllComponents(
|
|
153
|
+
appElements,
|
|
154
|
+
graph.loadedComponents,
|
|
155
|
+
page.sourcePath,
|
|
156
|
+
page.source,
|
|
157
|
+
ctx
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const csrSource = await fs.readFile(new URL("../runtime/index.js", import.meta.url), "utf-8");
|
|
161
|
+
const runtimeFiles = generateRuntimeScript(runtimeScript, csrSource, csrClasses);
|
|
162
|
+
|
|
163
|
+
await processAssets(doc, graph, out);
|
|
164
|
+
|
|
165
|
+
if (scopesCss) {
|
|
166
|
+
const fileName = "sc-" + deterministicHash(scopesCss, 6) + ".css";
|
|
167
|
+
out.files.push({ path: fileName, content: scopesCss });
|
|
168
|
+
appendStylesheetLink(doc, fileName);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
for (const file of runtimeFiles) {
|
|
172
|
+
out.files.push(file);
|
|
173
|
+
appendRuntimeScript(doc, file.path);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const html = await serializeDOM(dom);
|
|
177
|
+
|
|
178
|
+
await copyStaticDir(graph.paths.src, out);
|
|
179
|
+
|
|
180
|
+
return { html, hashMap, files: out.files, copies: out.copies };
|
|
181
|
+
}
|
|
@@ -1,19 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { genRandomId } from "./utils.js";
|
|
1
|
+
import { deterministicHash } from "./utils.js";
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* @param {string} runtimeScript - The JavaScript code for runtime
|
|
8
|
-
* @param {import("fs").PathLike} outDirPath
|
|
9
|
-
* @returns {Promise<string>} - filename of the generated runtime script
|
|
10
|
-
*/
|
|
11
|
-
export async function generateRuntimeScript(runtimeScript, outDirPath) {
|
|
12
|
-
const fileIds = [];
|
|
13
|
-
const runtimeFilename = "run-" + genRandomId(fileIds, 6) + ".js";
|
|
14
|
-
const runtimeFileContents = `document.addEventListener("DOMContentLoaded", () => {${runtimeScript}})`;
|
|
3
|
+
export function generateRuntimeScript(runtimeScript, csrSource, csrClasses) {
|
|
4
|
+
const files = [];
|
|
15
5
|
|
|
16
|
-
|
|
6
|
+
if (csrSource) {
|
|
7
|
+
const source = csrSource.replace(/^export\s+\{?\s*[^}]+\s*}?\s*;?\s*$/m, "");
|
|
8
|
+
const name = "run-" + deterministicHash(source, 6) + ".js";
|
|
9
|
+
files.push({ path: name, content: source });
|
|
10
|
+
}
|
|
17
11
|
|
|
18
|
-
|
|
12
|
+
if (csrClasses) {
|
|
13
|
+
const name = "run-" + deterministicHash(csrClasses, 6) + ".js";
|
|
14
|
+
files.push({ path: name, content: csrClasses });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (runtimeScript) {
|
|
18
|
+
const content = `document.addEventListener("DOMContentLoaded", () => {${runtimeScript}})`;
|
|
19
|
+
const name = "run-" + deterministicHash(content, 6) + ".js";
|
|
20
|
+
files.push({ path: name, content });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return files;
|
|
19
24
|
}
|
package/compiler/utils.js
CHANGED
|
@@ -1,87 +1,93 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import { pathToFileURL } from "url";
|
|
4
|
-
import chalk from "chalk";
|
|
1
|
+
import { createHash } from "crypto";
|
|
2
|
+
import chalk from "./chalk.js";
|
|
5
3
|
|
|
6
|
-
/**
|
|
7
|
-
* Throws a formatted error message and exits
|
|
8
|
-
* @param {string|Error} err - Error message or Error object
|
|
9
|
-
* @throws {Error}
|
|
10
|
-
*/
|
|
11
4
|
export function throwError(err) {
|
|
12
5
|
console.log(chalk.red.bold("Error!"), "A fatal error has occurred:\n");
|
|
13
6
|
throw new Error(err);
|
|
14
7
|
}
|
|
15
8
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
* import template from "./button.html";
|
|
24
|
-
* export default function Button() { return { body: template }; }
|
|
25
|
-
*/
|
|
26
|
-
export async function loadWithAssets(filePath) {
|
|
27
|
-
let code;
|
|
28
|
-
try {
|
|
29
|
-
code = await fs.readFile(filePath, "utf8");
|
|
30
|
-
} catch (err) {
|
|
31
|
-
throwError(`Failed to read component file "${filePath}": ${err.message || err}`);
|
|
32
|
-
}
|
|
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
|
+
}
|
|
33
16
|
|
|
34
|
-
|
|
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
|
+
}
|
|
35
23
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const absPath = path.resolve(path.dirname(filePath), relPath);
|
|
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
|
+
}
|
|
42
29
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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);
|
|
51
40
|
}
|
|
52
|
-
|
|
53
|
-
replacements.push({ from: match[0], to: `const ${varName} = ${JSON.stringify(content)};` });
|
|
54
41
|
}
|
|
42
|
+
return outerHTML;
|
|
43
|
+
}
|
|
55
44
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
45
|
+
function escapeRegex(str) {
|
|
46
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
47
|
+
}
|
|
59
48
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
49
|
+
function flexibleStartTagRegex(startTag) {
|
|
50
|
+
return new RegExp(
|
|
51
|
+
startTag
|
|
52
|
+
.split(/\s+/)
|
|
53
|
+
.filter(Boolean)
|
|
54
|
+
.map(escapeRegex)
|
|
55
|
+
.join("\\s+")
|
|
56
|
+
);
|
|
57
|
+
}
|
|
63
58
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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(/"/g, '"')
|
|
67
|
+
.replace(/&/g, "&")
|
|
68
|
+
.replace(/</g, "<")
|
|
69
|
+
.replace(/>/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
|
+
}
|
|
71
83
|
}
|
|
84
|
+
return null;
|
|
72
85
|
}
|
|
73
86
|
|
|
74
|
-
/**
|
|
75
|
-
* Generates a random ID string
|
|
76
|
-
* @param {Array} collection - Array to track used IDs (prevents duplicates)
|
|
77
|
-
* @param {number} length - Desired length of the ID (default: 10)
|
|
78
|
-
* @returns {string} - Unique random ID
|
|
79
|
-
*/
|
|
80
87
|
export function genRandomId(collection = null, length = 10, lettersOnly = false) {
|
|
81
88
|
let id;
|
|
82
89
|
if (lettersOnly) {
|
|
83
|
-
|
|
84
|
-
id = Array.from({ length }, () => letters[Math.floor(Math.random() * letters.length)]).join("");
|
|
90
|
+
id = Array.from({ length }, () => ID_LETTERS[Math.floor(Math.random() * ID_LETTERS.length)]).join("");
|
|
85
91
|
} else {
|
|
86
92
|
id = Math.random().toString(36).substring(2, length + 2);
|
|
87
93
|
}
|
|
@@ -94,70 +100,17 @@ export function genRandomId(collection = null, length = 10, lettersOnly = false)
|
|
|
94
100
|
}
|
|
95
101
|
}
|
|
96
102
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
* incrementAlfabet("a") // "b"
|
|
103
|
-
* incrementAlfabet("z") // "aa"
|
|
104
|
-
*/
|
|
105
|
-
export function incrementAlfabet(letters) {
|
|
106
|
-
const alfabet = "abcdefghijklmnopqrstuvwxyz";
|
|
107
|
-
let arr = letters.split("");
|
|
108
|
-
let i = arr.length - 1;
|
|
109
|
-
|
|
110
|
-
while (i >= 0) {
|
|
111
|
-
let pos = alfabet.indexOf(arr[i]);
|
|
112
|
-
if (pos < 25) {
|
|
113
|
-
arr[i] = alfabet[pos + 1];
|
|
114
|
-
return arr.join("");
|
|
115
|
-
} else {
|
|
116
|
-
arr[i] = "a";
|
|
117
|
-
i--;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return "a" + arr.join("");
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Checks if a string is a valid HTTP/HTTPS URL
|
|
126
|
-
* @param {string} str - String to check
|
|
127
|
-
* @returns {boolean} - True if valid web link, false otherwise
|
|
128
|
-
*/
|
|
129
|
-
export function isWebLink(str) {
|
|
130
|
-
try {
|
|
131
|
-
const url = new URL(str);
|
|
132
|
-
return url.protocol === "http:" || url.protocol === "https:";
|
|
133
|
-
} catch (e) {
|
|
134
|
-
return false;
|
|
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));
|
|
135
108
|
}
|
|
109
|
+
return result;
|
|
136
110
|
}
|
|
137
111
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Replaces HTML entities for curly braces with placeholders before JSDOM parsing.
|
|
143
|
-
* This prevents JSDOM from decoding them into literal braces before binding processing.
|
|
144
|
-
* @param {string} html - Raw HTML content
|
|
145
|
-
* @returns {string} - HTML with curly brace entities replaced
|
|
146
|
-
*/
|
|
147
|
-
export function protectCurlyBraces(html) {
|
|
148
|
-
return html
|
|
149
|
-
.replace(/&(?:lbrace|#123|#x7B);/gi, LBRACE_PH)
|
|
150
|
-
.replace(/&(?:rcub|#125|#x7D);/gi, RBRACE_PH);
|
|
112
|
+
export function runtimeFunctionId(compName) {
|
|
113
|
+
return "r_" + deterministicHash(compName, 8);
|
|
151
114
|
}
|
|
152
115
|
|
|
153
|
-
|
|
154
|
-
* Restores curly brace placeholders back to literal characters.
|
|
155
|
-
* Called after binding processing is complete and before final output.
|
|
156
|
-
* @param {string} html - Serialized HTML with placeholders
|
|
157
|
-
* @returns {string} - HTML with literal curly braces restored
|
|
158
|
-
*/
|
|
159
|
-
export function restoreCurlyBraces(html) {
|
|
160
|
-
return html
|
|
161
|
-
.replace(/_%%CHOCOLA-LBRACE%%_/g, "{")
|
|
162
|
-
.replace(/_%%CHOCOLA-RBRACE%%_/g, "}");
|
|
163
|
-
}
|
|
116
|
+
const ID_LETTERS = "abcdefghijklmnopqrstuvwxyz";
|
package/dev/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import http from "http";
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import chalk from "chalk";
|
|
4
|
+
import chalk from "../compiler/chalk.js";
|
|
5
5
|
import compile from "../compiler/index.js";
|
|
6
6
|
import { loadConfig, resolvePaths } from "../compiler/config.js";
|
|
7
|
-
import {
|
|
7
|
+
import { getConfig } from "../utils.js";
|
|
8
8
|
|
|
9
9
|
export async function serve(__rootdir) {
|
|
10
10
|
await compile(__rootdir);
|
|
@@ -17,7 +17,7 @@ export async function serve(__rootdir) {
|
|
|
17
17
|
|
|
18
18
|
let lastBuildTime = Date.now();
|
|
19
19
|
|
|
20
|
-
const fullConfig = await
|
|
20
|
+
const fullConfig = await getConfig(__rootdir);
|
|
21
21
|
const config = await loadConfig(__rootdir);
|
|
22
22
|
const paths = resolvePaths(__rootdir, config);
|
|
23
23
|
const devConfig = fullConfig.dev;
|
|
@@ -29,7 +29,6 @@ export async function serve(__rootdir) {
|
|
|
29
29
|
else { console.warn(chalk.bold.yellow("WARNING!"), `port not defined in chocola.config.json file: using default ${__config.port} port.`) }
|
|
30
30
|
|
|
31
31
|
const srcDir = paths.src;
|
|
32
|
-
const componentsDir = paths.components;
|
|
33
32
|
|
|
34
33
|
let compileTimeout;
|
|
35
34
|
const scheduleRecompile = () => {
|
|
@@ -51,11 +50,6 @@ export async function serve(__rootdir) {
|
|
|
51
50
|
scheduleRecompile();
|
|
52
51
|
}
|
|
53
52
|
});
|
|
54
|
-
fs.watch(componentsDir, { recursive: true }, (eventType, filename) => {
|
|
55
|
-
if (filename && !filename.includes("node_modules")) {
|
|
56
|
-
scheduleRecompile();
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
53
|
|
|
60
54
|
const server = http.createServer((req, res) => {
|
|
61
55
|
if (req.url === "/api/hot-reload") {
|
|
@@ -150,6 +144,29 @@ export async function serve(__rootdir) {
|
|
|
150
144
|
});
|
|
151
145
|
|
|
152
146
|
server.listen(__config.port, __config.hostname, () => {
|
|
153
|
-
console.log(
|
|
147
|
+
console.log('Live server running at', chalk.hex("#68C4EE").underline(`http://${__config.hostname}:${__config.port}/`));
|
|
154
148
|
});
|
|
155
|
-
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* An intrinsic object that contains the Chocola Dev methods.
|
|
153
|
+
*/
|
|
154
|
+
export const dev = {
|
|
155
|
+
/**
|
|
156
|
+
* Initializes your Chocola dev server using a root directory.
|
|
157
|
+
*
|
|
158
|
+
* ```js
|
|
159
|
+
* import { app } from "chocola/compiler"
|
|
160
|
+
import path from "path";
|
|
161
|
+
import { fileURLToPath } from "url";
|
|
162
|
+
|
|
163
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
164
|
+
const __dirname = path.dirname(__filename);
|
|
165
|
+
|
|
166
|
+
app.build(__dirname);
|
|
167
|
+
```
|
|
168
|
+
* @example
|
|
169
|
+
* @param {PathLike} __rootdir the directory where your Chocola App is
|
|
170
|
+
*/
|
|
171
|
+
async server(__rootdir) { return serve(__rootdir) }
|
|
172
|
+
};
|
package/index.js
CHANGED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import compile from "./compiler/index.js";
|
|
2
|
-
import * as development from "./dev/index.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* An intrinsic object that contains the Chocola App methods.
|
|
6
|
-
*/
|
|
7
|
-
export const app = {
|
|
8
|
-
/**
|
|
9
|
-
* Initializes your Chocola App using a root directory and a source directory.
|
|
10
|
-
*
|
|
11
|
-
* ```js
|
|
12
|
-
* import { app } from "chocola"
|
|
13
|
-
import path from "path";
|
|
14
|
-
import { fileURLToPath } from "url";
|
|
15
|
-
|
|
16
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
-
const __dirname = path.dirname(__filename);
|
|
18
|
-
|
|
19
|
-
app.build(__dirname, "src");
|
|
20
|
-
```
|
|
21
|
-
* @example
|
|
22
|
-
* @param {PathLike} __rootdir the directory where your Chocola App is
|
|
23
|
-
* @param {PathLike} __srcdir the directory where your app sources files are
|
|
24
|
-
* @returns {Object}
|
|
25
|
-
*/
|
|
26
|
-
build(__rootdir, __srcdir) { compile(__rootdir, __srcdir) }
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export const dev = {
|
|
30
|
-
server(__rootdir, __srcdir, port) { development.serve(__rootdir, __srcdir, port) }
|
|
31
|
-
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chocola",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-next.10",
|
|
4
4
|
"description": "The sweetest way to build the web",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"web",
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
],
|
|
14
14
|
"homepage": "https://chocolaframework.web.app/",
|
|
15
15
|
"bugs": {
|
|
16
|
-
"url": "https://github.com/
|
|
16
|
+
"url": "https://github.com/chocolajs/chocola/issues"
|
|
17
17
|
},
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
|
-
"url": "git+https://github.com/
|
|
20
|
+
"url": "git+https://github.com/chocolajs/chocola.git"
|
|
21
21
|
},
|
|
22
22
|
"license": "MIT",
|
|
23
23
|
"author": "SadGabi",
|
|
@@ -25,21 +25,19 @@
|
|
|
25
25
|
"main": "index.js",
|
|
26
26
|
"funding": {
|
|
27
27
|
"type": "github",
|
|
28
|
-
"url": "https://github.com/sponsors/
|
|
28
|
+
"url": "https://github.com/sponsors/chocolajs"
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
|
-
"test": "
|
|
31
|
+
"test": "node --test \"tests/**/*.test.js\"",
|
|
32
|
+
"bench": "node bench/index.js",
|
|
33
|
+
"bench:csv": "node bench/index.js --csv"
|
|
32
34
|
},
|
|
33
35
|
"exports": {
|
|
34
|
-
"
|
|
35
|
-
"./
|
|
36
|
+
"./compiler": "./compiler/index.js",
|
|
37
|
+
"./dev": "./dev/index.js",
|
|
38
|
+
"./server": "./server/index.js"
|
|
36
39
|
},
|
|
37
40
|
"dependencies": {
|
|
38
|
-
"
|
|
39
|
-
"js-beautify": "^1.15.4",
|
|
40
|
-
"jsdom": "^27.4.0"
|
|
41
|
-
},
|
|
42
|
-
"devDependencies": {
|
|
43
|
-
"js-beautify": "^1.15.4"
|
|
41
|
+
"linkedom": "^0.18.13"
|
|
44
42
|
}
|
|
45
43
|
}
|