From 99349aa0ad57dc6dd07104748b5a5cc9fd2e1a5d Mon Sep 17 00:00:00 2001 From: Jun Harada Date: Mon, 13 Jan 2025 11:53:34 +0900 Subject: [PATCH 1/7] feat(build): add Lambda Edge adapter support and corresponding tests - Updated package.json to include Lambda Edge adapter types and imports. - Added tests for the Lambda Edge adapter to ensure correct project building and output validation. --- packages/build/package.json | 7 ++++ .../build/src/adapter/lambda-edge/index.ts | 30 ++++++++++++++ packages/build/test/adapter.test.ts | 40 ++++++++++++++++++- 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 packages/build/src/adapter/lambda-edge/index.ts diff --git a/packages/build/package.json b/packages/build/package.json index e529637..d413318 100644 --- a/packages/build/package.json +++ b/packages/build/package.json @@ -47,6 +47,10 @@ "./vercel": { "types": "./dist/adapter/vercel/index.d.ts", "import": "./dist/adapter/vercel/index.js" + }, + "./lambda-edge": { + "types": "./dist/adapter/lambda-edge/index.d.ts", + "import": "./dist/adapter/lambda-edge/index.js" } }, "typesVersions": { @@ -74,6 +78,9 @@ ], "vercel": [ "./dist/adapter/vercel/index.d.ts" + ], + "lambda-edge": [ + "./dist/adapter/lambda-edge/index.d.ts" ] } }, diff --git a/packages/build/src/adapter/lambda-edge/index.ts b/packages/build/src/adapter/lambda-edge/index.ts new file mode 100644 index 0000000..1dbcf9c --- /dev/null +++ b/packages/build/src/adapter/lambda-edge/index.ts @@ -0,0 +1,30 @@ +import type { Plugin } from 'vite' +import type { BuildOptions } from '../../base.js' +import buildPlugin from '../../base.js' + +export type LambdaEdgeBuildOptions = BuildOptions + +// NOTE: Lambda Edge requires the file to be named with .mjs extension because the file has ES modules syntax. +const WORKER_JS_NAME = 'worker.mjs' + +const lambdaEdgeBuildPlugin = (pluginOptions?: LambdaEdgeBuildOptions): Plugin => { + + return { + ...buildPlugin({ + ...{ + entryContentAfterHooks: [ + async (appName) => { + let code = "import { handle } from 'hono/lambda-edge'\n" + code += `export const handler = handle(${appName})` + return code + }, + ], + }, + ...pluginOptions, + output: WORKER_JS_NAME, + }), + name: '@hono/vite-build/lambda-edge', + } +} + +export default lambdaEdgeBuildPlugin \ No newline at end of file diff --git a/packages/build/test/adapter.test.ts b/packages/build/test/adapter.test.ts index a8eb611..bd5779e 100644 --- a/packages/build/test/adapter.test.ts +++ b/packages/build/test/adapter.test.ts @@ -7,7 +7,7 @@ import denoBuildPlugin from '../src/adapter/deno' import netlifyFunctionsPlugin from '../src/adapter/netlify-functions' import nodeBuildPlugin from '../src/adapter/node' import vercelBuildPlugin from '../src/adapter/vercel' - +import lambdaEdgeBuildPlugin from '../src/adapter/lambda-edge' describe('Build Plugin with Bun Adapter', () => { const testDir = './test/mocks/app-static-files' const entry = './src/server.ts' @@ -351,3 +351,41 @@ describe('Build Plugin with Vercel Adapter', () => { expect(outputJsClientJs).toContain("console.log('foo')") }) }) + +describe('Build Plugin with Lambda Edge Adapter', () => { + const testDir = './test/mocks/app-static-files' + const entry = './src/server.ts' + + afterEach(() => { + rmSync(`${testDir}/dist`, { recursive: true, force: true }) + }) + + it('Should build the project correctly with the plugin', async () => { + const outputFile = `${testDir}/dist/worker.mjs` + + await build({ + root: testDir, + plugins: [ + lambdaEdgeBuildPlugin({ + entry, + minify: false, + }), + ], + }) + + expect(existsSync(outputFile)).toBe(true) + + const output = readFileSync(outputFile, 'utf-8') + expect(output).toContain('Hello World') + // check if the output contains the handler assignment + expect(output).toContain('const handler = handle(mainApp)') + // check if the output contains the export statement for the handler + expect(output).toMatch(/export {[a-zA-Z\n\r, ]*handler[a-zA-Z\n\r, ]*}/) + + const outputFooTxt = readFileSync(`${testDir}/dist/foo.txt`, 'utf-8') + expect(outputFooTxt).toContain('foo') + + const outputJsClientJs = readFileSync(`${testDir}/dist/js/client.js`, 'utf-8') + expect(outputJsClientJs).toContain("console.log('foo')") + }) +}) From d2c20a3d88286fc3975cbc0fe6fe0b93241dfe4c Mon Sep 17 00:00:00 2001 From: Jun Harada Date: Mon, 13 Jan 2025 23:59:40 +0900 Subject: [PATCH 2/7] feat(build): enhance BuildOptions with copyPublicDir support - Added `copyPublicDir` option to BuildOptions for controlling public directory copying during builds. - Updated default options to enable `copyPublicDir` by default. - Modified LambdaEdgeBuildOptions to include a `staticRoot` option and set `copyPublicDir` to false for Lambda Edge builds. --- packages/build/src/adapter/lambda-edge/index.ts | 8 ++++++-- packages/build/src/base.ts | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/build/src/adapter/lambda-edge/index.ts b/packages/build/src/adapter/lambda-edge/index.ts index 1dbcf9c..40e65cd 100644 --- a/packages/build/src/adapter/lambda-edge/index.ts +++ b/packages/build/src/adapter/lambda-edge/index.ts @@ -1,14 +1,16 @@ import type { Plugin } from 'vite' import type { BuildOptions } from '../../base.js' import buildPlugin from '../../base.js' +import { serveStaticHook } from '../../entry/serve-static.js' -export type LambdaEdgeBuildOptions = BuildOptions +export type LambdaEdgeBuildOptions = { + staticRoot?: string | undefined +} & BuildOptions // NOTE: Lambda Edge requires the file to be named with .mjs extension because the file has ES modules syntax. const WORKER_JS_NAME = 'worker.mjs' const lambdaEdgeBuildPlugin = (pluginOptions?: LambdaEdgeBuildOptions): Plugin => { - return { ...buildPlugin({ ...{ @@ -19,6 +21,8 @@ const lambdaEdgeBuildPlugin = (pluginOptions?: LambdaEdgeBuildOptions): Plugin = return code }, ], + // stop copying public dir to dist + copyPublicDir: false, }, ...pluginOptions, output: WORKER_JS_NAME, diff --git a/packages/build/src/base.ts b/packages/build/src/base.ts index ec62e01..975f90e 100644 --- a/packages/build/src/base.ts +++ b/packages/build/src/base.ts @@ -21,6 +21,7 @@ export type BuildOptions = { */ minify?: boolean emptyOutDir?: boolean + copyPublicDir?: boolean apply?: ((this: void, config: UserConfig, env: ConfigEnv) => boolean) | undefined } & Omit @@ -36,6 +37,7 @@ export const defaultOptions: Required< external: [], minify: true, emptyOutDir: false, + copyPublicDir: true, apply: (_config, { command, mode }) => { if (command === 'build' && mode !== 'client') { return true @@ -114,6 +116,7 @@ const buildPlugin = (options: BuildOptions): Plugin => { emptyOutDir: options?.emptyOutDir ?? defaultOptions.emptyOutDir, minify: options?.minify ?? defaultOptions.minify, ssr: true, + copyPublicDir: options?.copyPublicDir ?? defaultOptions.copyPublicDir, rollupOptions: { external: [...builtinModules, /^node:/], input: virtualEntryId, From 04d8b2f7bba91e7f3e31aca3166231e9b01b8d74 Mon Sep 17 00:00:00 2001 From: Jun Harada Date: Thu, 13 Feb 2025 23:19:07 +0900 Subject: [PATCH 3/7] feat(build): add AWS Lambda adapter support with new build options - Introduced AWS Lambda adapter with `lambdaBuildPlugin` - Added `aws-lambda` export in package.json for types and imports - Extended `BuildOptions` with new `sourcemap` option - Updated test suite to include AWS Lambda adapter build tests --- packages/build/package.json | 7 +++ .../build/src/adapter/aws-lambda/index.ts | 33 ++++++++++++++ packages/build/src/base.ts | 3 ++ packages/build/test/adapter.test.ts | 44 +++++++++++++++---- 4 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 packages/build/src/adapter/aws-lambda/index.ts diff --git a/packages/build/package.json b/packages/build/package.json index d413318..495f30d 100644 --- a/packages/build/package.json +++ b/packages/build/package.json @@ -51,6 +51,10 @@ "./lambda-edge": { "types": "./dist/adapter/lambda-edge/index.d.ts", "import": "./dist/adapter/lambda-edge/index.js" + }, + "./aws-lambda": { + "types": "./dist/adapter/aws-lambda/index.d.ts", + "import": "./dist/adapter/aws-lambda/index.js" } }, "typesVersions": { @@ -81,6 +85,9 @@ ], "lambda-edge": [ "./dist/adapter/lambda-edge/index.d.ts" + ], + "aws-lambda": [ + "./dist/adapter/aws-lambda/index.d.ts" ] } }, diff --git a/packages/build/src/adapter/aws-lambda/index.ts b/packages/build/src/adapter/aws-lambda/index.ts new file mode 100644 index 0000000..8002542 --- /dev/null +++ b/packages/build/src/adapter/aws-lambda/index.ts @@ -0,0 +1,33 @@ +import type { Plugin } from 'vite' +import type { BuildOptions } from '../../base.js' +import buildPlugin from '../../base.js' + +export type LambdaBuildOptions = { + staticRoot?: string | undefined +} & BuildOptions + +// NOTE: Lambda requires the file to be named with .mjs extension because the file has ES modules syntax. +const WORKER_JS_NAME = 'worker.mjs' + +const lambdaBuildPlugin = (pluginOptions?: LambdaBuildOptions): Plugin => { + return { + ...buildPlugin({ + ...{ + entryContentAfterHooks: [ + async (appName) => { + let code = "import { handle } from 'hono/aws-lambda'\n" + code += `export const handler = handle(${appName})` + return code + }, + ], + // stop copying public dir to dist + copyPublicDir: false, + }, + ...pluginOptions, + output: WORKER_JS_NAME, + }), + name: '@hono/vite-build/aws-lambda', + } +} + +export default lambdaBuildPlugin diff --git a/packages/build/src/base.ts b/packages/build/src/base.ts index 975f90e..11f65ea 100644 --- a/packages/build/src/base.ts +++ b/packages/build/src/base.ts @@ -22,6 +22,7 @@ export type BuildOptions = { minify?: boolean emptyOutDir?: boolean copyPublicDir?: boolean + sourcemap?: boolean apply?: ((this: void, config: UserConfig, env: ConfigEnv) => boolean) | undefined } & Omit @@ -38,6 +39,7 @@ export const defaultOptions: Required< minify: true, emptyOutDir: false, copyPublicDir: true, + sourcemap: false, apply: (_config, { command, mode }) => { if (command === 'build' && mode !== 'client') { return true @@ -117,6 +119,7 @@ const buildPlugin = (options: BuildOptions): Plugin => { minify: options?.minify ?? defaultOptions.minify, ssr: true, copyPublicDir: options?.copyPublicDir ?? defaultOptions.copyPublicDir, + sourcemap: options?.sourcemap ?? defaultOptions.sourcemap, rollupOptions: { external: [...builtinModules, /^node:/], input: virtualEntryId, diff --git a/packages/build/test/adapter.test.ts b/packages/build/test/adapter.test.ts index bd5779e..4f3fb61 100644 --- a/packages/build/test/adapter.test.ts +++ b/packages/build/test/adapter.test.ts @@ -8,6 +8,8 @@ import netlifyFunctionsPlugin from '../src/adapter/netlify-functions' import nodeBuildPlugin from '../src/adapter/node' import vercelBuildPlugin from '../src/adapter/vercel' import lambdaEdgeBuildPlugin from '../src/adapter/lambda-edge' +import awsLambdaBuildPlugin from '../src/adapter/aws-lambda' + describe('Build Plugin with Bun Adapter', () => { const testDir = './test/mocks/app-static-files' const entry = './src/server.ts' @@ -300,9 +302,6 @@ describe('Build Plugin with Node.js Adapter', () => { expect(output).toContain('use("/static/*", serveStatic({ root: "./" }))') expect(output).toContain('serve({ fetch: mainApp.fetch, port: 3001 })') - const outputFooTxt = readFileSync(`${testDir}/dist/foo.txt`, 'utf-8') - expect(outputFooTxt).toContain('foo') - const outputJsClientJs = readFileSync(`${testDir}/dist/js/client.js`, 'utf-8') // eslint-disable-next-line quotes expect(outputJsClientJs).toContain("console.log('foo')") @@ -381,11 +380,38 @@ describe('Build Plugin with Lambda Edge Adapter', () => { expect(output).toContain('const handler = handle(mainApp)') // check if the output contains the export statement for the handler expect(output).toMatch(/export {[a-zA-Z\n\r, ]*handler[a-zA-Z\n\r, ]*}/) - - const outputFooTxt = readFileSync(`${testDir}/dist/foo.txt`, 'utf-8') - expect(outputFooTxt).toContain('foo') - - const outputJsClientJs = readFileSync(`${testDir}/dist/js/client.js`, 'utf-8') - expect(outputJsClientJs).toContain("console.log('foo')") }) }) + +describe('Build Plugin with AWS Lambda Adapter', () => { + const testDir = './test/mocks/app-static-files'; + const entry = './src/server.ts'; + + afterEach(() => { + rmSync(`${testDir}/dist`, { recursive: true, force: true }); + }); + + it('Should build the project correctly with the AWS Lambda plugin', async () => { + const outputFile = `${testDir}/dist/worker.mjs`; + + await build({ + root: testDir, + plugins: [ + awsLambdaBuildPlugin({ + entry, + minify: false, + }), + ], + }); + + expect(existsSync(outputFile)).toBe(true); + + const output = readFileSync(outputFile, 'utf-8'); + expect(output).toContain('Hello World') + // check if the output contains the handler assignment + expect(output).toContain('const handler = handle(mainApp)') + // check if the output contains the export statement for the handler + expect(output).toMatch(/export {[a-zA-Z\n\r, ]*handler[a-zA-Z\n\r, ]*}/) + + }); +}); From 7aea60e8936445abb0f23a37d9d8a99c1ec31cc1 Mon Sep 17 00:00:00 2001 From: harajune Date: Fri, 23 May 2025 08:45:21 +0900 Subject: [PATCH 4/7] fix(build): add missing newline at end of package.json --- packages/build/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/build/package.json b/packages/build/package.json index 495f30d..ca0b4f7 100644 --- a/packages/build/package.json +++ b/packages/build/package.json @@ -117,4 +117,4 @@ "engines": { "node": ">=18.14.1" } -} \ No newline at end of file +} From 06c4af34767c146c1877f55ff7633f65cdcea667 Mon Sep 17 00:00:00 2001 From: harajune Date: Mon, 2 Jun 2025 19:48:59 +0900 Subject: [PATCH 5/7] revert the deleted test. --- packages/build/test/adapter.test.ts | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/build/test/adapter.test.ts b/packages/build/test/adapter.test.ts index 4f3fb61..68125df 100644 --- a/packages/build/test/adapter.test.ts +++ b/packages/build/test/adapter.test.ts @@ -1,14 +1,14 @@ import { build } from 'vite' import { existsSync, readFileSync, rmSync } from 'node:fs' +import awsLambdaBuildPlugin from '../src/adapter/aws-lambda' import bunBuildPlugin from '../src/adapter/bun' import cloudflarePagesPlugin from '../src/adapter/cloudflare-pages' import cloudflareWorkersPlugin from '../src/adapter/cloudflare-workers' import denoBuildPlugin from '../src/adapter/deno' +import lambdaEdgeBuildPlugin from '../src/adapter/lambda-edge' import netlifyFunctionsPlugin from '../src/adapter/netlify-functions' import nodeBuildPlugin from '../src/adapter/node' import vercelBuildPlugin from '../src/adapter/vercel' -import lambdaEdgeBuildPlugin from '../src/adapter/lambda-edge' -import awsLambdaBuildPlugin from '../src/adapter/aws-lambda' describe('Build Plugin with Bun Adapter', () => { const testDir = './test/mocks/app-static-files' @@ -302,6 +302,9 @@ describe('Build Plugin with Node.js Adapter', () => { expect(output).toContain('use("/static/*", serveStatic({ root: "./" }))') expect(output).toContain('serve({ fetch: mainApp.fetch, port: 3001 })') + const outputFooTxt = readFileSync(`${testDir}/dist/foo.txt`, 'utf-8') + expect(outputFooTxt).toContain('foo') + const outputJsClientJs = readFileSync(`${testDir}/dist/js/client.js`, 'utf-8') // eslint-disable-next-line quotes expect(outputJsClientJs).toContain("console.log('foo')") @@ -384,15 +387,15 @@ describe('Build Plugin with Lambda Edge Adapter', () => { }) describe('Build Plugin with AWS Lambda Adapter', () => { - const testDir = './test/mocks/app-static-files'; - const entry = './src/server.ts'; + const testDir = './test/mocks/app-static-files' + const entry = './src/server.ts' afterEach(() => { - rmSync(`${testDir}/dist`, { recursive: true, force: true }); - }); + rmSync(`${testDir}/dist`, { recursive: true, force: true }) + }) it('Should build the project correctly with the AWS Lambda plugin', async () => { - const outputFile = `${testDir}/dist/worker.mjs`; + const outputFile = `${testDir}/dist/worker.mjs` await build({ root: testDir, @@ -402,16 +405,16 @@ describe('Build Plugin with AWS Lambda Adapter', () => { minify: false, }), ], - }); + }) - expect(existsSync(outputFile)).toBe(true); + expect(existsSync(outputFile)).toBe(true) - const output = readFileSync(outputFile, 'utf-8'); + const output = readFileSync(outputFile, 'utf-8') expect(output).toContain('Hello World') // check if the output contains the handler assignment expect(output).toContain('const handler = handle(mainApp)') // check if the output contains the export statement for the handler expect(output).toMatch(/export {[a-zA-Z\n\r, ]*handler[a-zA-Z\n\r, ]*}/) - }); -}); + }) +}) From ca28de6b87f335f9950a0cab91a6f93d358049aa Mon Sep 17 00:00:00 2001 From: harajune Date: Mon, 2 Jun 2025 19:49:45 +0900 Subject: [PATCH 6/7] fix(tests): remove unnecessary blank line in adapter test file --- packages/build/test/adapter.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/build/test/adapter.test.ts b/packages/build/test/adapter.test.ts index 68125df..7dae5df 100644 --- a/packages/build/test/adapter.test.ts +++ b/packages/build/test/adapter.test.ts @@ -304,7 +304,7 @@ describe('Build Plugin with Node.js Adapter', () => { const outputFooTxt = readFileSync(`${testDir}/dist/foo.txt`, 'utf-8') expect(outputFooTxt).toContain('foo') - + const outputJsClientJs = readFileSync(`${testDir}/dist/js/client.js`, 'utf-8') // eslint-disable-next-line quotes expect(outputJsClientJs).toContain("console.log('foo')") From ae9189a834aaad2d82478611fa51d02b7153ed4e Mon Sep 17 00:00:00 2001 From: harajune Date: Mon, 2 Jun 2025 20:22:24 +0900 Subject: [PATCH 7/7] feat(lambda): add support for AWS Lambda and Lambda Edge --- .changeset/thick-flowers-punch.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/thick-flowers-punch.md diff --git a/.changeset/thick-flowers-punch.md b/.changeset/thick-flowers-punch.md new file mode 100644 index 0000000..429119d --- /dev/null +++ b/.changeset/thick-flowers-punch.md @@ -0,0 +1,5 @@ +--- +'@hono/vite-build': minor +--- + +Added the support for the AWS lambda / lambda edge