-
Notifications
You must be signed in to change notification settings - Fork 148
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: esbuild Transpiler Middleware (#231)
* feat: esbuild Transpiler Middleware * changeset * add linkt to docs * add `global.d.ts` for docs * update readme Co-authored-by: Andres C. Rodriguez <acrodrig@users.noreply.github.com>
- Loading branch information
Showing
12 changed files
with
1,303 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@hono/esbuild-transpiler': minor | ||
--- | ||
|
||
feat: introduce esbuild Transpiler Middleware |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
name: ci-esbuild-transpiler | ||
on: | ||
push: | ||
branches: [main] | ||
paths: | ||
- 'packages/esbuild-transpiler/**' | ||
pull_request: | ||
branches: ['*'] | ||
paths: | ||
- 'packages/esbuild-transpiler/**' | ||
|
||
jobs: | ||
ci: | ||
runs-on: ubuntu-latest | ||
defaults: | ||
run: | ||
working-directory: ./packages/esbuild-transpiler | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- uses: actions/setup-node@v4 | ||
with: | ||
node-version: 20.x | ||
- run: yarn install --frozen-lockfile | ||
- run: yarn build | ||
- run: yarn test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
# esbuild Transpiler Middleware | ||
|
||
The **esbuild Transpiler Middleware** is a Hono Middleware designed to transpile content such as TypeScript or TSX. | ||
You can place your script written in TypeScript in a directory and serve it using `serveStatic`. | ||
When you apply this Middleware, the script will be transpiled into JavaScript code. | ||
|
||
This Middleware uses esbuild. It works on _Cloudflare Workers, Deno, Deno Deploy, or Node.js_. | ||
|
||
## Usage | ||
|
||
Usage differs depending on the platform. | ||
|
||
### Cloudflare Workers / Pages | ||
|
||
#### Installation | ||
|
||
```text | ||
npm i hono @hono/esbuild-transpiler esbuild-wasm | ||
``` | ||
|
||
#### Example | ||
|
||
```ts | ||
import { Hono } from 'hono' | ||
import { serveStatic } from 'hono/cloudflare-workers' | ||
import { esbuildTranspiler } from '@hono/esbuild-transpiler/wasm' | ||
// Specify the path of the esbuild wasm file. | ||
import wasm from '../node_modules/esbuild-wasm/esbuild.wasm' | ||
|
||
const app = new Hono() | ||
|
||
app.get('/static/:scriptName{.+.tsx?}', esbuildTranspiler({ wasmModule: wasm })) | ||
app.get('/static/*', serveStatic({ root: './' })) | ||
|
||
export default app | ||
``` | ||
|
||
`global.d.ts`: | ||
|
||
```ts | ||
declare module '*.wasm' | ||
``` | ||
### Deno / Deno Deploy | ||
#### Example | ||
```ts | ||
import { Hono } from 'npm:hono' | ||
|
||
import { serveStatic } from 'npm:hono/deno' | ||
import { esbuildTranspiler } from 'npm:@hono/esbuild-transpiler' | ||
import * as esbuild from 'https://deno.land/x/esbuild@v0.19.5/wasm.js' | ||
|
||
const app = new Hono() | ||
|
||
await esbuild.initialize({ | ||
wasmURL: 'https://deno.land/x/esbuild@v0.19.5/esbuild.wasm', | ||
worker: false, | ||
}) | ||
|
||
app.get('/static/*', esbuildTranspiler({ esbuild })) | ||
app.get('/static/*', serveStatic()) | ||
|
||
Deno.serve(app.fetch) | ||
``` | ||
|
||
### Node.js | ||
|
||
#### Installation | ||
|
||
```text | ||
npm i hono @hono/node-server @hono/esbuild-transpiler esbuild | ||
``` | ||
|
||
#### Example | ||
|
||
```ts | ||
import { Hono } from 'hono' | ||
import { serve } from '@hono/node-server' | ||
import { serveStatic } from '@hono/node-server/serve-static' | ||
import { esbuildTranspiler } from '@hono/esbuild-transpiler/node' | ||
|
||
const app = new Hono() | ||
|
||
app.get('/static/:scriptName{.+.tsx?}', esbuildTranspiler()) | ||
app.get('/static/*', serveStatic({ root: './' })) | ||
|
||
serve(app) | ||
``` | ||
|
||
## Notes | ||
|
||
- This middleware does not have a cache feature. If you want to cache the transpiled code, use [Cache Middleware](https://hono.dev/middleware/builtin/cache) or your own custom middleware. | ||
- `@hono/vite-dev-server` does not support Wasm, so you can't use this Middleware with it. However, Vite can transpile them, so you might not need to use this. | ||
|
||
## Authors | ||
|
||
- Yusuke Wada <https://github.com/yusukebe> | ||
- Andres C. Rodriguez <https://github.com/acrodrig> | ||
|
||
Original idea and implementation for "_Typescript Transpiler Middleware_" is by Andres C. Rodriguez. | ||
|
||
## License | ||
|
||
MIT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
{ | ||
"name": "@hono/esbuild-transpiler", | ||
"version": "0.0.0", | ||
"description": "esbuild Transpiler Middleware for Hono", | ||
"main": "dist/index.js", | ||
"type": "module", | ||
"types": "dist/index.d.ts", | ||
"files": [ | ||
"dist" | ||
], | ||
"scripts": { | ||
"test": "vitest run", | ||
"build": "tsup ./src/*.ts ./src/transpilers/*.ts --format esm,cjs --dts --no-splitting --external esbuild-wasm,esbuild", | ||
"publint": "publint", | ||
"release": "yarn build && yarn test && yarn publint && yarn publish" | ||
}, | ||
"exports": { | ||
".": { | ||
"types": "./dist/index.d.ts", | ||
"import": "./dist/index.js" | ||
}, | ||
"./wasm": { | ||
"types": "./dist/transpilers/wasm.d.ts", | ||
"import": "./dist/transpilers/wasm.js" | ||
}, | ||
"./node": { | ||
"types": "./dist/transpilers/node.d.ts", | ||
"import": "./dist/transpilers/node.js" | ||
} | ||
}, | ||
"typesVersions": { | ||
"*": { | ||
"wasm": [ | ||
"./dist/transpilers/wasm" | ||
], | ||
"node": [ | ||
"./dist/transpilers/node" | ||
] | ||
} | ||
}, | ||
"license": "MIT", | ||
"private": false, | ||
"publishConfig": { | ||
"registry": "https://registry.npmjs.org", | ||
"access": "public" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/honojs/middleware.git" | ||
}, | ||
"homepage": "https://github.com/honojs/middleware", | ||
"peerDependencies": { | ||
"hono": ">=3.9.2" | ||
}, | ||
"devDependencies": { | ||
"esbuild": "^0.19.5", | ||
"esbuild-wasm": "^0.19.5", | ||
"hono": "^3.9.2", | ||
"vitest": "^0.34.6" | ||
}, | ||
"engines": { | ||
"node": ">=18.14.1" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { esbuildTranspiler } from './transpiler' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { createMiddleware } from 'hono/factory' | ||
import type { transform, initialize } from './types.esbuild' | ||
|
||
export type EsbuildLike = { | ||
transform: typeof transform | ||
initialize: typeof initialize | ||
} | ||
|
||
export type TransformOptions = Partial<Parameters<typeof transform>[1]> | ||
|
||
export type EsbuildTranspilerOptions = { | ||
extensions?: string[] | ||
cache?: boolean | ||
esbuild?: EsbuildLike | ||
contentType?: string | ||
transformOptions?: TransformOptions | ||
} | ||
|
||
export const esbuildTranspiler = (options?: EsbuildTranspilerOptions) => { | ||
const esbuild: EsbuildLike | undefined = options?.esbuild | ||
|
||
return createMiddleware(async (c, next) => { | ||
await next() | ||
if (esbuild) { | ||
const url = new URL(c.req.url) | ||
const extensions = options?.extensions ?? ['.ts', '.tsx'] | ||
|
||
if (extensions.every((ext) => !url.pathname.endsWith(ext))) return | ||
|
||
const headers = { 'content-type': options?.contentType ?? 'text/javascript' } | ||
const script = await c.res.text() | ||
|
||
const transformOptions: TransformOptions = options?.transformOptions ?? {} | ||
|
||
try { | ||
const { code } = await esbuild.transform(script, { | ||
loader: 'tsx', | ||
...transformOptions, | ||
}) | ||
c.res = c.body(code, { | ||
headers, | ||
}) | ||
c.res.headers.delete('content-length') | ||
} catch (ex) { | ||
console.warn('Error transpiling ' + url.pathname + ': ' + ex) | ||
c.res = new Response(script, { status: 500, headers }) | ||
} | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import * as esbuild from 'esbuild' | ||
import { createMiddleware } from 'hono/factory' | ||
import { esbuildTranspiler as baseTranspiler } from '../transpiler' | ||
import type { EsbuildTranspilerOptions } from '../transpiler' | ||
|
||
const transpiler = (options?: Partial<Omit<EsbuildTranspilerOptions, 'esbuild'>>) => { | ||
return createMiddleware(async (c, next) => { | ||
return await baseTranspiler({ | ||
esbuild, | ||
...options, | ||
})(c, next) | ||
}) | ||
} | ||
|
||
export { transpiler as esbuildTranspiler } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import * as esbuild from 'esbuild-wasm' | ||
import { createMiddleware } from 'hono/factory' | ||
import { esbuildTranspiler as baseTranspiler } from '../transpiler' | ||
import type { EsbuildTranspilerOptions } from '../transpiler' | ||
|
||
let initialized = false | ||
|
||
const transpiler = ( | ||
options: Partial<Omit<EsbuildTranspilerOptions, 'esbuild'>> & { | ||
wasmModule?: WebAssembly.Module | ||
wasmURL?: string | URL | ||
} | ||
) => { | ||
return createMiddleware(async (c, next) => { | ||
if (!initialized) { | ||
if (options.wasmModule) { | ||
await esbuild.initialize({ | ||
wasmModule: options.wasmModule, | ||
worker: false, | ||
}) | ||
} else if (options.wasmURL) { | ||
await esbuild.initialize({ | ||
wasmURL: options.wasmURL, | ||
worker: false, | ||
}) | ||
} else { | ||
throw 'wasmModule or wasmURL option is required.' | ||
} | ||
initialized = true | ||
} | ||
return await baseTranspiler({ | ||
esbuild, | ||
...options, | ||
})(c, next) | ||
}) | ||
} | ||
|
||
export { transpiler as esbuildTranspiler } |
Oops, something went wrong.