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/dev/index.js
DELETED
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import http from "http";
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import chalk from "../compiler/chalk.js";
|
|
5
|
-
import compile from "../compiler/index.js";
|
|
6
|
-
import { loadConfig, resolvePaths } from "../compiler/config.js";
|
|
7
|
-
import { getConfig, isMissingConfigFile, queueConfigWarning } from "../utils.js";
|
|
8
|
-
|
|
9
|
-
const warnedDevHostname = new Set();
|
|
10
|
-
const warnedDevPort = new Set();
|
|
11
|
-
|
|
12
|
-
export async function serve(__rootdir, { silent = false, port: cliPort, hostname: cliHostname, open } = {}) {
|
|
13
|
-
let __outdir = "dist";
|
|
14
|
-
let __config = {
|
|
15
|
-
hostname: "localhost",
|
|
16
|
-
port: 3000,
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
let lastBuildTime = Date.now();
|
|
20
|
-
|
|
21
|
-
const fullConfig = await getConfig(__rootdir, { silent });
|
|
22
|
-
const config = await loadConfig(__rootdir, { silent });
|
|
23
|
-
const paths = resolvePaths(__rootdir, config);
|
|
24
|
-
|
|
25
|
-
if (cliPort) {
|
|
26
|
-
__config.port = cliPort;
|
|
27
|
-
} else if (!silent) {
|
|
28
|
-
if (isMissingConfigFile(fullConfig)) {
|
|
29
|
-
// top-level already warned; use defaults silently
|
|
30
|
-
} else if (fullConfig.dev != null) {
|
|
31
|
-
const devConfig = fullConfig.dev;
|
|
32
|
-
if (devConfig.port) { __config.port = devConfig.port }
|
|
33
|
-
else if (!warnedDevPort.has(__rootdir)) {
|
|
34
|
-
warnedDevPort.add(__rootdir);
|
|
35
|
-
queueConfigWarning(__rootdir, chalk.bold.yellow("WARNING!"), `dev.port not defined in chocola.config.json file: using default ${__config.port} dev.port.`);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (cliHostname) {
|
|
41
|
-
__config.hostname = cliHostname;
|
|
42
|
-
} else if (!silent) {
|
|
43
|
-
if (!cliPort && isMissingConfigFile(fullConfig)) {
|
|
44
|
-
// top-level already warned
|
|
45
|
-
} else if (fullConfig.dev != null) {
|
|
46
|
-
const devConfig = fullConfig.dev;
|
|
47
|
-
if (devConfig.hostname) { __config.hostname = devConfig.hostname }
|
|
48
|
-
else if (!warnedDevHostname.has(__rootdir)) {
|
|
49
|
-
warnedDevHostname.add(__rootdir);
|
|
50
|
-
queueConfigWarning(__rootdir, chalk.bold.yellow("WARNING!"), `dev.hostname not defined in chocola.config.json file: using default ${__config.hostname} dev.hostname.`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
await compile(__rootdir);
|
|
56
|
-
|
|
57
|
-
const srcDir = paths.src;
|
|
58
|
-
|
|
59
|
-
let compileTimeout;
|
|
60
|
-
const scheduleRecompile = () => {
|
|
61
|
-
clearTimeout(compileTimeout);
|
|
62
|
-
compileTimeout = setTimeout(async () => {
|
|
63
|
-
try {
|
|
64
|
-
await compile(__rootdir, { isHotReload: true });
|
|
65
|
-
lastBuildTime = Date.now();
|
|
66
|
-
console.log(chalk.green("✓"), "Hot reload: compiled successfully");
|
|
67
|
-
} catch (error) {
|
|
68
|
-
console.error(chalk.red("✗"), "Hot reload compilation failed:", error.message);
|
|
69
|
-
}
|
|
70
|
-
}, 300);
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
fs.watch(srcDir, { recursive: true }, (eventType, filename) => {
|
|
75
|
-
if (filename && !filename.includes("node_modules")) {
|
|
76
|
-
scheduleRecompile();
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
const server = http.createServer((req, res) => {
|
|
81
|
-
if (req.url === "/api/hot-reload") {
|
|
82
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
83
|
-
res.end(JSON.stringify({ buildTime: lastBuildTime }));
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
let filePath = path.join(
|
|
88
|
-
__outdir,
|
|
89
|
-
req.url === "/" ? "index.html" : req.url
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
const extname = String(path.extname(filePath)).toLowerCase();
|
|
93
|
-
const mimeTypes = {
|
|
94
|
-
".html": "text/html",
|
|
95
|
-
".htm": "text/html",
|
|
96
|
-
".js": "text/javascript",
|
|
97
|
-
".css": "text/css",
|
|
98
|
-
".csv": "text/csv",
|
|
99
|
-
".json": "application/json",
|
|
100
|
-
".png": "image/png",
|
|
101
|
-
".jpg": "image/jpg",
|
|
102
|
-
".jpeg": "image/jpeg",
|
|
103
|
-
".bmp": "image/bmp",
|
|
104
|
-
".gif": "image/gif",
|
|
105
|
-
".svg": "image/svg+xml",
|
|
106
|
-
".icon": "image/vnd.microsoft.icon",
|
|
107
|
-
".pdf": "application/pdf",
|
|
108
|
-
".mp3": "audio/mpeg",
|
|
109
|
-
".md": "text/markdown",
|
|
110
|
-
".mp4": "video/mp4",
|
|
111
|
-
".mpeg": "video/mpeg",
|
|
112
|
-
".oga": "audio/ogg",
|
|
113
|
-
".ogv": "video/ogg",
|
|
114
|
-
".php": "application/x-httpd-php",
|
|
115
|
-
".rar": "application/vnd.rar",
|
|
116
|
-
".tar": "application/x-tar",
|
|
117
|
-
".ttf": "font/ttf",
|
|
118
|
-
".txt": "text/plain",
|
|
119
|
-
".wav": "audio/wav",
|
|
120
|
-
".weba": "audio/webm",
|
|
121
|
-
".webm": "video/webm",
|
|
122
|
-
".webmanifest": "application/manifest+json",
|
|
123
|
-
".webp": "image/webp",
|
|
124
|
-
".xhtml": "application/xhtml+xml",
|
|
125
|
-
".xml": "application/xml",
|
|
126
|
-
".zip": "application/zip"
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
const contentType = mimeTypes[extname] || "application/octet-stream";
|
|
130
|
-
|
|
131
|
-
fs.readFile(filePath, (error, content) => {
|
|
132
|
-
if (error) {
|
|
133
|
-
if (error.code === "ENOENT") {
|
|
134
|
-
res.writeHead(404, { "Content-Type": "text/html" });
|
|
135
|
-
res.end("<h1>404 Not Found</h1><p>The requested resource was not found on this server.</p>", "utf-8");
|
|
136
|
-
} else {
|
|
137
|
-
res.writeHead(500);
|
|
138
|
-
res.end("Internal server error: " + error.code);
|
|
139
|
-
}
|
|
140
|
-
} else {
|
|
141
|
-
if (extname === ".html" || extname === ".htm") {
|
|
142
|
-
const hotReloadScript = `
|
|
143
|
-
<script>
|
|
144
|
-
(function() {
|
|
145
|
-
let lastBuildTime = ${lastBuildTime};
|
|
146
|
-
setInterval(async () => {
|
|
147
|
-
try {
|
|
148
|
-
const res = await fetch('/api/hot-reload');
|
|
149
|
-
const data = await res.json();
|
|
150
|
-
if (data.buildTime > lastBuildTime) {
|
|
151
|
-
lastBuildTime = data.buildTime;
|
|
152
|
-
console.log('[Hot Reload] Changes detected, reloading...');
|
|
153
|
-
window.location.reload();
|
|
154
|
-
}
|
|
155
|
-
} catch (e) {
|
|
156
|
-
console.error('[Hot Reload] Check failed:', e);
|
|
157
|
-
}
|
|
158
|
-
}, 1000);
|
|
159
|
-
})();
|
|
160
|
-
</script>`;
|
|
161
|
-
const htmlWithReload = content.toString().replace('</body>', hotReloadScript + '</body>');
|
|
162
|
-
res.writeHead(200, { "Content-Type": contentType });
|
|
163
|
-
res.end(htmlWithReload, "utf-8");
|
|
164
|
-
} else {
|
|
165
|
-
res.writeHead(200, { "Content-Type": contentType });
|
|
166
|
-
res.end(content, "utf-8");
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
server.listen(__config.port, __config.hostname, async () => {
|
|
173
|
-
console.log('Live server running at', chalk.hex("#68C4EE").underline(`http://${__config.hostname}:${__config.port}/`));
|
|
174
|
-
if (open) {
|
|
175
|
-
const url = __config.hostname === "0.0.0.0" ? `http://localhost:${__config.port}` : `http://${__config.hostname}:${__config.port}`;
|
|
176
|
-
try {
|
|
177
|
-
const openPkg = await import("open");
|
|
178
|
-
openPkg.default(url);
|
|
179
|
-
} catch {
|
|
180
|
-
const { exec } = await import("child_process");
|
|
181
|
-
const cmd = process.platform === "win32" ? `start ${url}` : process.platform === "darwin" ? `open ${url}` : `xdg-open ${url}`;
|
|
182
|
-
exec(cmd);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
return server;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* An intrinsic object that contains the Chocola Dev methods.
|
|
191
|
-
*/
|
|
192
|
-
export const dev = {
|
|
193
|
-
/**
|
|
194
|
-
* Initializes your Chocola dev server using a root directory.
|
|
195
|
-
*
|
|
196
|
-
* ```js
|
|
197
|
-
* import { app } from "chocola/compiler"
|
|
198
|
-
import path from "path";
|
|
199
|
-
import { fileURLToPath } from "url";
|
|
200
|
-
|
|
201
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
202
|
-
const __dirname = path.dirname(__filename);
|
|
203
|
-
|
|
204
|
-
app.build(__dirname);
|
|
205
|
-
```
|
|
206
|
-
* @example
|
|
207
|
-
* @param {PathLike} __rootdir the directory where your Chocola App is
|
|
208
|
-
*/
|
|
209
|
-
async server(__rootdir) { return serve(__rootdir) }
|
|
210
|
-
};
|
package/parser/script.test.js
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { test } from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { parseScript, computeReachable } from "./script.js";
|
|
4
|
-
|
|
5
|
-
test("parseScript: simple let/const and props", () => {
|
|
6
|
-
const { props, topVars } = parseScript(`export let title = "Card"; let a = 1, b = 2; const c = 3;`);
|
|
7
|
-
assert.deepEqual(props.map(p=>p.name), ["title"]);
|
|
8
|
-
assert.equal(props[0].defaultValue, '"Card"');
|
|
9
|
-
assert.deepEqual(topVars.map(v=>v.name), ["a","b","c"]);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
test("parseScript: destructuring object and array", () => {
|
|
13
|
-
const { topVars } = parseScript(`const {x,y} = obj; let [p, ...rest] = arr; const {a: aa, b: bb} = obj2;`);
|
|
14
|
-
assert.equal(topVars.length, 3);
|
|
15
|
-
assert.deepEqual(topVars[0].names, ["x","y"]);
|
|
16
|
-
assert.equal(topVars[0].isDestructuring, true);
|
|
17
|
-
assert.deepEqual(topVars[1].names, ["p","rest"]);
|
|
18
|
-
assert.deepEqual(topVars[2].names, ["aa","bb"]);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test("parseScript: comma declarators", () => {
|
|
22
|
-
const { topVars } = parseScript(`let a = 1, b = 2, c;`);
|
|
23
|
-
assert.equal(topVars.length, 3);
|
|
24
|
-
assert.equal(topVars[0].name, "a");
|
|
25
|
-
assert.equal(topVars[0].value, "1");
|
|
26
|
-
assert.equal(topVars[1].name, "b");
|
|
27
|
-
assert.equal(topVars[2].name, "c");
|
|
28
|
-
assert.equal(topVars[2].value, undefined);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test("parseScript: imports default/named/namespace/side-effect", () => {
|
|
32
|
-
const { imports } = parseScript(`
|
|
33
|
-
import Action from "./Action.html";
|
|
34
|
-
import {x} from "./lib.js";
|
|
35
|
-
import * as ns from "./ns.js";
|
|
36
|
-
import "./polyfill.js";
|
|
37
|
-
import {y as z} from "./lib.js";
|
|
38
|
-
`);
|
|
39
|
-
assert.equal(imports.length, 5);
|
|
40
|
-
assert.equal(imports[0].specifiers[0].type, "default");
|
|
41
|
-
assert.equal(imports[0].specifiers[0].local, "Action");
|
|
42
|
-
assert.equal(imports[1].specifiers[0].type, "named");
|
|
43
|
-
assert.equal(imports[2].specifiers[0].type, "namespace");
|
|
44
|
-
assert.equal(imports[3].specifiers.length, 0);
|
|
45
|
-
assert.equal(imports[4].specifiers[0].imported, "y");
|
|
46
|
-
assert.equal(imports[4].specifiers[0].local, "z");
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("parseScript: comments containing function $runtime are ignored", () => {
|
|
50
|
-
const { runtime, topVars } = parseScript(`let a = 1; // function $runtime(){ fake }
|
|
51
|
-
/* function $runtime(){ fake } */
|
|
52
|
-
function $runtime(){ a++ }`);
|
|
53
|
-
assert.ok(runtime && runtime.includes("$runtime"));
|
|
54
|
-
assert.equal(topVars.length, 1);
|
|
55
|
-
assert.equal(topVars[0].name, "a");
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test("parseScript: nested braces in template literals not confused", () => {
|
|
59
|
-
const { topFuncs } = parseScript("function foo(){ return `a {b} c`; } function $runtime(){ foo(); }");
|
|
60
|
-
assert.equal(topFuncs.length, 1);
|
|
61
|
-
assert.ok(topFuncs[0].includes("foo"));
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test("parseScript: async $runtime", () => {
|
|
65
|
-
const { runtime } = parseScript(`async function $runtime(){ await fetch(); }`);
|
|
66
|
-
assert.ok(runtime && runtime.startsWith("async function $runtime"));
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test("computeReachable: transitive closure", () => {
|
|
70
|
-
const parsed = parseScript(`let a = b + 1; let b = 2; let unused = 99; function foo(){ return a; } function bar(){ return unused; } function $runtime(){ foo(); }`);
|
|
71
|
-
const reach = computeReachable(parsed, {bindings: []});
|
|
72
|
-
assert.ok(reach.neededVars.some(v=> v.names.includes("a")));
|
|
73
|
-
assert.ok(reach.neededVars.some(v=> v.names.includes("b")));
|
|
74
|
-
assert.equal(reach.neededVars.some(v=> v.names.includes("unused")), false);
|
|
75
|
-
assert.ok(reach.neededFuncs.some(s=> s.includes("foo")));
|
|
76
|
-
assert.equal(reach.neededFuncs.some(s=> s.includes("bar")), false);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("computeReachable: bindings only if referenced", () => {
|
|
80
|
-
const parsed = parseScript(`let count = 0; function inc(){ count++; } function $runtime(){ inc(); btn.addEventListener("click", inc); }`);
|
|
81
|
-
const reach = computeReachable(parsed, {bindings: ["btn","span"]});
|
|
82
|
-
assert.deepEqual(reach.neededBindings, ["btn"]);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test("computeReachable: props only if referenced", () => {
|
|
86
|
-
const parsed = parseScript(`export let title = "Card"; export let unused = "hi"; function $runtime(){ console.log(title); }`);
|
|
87
|
-
const reach = computeReachable(parsed, {bindings: []});
|
|
88
|
-
assert.deepEqual(reach.neededProps.map(p=>p.name), ["title"]);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test("computeReachable: no runtime -> empty", () => {
|
|
92
|
-
const parsed = parseScript(`let a = 1; function foo(){}`);
|
|
93
|
-
const reach = computeReachable(parsed, {bindings: []});
|
|
94
|
-
assert.equal(reach.neededVars.length, 0);
|
|
95
|
-
assert.equal(reach.neededFuncs.length, 0);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test("computeReachable: dynamic fallback", () => {
|
|
99
|
-
const parsed = parseScript(`let a = 1; function $runtime(){ eval("a"); }`);
|
|
100
|
-
const reach = computeReachable(parsed, {bindings: []});
|
|
101
|
-
assert.equal(reach.fallback, true);
|
|
102
|
-
assert.ok(reach.neededVars.length > 0);
|
|
103
|
-
});
|