Created
July 7, 2024 16:06
-
-
Save arisris/11fa812717b8f96d50bd4e1eb2be27f3 to your computer and use it in GitHub Desktop.
Vite plugin to develop fetch based web server like cloudflare workers & pages functions
This file contains hidden or 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
| import { type Connect, type Plugin } from "vite" | |
| import { getRequestListener } from "@hono/node-server" | |
| import { getPlatformProxy, type PlatformProxy } from "wrangler" | |
| type HonoDevServerPlugin = { | |
| entry?: string | |
| export?: string | |
| injectClientScript?: boolean | |
| exclude?: RegExp[] | |
| ignoreWatching?: RegExp[] | |
| } | |
| export default function honoDevServer(options: HonoDevServerPlugin = {}): Plugin { | |
| const defaultOptions: Required<HonoDevServerPlugin> = { | |
| entry: './src/index.ts', | |
| export: 'default', | |
| injectClientScript: true, | |
| exclude: [ | |
| /.*\.css$/, | |
| /.*\.ts$/, | |
| /.*\.tsx$/, | |
| /^\/@.+$/, | |
| /\?t\=\d+$/, | |
| /^\/favicon\.ico$/, | |
| /^\/assets\/.+/, | |
| /^\/node_modules\/.*/, | |
| ], | |
| ignoreWatching: [/\.wrangler/, /\.mf/] | |
| } | |
| const encoder = new TextEncoder(); | |
| options = { ...defaultOptions, ...options } | |
| let platformProxy: PlatformProxy | |
| const injectStringToResponse = (response: Response, str: string): Response => { | |
| if (!response.body) return response; | |
| let injectDone = false | |
| const transformedStream = response.body | |
| .pipeThrough(new TextDecoderStream()) | |
| .pipeThrough(new TransformStream({ | |
| async transform(chunk, controller) { | |
| if (!injectDone && chunk.includes("</head>")) { | |
| chunk = chunk.replace("</head>", `${str}</head>`) | |
| injectDone = true | |
| } | |
| controller.enqueue(encoder.encode(chunk)) | |
| }, | |
| flush(controller) { | |
| if (!injectDone) { | |
| controller.enqueue(encoder.encode(str)) | |
| } | |
| controller.terminate() | |
| }, | |
| })) | |
| return new Response(transformedStream, response) | |
| } | |
| return { | |
| name: "hono:dev-server", | |
| apply(_, env) { | |
| return env.command === "serve" | |
| }, | |
| config(config) { | |
| return { | |
| ssr: { | |
| noExternal: true, | |
| ...config.ssr | |
| } | |
| } | |
| }, | |
| async configureServer(server) { | |
| let logger = server.config.logger | |
| platformProxy = await getPlatformProxy() | |
| const handler: Connect.NextHandleFunction = async (req, res, next) => { | |
| if (options.exclude?.some((r) => r.test(req.url!))) { | |
| return next() | |
| } | |
| let appModule: any; | |
| try { | |
| const ssrModule = await server.ssrLoadModule(options.entry!, { fixStacktrace: true }) | |
| appModule = ssrModule?.default | |
| if (!appModule || typeof appModule.fetch !== "function") { | |
| throw new Error("SSR Module is not hono app.") | |
| } | |
| } catch (e) { | |
| return next(e) | |
| } | |
| return getRequestListener(async (req) => { | |
| Object.assign(globalThis, { caches: platformProxy.caches }) | |
| Object.assign(req, { cf: platformProxy.cf }) | |
| let response = await appModule.fetch(req, platformProxy.env, platformProxy.ctx) | |
| if (!(response instanceof Response)) { | |
| throw response | |
| } | |
| if (options.injectClientScript && response.headers.get("content-type")?.includes("text/html")) { | |
| response = injectStringToResponse(response, `<script>import("/@vite/client");</script>`) | |
| } | |
| return response | |
| }, { | |
| overrideGlobalObjects: false, | |
| errorHandler: (e) => { | |
| let err: Error | |
| if (e instanceof Error) { | |
| err = e | |
| server.ssrFixStacktrace(err) | |
| } else if (typeof e === 'string') { | |
| err = new Error(`The response is not an instance of "Response", but: ${e}`) | |
| } else { | |
| err = new Error(`Unknown error: ${e}`) | |
| } | |
| next(err) | |
| } | |
| })(req, res) | |
| } | |
| server.middlewares.use(handler) | |
| server.httpServer?.on("close", async () => { | |
| await platformProxy.dispose() | |
| logger.info("Workerd proxy restarted.", { timestamp: true }) | |
| }) | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment