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

Add bun and netlify-edge builds #143

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions .changeset/nervous-bugs-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@hono/vite-bun": major
"@hono/vite-netlify": major
---

New plugins to support netlify edge functions and bun builds
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ This is a monorepo managing Vite Plugins for Hono.
## Available Plugins

- [@hono/vite-dev-server](./packages/dev-server/)
- [@hono/vite-bun](./packages/bun/)
- [@hono/vite-netlify](./packages/netlify/)
- [@hono/vite-build](./packages/build/)
- [@hono/vite-ssg](./packages/ssg/)

Expand Down
1 change: 1 addition & 0 deletions packages/bun/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# @hono/vite-bun
119 changes: 119 additions & 0 deletions packages/bun/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# @hono/vite-bun

`@hono/vite-bun` is a Vite plugin to build your Hono application for Bun.

## Usage

### Installation

You can install `vite` and `@hono/vite-bun` via npm.

```plain
npm i -D vite @hono/vite-bun
```

Or you can install them with Bun.

```plain
bun add vite @hono/vite-bun
```

### Settings

Add `"type": "module"` to your `package.json`. Then, create `vite.config.ts` and edit it.

```ts
import { defineConfig } from 'vite'
import pages from '@hono/vite-bun'

export default defineConfig({
plugins: [pages()],
})
```

### Build

Just run `vite build`.

```text
npm exec vite build
```

Or

```text
bunx --bun vite build
```

### Run with bun

```text
cd dist && bun server.js
```

## Options

The options are below.

```ts
type BunOptions = {
entry?: string | string[]
outputDir?: string
external?: string[]
minify?: boolean
emptyOutDir?: boolean
}
```

Default values:

```ts
export const defaultOptions = {
entry: ['./src/index.tsx', './app/server.ts'],
outputDir: './dist',
external: [],
minify: true,
emptyOutDir: false,
}
```

## Build a client

If you also want to build a client-side script, you can configure it as follows.

```ts
export default defineConfig(({ mode }) => {
if (mode === 'client') {
return {
build: {
rollupOptions: {
input: './src/client.ts',
output: {
dir: './dist/static',
entryFileNames: 'client.js',
},
},
copyPublicDir: false,
},
}
} else {
return {
plugins: [pages()],
}
}
})
```

The build command:

```text
vite build --mode client && vite build
```

## Authors

- Clément Songis <https://github.com/chtibizoux>

## License

MIT
57 changes: 57 additions & 0 deletions packages/bun/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"name": "@hono/vite-bun",
"description": "Vite plugin to build your Hono app for Bun",
"version": "0.1.0",
"types": "dist/index.d.ts",
"module": "dist/index.js",
"type": "module",
"scripts": {
"test": "vitest --run",
"build": "rimraf dist && tsup && publint",
"watch": "tsup --watch",
"prerelease": "yarn build",
"release": "yarn publish"
},
"files": [
"dist"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"typesVersions": {
"*": {
"types": [
"./dist/types"
]
}
},
"author": "Clément Songis <[email protected]> (https://github.com/chtibizoux)",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/honojs/vite-plugins.git"
},
"publishConfig": {
"registry": "https://registry.npmjs.org",
"access": "public"
},
"homepage": "https://github.com/honojs/vite-plugins",
"devDependencies": {
"glob": "^10.3.10",
"hono": "^4.2.7",
"publint": "^0.1.12",
"rimraf": "^5.0.1",
"tsup": "^7.2.0",
"vite": "^5.2.10",
"vitest": "^1.2.1"
},
"peerDependencies": {
"hono": "*"
},
"engines": {
"node": ">=18.14.1"
}
}
76 changes: 76 additions & 0 deletions packages/bun/src/bun.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { builtinModules } from 'module'
import type { Plugin, UserConfig, ResolvedConfig } from 'vite'
import { getEntryContent } from './entry.js'

type BunOptions = {
/**
* @default ['./src/index.tsx', './app/server.ts']
*/
entry?: string | string[]
/**
* @default './dist'
*/
outputDir?: string
external?: string[]
/**
* @default true
*/
minify?: boolean
emptyOutDir?: boolean
}

export const defaultOptions: Required<Omit<BunOptions, 'serveStaticDir'>> = {
entry: ['./src/index.tsx', './app/server.ts'],
outputDir: './dist',
external: [],
minify: true,
emptyOutDir: false,
}

const FUNCTION_JS_NAME = 'server.js'

export const bunPlugin = (options?: BunOptions): Plugin => {
const virtualEntryId = 'virtual:bun-entry-module'
const resolvedVirtualEntryId = '\0' + virtualEntryId

return {
name: '@hono/vite-bun',
resolveId(id) {
if (id === virtualEntryId) {
return resolvedVirtualEntryId
}
},
async load(id) {
if (id === resolvedVirtualEntryId) {
return await getEntryContent({
entry: options?.entry
? Array.isArray(options.entry)
? options.entry
: [options.entry]
: [...defaultOptions.entry],
})
}
},
config: async (): Promise<UserConfig> => {
return {
ssr: {
external: options?.external ?? defaultOptions.external,
noExternal: true,
},
build: {
outDir: options?.outputDir ?? defaultOptions.outputDir,
emptyOutDir: options?.emptyOutDir ?? defaultOptions.emptyOutDir,
minify: options?.minify ?? defaultOptions.minify,
ssr: true,
rollupOptions: {
external: [...builtinModules, /^node:/],
input: virtualEntryId,
output: {
entryFileNames: FUNCTION_JS_NAME,
},
},
},
}
},
}
}
46 changes: 46 additions & 0 deletions packages/bun/src/entry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { normalize } from 'node:path'

export type Options = {
entry: string[]
}

const normalizePaths = (paths: string[]) => {
return paths.map((p) => {
let normalizedPath = normalize(p).replace(/\\/g, '/')
if (normalizedPath.startsWith('./')) {
normalizedPath = normalizedPath.substring(2)
}
return '/' + normalizedPath
})
}

export const getEntryContent = async (options: Options) => {
const globStr = normalizePaths(options.entry)
.map((e) => `'${e}'`)
.join(',')
const appStr = `const modules = import.meta.glob([${globStr}], { import: 'default', eager: true })
let added = false
for (const [, app] of Object.entries(modules)) {
if (app) {
worker.route('/', app)
worker.notFound(app.notFoundHandler)
added = true
}
}
if (!added) {
throw new Error("Can't import modules from [${globStr}]")
}
`

return `import { Hono } from 'hono'
import {serveStatic} from 'hono/bun'

const worker = new Hono()

worker.use('/static/*', serveStatic({root: './'}))
worker.use('/favicon.ico', serveStatic({path: './favicon.ico'}))

${appStr}

export default worker`
}
3 changes: 3 additions & 0 deletions packages/bun/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { bunPlugin, defaultOptions } from './bun.js'
export { defaultOptions }
export default bunPlugin
56 changes: 56 additions & 0 deletions packages/bun/test/bun.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as fs from 'node:fs'
import { build } from 'vite'
import { describe, it, expect, afterAll } from 'vitest'
import bunPlugin from '../src/index'

describe('cloudflarePagesPlugin', () => {
const testDir = './test/project'
const entryFile = `${testDir}/app/server.ts`

afterAll(() => {
fs.rmSync(`${testDir}/bun`, { recursive: true, force: true })
})

it('Should build the project correctly with the plugin', async () => {
const outputFile = `${testDir}/dist/server.js`

expect(fs.existsSync(entryFile)).toBe(true)

await build({
root: testDir,
plugins: [bunPlugin()],
})

expect(fs.existsSync(outputFile)).toBe(true)

const output = fs.readFileSync(outputFile, 'utf-8')
expect(output).toContain('Hello World')
})

it('Should build the project correctly with custom output directory', async () => {
const outputFile = `${testDir}/dist/server.js`

afterAll(() => {
fs.rmSync(`${testDir}/customDir/`, { recursive: true, force: true })
})

expect(fs.existsSync(entryFile)).toBe(true)

await build({
root: testDir,
plugins: [
bunPlugin({
outputDir: 'customDir',
}),
],
build: {
emptyOutDir: true,
},
})

expect(fs.existsSync(outputFile)).toBe(true)

const output = fs.readFileSync(outputFile, 'utf-8')
expect(output).toContain('Hello World')
})
})
3 changes: 3 additions & 0 deletions packages/bun/test/project/app/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
fetch: () => new Response('Hello World'),
}
Empty file.
1 change: 1 addition & 0 deletions packages/bun/test/project/public/static/foo.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foo
Loading