chocola 2.0.0-next.10 → 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 +16 -4
- package/bin/chocola.js +227 -0
- package/compiler/component-processor.js +308 -67
- package/compiler/config.js +42 -12
- package/compiler/index.js +34 -26
- package/compiler/module-graph.js +57 -10
- package/compiler/pipeline.js +3 -1
- package/compiler/render.js +3 -1
- package/dev/index.js +52 -14
- package/package.json +15 -1
- package/parser/component.js +77 -5
- package/parser/index.js +1 -0
- package/parser/script.js +485 -0
- package/parser/script.test.js +103 -0
- package/server/index.js +40 -10
- package/utils.js +73 -3
- 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
|
@@ -15,11 +15,10 @@ Import components. Instantiate them. Mount, update, remove. Client-side or serve
|
|
|
15
15
|
<script>
|
|
16
16
|
import CoolButton from './CoolButton.html';
|
|
17
17
|
|
|
18
|
-
let self;
|
|
19
|
-
let input;
|
|
20
|
-
|
|
21
18
|
export let title = "Hello";
|
|
22
19
|
|
|
20
|
+
let input;
|
|
21
|
+
|
|
23
22
|
function $runtime() {
|
|
24
23
|
input.focus();
|
|
25
24
|
}
|
|
@@ -40,4 +39,17 @@ Import components. Instantiate them. Mount, update, remove. Client-side or serve
|
|
|
40
39
|
|
|
41
40
|
## Documentation
|
|
42
41
|
|
|
43
|
-
|
|
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
|
+
}
|