Skip to content

Commit

Permalink
feat(dev-server): introduce "Plugins" (#53)
Browse files Browse the repository at this point in the history
* feat(dev-server): introduce "Plugins"

* add changeset
  • Loading branch information
yusukebe authored Jan 22, 2024
1 parent ecfd7dc commit 4c0b96f
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 103 deletions.
5 changes: 5 additions & 0 deletions .changeset/happy-needles-yell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hono/vite-dev-server': minor
---

feat: introduce Plugins
71 changes: 39 additions & 32 deletions packages/dev-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -94,14 +94,15 @@ 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 = {
entry?: string
injectClientScript?: boolean
exclude?: (string | RegExp)[]
env?: Env | EnvFunc
plugins?: Plugin[]
}
```
Expand All @@ -119,6 +120,7 @@ export const defaultOptions: Required<Omit<DevServerOptions, 'cf'>> = {
/^\/static\/.+/,
/^\/node_modules\/.*/,
],
plugins: [],
}
```

Expand Down Expand Up @@ -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'],
}),
],
}),
],
})
Expand All @@ -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,
}),
],
}),
],
})
Expand All @@ -206,13 +214,9 @@ app.get('/', (c) => {
<html>
<head>
{import.meta.env.PROD ? (
<>
<script type='module' src='/static/client.js'></script>
</>
<script type='module' src='/static/client.js'></script>
) : (
<>
<script type='module' src='/src/client.ts'></script>
</>
<script type='module' src='/src/client.ts'></script>
)}
</head>
<body>
Expand All @@ -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,
Expand All @@ -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',
}),
],
}
Expand Down
14 changes: 8 additions & 6 deletions packages/dev-server/e2e/vite.config.ts
Original file line number Diff line number Diff line change
@@ -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',
},
}),
],
}),
],
})
4 changes: 2 additions & 2 deletions packages/dev-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -74,7 +74,7 @@
},
"dependencies": {
"@hono/node-server": "^1.4.1",
"miniflare": "^3.20231030.4",
"miniflare": "^3.20231218.2",
"minimatch": "^9.0.3"
}
}
20 changes: 15 additions & 5 deletions packages/dev-server/src/cloudflare-pages/cloudflare-pages.test.ts
Original file line number Diff line number Diff line change
@@ -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')
Expand Down
24 changes: 21 additions & 3 deletions packages/dev-server/src/cloudflare-pages/cloudflare-pages.ts
Original file line number Diff line number Diff line change
@@ -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<
Expand All @@ -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> = (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,
Expand All @@ -43,3 +45,19 @@ export const getEnv: GetEnv<Options> = (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
2 changes: 2 additions & 0 deletions packages/dev-server/src/cloudflare-pages/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { getEnv } from './cloudflare-pages.js'
import plugin from './cloudflare-pages.js'
export default plugin
27 changes: 23 additions & 4 deletions packages/dev-server/src/dev-server.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -30,11 +31,12 @@ export const defaultOptions: Required<Omit<DevServerOptions, 'env' | 'cf'>> = {
/^\/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<Connect.HandleFunction> {
Expand Down Expand Up @@ -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: () => {
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions packages/dev-server/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ export interface ExecutionContext {
waitUntil(promise: Promise<unknown>): void
passThroughOnException(): void
}

export type Fetch = (
request: Request,
env: {},
executionContext: ExecutionContext
) => Promise<Response>

export interface Plugin {
env?: EnvFunc
onServerClose?: () => void | Promise<void>
}
Loading

0 comments on commit 4c0b96f

Please sign in to comment.