mono-jsx-dom 0.1.3 → 0.1.5

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/bin/build.mjs CHANGED
@@ -60,11 +60,11 @@ async function run() {
60
60
  const flags = parseFlags();
61
61
  const start = performance.now();
62
62
  const runtime = flags.node ?? "fetch-server";
63
- await build({ runtime });
63
+ await build({ serverType: runtime });
64
64
  console.log("\x1B[32m\u2728 Build completed.\x1B[0m", "\x1B[90m(%d ms)\x1B[0m", performance.now() - start);
65
65
  }
66
66
  async function build(options) {
67
- const workDir = join(cwd(), options?.dir ?? ".");
67
+ const workDir = join(cwd(), options?.appName ?? ".");
68
68
  const outdir = join(workDir, options?.outdir ?? "dist");
69
69
  if (!await exists(join(workDir, "index.html"))) {
70
70
  console.error("index.html not found");
@@ -159,7 +159,9 @@ async function build(options) {
159
159
  for (const file of outputFiles) {
160
160
  const contentType = file.path.endsWith(".js") ? "application/javascript" : "text/css";
161
161
  const filename = relative(outdir, file.path);
162
- vfs[filename] = await createVFile(indexHTML, filename, file.text, contentType);
162
+ if (filename !== tw?.entryCSS) {
163
+ vfs[filename] = await createVFile(indexHTML, filename, file.text, contentType);
164
+ }
163
165
  }
164
166
  if (tw) {
165
167
  const css = await tw.build();
@@ -173,10 +175,10 @@ async function build(options) {
173
175
  'import buildJSON$ from "./build.json" with { type: "json" };',
174
176
  "server$.setVFS(new Map(Object.entries(buildJSON$)));"
175
177
  ].join("");
176
- await buildServerJS(workDir, outdir, options?.runtime, extraJS);
178
+ await buildServerJS(workDir, outdir, options?.serverType, extraJS);
177
179
  await dispose();
178
180
  }
179
- async function buildServerJS(workDir, outdir, runtime = "fetch-server", extraJS, watch) {
181
+ async function buildServerJS(workDir, outdir, serverType = "fetch-server", extraJS, watch) {
180
182
  const stdin2 = {
181
183
  sourcefile: join(workDir, "server.js"),
182
184
  contents: 'import server from "mono-jsx-dom/server;export default server;',
@@ -185,7 +187,7 @@ async function buildServerJS(workDir, outdir, runtime = "fetch-server", extraJS,
185
187
  for (const loader of ["ts", "tsx", "js", "jsx"]) {
186
188
  const sourcefile = join(workDir, "server." + loader);
187
189
  if (await exists(sourcefile)) {
188
- if (runtime === "node") {
190
+ if (serverType === "node") {
189
191
  stdin2.sourcefile = join(workDir, "server-node.mjs");
190
192
  stdin2.resolveDir = workDir;
191
193
  stdin2.contents = [
@@ -235,6 +237,7 @@ async function buildServerJS(workDir, outdir, runtime = "fetch-server", extraJS,
235
237
  } else {
236
238
  await ctx.rebuild();
237
239
  await ctx.dispose();
240
+ await esbuild.stop();
238
241
  }
239
242
  }
240
243
  function initTailwindBuilder(workDir, entryCSS) {
@@ -297,7 +300,7 @@ async function paseIndexHtml(workDir) {
297
300
  }
298
301
  const relativePath = relative(workDir, join(workDir, href));
299
302
  entryPoints[relativePath] = relativePath;
300
- return `<link${attrs} href="/${relativePath}">`;
303
+ return `<link${attrs}href="/${relativePath}">`;
301
304
  });
302
305
  content = content.replace(/<script(\s[^>]*?)src="([^"]+\.(ts|tsx|js|jsx|mjs))"\s*>/g, (tag, attrs, src) => {
303
306
  if (isUrl(src)) {
@@ -306,7 +309,7 @@ async function paseIndexHtml(workDir) {
306
309
  const relativePath = relative(workDir, join(workDir, src));
307
310
  const resolvedPath = relativePath.slice(0, relativePath.lastIndexOf(".")) + ".js";
308
311
  entryPoints[relativePath] = resolvedPath;
309
- return `<script${attrs} src="/${resolvedPath}">`;
312
+ return `<script${attrs}src="/${resolvedPath}">`;
310
313
  });
311
314
  return { content, entryPoints };
312
315
  }
@@ -318,7 +321,7 @@ async function createVFile(indexHTML, filename, content, contentType) {
318
321
  if (ext !== ".css" && ext !== ".js") {
319
322
  filename = filename.slice(0, -ext.length) + ".js";
320
323
  }
321
- indexHTML.content = indexHTML.content.replace('"/' + filename + '"', '"/' + filename + "?hash=" + hash.slice(0, 8) + '"');
324
+ indexHTML.content = indexHTML.content.replace('"/' + filename + '"', '"/' + filename + "?hash=" + contentHash.slice(0, 8) + '"');
322
325
  }
323
326
  return {
324
327
  content,
package/bin/cli CHANGED
@@ -10,7 +10,7 @@ switch (argv[2]) {
10
10
  import("./dev.mjs").then(command => command.run())
11
11
  break;
12
12
  case "build": {
13
- import("./build.mjs").then(command => command.run( ))
13
+ import("./build.mjs").then(command => command.run())
14
14
  break;
15
15
  }
16
16
  default:
package/bin/dev.mjs CHANGED
@@ -50,9 +50,9 @@ async function exists(filename) {
50
50
  // bin/dev.ts
51
51
  import { build } from "./build.mjs";
52
52
  async function dev(options) {
53
- const ac = new AbortController();
54
- const isBun = "Bun" in globalThis;
55
- const workDir = join(cwd(), options?.dir ?? ".");
53
+ const runtime = "Deno" in globalThis ? "deno" : "Bun" in globalThis ? "bun" : "node";
54
+ const ac = options?.ac ?? new AbortController();
55
+ const workDir = join(cwd(), options?.appName ?? ".");
56
56
  const outdir = join(workDir, options?.outdir ?? "dist");
57
57
  const port = options?.port ?? 3e3;
58
58
  const devServerPort = 8798;
@@ -77,8 +77,11 @@ async function dev(options) {
77
77
  });
78
78
  await serve({
79
79
  port: devServerPort,
80
+ hostname: "localhost",
80
81
  idleTimeout: 32,
81
82
  // 32 seconds
83
+ onListen: (_localAddress) => {
84
+ },
82
85
  fetch: async (req) => {
83
86
  const { pathname } = new URL(req.url);
84
87
  if (pathname === "/__dev_vfs.json") {
@@ -162,30 +165,49 @@ async function dev(options) {
162
165
  }
163
166
  });
164
167
  const serverScript = await resolveModule(join(workDir, "server"), [".ts", ".mjs", ".js"]) ?? join(workDir, "node_modules/mono-jsx-dom/server/index.mjs");
168
+ const args = ["--watch", serverScript];
169
+ if (runtime === "node") {
170
+ args.push("--port", port.toString());
171
+ } else {
172
+ args.unshift("--port", port.toString());
173
+ }
174
+ if (runtime === "deno") {
175
+ args.unshift("serve", "-A");
176
+ }
165
177
  const serverProcess = spawn(
166
- isBun ? "bun" : "node",
167
- ["--watch", serverScript, "--port", port.toString()],
178
+ runtime,
179
+ args,
168
180
  {
169
181
  cwd: workDir,
170
182
  env: { ...env, DEV_SERVER: devServerUrl },
171
183
  stdio: "inherit"
172
184
  }
173
185
  );
174
- serverProcess.on("close", () => ac.abort());
186
+ const onClose = () => ac.abort();
187
+ serverProcess.on("close", onClose);
188
+ ac.signal.addEventListener("abort", () => {
189
+ serverProcess.off("close", onClose);
190
+ serverProcess.kill();
191
+ });
175
192
  };
176
193
  const onError = (error) => {
177
194
  console.error(error);
178
195
  ac.abort();
179
196
  };
180
- if (!isBun) {
197
+ if (runtime === "node") {
181
198
  const [major, minor] = versions.node.split(".").map(Number);
182
199
  if (major < 22 || major === 22 && minor < 18) {
183
200
  console.error("Node.js version 22.18.0 or higher is required to use the dev command.");
184
201
  exit2(1);
185
202
  }
186
203
  }
204
+ process.on("SIGINT", () => {
205
+ console.log("\x1B[90mShutting down dev server...\x1B[0m");
206
+ ac.abort();
207
+ exit2(0);
208
+ });
187
209
  await build({
188
- dir: options?.dir,
210
+ appName: options?.appName,
189
211
  outdir: options?.outdir,
190
212
  dev: {
191
213
  signal: ac.signal,
@@ -194,9 +216,23 @@ async function dev(options) {
194
216
  }).catch(onError);
195
217
  }
196
218
  async function serve(options) {
197
- const serve2 = globalThis.Bun?.serve;
198
- if (serve2) {
199
- const server = serve2(options);
219
+ const denoServe = globalThis.Deno?.serve;
220
+ const bunServe = globalThis.Bun?.serve;
221
+ if (denoServe) {
222
+ const { fetch, port, signal, hostname, onListen } = options;
223
+ denoServe(
224
+ {
225
+ port,
226
+ signal,
227
+ hostname,
228
+ onListen: onListen ?? ((localAddress) => {
229
+ console.log(`Server is running on http://${hostname ?? "localhost"}:${localAddress.port}`);
230
+ })
231
+ },
232
+ fetch
233
+ );
234
+ } else if (bunServe) {
235
+ const server = bunServe(options);
200
236
  options.signal?.addEventListener("abort", () => server.stop());
201
237
  } else {
202
238
  await import("../server/node-fetch-server.mjs").then((m) => m.serve(options));
package/bin/init.mjs CHANGED
@@ -9,11 +9,74 @@ import { argv, exit, stdin, stdout } from "node:process";
9
9
  import { access, mkdir } from "node:fs/promises";
10
10
  import { createInterface } from "node:readline/promises";
11
11
  async function input(prompt, placeholder = "") {
12
+ const hint = `\x1B[34m?\x1B[0m ${prompt}:`;
13
+ if (stdin.isTTY && stdout.isTTY) {
14
+ let text = "";
15
+ const render = () => {
16
+ stdout.write(`\r\x1B[2K${hint} `);
17
+ if (text) {
18
+ stdout.write(text);
19
+ } else if (placeholder) {
20
+ stdout.write(`\x1B[90m${placeholder}\x1B[0m`);
21
+ stdout.write(`\x1B[${placeholder.length}D`);
22
+ }
23
+ };
24
+ render();
25
+ const wasRaw = stdin.isRaw;
26
+ stdin.setRawMode(true);
27
+ stdin.resume();
28
+ try {
29
+ text = await new Promise((resolve) => {
30
+ const decoder = new TextDecoder("utf8");
31
+ const onData = (buf) => {
32
+ const chars = typeof buf === "string" ? buf : decoder.decode(buf);
33
+ switch (chars) {
34
+ case "":
35
+ stdout.write("\x1B[999C\n\x1B[90mcancelled\x1B[0m\n");
36
+ exit(130);
37
+ return;
38
+ case "\r":
39
+ case "\n":
40
+ stdin.off("data", onData);
41
+ resolve(text.trim() || placeholder);
42
+ return;
43
+ case "\x7F":
44
+ case "\b":
45
+ text = text.slice(0, -1);
46
+ render();
47
+ return;
48
+ case "":
49
+ text = "";
50
+ render();
51
+ return;
52
+ case " ":
53
+ if (!text && placeholder) {
54
+ text = placeholder;
55
+ render();
56
+ }
57
+ return;
58
+ }
59
+ if (chars.startsWith("\x1B")) {
60
+ return;
61
+ }
62
+ text += chars;
63
+ render();
64
+ };
65
+ stdin.on("data", onData);
66
+ });
67
+ stdout.write(`\r\x1B[2K${hint} \x1B[32m${text}\x1B[0m
68
+ `);
69
+ return text;
70
+ } finally {
71
+ stdin.setRawMode(wasRaw ?? false);
72
+ stdin.pause();
73
+ }
74
+ }
12
75
  const rl = createInterface({ input: stdin, output: stdout });
13
76
  try {
14
- const line = await rl.question("\x1B[34m?\x1B[0m " + prompt + ":" + (placeholder ? " \x1B[90m" + placeholder + "\x1B[0m" : ""));
77
+ const line = await rl.question(hint + (placeholder ? " \x1B[90m" + placeholder + "\x1B[0m" : ""));
15
78
  if (stdout.isTTY) {
16
- stdout.write(`\x1B[1A\r\x1B[34m?\x1B[0m ${prompt} "\x1B[32m${line.trim()}\x1B[0m"\x1B[K
79
+ stdout.write(`\x1B[1A\r\x1B[34m?\x1B[0m ${prompt} \x1B[32m${line.trim()}\x1B[0m\x1B[K
17
80
  `);
18
81
  }
19
82
  return line.trim() || placeholder;
@@ -27,12 +90,14 @@ async function confirm(prompt) {
27
90
  stdout.write(hint);
28
91
  const wasRaw = stdin.isRaw;
29
92
  stdin.setRawMode(true);
93
+ stdin.resume();
30
94
  try {
31
95
  const yes = await new Promise((resolve) => {
32
96
  stdin.once("data", (buf) => {
33
97
  const c = typeof buf === "string" ? buf.charCodeAt(0) : buf[0];
34
98
  switch (c) {
35
99
  case 3:
100
+ stdout.write("\x1B[999C\n\x1B[90mcancelled\x1B[0m\n");
36
101
  exit(130);
37
102
  break;
38
103
  case 89:
@@ -54,6 +119,7 @@ async function confirm(prompt) {
54
119
  return yes;
55
120
  } finally {
56
121
  stdin.setRawMode(wasRaw ?? false);
122
+ stdin.pause();
57
123
  }
58
124
  }
59
125
  const rl = createInterface({ input: stdin, output: stdout });
@@ -101,8 +167,8 @@ var template = {
101
167
  version: "0.0.0",
102
168
  private: true,
103
169
  scripts: {
104
- dev: (bun ? "bun --bun" : "") + " mono-jsx-dom dev",
105
- build: (bun ? "bun --bun" : "") + " mono-jsx-dom build",
170
+ dev: (bun ? "bun --bun " : "") + "mono-jsx-dom dev",
171
+ build: (bun ? "bun --bun " : "") + "mono-jsx-dom build",
106
172
  start: bun ? "bun --bun mono-jsx-dom build && bun dist/server.mjs" : "mono-jsx-dom build --node && node dist/server.mjs"
107
173
  }
108
174
  },
@@ -158,18 +224,41 @@ export default {
158
224
  `
159
225
  };
160
226
  async function run() {
161
- const appName = argv2[3] ?? await input("Enter the name of the app:", "mono-app");
162
- return init(appName);
163
- }
164
- async function init(appName) {
165
- const appDir = join(cwd(), appName);
166
- if (await exists(appDir) && !await confirm(`Directory ${appName} already exists. Overwrite?`)) {
227
+ const appName = argv2[3] ?? await input("Enter the name of the app", "mono-app");
228
+ const dir = join(cwd(), appName);
229
+ if (await exists(dir) && !await confirm(`Directory ${appName} already exists. Overwrite?`)) {
167
230
  return;
168
231
  }
232
+ const tailwindCSS = await confirm("Use TailwindCSS for styling?");
233
+ const wrangler = await confirm("Add Cloudflare Workers integration?");
234
+ const npmCmd = bun ? "bun" : "npm";
235
+ await init({
236
+ dir,
237
+ appName,
238
+ tailwindCSS,
239
+ wrangler,
240
+ onInstall: () => {
241
+ console.log("\x1B[90mInstalling dependencies...\x1B[0m");
242
+ }
243
+ });
244
+ console.log("");
245
+ console.log("\u2728 \x1B[32mSetup completed.\x1B[0m");
246
+ console.log("You can now start or build the app with the following commands:");
247
+ console.log("");
248
+ console.log(`cd ${appName}`);
249
+ console.log(`${npmCmd} dev${bun ? " " : ""} \x1B[90m# start the app in development mode.\x1B[0m`);
250
+ if (wrangler) {
251
+ console.log(`${npmCmd}${bun ? " run" : ""} deploy \x1B[90m# deploy the app to Cloudflare Workers.\x1B[0m`);
252
+ } else {
253
+ console.log(`${npmCmd}${bun ? " run" : ""} build \x1B[90m# build the app for production.\x1B[0m`);
254
+ console.log(`${npmCmd} start${bun ? " " : ""} \x1B[90m# build and start the app in production mode.\x1B[0m`);
255
+ }
256
+ console.log("");
257
+ }
258
+ async function init(options) {
259
+ const { dir, appName, tailwindCSS, wrangler } = options;
169
260
  const scaffold = { ...template };
170
- const withTailwind = await confirm("Use TailwindCSS for styling?");
171
- const withWrangler = await confirm("Add Cloudflare Workers integration?");
172
- if (withWrangler) {
261
+ if (wrangler) {
173
262
  scaffold["wrangler.jsonc"] = JSON.stringify(
174
263
  {
175
264
  $schema: "./node_modules/wrangler/config-schema.json",
@@ -188,17 +277,17 @@ async function init(appName) {
188
277
  2
189
278
  );
190
279
  }
191
- if (!withTailwind) {
280
+ if (!tailwindCSS) {
192
281
  scaffold["app/style.css"] = "/* app styles */\n";
193
282
  }
194
- await ensureDir(appDir);
283
+ await ensureDir(dir);
195
284
  await Promise.all(
196
285
  Object.entries(scaffold).map(async ([filename, content]) => {
197
- const filepath = join(appDir, filename);
286
+ const filepath = join(dir, filename);
198
287
  if (filename === "package.json") {
199
288
  const pkg = JSON.parse(content);
200
289
  pkg.name = appName;
201
- if (withWrangler) {
290
+ if (wrangler) {
202
291
  pkg.scripts.dev = "wrangler dev";
203
292
  pkg.scripts.deploy = "wrangler deploy";
204
293
  delete pkg.scripts.build;
@@ -214,7 +303,7 @@ async function init(appName) {
214
303
  );
215
304
  let tsConfig = /* @__PURE__ */ Object.create(null);
216
305
  try {
217
- const data = await readFile(join(appDir, "tsconfig.json"), "utf8");
306
+ const data = await readFile(join(dir, "tsconfig.json"), "utf8");
218
307
  tsConfig = JSON.parse(data);
219
308
  } catch {
220
309
  }
@@ -226,44 +315,40 @@ async function init(appName) {
226
315
  compilerOptions.noEmit ??= true;
227
316
  compilerOptions.jsx = "react-jsx";
228
317
  compilerOptions.jsxImportSource = "mono-jsx-dom";
229
- await writeFile(join(appDir, "tsconfig.json"), JSON.stringify(tsConfig, null, 2));
230
- console.log("\x1B[90mInstalling dependencies...\x1B[0m");
231
- const cmd = install(appDir, withTailwind, withWrangler);
232
- const isBun = cmd === "bun";
233
- console.log("");
234
- console.log("\u2728 \x1B[32mSetup completed.\x1B[0m");
235
- console.log("You can now start or build the app with the following commands:");
236
- console.log("");
237
- console.log(`cd ${appName}`);
238
- console.log(`${cmd} dev${isBun ? " " : ""} \x1B[90m# start the app in development mode.\x1B[0m`);
239
- if (withWrangler) {
240
- console.log(`${cmd}${isBun ? " run" : ""} deploy \x1B[90m# deploy the app to Cloudflare Workers.\x1B[0m`);
241
- } else {
242
- console.log(`${cmd}${isBun ? " run" : ""} build \x1B[90m# build the app for production.\x1B[0m`);
243
- console.log(`${cmd} start${isBun ? " " : ""} \x1B[90m# build and start the app in production mode.\x1B[0m`);
244
- }
245
- console.log("");
318
+ await writeFile(join(dir, "tsconfig.json"), JSON.stringify(tsConfig, null, 2));
319
+ const depsOptions = { tailwindCSS, wrangler };
320
+ options.onInstall?.(depsOptions);
321
+ installDependencies(dir, depsOptions);
246
322
  }
247
- function install(cwd2, withTailwind, withWrangler) {
248
- let cmd = "npm";
249
- if (bun) {
250
- cmd = "bun";
251
- }
252
- spawnSync(cmd, ["add", "mono-jsx-dom"], { cwd: cwd2 });
323
+ function installDependencies(cwd2, options) {
324
+ const { tailwindCSS, wrangler, extraDependencies, extraDevDependencies } = options;
325
+ const npmCmd = bun ? "bun" : "npm";
326
+ const deps = ["mono-jsx-dom"];
253
327
  const devDeps = ["esbuild"];
254
- if (withTailwind) {
328
+ if (tailwindCSS) {
255
329
  devDeps.push("tailwindcss", "oxide-wasm");
256
330
  }
257
- if (withWrangler) {
331
+ if (wrangler) {
258
332
  devDeps.push("wrangler");
259
333
  }
260
- spawnSync(cmd, ["add", "-D", ...devDeps], { cwd: cwd2 });
261
- if (withWrangler) {
262
- spawnSync(cmd, ["wrangler", "types"], { cwd: cwd2 });
334
+ if (extraDependencies) {
335
+ deps.push(...extraDependencies);
336
+ }
337
+ if (extraDevDependencies) {
338
+ devDeps.push(...extraDevDependencies);
339
+ }
340
+ spawnSync(npmCmd, ["add", ...deps], { cwd: cwd2 });
341
+ spawnSync(npmCmd, ["add", "-D", ...devDeps], { cwd: cwd2 });
342
+ if (wrangler) {
343
+ if (npmCmd === "bun") {
344
+ spawnSync(npmCmd, ["--bun", "wrangler", "types"], { cwd: cwd2 });
345
+ } else {
346
+ spawnSync(npmCmd, ["wrangler", "types"], { cwd: cwd2 });
347
+ }
263
348
  }
264
- return cmd;
265
349
  }
266
350
  export {
267
351
  init,
352
+ installDependencies,
268
353
  run
269
354
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mono-jsx-dom",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "A JSX runtime for browsers.",
5
5
  "keywords": [
6
6
  "jsx",
@@ -115,12 +115,16 @@ async function sendResponse(res, response) {
115
115
  res.end();
116
116
  }
117
117
  function serve(options) {
118
- const port = options?.port ?? getDefaultPort();
119
- const server = createServer(createRequestListener(options.fetch, options));
118
+ const { port = getDefaultPort(), hostname, signal, fetch, onListen } = options;
119
+ const server = createServer(createRequestListener(fetch, options));
120
120
  server.listen(port, options?.hostname, () => {
121
- console.log(`Server is running on http://${options?.hostname ?? "localhost"}:${port}`);
121
+ if (onListen) {
122
+ onListen({ port });
123
+ } else {
124
+ console.log(`Server is running on http://${hostname ?? "localhost"}:${port}`);
125
+ }
122
126
  });
123
- options?.signal?.addEventListener("abort", () => {
127
+ signal?.addEventListener("abort", () => {
124
128
  server.close();
125
129
  });
126
130
  return new Promise((resolve, reject) => {