From 3f39be7a27530967e2e7f320ac7118c6f6dd310e Mon Sep 17 00:00:00 2001 From: floriankapaun <55788142+floriankapaun@users.noreply.github.com> Date: Sun, 7 Jan 2024 23:03:56 +0100 Subject: [PATCH] Add Bun Transpiler middleware #344 (#345) * :tada: Create Bun Transpiler Middleware Package * :heavy_plus_sign: Add @types/bun dev dependency * :wrench: Use Bun Testrunner * :sparkles: Implement Bun Transpiler Middleware * :white_check_mark: Add Tests for Bun Transpiler middleware * :memo: Add README to Bun Transpiler middleware * :memo: Add Changeset * :construction_worker: Setup CI for Bun Transpiler middleware * :wrench: Configure Bun as external during Build * :wrench: Setup global build script for Bun Transpiler middleware --- .changeset/seven-fireants-teach.md | 5 +++ .github/workflows/ci-bun-transpiler.yml | 23 ++++++++++ package.json | 1 + packages/bun-transpiler/README.md | 42 ++++++++++++++++++ packages/bun-transpiler/package.json | 43 ++++++++++++++++++ packages/bun-transpiler/src/index.test.ts | 53 ++++++++++++++++++++++ packages/bun-transpiler/src/index.ts | 54 +++++++++++++++++++++++ packages/bun-transpiler/tsconfig.json | 10 +++++ yarn.lock | 29 ++++++++++++ 9 files changed, 260 insertions(+) create mode 100644 .changeset/seven-fireants-teach.md create mode 100644 .github/workflows/ci-bun-transpiler.yml create mode 100644 packages/bun-transpiler/README.md create mode 100644 packages/bun-transpiler/package.json create mode 100644 packages/bun-transpiler/src/index.test.ts create mode 100644 packages/bun-transpiler/src/index.ts create mode 100644 packages/bun-transpiler/tsconfig.json diff --git a/.changeset/seven-fireants-teach.md b/.changeset/seven-fireants-teach.md new file mode 100644 index 00000000..09af003f --- /dev/null +++ b/.changeset/seven-fireants-teach.md @@ -0,0 +1,5 @@ +--- +'@hono/bun-transpiler': minor +--- + +Added @hono/bun-transpiler middleware diff --git a/.github/workflows/ci-bun-transpiler.yml b/.github/workflows/ci-bun-transpiler.yml new file mode 100644 index 00000000..00e0d947 --- /dev/null +++ b/.github/workflows/ci-bun-transpiler.yml @@ -0,0 +1,23 @@ +name: ci-bun-transpiler +on: + push: + branches: [main] + paths: + - 'packages/bun-transpiler/**' + pull_request: + branches: ['*'] + paths: + - 'packages/bun-transpiler/**' + +jobs: + ci: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./packages/bun-transpiler + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v1 + - run: yarn install --frozen-lockfile + - run: yarn build + - run: yarn test \ No newline at end of file diff --git a/package.json b/package.json index 5c68bec9..42caf445 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "build:oauth-providers": "yarn workspace @hono/oauth-providers build", "build:react-renderer": "yarn workspace @hono/react-renderer build", "build:auth-js": "yarn workspace @hono/auth-js build", + "build:bun-transpiler": "yarn workspace @hono/bun-transpiler build", "build": "run-p 'build:*'", "lint": "eslint 'packages/**/*.{ts,tsx}'", "lint:fix": "eslint --fix 'packages/**/*.{ts,tsx}'", diff --git a/packages/bun-transpiler/README.md b/packages/bun-transpiler/README.md new file mode 100644 index 00000000..9192bf49 --- /dev/null +++ b/packages/bun-transpiler/README.md @@ -0,0 +1,42 @@ +# Bun Transpiler middleware for Hono + +The Bun 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, your script will automatically be served transpiled into JavaScript code. + +This middleware works only with [Bun](https://bun.sh/). + +## Usage + +### Installation + +```sh +npm i @hono/bun-transpiler +``` + +### Example + +```ts +import { Hono } from 'hono' +import { serveStatic } from 'hono/bun' +import { bunTranspiler } from '@hono/bun-transpiler' + +const app = new Hono() + +app.get('/static/:scriptName{.+.tsx?}', bunTranspiler()) +app.get('/static/*', serveStatic({ root: './' })) + +export default 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. + +## Author + +Florian Kapaun + +Heavily inspired by [esbuild-transpiler](https://github.com/honojs/middleware/tree/main/packages/esbuild-transpiler) by [Andres C. Rodriguez](https://github.com/acrodrig) and [Yusuke Wada](https://github.com/yusukebe). + +## License + +MIT diff --git a/packages/bun-transpiler/package.json b/packages/bun-transpiler/package.json new file mode 100644 index 00000000..be2412ea --- /dev/null +++ b/packages/bun-transpiler/package.json @@ -0,0 +1,43 @@ +{ + "name": "@hono/bun-transpiler", + "version": "0.1.1", + "description": "Bun Transpiler Middleware for Hono", + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "test": "bun test", + "build": "tsup ./src/index.ts --format esm,cjs --dts --external bun", + "publint": "publint", + "release": "yarn build && yarn test && yarn publint && yarn publish" + }, + "exports": { + ".": { + "types": "./dist/index.d.mts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "license": "MIT", + "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": "*" + }, + "devDependencies": { + "@types/bun": "^1.0.0", + "hono": "^3.11.7", + "tsup": "^8.0.1", + "vitest": "^1.0.4" + } +} diff --git a/packages/bun-transpiler/src/index.test.ts b/packages/bun-transpiler/src/index.test.ts new file mode 100644 index 00000000..b43d3578 --- /dev/null +++ b/packages/bun-transpiler/src/index.test.ts @@ -0,0 +1,53 @@ +import { Hono } from 'hono' +import { bunTranspiler } from '.' + +const HOST = 'http://localhost' + +const TS = 'const add = (a: number, b: number): number => a + b' +const TS_TRANSPILED = 'const add=(a,b)=>a+b;' +const TSX = 'const element =

hello world

' +const TSX_TRANSPILED = + 'const element=jsxDEV("h1",{children:"hello world"},undefined,false,undefined,this);' +const BAD = 'function { !!! !@#$ add(a: INT) return a + b + c; }' + +describe('Bun Transpiler middleware', () => { + const app = new Hono() + + app.use('*', bunTranspiler()) + + app.get('/script.js', (c) => c.text(TS)) // Serve TS to test if it gets transpiled + app.get('/script.ts', (c) => c.text(TS)) + app.get('/script.tsx', (c) => c.text(TSX)) + app.get('/bad.ts', (c) => c.text(BAD)) + + it('Should ignore non typescript content paths', async () => { + const res = await app.request(`${HOST}/script.js`) + expect(res).not.toBeNull() + expect(res.status).toBe(200) + expect(await res.text()).toBe(TS) + }) + + it('Should transpile typescript', async () => { + const res = await app.request(`${HOST}/script.ts`) + expect(res).not.toBeNull() + expect(res.status).toBe(200) + expect(await res.text()).toBe(TS_TRANSPILED) + expect(res.headers.get('content-type')).toBe('application/javascript') + }) + + it('Should transpile TSX', async () => { + const res = await app.request(`${HOST}/script.tsx`) + expect(res).not.toBeNull() + expect(res.status).toBe(200) + expect(await res.text()).toBe(TSX_TRANSPILED) + expect(res.headers.get('content-type')).toBe('application/javascript') + }) + + it('Should return error on badly formed typescript', async () => { + const res = await app.request(`${HOST}/bad.ts`) + expect(res).not.toBeNull() + expect(res.status).toBe(500) + expect(await res.text()).toBe('Parse error') + expect(res.headers.get('content-type')).toBe('text/plain') + }) +}) diff --git a/packages/bun-transpiler/src/index.ts b/packages/bun-transpiler/src/index.ts new file mode 100644 index 00000000..e92dd7b4 --- /dev/null +++ b/packages/bun-transpiler/src/index.ts @@ -0,0 +1,54 @@ +import Bun from 'bun' +import { createMiddleware } from 'hono/factory' + +type BunTranspilerOptions = { + extensions: string[] + headers: HeadersInit + transpilerOptions: Bun.TranspilerOptions +} + +export const bunTranspiler = ( + options: BunTranspilerOptions = { + extensions: ['.ts', '.tsx'], + headers: { 'content-type': 'application/javascript' }, + transpilerOptions: { + minifyWhitespace: true, + target: 'browser', + }, + } +) => { + return createMiddleware(async (c, next) => { + await next() + const url = new URL(c.req.url) + const { extensions, headers, transpilerOptions } = options + + if (extensions.every((ext) => !url.pathname.endsWith(ext))) return + + try { + const loader = url.pathname.split('.').pop() as Bun.TranspilerOptions['loader'] + const transpiler = new Bun.Transpiler({ + loader, + ...transpilerOptions, + }) + const transpiledCode = await transpiler.transformSync(await c.res.text()) + c.res = c.newResponse(transpiledCode, { headers }) + } catch (error) { + console.warn(`Error transpiling ${url.pathname}: ${error}`) + const errorHeaders = { + ...headers, + 'content-type': 'text/plain', + } + if (error instanceof Error) { + c.res = c.newResponse(error.message, { + status: 500, + headers: errorHeaders, + }) + } else { + c.res = c.newResponse('Malformed Input', { + status: 500, + headers: errorHeaders, + }) + } + } + }) +} diff --git a/packages/bun-transpiler/tsconfig.json b/packages/bun-transpiler/tsconfig.json new file mode 100644 index 00000000..acfcd843 --- /dev/null +++ b/packages/bun-transpiler/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", + }, + "include": [ + "src/**/*.ts" + ], +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 4fc2021c..f6c05bbf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1379,6 +1379,19 @@ __metadata: languageName: unknown linkType: soft +"@hono/bun-transpiler@workspace:packages/bun-transpiler": + version: 0.0.0-use.local + resolution: "@hono/bun-transpiler@workspace:packages/bun-transpiler" + dependencies: + "@types/bun": "npm:^1.0.0" + hono: "npm:^3.11.7" + tsup: "npm:^8.0.1" + vitest: "npm:^1.0.4" + peerDependencies: + hono: "*" + languageName: unknown + linkType: soft + "@hono/clerk-auth@workspace:packages/clerk-auth": version: 0.0.0-use.local resolution: "@hono/clerk-auth@workspace:packages/clerk-auth" @@ -3284,6 +3297,15 @@ __metadata: languageName: node linkType: hard +"@types/bun@npm:^1.0.0": + version: 1.0.0 + resolution: "@types/bun@npm:1.0.0" + dependencies: + bun-types: "npm:1.0.18" + checksum: 01784df20c982da2a5e41f4e84733436472e6d897bdb01c5b4154cf8bc38ff102718ce2d66d6e34ca2217683499c5a3767e542147efbdf510baaf4b72a854008 + languageName: node + linkType: hard + "@types/cacheable-request@npm:^6.0.1": version: 6.0.3 resolution: "@types/cacheable-request@npm:6.0.3" @@ -5055,6 +5077,13 @@ __metadata: languageName: node linkType: hard +"bun-types@npm:1.0.18": + version: 1.0.18 + resolution: "bun-types@npm:1.0.18" + checksum: 62fca2f8ee86b32def4065bf0490720c0e3bd3afe8b4fd70177dccda08e799dfe6317279c791ade502376ead3197ed7627ec6b8fa103466d398e968f70f05178 + languageName: node + linkType: hard + "bundle-require@npm:^4.0.0": version: 4.0.2 resolution: "bundle-require@npm:4.0.2"