Skip to content

Commit 3cc9e13

Browse files
authored
feat(optimize): introduce target option (#47)
* feat(optimize): introduce `target` option Signed-off-by: ysknsid25 <kengo071225@gmail.com> * feat(optimize): fix target type Signed-off-by: ysknsid25 <kengo071225@gmail.com> --------- Signed-off-by: ysknsid25 <kengo071225@gmail.com>
1 parent 1a3f8dc commit 3cc9e13

File tree

3 files changed

+167
-90
lines changed

3 files changed

+167
-90
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,8 @@ hono optimize [entry] [options]
255255
**Options:**
256256

257257
- `-o, --outfile <outfile>` - Output file
258+
- `-m, --minify` - minify output file
259+
- `-t, --target [target]` - environment target
258260

259261
**Examples:**
260262

@@ -267,6 +269,9 @@ hono optimize -o dist/app.js src/app.ts
267269

268270
# Export bundled file with minification
269271
hono optimize -m
272+
273+
# Specify environment target
274+
hono optimize -t es2024
270275
```
271276

272277
## Tips

src/commands/optimize/index.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,4 +227,72 @@ describe('optimizeCommand', () => {
227227
}
228228
}
229229
)
230+
231+
it.each(
232+
[
233+
'node14',
234+
'node18',
235+
'node20',
236+
'node22',
237+
'node24',
238+
'node26',
239+
'deno1',
240+
'deno2',
241+
'es2022',
242+
'es2024',
243+
].map((target) => ({
244+
name: `should success with environment target: ${target}`,
245+
target: target,
246+
}))
247+
)('$name', { timeout: 0 }, async ({ target }) => {
248+
writeFileSync(
249+
join(dir, 'package.json'),
250+
JSON.stringify({
251+
name: 'hono-cli-optimize-test',
252+
type: 'module',
253+
dependencies: {
254+
hono: 'latest',
255+
},
256+
})
257+
)
258+
await npmInstall()
259+
writeFileSync(
260+
join(dir, './src/index.ts'),
261+
`
262+
import { Hono } from 'hono'
263+
const app = new Hono()
264+
app.get('/', (c) => c.text('Hello, World!'))
265+
export default app
266+
`
267+
)
268+
269+
const promise = program.parseAsync(['node', 'hono', 'optimize', '-t', target])
270+
await expect(promise).resolves.not.toThrow()
271+
})
272+
273+
it('should throw an error with invalid environment target', async () => {
274+
writeFileSync(
275+
join(dir, 'package.json'),
276+
JSON.stringify({
277+
name: 'hono-cli-optimize-test',
278+
type: 'module',
279+
dependencies: {
280+
hono: 'latest',
281+
},
282+
})
283+
)
284+
await npmInstall()
285+
writeFileSync(
286+
join(dir, './src/index.ts'),
287+
`
288+
import { Hono } from 'hono'
289+
const app = new Hono()
290+
app.get('/', (c) => c.text('Hello, World!'))
291+
export default app
292+
`
293+
)
294+
295+
const promise = program.parseAsync(['node', 'hono', 'optimize', '-t', 'hoge'])
296+
await expect(promise).rejects.toThrowError()
297+
})
230298
})

src/commands/optimize/index.ts

Lines changed: 94 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -16,102 +16,105 @@ export function optimizeCommand(program: Command) {
1616
.argument('[entry]', 'entry file')
1717
.option('-o, --outfile [outfile]', 'output file', 'dist/index.js')
1818
.option('-m, --minify', 'minify output file')
19-
.action(async (entry: string, options: { outfile: string; minify?: boolean }) => {
20-
if (!entry) {
21-
entry =
22-
DEFAULT_ENTRY_CANDIDATES.find((entry) => existsSync(entry)) ?? DEFAULT_ENTRY_CANDIDATES[0]
23-
}
19+
.option('-t, --target [target]', 'environment target (e.g., node24, deno2, es2024)', 'node20')
20+
.action(
21+
async (entry: string, options: { outfile: string; minify?: boolean; target: string }) => {
22+
if (!entry) {
23+
entry =
24+
DEFAULT_ENTRY_CANDIDATES.find((entry) => existsSync(entry)) ??
25+
DEFAULT_ENTRY_CANDIDATES[0]
26+
}
2427

25-
const appPath = resolve(process.cwd(), entry)
28+
const appPath = resolve(process.cwd(), entry)
2629

27-
if (!existsSync(appPath)) {
28-
throw new Error(`Entry file ${entry} does not exist`)
29-
}
30+
if (!existsSync(appPath)) {
31+
throw new Error(`Entry file ${entry} does not exist`)
32+
}
3033

31-
const appFilePath = realpathSync(appPath)
32-
const buildIterator = buildAndImportApp(appFilePath, {
33-
external: ['@hono/node-server'],
34-
})
35-
const app: Hono = (await buildIterator.next()).value
34+
const appFilePath = realpathSync(appPath)
35+
const buildIterator = buildAndImportApp(appFilePath, {
36+
external: ['@hono/node-server'],
37+
})
38+
const app: Hono = (await buildIterator.next()).value
3639

37-
let routerName
38-
let importStatement
39-
let assignRouterStatement
40-
try {
41-
const serialized = serializeInitParams(
42-
buildInitParams({
43-
paths: app.routes.map(({ path }) => path),
44-
})
45-
)
40+
let routerName
41+
let importStatement
42+
let assignRouterStatement
43+
try {
44+
const serialized = serializeInitParams(
45+
buildInitParams({
46+
paths: app.routes.map(({ path }) => path),
47+
})
48+
)
4649

47-
const hasPreparedRegExpRouter = await new Promise<boolean>((resolve) => {
48-
const child = execFile(process.execPath, [
49-
'--input-type=module',
50-
'-e',
51-
"try { (await import('hono/router/reg-exp-router')).PreparedRegExpRouter && process.exit(0) } finally { process.exit(1) }",
52-
])
53-
child.on('exit', (code) => {
54-
resolve(code === 0)
50+
const hasPreparedRegExpRouter = await new Promise<boolean>((resolve) => {
51+
const child = execFile(process.execPath, [
52+
'--input-type=module',
53+
'-e',
54+
"try { (await import('hono/router/reg-exp-router')).PreparedRegExpRouter && process.exit(0) } finally { process.exit(1) }",
55+
])
56+
child.on('exit', (code) => {
57+
resolve(code === 0)
58+
})
5559
})
56-
})
5760

58-
if (hasPreparedRegExpRouter) {
59-
routerName = 'PreparedRegExpRouter'
60-
importStatement = "import { PreparedRegExpRouter } from 'hono/router/reg-exp-router'"
61-
assignRouterStatement = `const routerParams = ${serialized}
61+
if (hasPreparedRegExpRouter) {
62+
routerName = 'PreparedRegExpRouter'
63+
importStatement = "import { PreparedRegExpRouter } from 'hono/router/reg-exp-router'"
64+
assignRouterStatement = `const routerParams = ${serialized}
6265
this.router = new PreparedRegExpRouter(...routerParams)`
63-
} else {
64-
routerName = 'RegExpRouter'
65-
importStatement = "import { RegExpRouter } from 'hono/router/reg-exp-router'"
66-
assignRouterStatement = 'this.router = new RegExpRouter()'
66+
} else {
67+
routerName = 'RegExpRouter'
68+
importStatement = "import { RegExpRouter } from 'hono/router/reg-exp-router'"
69+
assignRouterStatement = 'this.router = new RegExpRouter()'
70+
}
71+
} catch {
72+
// fallback to default router
73+
routerName = 'TrieRouter'
74+
importStatement = "import { TrieRouter } from 'hono/router/trie-router'"
75+
assignRouterStatement = 'this.router = new TrieRouter()'
6776
}
68-
} catch {
69-
// fallback to default router
70-
routerName = 'TrieRouter'
71-
importStatement = "import { TrieRouter } from 'hono/router/trie-router'"
72-
assignRouterStatement = 'this.router = new TrieRouter()'
73-
}
7477

75-
console.log('[Optimized]')
76-
console.log(` Router: ${routerName}`)
78+
console.log('[Optimized]')
79+
console.log(` Router: ${routerName}`)
7780

78-
const outfile = resolve(process.cwd(), options.outfile)
79-
await esbuild.build({
80-
entryPoints: [appFilePath],
81-
outfile,
82-
bundle: true,
83-
minify: options.minify,
84-
format: 'esm',
85-
target: 'node20',
86-
platform: 'node',
87-
jsx: 'automatic',
88-
jsxImportSource: 'hono/jsx',
89-
plugins: [
90-
{
91-
name: 'hono-optimize',
92-
setup(build) {
93-
const honoPseudoImportPath = 'hono-optimized-pseudo-import-path'
81+
const outfile = resolve(process.cwd(), options.outfile)
82+
await esbuild.build({
83+
entryPoints: [appFilePath],
84+
outfile,
85+
bundle: true,
86+
minify: options.minify,
87+
format: 'esm',
88+
target: options.target,
89+
platform: 'node',
90+
jsx: 'automatic',
91+
jsxImportSource: 'hono/jsx',
92+
plugins: [
93+
{
94+
name: 'hono-optimize',
95+
setup(build) {
96+
const honoPseudoImportPath = 'hono-optimized-pseudo-import-path'
9497

95-
build.onResolve({ filter: /^hono$/ }, async (args) => {
96-
if (!args.importer) {
97-
// prevent recursive resolution of "hono"
98-
return undefined
99-
}
98+
build.onResolve({ filter: /^hono$/ }, async (args) => {
99+
if (!args.importer) {
100+
// prevent recursive resolution of "hono"
101+
return undefined
102+
}
100103

101-
// resolve original import path for "hono"
102-
const resolved = await build.resolve(args.path, {
103-
kind: 'import-statement',
104-
resolveDir: args.resolveDir,
105-
})
104+
// resolve original import path for "hono"
105+
const resolved = await build.resolve(args.path, {
106+
kind: 'import-statement',
107+
resolveDir: args.resolveDir,
108+
})
106109

107-
// mark "honoOptimize" to the resolved path for filtering
108-
return {
109-
path: join(dirname(resolved.path), honoPseudoImportPath),
110-
}
111-
})
112-
build.onLoad({ filter: new RegExp(`/${honoPseudoImportPath}$`) }, async () => {
113-
return {
114-
contents: `
110+
// mark "honoOptimize" to the resolved path for filtering
111+
return {
112+
path: join(dirname(resolved.path), honoPseudoImportPath),
113+
}
114+
})
115+
build.onLoad({ filter: new RegExp(`/${honoPseudoImportPath}$`) }, async () => {
116+
return {
117+
contents: `
115118
import { HonoBase } from 'hono/hono-base'
116119
${importStatement}
117120
export class Hono extends HonoBase {
@@ -121,14 +124,15 @@ export class Hono extends HonoBase {
121124
}
122125
}
123126
`,
124-
}
125-
})
127+
}
128+
})
129+
},
126130
},
127-
},
128-
],
129-
})
131+
],
132+
})
130133

131-
const outfileStat = statSync(outfile)
132-
console.log(` Output: ${options.outfile} (${(outfileStat.size / 1024).toFixed(2)} KB)`)
133-
})
134+
const outfileStat = statSync(outfile)
135+
console.log(` Output: ${options.outfile} (${(outfileStat.size / 1024).toFixed(2)} KB)`)
136+
}
137+
)
134138
}

0 commit comments

Comments
 (0)