From c825132fa46839c7ae6ce25dfbb5c6e54d751f3b Mon Sep 17 00:00:00 2001 From: Yusuke Wada Date: Mon, 22 Jan 2024 15:09:49 +0900 Subject: [PATCH 1/2] feat(dev-server): introduce "Plugins" --- packages/dev-server/README.md | 71 ++++++----- packages/dev-server/e2e/vite.config.ts | 14 ++- packages/dev-server/package.json | 4 +- .../cloudflare-pages/cloudflare-pages.test.ts | 20 +++- .../src/cloudflare-pages/cloudflare-pages.ts | 24 +++- .../dev-server/src/cloudflare-pages/index.ts | 2 + packages/dev-server/src/dev-server.ts | 27 ++++- packages/dev-server/src/types.ts | 6 + yarn.lock | 111 ++++++++++-------- 9 files changed, 176 insertions(+), 103 deletions(-) diff --git a/packages/dev-server/README.md b/packages/dev-server/README.md index f2417d9..11dbad1 100644 --- a/packages/dev-server/README.md +++ b/packages/dev-server/README.md @@ -9,7 +9,7 @@ You can develop your application with Vite. It's fast. - Hono applications run on. - Fast by Vite. - HMR (Only for the Client-side. [Currently, Vite doesn't support HMR for SSR](https://github.com/vitejs/vite/issues/7887)). -- Supporting Cloudflare Bindings. +- Plugins are available, e.g., Cloudflare Pages. - Also runs on Bun. ## Demo @@ -94,7 +94,7 @@ bunx --bun vite ## Options -The options are below. `WorkerOptions` imported from `miniflare` are used for Cloudflare Bindings. +The options are below. ```ts export type DevServerOptions = { @@ -102,6 +102,7 @@ export type DevServerOptions = { injectClientScript?: boolean exclude?: (string | RegExp)[] env?: Env | EnvFunc + plugins?: Plugin[] } ``` @@ -119,6 +120,7 @@ export const defaultOptions: Required> = { /^\/static\/.+/, /^\/node_modules\/.*/, ], + plugins: [], } ``` @@ -150,26 +152,30 @@ export default defineConfig({ You can pass `ENV` variables to your application by setting the `env` option. `ENV` values can be accessed using `c.env` in your Hono application. -Presets are available to define the `ENV`. +### `plugins` -## `ENV` presets +There are plugins for each platform to set up their own environment, etc. + +## Plugins ### Cloudflare Pages -You can use Cloudflare Pages `ENV` presets, which allow you to access bindings such as variables, KV, D1, and others. +You can use Cloudflare Pages plugin, which allow you to access bindings such as variables, KV, D1, and others. ```ts -import { getEnv } from '@hono/vite-dev-server/cloudflare-pages' +import pages from '@hono/vite-dev-server/cloudflare-pages' export default defineConfig({ plugins: [ devServer({ - env: getEnv({ - bindings: { - NAME: 'Hono', - }, - kvNamespaces: ['MY_KV'], - }), + plugins: plugins: [ + pages({ + bindings: { + NAME: 'Hono', + }, + kvNamespaces: ['MY_KV'], + }), + ], }), ], }) @@ -185,10 +191,12 @@ When using D1, your app will read `.mf/d1/DB/db.sqlite` which is generated autom export default defineConfig({ plugins: [ devServer({ - cf: { - d1Databases: ['DB'], - d1Persist: true, - }, + plugins: [ + pages({ + d1Databases: ['DB'], + d1Persist: true, + }), + ], }), ], }) @@ -206,13 +214,9 @@ app.get('/', (c) => { {import.meta.env.PROD ? ( - <> - - + ) : ( - <> - - + )} @@ -228,21 +232,17 @@ In order to build the script properly, you can use the example config file `vite ```ts import { defineConfig } from 'vite' import devServer from '@hono/vite-dev-server' -import pages from '@hono/vite-cloudflare-pages' export default defineConfig(({ mode }) => { if (mode === 'client') { return { build: { - lib: { - entry: './src/client.ts', - formats: ['es'], - fileName: 'client', - name: 'client', - }, rollupOptions: { + input: ['./app/client.ts'], output: { - dir: './dist/static', + entryFileNames: 'static/client.js', + chunkFileNames: 'static/assets/[name]-[hash].js', + assetFileNames: 'static/assets/[name].[ext]', }, }, emptyOutDir: false, @@ -251,10 +251,17 @@ export default defineConfig(({ mode }) => { } } else { return { + build: { + minify: true, + rollupOptions: { + output: { + entryFileNames: '_worker.js', + }, + }, + }, plugins: [ - pages(), devServer({ - entry: 'src/index.tsx', + entry: './app/server.ts', }), ], } diff --git a/packages/dev-server/e2e/vite.config.ts b/packages/dev-server/e2e/vite.config.ts index e3f034a..877d58d 100644 --- a/packages/dev-server/e2e/vite.config.ts +++ b/packages/dev-server/e2e/vite.config.ts @@ -1,17 +1,19 @@ import { defineConfig } from 'vite' import devServer, { defaultOptions } from '../src' -import { getEnv } from '../src/cloudflare-pages' +import pages from '../src/cloudflare-pages' export default defineConfig({ plugins: [ devServer({ entry: './mock/worker.ts', exclude: [...defaultOptions.exclude, '/app/**'], - env: getEnv({ - bindings: { - NAME: 'Hono', - }, - }), + plugins: [ + pages({ + bindings: { + NAME: 'Hono', + }, + }), + ], }), ], }) diff --git a/packages/dev-server/package.json b/packages/dev-server/package.json index 76b18d9..34001c2 100644 --- a/packages/dev-server/package.json +++ b/packages/dev-server/package.json @@ -58,7 +58,7 @@ "devDependencies": { "@playwright/test": "^1.37.1", "glob": "^10.3.10", - "hono": "^3.11.7", + "hono": "^3.12.6", "playwright": "^1.39.0", "publint": "^0.2.5", "rimraf": "^5.0.1", @@ -74,7 +74,7 @@ }, "dependencies": { "@hono/node-server": "^1.4.1", - "miniflare": "^3.20231030.4", + "miniflare": "^3.20231218.2", "minimatch": "^9.0.3" } } diff --git a/packages/dev-server/src/cloudflare-pages/cloudflare-pages.test.ts b/packages/dev-server/src/cloudflare-pages/cloudflare-pages.test.ts index 3007d71..ad1674a 100644 --- a/packages/dev-server/src/cloudflare-pages/cloudflare-pages.test.ts +++ b/packages/dev-server/src/cloudflare-pages/cloudflare-pages.test.ts @@ -1,11 +1,21 @@ -import { getEnv } from './cloudflare-pages.js' +import type { EnvFunc } from '../types.js' +import { getEnv, disposeMf } from './cloudflare-pages.js' describe('getEnv()', () => { - const envFunc = getEnv({ - bindings: { - TOKEN: 'MY TOKEN', - }, + let envFunc: EnvFunc + + beforeEach(() => { + envFunc = getEnv({ + bindings: { + TOKEN: 'MY TOKEN', + }, + }) + }) + + afterEach(() => { + disposeMf() }) + it('Should return the value for bindings', async () => { const env = await envFunc() expect(env['TOKEN']).toBe('MY TOKEN') diff --git a/packages/dev-server/src/cloudflare-pages/cloudflare-pages.ts b/packages/dev-server/src/cloudflare-pages/cloudflare-pages.ts index 711f908..332e8ab 100644 --- a/packages/dev-server/src/cloudflare-pages/cloudflare-pages.ts +++ b/packages/dev-server/src/cloudflare-pages/cloudflare-pages.ts @@ -1,5 +1,5 @@ -import type { MiniflareOptions, WorkerOptions } from 'miniflare' -import type { GetEnv } from '../types.js' +import type { Miniflare, MiniflareOptions, WorkerOptions } from 'miniflare' +import type { GetEnv, Plugin } from '../types.js' type Options = Partial< Omit< @@ -18,10 +18,12 @@ type Options = Partial< const nullScript = 'export default { fetch: () => new Response(null, { status: 404 }) };' +let mf: Miniflare | undefined = undefined + export const getEnv: GetEnv = (options) => async () => { // Dynamic import Miniflare for environments like Bun. const { Miniflare } = await import('miniflare') - const mf = new Miniflare({ + mf = new Miniflare({ modules: true, script: nullScript, ...options, @@ -43,3 +45,19 @@ export const getEnv: GetEnv = (options) => async () => { } return env } + +export const disposeMf = async () => { + mf?.dispose() +} + +const plugin = (options?: Options): Plugin => { + const env = getEnv(options ?? {}) + return { + env, + onServerClose: async () => { + await disposeMf() + }, + } +} + +export default plugin diff --git a/packages/dev-server/src/cloudflare-pages/index.ts b/packages/dev-server/src/cloudflare-pages/index.ts index ab3bea2..ea7fb3e 100644 --- a/packages/dev-server/src/cloudflare-pages/index.ts +++ b/packages/dev-server/src/cloudflare-pages/index.ts @@ -1 +1,3 @@ export { getEnv } from './cloudflare-pages.js' +import plugin from './cloudflare-pages.js' +export default plugin diff --git a/packages/dev-server/src/dev-server.ts b/packages/dev-server/src/dev-server.ts index 22f1de7..2439369 100644 --- a/packages/dev-server/src/dev-server.ts +++ b/packages/dev-server/src/dev-server.ts @@ -1,15 +1,16 @@ import type http from 'http' import { getRequestListener } from '@hono/node-server' import { minimatch } from 'minimatch' -import type { Plugin, ViteDevServer, Connect } from 'vite' +import type { Plugin as VitePlugin, ViteDevServer, Connect } from 'vite' import { getEnv as cloudflarePagesGetEnv } from './cloudflare-pages/index.js' -import type { Env, Fetch, EnvFunc } from './types.js' +import type { Env, Fetch, EnvFunc, Plugin } from './types.js' export type DevServerOptions = { entry?: string injectClientScript?: boolean exclude?: (string | RegExp)[] env?: Env | EnvFunc + plugins?: Plugin[] } & { /** * @deprecated @@ -30,11 +31,12 @@ export const defaultOptions: Required> = { /^\/static\/.+/, /^\/node_modules\/.*/, ], + plugins: [], } -export function devServer(options?: DevServerOptions): Plugin { +export function devServer(options?: DevServerOptions): VitePlugin { const entry = options?.entry ?? defaultOptions.entry - const plugin: Plugin = { + const plugin: VitePlugin = { name: '@hono/vite-dev-server', configureServer: async (server) => { async function createMiddleware(server: ViteDevServer): Promise { @@ -84,6 +86,14 @@ export function devServer(options?: DevServerOptions): Plugin { env = await cloudflarePagesGetEnv(options.cf)() } + if (options?.plugins) { + for (const plugin of options.plugins) { + if (plugin.env) { + env = typeof plugin.env === 'function' ? await plugin.env() : plugin.env + } + } + } + let response = await app.fetch(request, env, { waitUntil: async (fn) => fn, passThroughOnException: () => { @@ -114,6 +124,15 @@ export function devServer(options?: DevServerOptions): Plugin { } server.middlewares.use(await createMiddleware(server)) + server.httpServer?.on('close', async () => { + if (options?.plugins) { + for (const plugin of options.plugins) { + if (plugin.onServerClose) { + await plugin.onServerClose() + } + } + } + }) }, } return plugin diff --git a/packages/dev-server/src/types.ts b/packages/dev-server/src/types.ts index 834e527..7db249b 100644 --- a/packages/dev-server/src/types.ts +++ b/packages/dev-server/src/types.ts @@ -5,8 +5,14 @@ export interface ExecutionContext { waitUntil(promise: Promise): void passThroughOnException(): void } + export type Fetch = ( request: Request, env: {}, executionContext: ExecutionContext ) => Promise + +export interface Plugin { + env?: EnvFunc + onServerClose?: () => void | Promise +} diff --git a/yarn.lock b/yarn.lock index 54d052f..5742d04 100644 --- a/yarn.lock +++ b/yarn.lock @@ -312,41 +312,50 @@ __metadata: languageName: node linkType: hard -"@cloudflare/workerd-darwin-64@npm:1.20231030.0": - version: 1.20231030.0 - resolution: "@cloudflare/workerd-darwin-64@npm:1.20231030.0" +"@cloudflare/workerd-darwin-64@npm:1.20231218.0": + version: 1.20231218.0 + resolution: "@cloudflare/workerd-darwin-64@npm:1.20231218.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@cloudflare/workerd-darwin-arm64@npm:1.20231030.0": - version: 1.20231030.0 - resolution: "@cloudflare/workerd-darwin-arm64@npm:1.20231030.0" +"@cloudflare/workerd-darwin-arm64@npm:1.20231218.0": + version: 1.20231218.0 + resolution: "@cloudflare/workerd-darwin-arm64@npm:1.20231218.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@cloudflare/workerd-linux-64@npm:1.20231030.0": - version: 1.20231030.0 - resolution: "@cloudflare/workerd-linux-64@npm:1.20231030.0" +"@cloudflare/workerd-linux-64@npm:1.20231218.0": + version: 1.20231218.0 + resolution: "@cloudflare/workerd-linux-64@npm:1.20231218.0" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@cloudflare/workerd-linux-arm64@npm:1.20231030.0": - version: 1.20231030.0 - resolution: "@cloudflare/workerd-linux-arm64@npm:1.20231030.0" +"@cloudflare/workerd-linux-arm64@npm:1.20231218.0": + version: 1.20231218.0 + resolution: "@cloudflare/workerd-linux-arm64@npm:1.20231218.0" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@cloudflare/workerd-windows-64@npm:1.20231030.0": - version: 1.20231030.0 - resolution: "@cloudflare/workerd-windows-64@npm:1.20231030.0" +"@cloudflare/workerd-windows-64@npm:1.20231218.0": + version: 1.20231218.0 + resolution: "@cloudflare/workerd-windows-64@npm:1.20231218.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard +"@cspotcode/source-map-support@npm:0.8.1": + version: 0.8.1 + resolution: "@cspotcode/source-map-support@npm:0.8.1" + dependencies: + "@jridgewell/trace-mapping": 0.3.9 + checksum: 5718f267085ed8edb3e7ef210137241775e607ee18b77d95aa5bd7514f47f5019aa2d82d96b3bf342ef7aa890a346fa1044532ff7cc3009e7d24fce3ce6200fa + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/android-arm64@npm:0.18.20" @@ -751,8 +760,8 @@ __metadata: "@hono/node-server": ^1.4.1 "@playwright/test": ^1.37.1 glob: ^10.3.10 - hono: ^3.11.7 - miniflare: ^3.20231030.4 + hono: ^3.12.6 + miniflare: ^3.20231218.2 minimatch: ^9.0.3 playwright: ^1.39.0 publint: ^0.2.5 @@ -824,7 +833,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/resolve-uri@npm:^3.1.0": +"@jridgewell/resolve-uri@npm:^3.0.3, @jridgewell/resolve-uri@npm:^3.1.0": version: 3.1.1 resolution: "@jridgewell/resolve-uri@npm:3.1.1" checksum: f5b441fe7900eab4f9155b3b93f9800a916257f4e8563afbcd3b5a5337b55e52bd8ae6735453b1b745457d9f6cdb16d74cd6220bbdd98cf153239e13f6cbb653 @@ -845,6 +854,16 @@ __metadata: languageName: node linkType: hard +"@jridgewell/trace-mapping@npm:0.3.9": + version: 0.3.9 + resolution: "@jridgewell/trace-mapping@npm:0.3.9" + dependencies: + "@jridgewell/resolve-uri": ^3.0.3 + "@jridgewell/sourcemap-codec": ^1.4.10 + checksum: d89597752fd88d3f3480845691a05a44bd21faac18e2185b6f436c3b0fd0c5a859fbbd9aaa92050c4052caf325ad3e10e2e1d1b64327517471b7d51babc0ddef + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:^0.3.9": version: 0.3.20 resolution: "@jridgewell/trace-mapping@npm:0.3.20" @@ -1616,13 +1635,6 @@ __metadata: languageName: node linkType: hard -"buffer-from@npm:^1.0.0": - version: 1.1.2 - resolution: "buffer-from@npm:1.1.2" - checksum: 0448524a562b37d4d7ed9efd91685a5b77a50672c556ea254ac9a6d30e3403a517d8981f10e565db24e8339413b43c97ca2951f10e399c6125a0d8911f5679bb - languageName: node - linkType: hard - "bundle-require@npm:^4.0.0": version: 4.0.2 resolution: "bundle-require@npm:4.0.2" @@ -3241,6 +3253,13 @@ __metadata: languageName: node linkType: hard +"hono@npm:^3.12.6": + version: 3.12.6 + resolution: "hono@npm:3.12.6" + checksum: e6dd505c57a0532cab79efa15bf49c5450c0c1ebddce7085681522b9f68efaef313b3a87dd71ae3f12a8532b5ebf971f85e2632a40a39ec42cfa12b3082db806 + languageName: node + linkType: hard + "hosted-git-info@npm:^2.1.4": version: 2.8.9 resolution: "hosted-git-info@npm:2.8.9" @@ -3973,25 +3992,25 @@ __metadata: languageName: node linkType: hard -"miniflare@npm:^3.20231030.4": - version: 3.20231030.4 - resolution: "miniflare@npm:3.20231030.4" +"miniflare@npm:^3.20231218.2": + version: 3.20231218.2 + resolution: "miniflare@npm:3.20231218.2" dependencies: + "@cspotcode/source-map-support": 0.8.1 acorn: ^8.8.0 acorn-walk: ^8.2.0 capnp-ts: ^0.7.0 exit-hook: ^2.2.1 glob-to-regexp: ^0.4.1 - source-map-support: 0.5.21 stoppable: ^1.1.0 undici: ^5.22.1 - workerd: 1.20231030.0 + workerd: 1.20231218.0 ws: ^8.11.0 youch: ^3.2.2 zod: ^3.20.6 bin: miniflare: bootstrap.js - checksum: 7b4316ca1fb14d2ddd59d1f5527ce9d221acc4430ccfe4df065dbdce5ab1fa44e64b0f06a585d50b97938556d55722fe90e9349e686ae870db6db68011b65af4 + checksum: 299bc0d802bf7ce05eb24b00192bd42d51485224a94cbd817b6bca4b1485497d1655eb893bae46be1870bc32f57f46bf26ec481eed0f6275c61556acf09ec066 languageName: node linkType: hard @@ -5295,16 +5314,6 @@ __metadata: languageName: node linkType: hard -"source-map-support@npm:0.5.21": - version: 0.5.21 - resolution: "source-map-support@npm:0.5.21" - dependencies: - buffer-from: ^1.0.0 - source-map: ^0.6.0 - checksum: 43e98d700d79af1d36f859bdb7318e601dfc918c7ba2e98456118ebc4c4872b327773e5a1df09b0524e9e5063bb18f0934538eace60cca2710d1fa687645d137 - languageName: node - linkType: hard - "source-map@npm:0.8.0-beta.0": version: 0.8.0-beta.0 resolution: "source-map@npm:0.8.0-beta.0" @@ -5314,7 +5323,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.6.0, source-map@npm:^0.6.1": +"source-map@npm:^0.6.1": version: 0.6.1 resolution: "source-map@npm:0.6.1" checksum: 59ce8640cf3f3124f64ac289012c2b8bd377c238e316fb323ea22fbfe83da07d81e000071d7242cad7a23cd91c7de98e4df8830ec3f133cb6133a5f6e9f67bc2 @@ -6266,15 +6275,15 @@ __metadata: languageName: node linkType: hard -"workerd@npm:1.20231030.0": - version: 1.20231030.0 - resolution: "workerd@npm:1.20231030.0" +"workerd@npm:1.20231218.0": + version: 1.20231218.0 + resolution: "workerd@npm:1.20231218.0" dependencies: - "@cloudflare/workerd-darwin-64": 1.20231030.0 - "@cloudflare/workerd-darwin-arm64": 1.20231030.0 - "@cloudflare/workerd-linux-64": 1.20231030.0 - "@cloudflare/workerd-linux-arm64": 1.20231030.0 - "@cloudflare/workerd-windows-64": 1.20231030.0 + "@cloudflare/workerd-darwin-64": 1.20231218.0 + "@cloudflare/workerd-darwin-arm64": 1.20231218.0 + "@cloudflare/workerd-linux-64": 1.20231218.0 + "@cloudflare/workerd-linux-arm64": 1.20231218.0 + "@cloudflare/workerd-windows-64": 1.20231218.0 dependenciesMeta: "@cloudflare/workerd-darwin-64": optional: true @@ -6288,7 +6297,7 @@ __metadata: optional: true bin: workerd: bin/workerd - checksum: a0c7af094c05260ed07b1217fd8542789d39fef3e9b6ba4f97cc1902136c1d5a76f4870e2d04789570925761fc8ec65e7ddfbac53f35ec8c5f939e08d128f55b + checksum: a6fe986402c488b20a37cfba5dc64def1b25e5d79214bc9decacef21179cb036d02159257070262fd6d62bee40e1ae8c657694c1b09f1d16a8a2f9365ffd85b6 languageName: node linkType: hard From 3ee3deb9f78ad2d0df4b2c5163041de17d86a8ae Mon Sep 17 00:00:00 2001 From: Yusuke Wada Date: Mon, 22 Jan 2024 15:13:19 +0900 Subject: [PATCH 2/2] add changeset --- .changeset/happy-needles-yell.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/happy-needles-yell.md diff --git a/.changeset/happy-needles-yell.md b/.changeset/happy-needles-yell.md new file mode 100644 index 0000000..608654e --- /dev/null +++ b/.changeset/happy-needles-yell.md @@ -0,0 +1,5 @@ +--- +'@hono/vite-dev-server': minor +--- + +feat: introduce Plugins