Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dev-server): introduce "Plugins" #53

Merged
merged 2 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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