chocola 2.0.0-next.1 → 2.0.0-next.11
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 +45 -35
- package/bin/chocola.js +227 -0
- package/compiler/chalk.js +34 -0
- package/compiler/component-processor.js +771 -402
- package/compiler/config.js +43 -66
- package/compiler/dom-processor.js +12 -75
- package/compiler/fs.js +0 -11
- package/compiler/index.js +87 -230
- package/compiler/module-graph.js +257 -0
- package/compiler/pipeline.js +56 -196
- package/compiler/render.js +183 -0
- package/compiler/runtime-generator.js +20 -15
- package/compiler/utils.js +79 -126
- package/dev/index.js +54 -22
- package/package.json +23 -12
- package/parser/component.js +340 -0
- package/parser/context.js +59 -0
- package/parser/css.js +96 -0
- package/parser/index.js +6 -0
- package/parser/script.js +485 -0
- package/parser/script.test.js +103 -0
- package/parser/template.js +120 -0
- package/parser/utils.js +32 -0
- package/runtime/index.js +312 -0
- package/server/index.js +432 -0
- package/utils.js +113 -8
- package/index.js +0 -0
package/LICENSE
CHANGED
|
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,45 +1,55 @@
|
|
|
1
|
-
|
|
1
|
+
<picture>
|
|
2
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/banner_dark.jpg">
|
|
3
|
+
<img src="assets/banner.jpg" alt="Chocola • The sweetest way to build the web" />
|
|
4
|
+
</picture>
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
## What is Chocola
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
Chocola is a new and sweeter way to build your web apps.
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
No bundler config. No virtual DOM. No hydration ceremony. 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
|
+
|
|
12
|
+
Import components. Instantiate them. Mount, update, remove. Client-side or server-side. Same file, minimal overhead.
|
|
8
13
|
|
|
9
14
|
```html
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
<script>
|
|
16
|
+
import CoolButton from './CoolButton.html';
|
|
17
|
+
|
|
18
|
+
export let title = "Hello";
|
|
19
|
+
|
|
20
|
+
let input;
|
|
21
|
+
|
|
22
|
+
function $runtime() {
|
|
23
|
+
input.focus();
|
|
24
|
+
}
|
|
25
|
+
</script>
|
|
19
26
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
})
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export default function Counter() {
|
|
35
|
-
return {
|
|
36
|
-
body,
|
|
37
|
-
styles,
|
|
38
|
-
script: RUNTIME
|
|
39
|
-
}
|
|
40
|
-
}
|
|
27
|
+
<template>
|
|
28
|
+
<div>
|
|
29
|
+
<h1>{title}</h1>
|
|
30
|
+
<input bind:self="input" type="text" placeholder="Your name">
|
|
31
|
+
<CoolButton label="Not lame button"></CoolButton>
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<style>
|
|
36
|
+
h1 { color: chocolate; }
|
|
37
|
+
</style>
|
|
41
38
|
```
|
|
42
39
|
|
|
43
40
|
## Documentation
|
|
44
41
|
|
|
45
|
-
|
|
42
|
+
- [Getting started](documentation/01-introduction/02-getting-started.md)
|
|
43
|
+
- [Project structure](documentation/01-introduction/03-project-structure.md)
|
|
44
|
+
- [CLI reference](documentation/07-reference/04-cli.md)
|
|
45
|
+
|
|
46
|
+
## Quick start
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
npm install chocola
|
|
50
|
+
npx chocola dev # dev server
|
|
51
|
+
npx chocola build # production build
|
|
52
|
+
npx chocola serve # SSR production server
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
No init scripts required: `chocola.config.json` is optional. See [Getting started](documentation/01-introduction/02-getting-started.md) for more.
|
package/bin/chocola.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
4
|
+
import { createRequire } from "module";
|
|
5
|
+
import { readFile } from "fs/promises";
|
|
6
|
+
import { getConfig, isMissingConfigFile } from "../utils.js";
|
|
7
|
+
import { loadConfig } from "../compiler/config.js";
|
|
8
|
+
|
|
9
|
+
import chalk from "../compiler/chalk.js";
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = path.dirname(__filename);
|
|
13
|
+
const isPlain = process.env.NO_COLOR === "1" || process.argv.includes("--plain");
|
|
14
|
+
const c = isPlain ? {
|
|
15
|
+
hex: () => (s) => s, bold: (s) => s, green: (s) => s, yellow: (s) => s, red: (s) => s, underline: (s) => s,
|
|
16
|
+
} : chalk;
|
|
17
|
+
|
|
18
|
+
export default __dirname;
|
|
19
|
+
|
|
20
|
+
export async function resolveConfig(rootDir, cliOverrides, configPathOpt) {
|
|
21
|
+
const customPath = configPathOpt ? path.resolve(rootDir, configPathOpt) : null;
|
|
22
|
+
const fullConfig = customPath
|
|
23
|
+
? await readFile(customPath, "utf-8").then(r => JSON.parse(r))
|
|
24
|
+
: await getConfig(rootDir, { silent: true });
|
|
25
|
+
const isMissing = customPath ? false : isMissingConfigFile(fullConfig);
|
|
26
|
+
const base = await loadConfig(rootDir, { silent: true, customPath });
|
|
27
|
+
if (cliOverrides.outDir) base.outDir = path.resolve(rootDir, cliOverrides.outDir);
|
|
28
|
+
if (cliOverrides.srcDir) base.srcDir = path.resolve(rootDir, cliOverrides.srcDir);
|
|
29
|
+
if (cliOverrides.libDir) base.libDir = path.resolve(rootDir, cliOverrides.libDir);
|
|
30
|
+
if (cliOverrides.emptyOutDir != null) base.emptyOutDir = cliOverrides.emptyOutDir;
|
|
31
|
+
const effectiveDev = { hostname: "localhost", port: 3000, ...(!isMissing && fullConfig.dev || {}), ...cliOverrides.dev };
|
|
32
|
+
const effectiveServer = { hostname: "localhost", port: 8080, middleware: null, ...(!isMissing && fullConfig.server || {}), ...cliOverrides.server };
|
|
33
|
+
if (!cliOverrides.server?.port && process.env.PORT) {
|
|
34
|
+
const envPort = parseInt(process.env.PORT, 10);
|
|
35
|
+
if (Number.isFinite(envPort)) effectiveServer.port = envPort;
|
|
36
|
+
}
|
|
37
|
+
if (!cliOverrides.server?.hostname && !fullConfig.server?.hostname && !fullConfig.server?.host && process.env.PORT) {
|
|
38
|
+
effectiveServer.hostname = "0.0.0.0";
|
|
39
|
+
}
|
|
40
|
+
return { fullConfig, base, effectiveDev, effectiveServer, isMissing };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function parsePort(val) {
|
|
44
|
+
const n = parseInt(val, 10);
|
|
45
|
+
if (Number.isNaN(n) || n < 1 || n > 65535) {
|
|
46
|
+
throw new Error(`Invalid port: ${val}. Must be between 1 and 65535.`);
|
|
47
|
+
}
|
|
48
|
+
return n;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function kebabToCamel(str) {
|
|
52
|
+
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function parseArgs(argv) {
|
|
56
|
+
const args = argv.slice(2).filter(a => a !== "--plain");
|
|
57
|
+
const cmd = args[0];
|
|
58
|
+
if (!cmd || cmd === "--help" || cmd === "-h") return { cmd: "help", rest: [] };
|
|
59
|
+
if (cmd === "--version" || cmd === "-v") return { cmd: "version", rest: [] };
|
|
60
|
+
const positional = [];
|
|
61
|
+
const flags = {};
|
|
62
|
+
let configPath = null;
|
|
63
|
+
let i = 1;
|
|
64
|
+
while (i < args.length) {
|
|
65
|
+
const arg = args[i];
|
|
66
|
+
if (arg === "--config" || arg === "-c") {
|
|
67
|
+
configPath = args[++i];
|
|
68
|
+
} else if (arg.startsWith("--")) {
|
|
69
|
+
const key = kebabToCamel(arg.slice(2));
|
|
70
|
+
if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
|
|
71
|
+
flags[key] = args[++i];
|
|
72
|
+
} else {
|
|
73
|
+
flags[key] = true;
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
positional.push(arg);
|
|
77
|
+
}
|
|
78
|
+
i++;
|
|
79
|
+
}
|
|
80
|
+
return { cmd, root: positional[0] || undefined, flags, configPath, rest: args };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function getLocalModules(rootDir) {
|
|
84
|
+
try {
|
|
85
|
+
const req = createRequire(pathToFileURL(path.join(rootDir, "package.json")).href);
|
|
86
|
+
const localPkgPath = req.resolve("chocola/package.json");
|
|
87
|
+
const localRoot = path.dirname(localPkgPath);
|
|
88
|
+
const cliDir = path.dirname(path.dirname(__filename));
|
|
89
|
+
if (localRoot === cliDir) return null;
|
|
90
|
+
const mod = await import(pathToFileURL(path.join(localRoot, "compiler/index.js")).href);
|
|
91
|
+
return { rootDir: localRoot, mod };
|
|
92
|
+
} catch { return null; }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function banner() {
|
|
96
|
+
if (!isPlain) {
|
|
97
|
+
const { logBanner } = await import("../compiler/index.js");
|
|
98
|
+
logBanner();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function main() {
|
|
103
|
+
const { cmd, root, flags, configPath } = parseArgs(process.argv);
|
|
104
|
+
|
|
105
|
+
if (cmd === "help") {
|
|
106
|
+
await banner();
|
|
107
|
+
console.log(c.bold("Usage: ") + "chocola <command> [root] [options]");
|
|
108
|
+
console.log("");
|
|
109
|
+
console.log("Commands:");
|
|
110
|
+
console.log(" build Build for production");
|
|
111
|
+
console.log(" dev Start dev server with hot-reload");
|
|
112
|
+
console.log(" serve Start SSR production server");
|
|
113
|
+
console.log("");
|
|
114
|
+
console.log("Options:");
|
|
115
|
+
console.log(" -c, --config <path> Path to config file");
|
|
116
|
+
console.log(" --help, -h Show help");
|
|
117
|
+
console.log(" --version, -v Show version");
|
|
118
|
+
console.log(" --plain, NO_COLOR=1 Disable color output");
|
|
119
|
+
console.log("");
|
|
120
|
+
console.log("build options:");
|
|
121
|
+
console.log(" --srcDir <dir> Source directory (default: src)");
|
|
122
|
+
console.log(" --outDir <dir> Output directory (default: dist)");
|
|
123
|
+
console.log(" --libDir <dir> Components subdir (default: lib)");
|
|
124
|
+
console.log(" --no-emptyOutDir Do not clean outDir before build");
|
|
125
|
+
console.log("");
|
|
126
|
+
console.log("dev options:");
|
|
127
|
+
console.log(" --host <hostname> Hostname (default: localhost)");
|
|
128
|
+
console.log(" --port <number> Port (default: 3000)");
|
|
129
|
+
console.log(" --open Open browser after start");
|
|
130
|
+
console.log("");
|
|
131
|
+
console.log("serve options:");
|
|
132
|
+
console.log(" --host <hostname> Hostname (default: localhost)");
|
|
133
|
+
console.log(" --port <number> Port (default: 8080)");
|
|
134
|
+
console.log(" --middleware <path> Path to middleware file");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (cmd === "version") {
|
|
138
|
+
const pkg = await readFile(path.join(__dirname, "../package.json"), "utf-8");
|
|
139
|
+
console.log(JSON.parse(pkg).version);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!["build", "dev", "serve"].includes(cmd)) {
|
|
144
|
+
console.error(c.red(`Unknown command: ${cmd}. Use "chocola --help" for usage.`));
|
|
145
|
+
process.exit(2);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const rootDir = path.resolve(root || process.cwd());
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
if (configPath) {
|
|
152
|
+
await readFile(path.resolve(rootDir, configPath), "utf-8");
|
|
153
|
+
}
|
|
154
|
+
const rawPort = flags.port ? parsePort(flags.port) : undefined;
|
|
155
|
+
const effectiveHost = flags.host || flags.hostname;
|
|
156
|
+
|
|
157
|
+
const { base, effectiveDev, effectiveServer } = await resolveConfig(rootDir, {
|
|
158
|
+
outDir: flags.outDir,
|
|
159
|
+
srcDir: flags.srcDir,
|
|
160
|
+
libDir: flags.libDir,
|
|
161
|
+
emptyOutDir: flags.emptyOutDir === false ? false : (flags.noEmptyOutDir === true ? false : undefined),
|
|
162
|
+
dev: { port: rawPort, hostname: effectiveHost },
|
|
163
|
+
server: { port: rawPort, hostname: effectiveHost },
|
|
164
|
+
}, configPath);
|
|
165
|
+
|
|
166
|
+
let serverInstance = null;
|
|
167
|
+
let devServer = null;
|
|
168
|
+
const cleanup = () => {
|
|
169
|
+
if (devServer) { try { devServer.close(); } catch {} }
|
|
170
|
+
if (serverInstance) { try { serverInstance.close(); } catch {} }
|
|
171
|
+
process.exit(0);
|
|
172
|
+
};
|
|
173
|
+
process.on("SIGINT", cleanup);
|
|
174
|
+
process.on("SIGTERM", cleanup);
|
|
175
|
+
|
|
176
|
+
switch (cmd) {
|
|
177
|
+
case "build": {
|
|
178
|
+
const local = await getLocalModules(rootDir);
|
|
179
|
+
const compile = local ? local.mod.default : (await import("../compiler/index.js")).default;
|
|
180
|
+
await compile(rootDir, { overrides: base });
|
|
181
|
+
process.exit(0);
|
|
182
|
+
}
|
|
183
|
+
case "dev": {
|
|
184
|
+
const local = await getLocalModules(rootDir);
|
|
185
|
+
const devMod = local
|
|
186
|
+
? await import(pathToFileURL(path.join(local.rootDir, "dev/index.js")).href)
|
|
187
|
+
: await import("../dev/index.js");
|
|
188
|
+
const serve = devMod.serve || devMod.default;
|
|
189
|
+
serverInstance = await serve(rootDir, {
|
|
190
|
+
port: rawPort || effectiveDev.port,
|
|
191
|
+
hostname: effectiveHost || effectiveDev.hostname,
|
|
192
|
+
open: flags.open || false,
|
|
193
|
+
silent: true,
|
|
194
|
+
});
|
|
195
|
+
devServer = serverInstance;
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
case "serve": {
|
|
199
|
+
const local = await getLocalModules(rootDir);
|
|
200
|
+
const serverMod = local
|
|
201
|
+
? await import(pathToFileURL(path.join(local.rootDir, "server/index.js")).href)
|
|
202
|
+
: await import("../server/index.js");
|
|
203
|
+
const serve = serverMod.serve || serverMod.default?.serve;
|
|
204
|
+
serverInstance = await serve(rootDir, {
|
|
205
|
+
port: rawPort || effectiveServer.port,
|
|
206
|
+
hostname: effectiveHost || effectiveServer.hostname,
|
|
207
|
+
middleware: flags.middleware || null,
|
|
208
|
+
silent: true,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
} catch (err) {
|
|
213
|
+
if (err.code === "ENOENT" && configPath) {
|
|
214
|
+
console.error(c.red(`Config file not found: ${path.resolve(rootDir, configPath)}`));
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
console.error(c.red(err.message || err));
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (process.argv[1] === __filename) {
|
|
223
|
+
main().catch(err => {
|
|
224
|
+
console.error(err.message || err);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const ansi = {
|
|
2
|
+
bold: "\x1b[1m",
|
|
3
|
+
underline: "\x1b[4m",
|
|
4
|
+
red: "\x1b[31m",
|
|
5
|
+
green: "\x1b[32m",
|
|
6
|
+
yellow: "\x1b[33m",
|
|
7
|
+
blue: "\x1b[36m",
|
|
8
|
+
dim: "\x1b[90m"
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const reset = "\x1b[0m";
|
|
12
|
+
|
|
13
|
+
function hexToAnsi(h) {
|
|
14
|
+
const r = parseInt(h.slice(1, 3), 16);
|
|
15
|
+
const g = parseInt(h.slice(3, 5), 16);
|
|
16
|
+
const b = parseInt(h.slice(5, 7), 16);
|
|
17
|
+
return `\x1b[38;2;${r};${g};${b}m`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function makeChalk(stack) {
|
|
21
|
+
const fn = (s) => {
|
|
22
|
+
if (stack.length === 0) return s;
|
|
23
|
+
return stack.map(n => ansi[n] || hexToAnsi(n)).join("") + s + reset;
|
|
24
|
+
};
|
|
25
|
+
return new Proxy(fn, {
|
|
26
|
+
get(_, prop) {
|
|
27
|
+
if (prop === "hex") return (h) => makeChalk([...stack, h]);
|
|
28
|
+
return makeChalk([...stack, prop]);
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const chalk = makeChalk([]);
|
|
34
|
+
export default chalk;
|