@hono/cli 0.1.2 → 0.1.4

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.
Files changed (3) hide show
  1. package/README.md +5 -0
  2. package/dist/cli.js +154 -115
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -255,6 +255,8 @@ hono optimize [entry] [options]
255
255
  **Options:**
256
256
 
257
257
  - `-o, --outfile <outfile>` - Output file
258
+ - `-m, --minify` - minify output file
259
+ - `-t, --target [target]` - environment target
258
260
 
259
261
  **Examples:**
260
262
 
@@ -267,6 +269,9 @@ hono optimize -o dist/app.js src/app.ts
267
269
 
268
270
  # Export bundled file with minification
269
271
  hono optimize -m
272
+
273
+ # Specify environment target
274
+ hono optimize -t es2024
270
275
  ```
271
276
 
272
277
  ## Tips
package/dist/cli.js CHANGED
@@ -70,114 +70,147 @@ import { dirname, join, resolve } from "path";
70
70
 
71
71
  // src/utils/build.ts
72
72
  import * as esbuild from "esbuild";
73
- import { extname } from "path";
74
- import { pathToFileURL } from "url";
75
- async function buildAndImportApp(filePath, options = {}) {
76
- const ext = extname(filePath);
77
- if ([".ts", ".tsx", ".jsx"].includes(ext)) {
78
- const result = await esbuild.build({
79
- entryPoints: [filePath],
80
- bundle: true,
81
- write: false,
82
- format: "esm",
83
- target: "node20",
84
- jsx: "automatic",
85
- jsxImportSource: "hono/jsx",
86
- platform: "node",
87
- external: options.external || []
73
+ async function* buildAndImportApp(filePath, options = {}) {
74
+ let resolveApp;
75
+ let appPromise;
76
+ const preparePromise = () => {
77
+ appPromise = new Promise((resolve4) => {
78
+ resolveApp = resolve4;
88
79
  });
89
- const code = result.outputFiles[0].text;
90
- const dataUrl = `data:text/javascript;base64,${Buffer.from(code).toString("base64")}`;
91
- const module = await import(dataUrl);
92
- return module.default;
93
- } else {
94
- const module = await import(pathToFileURL(filePath).href);
95
- return module.default;
80
+ };
81
+ preparePromise();
82
+ const context2 = await esbuild.context({
83
+ entryPoints: [filePath],
84
+ bundle: true,
85
+ write: false,
86
+ format: "esm",
87
+ target: "node20",
88
+ jsx: "automatic",
89
+ jsxImportSource: "hono/jsx",
90
+ platform: "node",
91
+ external: options.external || [],
92
+ plugins: [
93
+ {
94
+ name: "watch",
95
+ setup(build2) {
96
+ build2.onEnd(async (result) => {
97
+ try {
98
+ const code = result.outputFiles?.[0]?.text || "";
99
+ const dataUrl = `data:text/javascript;base64,${Buffer.from(code).toString("base64")}`;
100
+ const module = await import(dataUrl);
101
+ const app = module.default;
102
+ if (!app) {
103
+ throw new Error("Failed to build app");
104
+ }
105
+ if (!app || typeof app.request !== "function") {
106
+ throw new Error("No valid Hono app exported from the file");
107
+ }
108
+ try {
109
+ resolveApp(app);
110
+ } catch {
111
+ }
112
+ } catch (error) {
113
+ console.error("Error building app", error);
114
+ }
115
+ });
116
+ }
117
+ }
118
+ ]
119
+ });
120
+ await context2.watch();
121
+ if (!options.watch) {
122
+ await context2.dispose();
96
123
  }
124
+ do {
125
+ yield await appPromise;
126
+ preparePromise();
127
+ } while (options.watch);
97
128
  }
98
129
 
99
130
  // src/commands/optimize/index.ts
100
131
  var DEFAULT_ENTRY_CANDIDATES = ["src/index.ts", "src/index.tsx", "src/index.js", "src/index.jsx"];
101
132
  function optimizeCommand(program2) {
102
- program2.command("optimize").description("Build optimized Hono class").argument("[entry]", "entry file").option("-o, --outfile [outfile]", "output file", "dist/index.js").option("-m, --minify", "minify output file").action(async (entry, options) => {
103
- if (!entry) {
104
- entry = DEFAULT_ENTRY_CANDIDATES.find((entry2) => existsSync(entry2)) ?? DEFAULT_ENTRY_CANDIDATES[0];
105
- }
106
- const appPath = resolve(process.cwd(), entry);
107
- if (!existsSync(appPath)) {
108
- throw new Error(`Entry file ${entry} does not exist`);
109
- }
110
- const appFilePath = realpathSync(appPath);
111
- const app = await buildAndImportApp(appFilePath, {
112
- external: ["@hono/node-server"]
113
- });
114
- let routerName;
115
- let importStatement;
116
- let assignRouterStatement;
117
- try {
118
- const serialized = serializeInitParams(
119
- buildInitParams({
120
- paths: app.routes.map(({ path }) => path)
121
- })
122
- );
123
- const hasPreparedRegExpRouter = await new Promise((resolve4) => {
124
- const child = execFile(process.execPath, [
125
- "--input-type=module",
126
- "-e",
127
- "try { (await import('hono/router/reg-exp-router')).PreparedRegExpRouter && process.exit(0) } finally { process.exit(1) }"
128
- ]);
129
- child.on("exit", (code) => {
130
- resolve4(code === 0);
131
- });
133
+ program2.command("optimize").description("Build optimized Hono class").argument("[entry]", "entry file").option("-o, --outfile [outfile]", "output file", "dist/index.js").option("-m, --minify", "minify output file").option("-t, --target [target]", "environment target (e.g., node24, deno2, es2024)", "node20").action(
134
+ async (entry, options) => {
135
+ if (!entry) {
136
+ entry = DEFAULT_ENTRY_CANDIDATES.find((entry2) => existsSync(entry2)) ?? DEFAULT_ENTRY_CANDIDATES[0];
137
+ }
138
+ const appPath = resolve(process.cwd(), entry);
139
+ if (!existsSync(appPath)) {
140
+ throw new Error(`Entry file ${entry} does not exist`);
141
+ }
142
+ const appFilePath = realpathSync(appPath);
143
+ const buildIterator = buildAndImportApp(appFilePath, {
144
+ external: ["@hono/node-server"]
132
145
  });
133
- if (hasPreparedRegExpRouter) {
134
- routerName = "PreparedRegExpRouter";
135
- importStatement = "import { PreparedRegExpRouter } from 'hono/router/reg-exp-router'";
136
- assignRouterStatement = `const routerParams = ${serialized}
146
+ const app = (await buildIterator.next()).value;
147
+ let routerName;
148
+ let importStatement;
149
+ let assignRouterStatement;
150
+ try {
151
+ const serialized = serializeInitParams(
152
+ buildInitParams({
153
+ paths: app.routes.map(({ path }) => path)
154
+ })
155
+ );
156
+ const hasPreparedRegExpRouter = await new Promise((resolve4) => {
157
+ const child = execFile(process.execPath, [
158
+ "--input-type=module",
159
+ "-e",
160
+ "try { (await import('hono/router/reg-exp-router')).PreparedRegExpRouter && process.exit(0) } finally { process.exit(1) }"
161
+ ]);
162
+ child.on("exit", (code) => {
163
+ resolve4(code === 0);
164
+ });
165
+ });
166
+ if (hasPreparedRegExpRouter) {
167
+ routerName = "PreparedRegExpRouter";
168
+ importStatement = "import { PreparedRegExpRouter } from 'hono/router/reg-exp-router'";
169
+ assignRouterStatement = `const routerParams = ${serialized}
137
170
  this.router = new PreparedRegExpRouter(...routerParams)`;
138
- } else {
139
- routerName = "RegExpRouter";
140
- importStatement = "import { RegExpRouter } from 'hono/router/reg-exp-router'";
141
- assignRouterStatement = "this.router = new RegExpRouter()";
171
+ } else {
172
+ routerName = "RegExpRouter";
173
+ importStatement = "import { RegExpRouter } from 'hono/router/reg-exp-router'";
174
+ assignRouterStatement = "this.router = new RegExpRouter()";
175
+ }
176
+ } catch {
177
+ routerName = "TrieRouter";
178
+ importStatement = "import { TrieRouter } from 'hono/router/trie-router'";
179
+ assignRouterStatement = "this.router = new TrieRouter()";
142
180
  }
143
- } catch {
144
- routerName = "TrieRouter";
145
- importStatement = "import { TrieRouter } from 'hono/router/trie-router'";
146
- assignRouterStatement = "this.router = new TrieRouter()";
147
- }
148
- console.log("[Optimized]");
149
- console.log(` Router: ${routerName}`);
150
- const outfile = resolve(process.cwd(), options.outfile);
151
- await esbuild2.build({
152
- entryPoints: [appFilePath],
153
- outfile,
154
- bundle: true,
155
- minify: options.minify,
156
- format: "esm",
157
- target: "node20",
158
- platform: "node",
159
- jsx: "automatic",
160
- jsxImportSource: "hono/jsx",
161
- plugins: [
162
- {
163
- name: "hono-optimize",
164
- setup(build3) {
165
- const honoPseudoImportPath = "hono-optimized-pseudo-import-path";
166
- build3.onResolve({ filter: /^hono$/ }, async (args) => {
167
- if (!args.importer) {
168
- return void 0;
169
- }
170
- const resolved = await build3.resolve(args.path, {
171
- kind: "import-statement",
172
- resolveDir: args.resolveDir
181
+ console.log("[Optimized]");
182
+ console.log(` Router: ${routerName}`);
183
+ const outfile = resolve(process.cwd(), options.outfile);
184
+ await esbuild2.build({
185
+ entryPoints: [appFilePath],
186
+ outfile,
187
+ bundle: true,
188
+ minify: options.minify,
189
+ format: "esm",
190
+ target: options.target,
191
+ platform: "node",
192
+ jsx: "automatic",
193
+ jsxImportSource: "hono/jsx",
194
+ plugins: [
195
+ {
196
+ name: "hono-optimize",
197
+ setup(build2) {
198
+ const honoPseudoImportPath = "hono-optimized-pseudo-import-path";
199
+ build2.onResolve({ filter: /^hono$/ }, async (args) => {
200
+ if (!args.importer) {
201
+ return void 0;
202
+ }
203
+ const resolved = await build2.resolve(args.path, {
204
+ kind: "import-statement",
205
+ resolveDir: args.resolveDir
206
+ });
207
+ return {
208
+ path: join(dirname(resolved.path), honoPseudoImportPath)
209
+ };
173
210
  });
174
- return {
175
- path: join(dirname(resolved.path), honoPseudoImportPath)
176
- };
177
- });
178
- build3.onLoad({ filter: new RegExp(`/${honoPseudoImportPath}$`) }, async () => {
179
- return {
180
- contents: `
211
+ build2.onLoad({ filter: new RegExp(`/${honoPseudoImportPath}$`) }, async () => {
212
+ return {
213
+ contents: `
181
214
  import { HonoBase } from 'hono/hono-base'
182
215
  ${importStatement}
183
216
  export class Hono extends HonoBase {
@@ -187,15 +220,16 @@ export class Hono extends HonoBase {
187
220
  }
188
221
  }
189
222
  `
190
- };
191
- });
223
+ };
224
+ });
225
+ }
192
226
  }
193
- }
194
- ]
195
- });
196
- const outfileStat = statSync(outfile);
197
- console.log(` Output: ${options.outfile} (${(outfileStat.size / 1024).toFixed(2)} KB)`);
198
- });
227
+ ]
228
+ });
229
+ const outfileStat = statSync(outfile);
230
+ console.log(` Output: ${options.outfile} (${(outfileStat.size / 1024).toFixed(2)} KB)`);
231
+ }
232
+ );
199
233
  }
200
234
 
201
235
  // src/commands/request/index.ts
@@ -203,7 +237,7 @@ import { existsSync as existsSync2, realpathSync as realpathSync2 } from "fs";
203
237
  import { resolve as resolve2 } from "path";
204
238
  var DEFAULT_ENTRY_CANDIDATES2 = ["src/index.ts", "src/index.tsx", "src/index.js", "src/index.jsx"];
205
239
  function requestCommand(program2) {
206
- program2.command("request").description("Send request to Hono app using app.request()").argument("[file]", "Path to the Hono app file").option("-P, --path <path>", "Request path", "/").option("-X, --method <method>", "HTTP method", "GET").option("-d, --data <data>", "Request body data").option(
240
+ program2.command("request").description("Send request to Hono app using app.request()").argument("[file]", "Path to the Hono app file").option("-P, --path <path>", "Request path", "/").option("-X, --method <method>", "HTTP method", "GET").option("-d, --data <data>", "Request body data").option("-w, --watch", "Watch for changes and resend request", false).option(
207
241
  "-H, --header <header>",
208
242
  "Custom headers",
209
243
  (value, previous) => {
@@ -212,11 +246,15 @@ function requestCommand(program2) {
212
246
  []
213
247
  ).action(async (file, options) => {
214
248
  const path = options.path || "/";
215
- const result = await executeRequest(file, path, options);
216
- console.log(JSON.stringify(result, null, 2));
249
+ const watch = options.watch;
250
+ const buildIterator = getBuildIterator(file, watch);
251
+ for await (const app of buildIterator) {
252
+ const result = await executeRequest(app, path, options);
253
+ console.log(JSON.stringify(result, null, 2));
254
+ }
217
255
  });
218
256
  }
219
- async function executeRequest(appPath, requestPath, options) {
257
+ function getBuildIterator(appPath, watch) {
220
258
  let entry;
221
259
  let resolvedAppPath;
222
260
  if (appPath) {
@@ -230,12 +268,12 @@ async function executeRequest(appPath, requestPath, options) {
230
268
  throw new Error(`Entry file ${entry} does not exist`);
231
269
  }
232
270
  const appFilePath = realpathSync2(resolvedAppPath);
233
- const app = await buildAndImportApp(appFilePath, {
234
- external: ["@hono/node-server"]
271
+ return buildAndImportApp(appFilePath, {
272
+ external: ["@hono/node-server"],
273
+ watch
235
274
  });
236
- if (!app || typeof app.request !== "function") {
237
- throw new Error("No valid Hono app exported from the file");
238
- }
275
+ }
276
+ async function executeRequest(app, requestPath, options) {
239
277
  const url = new URL(requestPath, "http://localhost");
240
278
  const requestInit = {
241
279
  method: options.method || "GET"
@@ -495,9 +533,10 @@ function serveCommand(program2) {
495
533
  app = new Hono();
496
534
  } else {
497
535
  const appFilePath = realpathSync3(appPath);
498
- app = await buildAndImportApp(appFilePath, {
536
+ const buildIterator = buildAndImportApp(appFilePath, {
499
537
  external: ["@hono/node-server"]
500
538
  });
539
+ app = (await buildIterator.next()).value;
501
540
  }
502
541
  }
503
542
  const allFunctions = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hono/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "hono": "dist/cli.js"