mono-jsx-dom 0.1.4 → 0.1.6

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
@@ -56,15 +56,8 @@ async function ensureDir(path) {
56
56
  }
57
57
 
58
58
  // bin/build.ts
59
- async function run() {
60
- const flags = parseFlags();
61
- const start = performance.now();
62
- const runtime = flags.node ?? "fetch-server";
63
- await build({ runtime });
64
- console.log("\x1B[32m\u2728 Build completed.\x1B[0m", "\x1B[90m(%d ms)\x1B[0m", performance.now() - start);
65
- }
66
59
  async function build(options) {
67
- const workDir = join(cwd(), options?.dir ?? ".");
60
+ const workDir = join(cwd(), options?.appName ?? ".");
68
61
  const outdir = join(workDir, options?.outdir ?? "dist");
69
62
  if (!await exists(join(workDir, "index.html"))) {
70
63
  console.error("index.html not found");
@@ -159,7 +152,9 @@ async function build(options) {
159
152
  for (const file of outputFiles) {
160
153
  const contentType = file.path.endsWith(".js") ? "application/javascript" : "text/css";
161
154
  const filename = relative(outdir, file.path);
162
- vfs[filename] = await createVFile(indexHTML, filename, file.text, contentType);
155
+ if (filename !== tw?.entryCSS) {
156
+ vfs[filename] = await createVFile(indexHTML, filename, file.text, contentType);
157
+ }
163
158
  }
164
159
  if (tw) {
165
160
  const css = await tw.build();
@@ -173,10 +168,17 @@ async function build(options) {
173
168
  'import buildJSON$ from "./build.json" with { type: "json" };',
174
169
  "server$.setVFS(new Map(Object.entries(buildJSON$)));"
175
170
  ].join("");
176
- await buildServerJS(workDir, outdir, options?.runtime, extraJS);
171
+ await buildServerJS(workDir, outdir, options?.serverType, extraJS);
177
172
  await dispose();
178
173
  }
179
- async function buildServerJS(workDir, outdir, runtime = "fetch-server", extraJS, watch) {
174
+ async function run() {
175
+ const flags = parseFlags();
176
+ const start = performance.now();
177
+ const serverType = flags.node ? "node" : "fetch-server";
178
+ await build({ serverType });
179
+ console.log("\x1B[32m\u2728 Build completed.\x1B[0m", "\x1B[90m(" + Math.ceil(performance.now() - start) + " ms)\x1B[0m");
180
+ }
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,11 +187,11 @@ 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 = [
192
- 'import { serve$ } from "mono-jsx-dom/server/node-fetch-server";',
194
+ 'import { serve as serve$ } from "mono-jsx-dom/node-fetch-server";',
193
195
  'import server$ from "./server.' + loader + '";',
194
196
  "serve$(server$);"
195
197
  ].join("\n");
@@ -213,7 +215,7 @@ async function buildServerJS(workDir, outdir, runtime = "fetch-server", extraJS,
213
215
  platform: "node",
214
216
  format: "esm",
215
217
  target: "es2024",
216
- external: ["mono-jsx-dom/server", "mono-jsx-dom/server/node-fetch-server"]
218
+ external: ["mono-jsx-dom/server", "mono-jsx-dom/node-fetch-server"]
217
219
  };
218
220
  if (extraJS) {
219
221
  esbOptions.banner = { js: extraJS };
@@ -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,
@@ -332,6 +335,5 @@ function isUrl(url) {
332
335
  }
333
336
  export {
334
337
  build,
335
- buildServerJS,
336
338
  run
337
339
  };
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,50 @@ 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.unshift("--import", "mono-jsx-dom/node-server-hooks");
171
+ args.push("--port", port.toString());
172
+ } else {
173
+ args.unshift("--port", port.toString());
174
+ }
175
+ if (runtime === "deno") {
176
+ args.unshift("serve", "-A");
177
+ }
165
178
  const serverProcess = spawn(
166
- isBun ? "bun" : "node",
167
- ["--watch", serverScript, "--port", port.toString()],
179
+ runtime,
180
+ args,
168
181
  {
169
182
  cwd: workDir,
170
183
  env: { ...env, DEV_SERVER: devServerUrl },
171
184
  stdio: "inherit"
172
185
  }
173
186
  );
174
- serverProcess.on("close", () => ac.abort());
187
+ const onClose = () => ac.abort();
188
+ serverProcess.on("close", onClose);
189
+ ac.signal.addEventListener("abort", () => {
190
+ serverProcess.off("close", onClose);
191
+ serverProcess.kill();
192
+ });
175
193
  };
176
194
  const onError = (error) => {
177
195
  console.error(error);
178
196
  ac.abort();
179
197
  };
180
- if (!isBun) {
198
+ if (runtime === "node") {
181
199
  const [major, minor] = versions.node.split(".").map(Number);
182
200
  if (major < 22 || major === 22 && minor < 18) {
183
201
  console.error("Node.js version 22.18.0 or higher is required to use the dev command.");
184
202
  exit2(1);
185
203
  }
186
204
  }
205
+ process.on("SIGINT", () => {
206
+ console.log("\x1B[90mShutting down dev server...\x1B[0m");
207
+ ac.abort();
208
+ exit2(0);
209
+ });
187
210
  await build({
188
- dir: options?.dir,
211
+ appName: options?.appName,
189
212
  outdir: options?.outdir,
190
213
  dev: {
191
214
  signal: ac.signal,
@@ -194,9 +217,23 @@ async function dev(options) {
194
217
  }).catch(onError);
195
218
  }
196
219
  async function serve(options) {
197
- const serve2 = globalThis.Bun?.serve;
198
- if (serve2) {
199
- const server = serve2(options);
220
+ const denoServe = globalThis.Deno?.serve;
221
+ const bunServe = globalThis.Bun?.serve;
222
+ if (denoServe) {
223
+ const { fetch, port, signal, hostname, onListen } = options;
224
+ denoServe(
225
+ {
226
+ port,
227
+ signal,
228
+ hostname,
229
+ onListen: onListen ?? ((localAddress) => {
230
+ console.log(`Server is running on http://${hostname ?? "localhost"}:${localAddress.port}`);
231
+ })
232
+ },
233
+ fetch
234
+ );
235
+ } else if (bunServe) {
236
+ const server = bunServe(options);
200
237
  options.signal?.addEventListener("abort", () => server.stop());
201
238
  } else {
202
239
  await import("../server/node-fetch-server.mjs").then((m) => m.serve(options));
package/bin/init.mjs CHANGED
@@ -69,6 +69,7 @@ async function input(prompt, placeholder = "") {
69
69
  return text;
70
70
  } finally {
71
71
  stdin.setRawMode(wasRaw ?? false);
72
+ stdin.pause();
72
73
  }
73
74
  }
74
75
  const rl = createInterface({ input: stdin, output: stdout });
@@ -89,6 +90,7 @@ async function confirm(prompt) {
89
90
  stdout.write(hint);
90
91
  const wasRaw = stdin.isRaw;
91
92
  stdin.setRawMode(true);
93
+ stdin.resume();
92
94
  try {
93
95
  const yes = await new Promise((resolve) => {
94
96
  stdin.once("data", (buf) => {
@@ -117,6 +119,7 @@ async function confirm(prompt) {
117
119
  return yes;
118
120
  } finally {
119
121
  stdin.setRawMode(wasRaw ?? false);
122
+ stdin.pause();
120
123
  }
121
124
  }
122
125
  const rl = createInterface({ input: stdin, output: stdout });
@@ -163,9 +166,10 @@ var template = {
163
166
  name: "mono-app",
164
167
  version: "0.0.0",
165
168
  private: true,
169
+ type: "module",
166
170
  scripts: {
167
- dev: (bun ? "bun --bun" : "") + " mono-jsx-dom dev",
168
- build: (bun ? "bun --bun" : "") + " mono-jsx-dom build",
171
+ dev: (bun ? "bun --bun " : "") + "mono-jsx-dom dev",
172
+ build: (bun ? "bun --bun " : "") + "mono-jsx-dom build",
169
173
  start: bun ? "bun --bun mono-jsx-dom build && bun dist/server.mjs" : "mono-jsx-dom build --node && node dist/server.mjs"
170
174
  }
171
175
  },
@@ -222,17 +226,40 @@ export default {
222
226
  };
223
227
  async function run() {
224
228
  const appName = argv2[3] ?? await input("Enter the name of the app", "mono-app");
225
- return init(appName);
226
- }
227
- async function init(appName) {
228
- const appDir = join(cwd(), appName);
229
- if (await exists(appDir) && !await confirm(`Directory ${appName} already exists. Overwrite?`)) {
229
+ const dir = join(cwd(), appName);
230
+ if (await exists(dir) && !await confirm(`Directory ${appName} already exists. Overwrite?`)) {
230
231
  return;
231
232
  }
233
+ const tailwindCSS = await confirm("Use TailwindCSS for styling?");
234
+ const wrangler = await confirm("Add Cloudflare Workers integration?");
235
+ const npmCmd = bun ? "bun" : "npm";
236
+ await init({
237
+ dir,
238
+ appName,
239
+ tailwindCSS,
240
+ wrangler,
241
+ onInstall: () => {
242
+ console.log("\x1B[90mInstalling dependencies...\x1B[0m");
243
+ }
244
+ });
245
+ console.log("");
246
+ console.log("\u2728 \x1B[32mSetup completed.\x1B[0m");
247
+ console.log("You can now start or build the app with the following commands:");
248
+ console.log("");
249
+ console.log(`cd ${appName}`);
250
+ console.log(`${npmCmd} run dev \x1B[90m# start the app in development mode.\x1B[0m`);
251
+ if (wrangler) {
252
+ console.log(`${npmCmd} run deploy \x1B[90m# deploy the app to Cloudflare Workers.\x1B[0m`);
253
+ } else {
254
+ console.log(`${npmCmd} run build \x1B[90m# build the app for production.\x1B[0m`);
255
+ console.log(`${npmCmd} run start \x1B[90m# build and start the app in production mode.\x1B[0m`);
256
+ }
257
+ console.log("");
258
+ }
259
+ async function init(options) {
260
+ const { dir, appName, tailwindCSS, wrangler } = options;
232
261
  const scaffold = { ...template };
233
- const withTailwind = await confirm("Use TailwindCSS for styling?");
234
- const withWrangler = await confirm("Add Cloudflare Workers integration?");
235
- if (withWrangler) {
262
+ if (wrangler) {
236
263
  scaffold["wrangler.jsonc"] = JSON.stringify(
237
264
  {
238
265
  $schema: "./node_modules/wrangler/config-schema.json",
@@ -251,17 +278,17 @@ async function init(appName) {
251
278
  2
252
279
  );
253
280
  }
254
- if (!withTailwind) {
281
+ if (!tailwindCSS) {
255
282
  scaffold["app/style.css"] = "/* app styles */\n";
256
283
  }
257
- await ensureDir(appDir);
284
+ await ensureDir(dir);
258
285
  await Promise.all(
259
286
  Object.entries(scaffold).map(async ([filename, content]) => {
260
- const filepath = join(appDir, filename);
287
+ const filepath = join(dir, filename);
261
288
  if (filename === "package.json") {
262
289
  const pkg = JSON.parse(content);
263
290
  pkg.name = appName;
264
- if (withWrangler) {
291
+ if (wrangler) {
265
292
  pkg.scripts.dev = "wrangler dev";
266
293
  pkg.scripts.deploy = "wrangler deploy";
267
294
  delete pkg.scripts.build;
@@ -277,7 +304,7 @@ async function init(appName) {
277
304
  );
278
305
  let tsConfig = /* @__PURE__ */ Object.create(null);
279
306
  try {
280
- const data = await readFile(join(appDir, "tsconfig.json"), "utf8");
307
+ const data = await readFile(join(dir, "tsconfig.json"), "utf8");
281
308
  tsConfig = JSON.parse(data);
282
309
  } catch {
283
310
  }
@@ -289,44 +316,40 @@ async function init(appName) {
289
316
  compilerOptions.noEmit ??= true;
290
317
  compilerOptions.jsx = "react-jsx";
291
318
  compilerOptions.jsxImportSource = "mono-jsx-dom";
292
- await writeFile(join(appDir, "tsconfig.json"), JSON.stringify(tsConfig, null, 2));
293
- console.log("\x1B[90mInstalling dependencies...\x1B[0m");
294
- const cmd = install(appDir, withTailwind, withWrangler);
295
- const isBun = cmd === "bun";
296
- console.log("");
297
- console.log("\u2728 \x1B[32mSetup completed.\x1B[0m");
298
- console.log("You can now start or build the app with the following commands:");
299
- console.log("");
300
- console.log(`cd ${appName}`);
301
- console.log(`${cmd} dev${isBun ? " " : ""} \x1B[90m# start the app in development mode.\x1B[0m`);
302
- if (withWrangler) {
303
- console.log(`${cmd}${isBun ? " run" : ""} deploy \x1B[90m# deploy the app to Cloudflare Workers.\x1B[0m`);
304
- } else {
305
- console.log(`${cmd}${isBun ? " run" : ""} build \x1B[90m# build the app for production.\x1B[0m`);
306
- console.log(`${cmd} start${isBun ? " " : ""} \x1B[90m# build and start the app in production mode.\x1B[0m`);
307
- }
308
- console.log("");
319
+ await writeFile(join(dir, "tsconfig.json"), JSON.stringify(tsConfig, null, 2));
320
+ const depsOptions = { tailwindCSS, wrangler };
321
+ options.onInstall?.(depsOptions);
322
+ installDependencies(dir, depsOptions);
309
323
  }
310
- function install(cwd2, withTailwind, withWrangler) {
311
- let cmd = "npm";
312
- if (bun) {
313
- cmd = "bun";
314
- }
315
- spawnSync(cmd, ["add", "mono-jsx-dom"], { cwd: cwd2 });
324
+ function installDependencies(cwd2, options) {
325
+ const { tailwindCSS, wrangler, extraDependencies, extraDevDependencies } = options;
326
+ const npmCmd = bun ? "bun" : "npm";
327
+ const deps = ["mono-jsx-dom"];
316
328
  const devDeps = ["esbuild"];
317
- if (withTailwind) {
329
+ if (tailwindCSS) {
318
330
  devDeps.push("tailwindcss", "oxide-wasm");
319
331
  }
320
- if (withWrangler) {
332
+ if (wrangler) {
321
333
  devDeps.push("wrangler");
322
334
  }
323
- spawnSync(cmd, ["add", "-D", ...devDeps], { cwd: cwd2 });
324
- if (withWrangler) {
325
- spawnSync(cmd, ["wrangler", "types"], { cwd: cwd2 });
335
+ if (extraDependencies) {
336
+ deps.push(...extraDependencies);
337
+ }
338
+ if (extraDevDependencies) {
339
+ devDeps.push(...extraDevDependencies);
340
+ }
341
+ spawnSync(npmCmd, ["add", ...deps], { cwd: cwd2 });
342
+ spawnSync(npmCmd, ["add", "-D", ...devDeps], { cwd: cwd2 });
343
+ if (wrangler) {
344
+ if (npmCmd === "bun") {
345
+ spawnSync(npmCmd, ["--bun", "wrangler", "types"], { cwd: cwd2 });
346
+ } else {
347
+ spawnSync(npmCmd, ["wrangler", "types"], { cwd: cwd2 });
348
+ }
326
349
  }
327
- return cmd;
328
350
  }
329
351
  export {
330
352
  init,
353
+ installDependencies,
331
354
  run
332
355
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mono-jsx-dom",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "A JSX runtime for browsers.",
5
5
  "keywords": [
6
6
  "jsx",
@@ -35,9 +35,13 @@
35
35
  "node": "./server/index.mjs",
36
36
  "import": "./server/index.mjs"
37
37
  },
38
- "./server/node-fetch-server": {
38
+ "./node-fetch-server": {
39
39
  "node": "./server/node-fetch-server.mjs",
40
40
  "import": "./server/node-fetch-server.mjs"
41
+ },
42
+ "./node-server-hooks": {
43
+ "node": "./server/node-server-hooks.mjs",
44
+ "import": "./server/node-server-hooks.mjs"
41
45
  }
42
46
  },
43
47
  "scripts": {
@@ -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) => {
@@ -0,0 +1,17 @@
1
+ // server/node-server-hooks.ts
2
+ import { argv } from "node:process";
3
+ import { registerHooks } from "node:module";
4
+ var serverHook = `
5
+ import { serve as serve$ } from "mono-jsx-dom/node-fetch-server";
6
+ import(import.meta.url).then(m => serve$(m.default));
7
+ `;
8
+ registerHooks({
9
+ load(url, context, nextLoad) {
10
+ const result = nextLoad(url, context);
11
+ if (result.source && "file://" + argv[1] === url) {
12
+ const source = typeof result.source === "string" ? result.source : new TextDecoder().decode(result.source);
13
+ return { ...result, source: source + serverHook };
14
+ }
15
+ return result;
16
+ }
17
+ });