@@ -9,6 +9,17 @@ import { optimizeCommand } from './index'
99const program = new Command ( )
1010optimizeCommand ( program )
1111
12+ const writePackageJSON = ( dir : string , honoVersion : string = 'latest' ) => {
13+ writeFileSync (
14+ join ( dir , 'package.json' ) ,
15+ JSON . stringify ( {
16+ name : 'hono-cli-optimize-test' ,
17+ type : 'module' ,
18+ dependencies : { hono : honoVersion } ,
19+ } )
20+ )
21+ }
22+
1223const npmInstall = async ( ) =>
1324 new Promise < void > ( ( resolve ) => {
1425 const child = execFile ( 'npm' , [ 'install' ] )
@@ -202,16 +213,7 @@ describe('optimizeCommand', () => {
202213 'should success to optimize: $name' ,
203214 { timeout : 0 } ,
204215 async ( { honoVersion, files, result, args } ) => {
205- writeFileSync (
206- join ( dir , 'package.json' ) ,
207- JSON . stringify ( {
208- name : 'hono-cli-optimize-test' ,
209- type : 'module' ,
210- dependencies : {
211- hono : honoVersion ?? '4.9.11' ,
212- } ,
213- } )
214- )
216+ writePackageJSON ( dir , honoVersion )
215217 await npmInstall ( )
216218 for ( const file of files ) {
217219 writeFileSync ( join ( dir , file . path ) , file . content )
@@ -245,16 +247,7 @@ describe('optimizeCommand', () => {
245247 target : target ,
246248 } ) )
247249 ) ( '$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- )
250+ writePackageJSON ( dir )
258251 await npmInstall ( )
259252 writeFileSync (
260253 join ( dir , './src/index.ts' ) ,
@@ -271,16 +264,7 @@ describe('optimizeCommand', () => {
271264 } )
272265
273266 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- )
267+ writePackageJSON ( dir )
284268 await npmInstall ( )
285269 writeFileSync (
286270 join ( dir , './src/index.ts' ) ,
@@ -295,4 +279,228 @@ describe('optimizeCommand', () => {
295279 const promise = program . parseAsync ( [ 'node' , 'hono' , 'optimize' , '-t' , 'hoge' ] )
296280 await expect ( promise ) . rejects . toThrowError ( )
297281 } )
282+
283+ describe ( 'request body API removal' , ( ) => {
284+ it (
285+ 'should remove request body APIs when only GET/OPTIONS methods are used' ,
286+ { timeout : 0 } ,
287+ async ( ) => {
288+ writePackageJSON ( dir )
289+ await npmInstall ( )
290+ writeFileSync (
291+ join ( dir , './src/index.ts' ) ,
292+ `
293+ import { Hono } from 'hono'
294+ const app = new Hono()
295+ app.get('/', (c) => c.text('Hello'))
296+ app.options('/cors', (c) => c.text('OK'))
297+ export default app
298+ `
299+ )
300+ await program . parseAsync ( [ 'node' , 'hono' , 'optimize' ] )
301+
302+ const content = readFileSync ( join ( dir , './dist/index.js' ) , 'utf-8' )
303+ expect ( content ) . not . toMatch ( / p a r s e B o d y / )
304+ expect ( content ) . not . toMatch ( / # c a c h e d B o d y / )
305+ }
306+ )
307+
308+ it ( 'should keep request body APIs when POST method is used' , { timeout : 0 } , async ( ) => {
309+ writePackageJSON ( dir )
310+ await npmInstall ( )
311+ writeFileSync (
312+ join ( dir , './src/index.ts' ) ,
313+ `
314+ import { Hono } from 'hono'
315+ const app = new Hono()
316+ app.get('/', (c) => c.text('Hello'))
317+ app.post('/data', async (c) => {
318+ const body = await c.req.json()
319+ return c.json(body)
320+ })
321+ export default app
322+ `
323+ )
324+ await program . parseAsync ( [ 'node' , 'hono' , 'optimize' ] )
325+
326+ const content = readFileSync ( join ( dir , './dist/index.js' ) , 'utf-8' )
327+ expect ( content ) . toMatch ( / p a r s e B o d y / )
328+ } )
329+
330+ it (
331+ 'should keep request body APIs when --no-request-body-api-removal is specified' ,
332+ { timeout : 0 } ,
333+ async ( ) => {
334+ writePackageJSON ( dir )
335+ await npmInstall ( )
336+ writeFileSync (
337+ join ( dir , './src/index.ts' ) ,
338+ `
339+ import { Hono } from 'hono'
340+ const app = new Hono()
341+ app.get('/', (c) => c.text('Hello'))
342+ export default app
343+ `
344+ )
345+ await program . parseAsync ( [ 'node' , 'hono' , 'optimize' , '--no-request-body-api-removal' ] )
346+
347+ const content = readFileSync ( join ( dir , './dist/index.js' ) , 'utf-8' )
348+ expect ( content ) . toMatch ( / p a r s e B o d y / )
349+ }
350+ )
351+ } )
352+
353+ describe ( 'Hono API removal' , ( ) => {
354+ it ( 'should remove unused Hono APIs (route, mount, fire)' , { timeout : 0 } , async ( ) => {
355+ writePackageJSON ( dir )
356+ await npmInstall ( )
357+ writeFileSync (
358+ join ( dir , './src/index.ts' ) ,
359+ `
360+ import { Hono } from 'hono'
361+ const app = new Hono()
362+ app.get('/', (c) => c.text('Hello'))
363+ export default app
364+ `
365+ )
366+ await program . parseAsync ( [ 'node' , 'hono' , 'optimize' , '-m' ] )
367+
368+ const content = readFileSync ( join ( dir , './dist/index.js' ) , 'utf-8' )
369+ // These methods should be removed when unused
370+ expect ( content ) . not . toMatch ( / \b f i r e \s * \( / )
371+ } )
372+
373+ it ( 'should keep Hono APIs when route() is used' , { timeout : 0 } , async ( ) => {
374+ writePackageJSON ( dir )
375+ await npmInstall ( )
376+ writeFileSync (
377+ join ( dir , './src/index.ts' ) ,
378+ `
379+ import { Hono } from 'hono'
380+ const app = new Hono()
381+ const subApp = new Hono()
382+ subApp.get('/', (c) => c.text('Sub'))
383+ app.route('/sub', subApp)
384+ export default app
385+ `
386+ )
387+ await program . parseAsync ( [ 'node' , 'hono' , 'optimize' , '-m' ] )
388+
389+ const content = readFileSync ( join ( dir , './dist/index.js' ) , 'utf-8' )
390+ // route method should be kept when used
391+ expect ( content ) . toMatch ( / \b r o u t e \b / )
392+ } )
393+
394+ it ( 'should keep Hono APIs when mount() is used' , { timeout : 0 } , async ( ) => {
395+ writePackageJSON ( dir )
396+ await npmInstall ( )
397+ writeFileSync (
398+ join ( dir , './src/index.ts' ) ,
399+ `
400+ import { Hono } from 'hono'
401+ const app = new Hono()
402+ app.mount('/static', (req) => new Response('static'))
403+ export default app
404+ `
405+ )
406+ await program . parseAsync ( [ 'node' , 'hono' , 'optimize' , '-m' ] )
407+
408+ const content = readFileSync ( join ( dir , './dist/index.js' ) , 'utf-8' )
409+ // mount method should be kept when used
410+ expect ( content ) . toMatch ( / \b m o u n t \b / )
411+ } )
412+
413+ it (
414+ 'should keep Hono APIs when --no-hono-api-removal is specified' ,
415+ { timeout : 0 } ,
416+ async ( ) => {
417+ writePackageJSON ( dir )
418+ await npmInstall ( )
419+ writeFileSync (
420+ join ( dir , './src/index.ts' ) ,
421+ `
422+ import { Hono } from 'hono'
423+ const app = new Hono()
424+ app.get('/', (c) => c.text('Hello'))
425+ export default app
426+ `
427+ )
428+ await program . parseAsync ( [ 'node' , 'hono' , 'optimize' , '-m' , '--no-hono-api-removal' ] )
429+
430+ const content = readFileSync ( join ( dir , './dist/index.js' ) , 'utf-8' )
431+ // fire method should be kept when --no-hono-api-removal is specified
432+ expect ( content ) . toMatch ( / \b f i r e \b / )
433+ }
434+ )
435+ } )
436+
437+ describe ( 'context response API removal' , ( ) => {
438+ it ( 'should remove unused context response APIs' , { timeout : 0 } , async ( ) => {
439+ writePackageJSON ( dir )
440+ await npmInstall ( )
441+ writeFileSync (
442+ join ( dir , './src/index.ts' ) ,
443+ `
444+ import { Hono } from 'hono'
445+ const app = new Hono()
446+ app.get('/', (c) => c.text('Hello'))
447+ export default app
448+ `
449+ )
450+ await program . parseAsync ( [ 'node' , 'hono' , 'optimize' ] )
451+
452+ const content = readFileSync ( join ( dir , './dist/index.js' ) , 'utf-8' )
453+ // Unused response methods should be removed (json, html, redirect)
454+ // text is used, so it should remain
455+ expect ( content ) . toMatch ( / \b t e x t \s * \( / )
456+ } )
457+
458+ it ( 'should keep context response APIs when they are used' , { timeout : 0 } , async ( ) => {
459+ writePackageJSON ( dir )
460+ await npmInstall ( )
461+ writeFileSync (
462+ join ( dir , './src/index.ts' ) ,
463+ `
464+ import { Hono } from 'hono'
465+ const app = new Hono()
466+ app.get('/', (c) => c.json({ message: 'Hello' }))
467+ app.get('/html', (c) => c.html('<h1>Hello</h1>'))
468+ app.get('/redirect', (c) => c.redirect('/'))
469+ export default app
470+ `
471+ )
472+ await program . parseAsync ( [ 'node' , 'hono' , 'optimize' ] )
473+
474+ const content = readFileSync ( join ( dir , './dist/index.js' ) , 'utf-8' )
475+ // Used response methods should be kept
476+ expect ( content ) . toMatch ( / \b j s o n \s * \( / )
477+ expect ( content ) . toMatch ( / \b h t m l \s * \( / )
478+ expect ( content ) . toMatch ( / \b r e d i r e c t \s * \( / )
479+ } )
480+
481+ it (
482+ 'should keep context response APIs when --no-context-response-api-removal is specified' ,
483+ { timeout : 0 } ,
484+ async ( ) => {
485+ writePackageJSON ( dir )
486+ await npmInstall ( )
487+ writeFileSync (
488+ join ( dir , './src/index.ts' ) ,
489+ `
490+ import { Hono } from 'hono'
491+ const app = new Hono()
492+ app.get('/', (c) => c.text('Hello'))
493+ export default app
494+ `
495+ )
496+ await program . parseAsync ( [ 'node' , 'hono' , 'optimize' , '--no-context-response-api-removal' ] )
497+
498+ const content = readFileSync ( join ( dir , './dist/index.js' ) , 'utf-8' )
499+ // All response methods should be kept when --no-context-response-api-removal is specified
500+ expect ( content ) . toMatch ( / \b j s o n \s * \( / )
501+ expect ( content ) . toMatch ( / \b h t m l \s * \( / )
502+ expect ( content ) . toMatch ( / \b r e d i r e c t \s * \( / )
503+ }
504+ )
505+ } )
298506} )
0 commit comments