UEA-Prodem

This commit is contained in:
2026-06-10 12:38:42 -03:00
parent 3f33154e16
commit c41625e542
9352 changed files with 1128292 additions and 14752 deletions
+361
View File
@@ -0,0 +1,361 @@
# @hono/vite-dev-server
`@hono/vite-dev-server` is a Vite Plugin that provides a custom dev-server for `fetch`-based web applications like those using Hono.
You can develop your application with Vite. It's fast.
## Features
- Support any `fetch`-based applications.
- Hono applications run on.
- Fast by Vite.
- HMR (Only for the client side).
- Adapters are available, e.g., Cloudflare.
- Also runs on Bun.
## Demo
https://github.com/honojs/vite-plugins/assets/10682/a93ee4c5-2e1a-4b17-8bb2-64f955f2f0b0
## Supported applications
You can run any application on `@hono/vite-dev-server` that uses `fetch` and is built with Web Standard APIs. The minimal application is the following.
```ts
export default {
fetch(_request: Request) {
return new Response('Hello Vite!')
},
}
```
This code can also run on Cloudflare Workers or Bun.
And if you change the entry point, you can run on Deno, Vercel, Lagon, and other platforms.
Hono is designed for `fetch`-based applications like this.
```ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Vite!'))
export default app
```
So, any Hono application will run on `@hono/vite-dev-server`.
## Usage
### Installation
You can install `vite` and `@hono/vite-dev-server` via npm.
```bash
npm i -D vite @hono/vite-dev-server
```
Or you can install them with Bun.
```bash
bun add vite @hono/vite-dev-server
```
### Settings
Add `"type": "module"` to your `package.json`. Then, create `vite.config.ts` and edit it.
```ts
import { defineConfig } from 'vite'
import devServer from '@hono/vite-dev-server'
export default defineConfig({
plugins: [
devServer({
entry: 'src/index.ts', // The file path of your application.
}),
],
})
```
### Development
Just run `vite`.
```bash
npm exec vite
```
Or
```bash
bunx --bun vite
```
## Options
The options are below.
```ts
export type DevServerOptions = {
entry?: string
export?: string
injectClientScript?: boolean
exclude?: (string | RegExp)[]
ignoreWatching?: (string | RegExp)[]
adapter?: {
env?: Env
onServerClose?: () => Promise<void>
}
handleHotUpdate?: VitePlugin['handleHotUpdate']
}
```
Default values:
```ts
export const defaultOptions: Required<Omit<DevServerOptions, 'cf'>> = {
entry: './src/index.ts',
injectClientScript: true,
exclude: [
/.*\.css$/,
/.*\.ts$/,
/.*\.tsx$/,
/^\/@.+$/,
/\?t\=\d+$/,
/^\/favicon\.ico$/,
/^\/static\/.+/,
/^\/node_modules\/.*/,
],
ignoreWatching: [/\.wrangler/],
handleHotUpdate: ({ server, modules }) => {
const isSSR = modules.some((mod) => mod._ssrModule)
if (isSSR) {
server.hot.send({ type: 'full-reload' })
return []
}
},
}
```
### `injectClientScript`
If it's `true` and the response content type is "HTML", inject the script that enables Hot-reload. default is `true`.
### `exclude`
The paths that are not served by the dev-server.
If you have static files in `public/assets/*` and want to return them, exclude `/assets/*` as follows:
```ts
import devServer, { defaultOptions } from '@hono/vite-dev-server'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
devServer({
exclude: ['/assets/*', ...defaultOptions.exclude],
}),
],
})
```
### `ignoreWatching`
You can add target directories for the server to watch.
### `adapter`
You can pass the `env` value of a specified environment to the application.
## Adapter
### Cloudflare
You can pass the Bindings specified in `wrangler.toml` to your application by using "Cloudflare Adapter".
Install miniflare and wrangler to develop and deploy your cf project.
```bash
npm i -D wrangler miniflare
```
```ts
import devServer from '@hono/vite-dev-server'
import cloudflareAdapter from '@hono/vite-dev-server/cloudflare'
import { defineConfig } from 'vite'
export default defineConfig(async () => {
return {
plugins: [
devServer({
adapter: cloudflareAdapter,
}),
],
}
})
```
### Node.js & Bun
No additional dependencies needed.
```ts
import devServer from '@hono/vite-dev-server'
import nodeAdapter from '@hono/vite-dev-server/node'
// OR
// import bunAdapter from '@hono/vite-dev-server/bun'
import { defineConfig } from 'vite'
export default defineConfig(async () => {
return {
plugins: [
devServer({
adapter: nodeAdapter,
// OR
// adapter: bunAdapter,
}),
],
}
})
```
## Client-side
You can write client-side scripts and import them into your application using Vite's features.
If `/src/client.ts` is the entry point, simply write it in the `script` tag.
Additionally, `import.meta.env.PROD` is useful for detecting whether it's running on a dev server or in the build phase.
```tsx
app.get('/', (c) => {
return c.html(
<html>
<head>
{import.meta.env.PROD ? (
<script type='module' src='/static/client.js'></script>
) : (
<script type='module' src='/src/client.ts'></script>
)}
</head>
<body>
<h1>Hello</h1>
</body>
</html>
)
})
```
In order to build the script properly, you can use the example config file `vite.config.ts` as shown below.
```ts
import { defineConfig } from 'vite'
import devServer from '@hono/vite-dev-server'
export default defineConfig(({ mode }) => {
if (mode === 'client') {
return {
build: {
rollupOptions: {
input: ['./app/client.ts'],
output: {
entryFileNames: 'static/client.js',
chunkFileNames: 'static/assets/[name]-[hash].js',
assetFileNames: 'static/assets/[name].[ext]',
},
},
emptyOutDir: false,
copyPublicDir: false,
},
}
} else {
return {
build: {
minify: true,
rollupOptions: {
output: {
entryFileNames: '_worker.js',
},
},
},
plugins: [
devServer({
entry: './app/server.ts',
}),
],
}
}
})
```
You can run the following command to build the client script.
```bash
vite build --mode client
```
## Frequently Asked Questions
### exports is not defined
If you use a package that only supports CommonJS, you will encounter the error `exports is not defined`.
![exports is not defined](https://github.com/honojs/vite-plugins/assets/10682/6b36f8c2-50a8-4672-a177-4321f325a39f)
In that case, specify the target package in `ssr.external` in `vite.config.ts`:
```ts
export default defineConfig({
ssr: {
external: ['react', 'react-dom'],
},
plugins: [devServer()],
})
```
### Importing Asset as URL is not working
If you want to [import assets as URL](https://vitejs.dev/guide/assets) with the following code, the `logo` image may not be found.
```tsx
import { Hono } from 'hono'
import logo from './logo.png'
const app = new Hono()
app.get('/', async (c) => {
return c.html(
<html>
<body>
<img src={logo} />
</body>
</html>
)
})
export default app
```
This is because `logo.png` is served from this dev-server, even though it is expected to be served from Vite itself. So to fix it, you can add `*.png` to the `exclude` option:
```ts
import devServer, { defaultOptions } from '@hono/vite-dev-server'
export default defineConfig({
plugins: [
devServer({
entry: 'src/index.tsx',
exclude: [/.*\.png$/, ...defaultOptions.exclude],
}),
],
})
```
## Authors
- Yusuke Wada <https://github.com/yusukebe>
## License
MIT
+44
View File
@@ -0,0 +1,44 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var bun_exports = {};
__export(bun_exports, {
bunAdapter: () => bunAdapter,
default: () => bun_default
});
module.exports = __toCommonJS(bun_exports);
const bunAdapter = () => {
if (typeof globalThis.navigator === "undefined") {
globalThis.navigator = {
userAgent: "Bun"
};
} else {
Object.defineProperty(globalThis.navigator, "userAgent", {
value: "Bun",
writable: false
});
}
return {
env: process.env
};
};
var bun_default = bunAdapter;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
bunAdapter
});
+6
View File
@@ -0,0 +1,6 @@
import { Adapter } from '../types.cjs';
import 'vite';
declare const bunAdapter: () => Adapter;
export { bunAdapter, bunAdapter as default };
+6
View File
@@ -0,0 +1,6 @@
import { Adapter } from '../types.js';
import 'vite';
declare const bunAdapter: () => Adapter;
export { bunAdapter, bunAdapter as default };
+20
View File
@@ -0,0 +1,20 @@
const bunAdapter = () => {
if (typeof globalThis.navigator === "undefined") {
globalThis.navigator = {
userAgent: "Bun"
};
} else {
Object.defineProperty(globalThis.navigator, "userAgent", {
value: "Bun",
writable: false
});
}
return {
env: process.env
};
};
var bun_default = bunAdapter;
export {
bunAdapter,
bun_default as default
};
+71
View File
@@ -0,0 +1,71 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var cloudflare_exports = {};
__export(cloudflare_exports, {
cloudflareAdapter: () => cloudflareAdapter,
default: () => cloudflare_default
});
module.exports = __toCommonJS(cloudflare_exports);
var import_miniflare = require("miniflare");
var import_wrangler = require("wrangler");
Object.assign(globalThis, { WebSocketPair: import_miniflare.WebSocketPair });
let proxy = void 0;
const cloudflareAdapter = async (options) => {
proxy ??= await (0, import_wrangler.getPlatformProxy)(options?.proxy);
Object.assign(globalThis, { caches: proxy.caches });
if (typeof globalThis.navigator === "undefined") {
globalThis.navigator = {
userAgent: "Cloudflare-Workers"
};
} else {
Object.defineProperty(globalThis.navigator, "userAgent", {
value: "Cloudflare-Workers",
writable: false
});
}
Object.defineProperty(Request.prototype, "cf", {
get: function() {
if (proxy !== void 0) {
return proxy.cf;
}
throw new Error("Proxy is not initialized");
},
configurable: true,
enumerable: true
});
return {
env: proxy.env,
executionContext: proxy.ctx,
onServerClose: async () => {
if (proxy !== void 0) {
try {
await proxy.dispose();
} catch {
} finally {
proxy = void 0;
}
}
}
};
};
var cloudflare_default = cloudflareAdapter;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
cloudflareAdapter
});
+10
View File
@@ -0,0 +1,10 @@
import { getPlatformProxy } from 'wrangler';
import { Adapter } from '../types.cjs';
import 'vite';
type CloudflareAdapterOptions = {
proxy: Parameters<typeof getPlatformProxy>[0];
};
declare const cloudflareAdapter: (options?: CloudflareAdapterOptions) => Promise<Adapter>;
export { cloudflareAdapter, cloudflareAdapter as default };
+10
View File
@@ -0,0 +1,10 @@
import { getPlatformProxy } from 'wrangler';
import { Adapter } from '../types.js';
import 'vite';
type CloudflareAdapterOptions = {
proxy: Parameters<typeof getPlatformProxy>[0];
};
declare const cloudflareAdapter: (options?: CloudflareAdapterOptions) => Promise<Adapter>;
export { cloudflareAdapter, cloudflareAdapter as default };
+47
View File
@@ -0,0 +1,47 @@
import { WebSocketPair } from "miniflare";
import { getPlatformProxy } from "wrangler";
Object.assign(globalThis, { WebSocketPair });
let proxy = void 0;
const cloudflareAdapter = async (options) => {
proxy ??= await getPlatformProxy(options?.proxy);
Object.assign(globalThis, { caches: proxy.caches });
if (typeof globalThis.navigator === "undefined") {
globalThis.navigator = {
userAgent: "Cloudflare-Workers"
};
} else {
Object.defineProperty(globalThis.navigator, "userAgent", {
value: "Cloudflare-Workers",
writable: false
});
}
Object.defineProperty(Request.prototype, "cf", {
get: function() {
if (proxy !== void 0) {
return proxy.cf;
}
throw new Error("Proxy is not initialized");
},
configurable: true,
enumerable: true
});
return {
env: proxy.env,
executionContext: proxy.ctx,
onServerClose: async () => {
if (proxy !== void 0) {
try {
await proxy.dispose();
} catch {
} finally {
proxy = void 0;
}
}
}
};
};
var cloudflare_default = cloudflareAdapter;
export {
cloudflareAdapter,
cloudflare_default as default
};
+44
View File
@@ -0,0 +1,44 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var node_exports = {};
__export(node_exports, {
default: () => node_default,
nodeAdapter: () => nodeAdapter
});
module.exports = __toCommonJS(node_exports);
const nodeAdapter = () => {
if (typeof globalThis.navigator === "undefined") {
globalThis.navigator = {
userAgent: "Node.js"
};
} else {
Object.defineProperty(globalThis.navigator, "userAgent", {
value: "Node.js",
writable: false
});
}
return {
env: process.env
};
};
var node_default = nodeAdapter;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
nodeAdapter
});
+6
View File
@@ -0,0 +1,6 @@
import { Adapter } from '../types.cjs';
import 'vite';
declare const nodeAdapter: () => Adapter;
export { nodeAdapter as default, nodeAdapter };
+6
View File
@@ -0,0 +1,6 @@
import { Adapter } from '../types.js';
import 'vite';
declare const nodeAdapter: () => Adapter;
export { nodeAdapter as default, nodeAdapter };
+20
View File
@@ -0,0 +1,20 @@
const nodeAdapter = () => {
if (typeof globalThis.navigator === "undefined") {
globalThis.navigator = {
userAgent: "Node.js"
};
} else {
Object.defineProperty(globalThis.navigator, "userAgent", {
value: "Node.js",
writable: false
});
}
return {
env: process.env
};
};
var node_default = nodeAdapter;
export {
node_default as default,
nodeAdapter
};
+246
View File
@@ -0,0 +1,246 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var dev_server_exports = {};
__export(dev_server_exports, {
defaultOptions: () => defaultOptions,
devServer: () => devServer
});
module.exports = __toCommonJS(dev_server_exports);
var import_node_server = require("@hono/node-server");
var import_minimatch = require("minimatch");
var import_fs = __toESM(require("fs"), 1);
var import_path = __toESM(require("path"), 1);
const defaultOptions = {
entry: "./src/index.ts",
export: "default",
injectClientScript: true,
exclude: [
/.*\.css$/,
/.*\.ts$/,
/.*\.tsx$/,
/^\/@.+$/,
/\?t\=\d+$/,
/^\/favicon\.ico$/,
/^\/static\/.+/,
/^\/node_modules\/.*/
],
ignoreWatching: [/\.wrangler/, /\.mf/],
handleHotUpdate: ({ server, modules }) => {
const isSSR = modules.some((mod) => mod._ssrModule);
if (isSSR) {
server.hot.send({ type: "full-reload" });
return [];
}
}
};
function devServer(options) {
let publicDirPath = "";
const entry = options?.entry ?? defaultOptions.entry;
const plugin = {
name: "@hono/vite-dev-server",
configResolved(config) {
publicDirPath = config.publicDir;
},
configureServer: async (server) => {
async function createMiddleware(server2) {
return async function(req, res, next) {
if (req.url) {
const filePath = import_path.default.join(publicDirPath, req.url);
try {
if (import_fs.default.existsSync(filePath) && import_fs.default.statSync(filePath).isFile()) {
return next();
}
} catch {
}
}
const exclude = options?.exclude ?? defaultOptions.exclude;
for (const pattern of exclude) {
if (req.url) {
if (pattern instanceof RegExp) {
if (pattern.test(req.url)) {
return next();
}
} else if ((0, import_minimatch.minimatch)(req.url?.toString(), pattern)) {
return next();
}
}
}
let loadModule;
if (options?.loadModule) {
loadModule = options.loadModule;
} else {
loadModule = async (server3, entry2) => {
let appModule;
try {
appModule = await server3.ssrLoadModule(entry2);
} catch (e) {
if (e instanceof Error) {
server3.ssrFixStacktrace(e);
}
throw e;
}
const exportName = options?.export ?? defaultOptions.export;
const app2 = appModule[exportName];
if (!app2) {
throw new Error(`Failed to find a named export "${exportName}" from ${entry2}`);
}
return app2;
};
}
let app;
try {
app = await loadModule(server2, entry);
} catch (e) {
return next(e);
}
(0, import_node_server.getRequestListener)(
async (request) => {
let env = {};
if (options?.env) {
if (typeof options.env === "function") {
env = { ...env, ...await options.env() };
} else {
env = { ...env, ...options.env };
}
}
const adapter = await getAdapterFromOptions(options);
if (adapter?.env) {
env = { ...env, ...adapter.env };
}
const executionContext = adapter?.executionContext ?? {
waitUntil: async (fn) => fn,
passThroughOnException: () => {
throw new Error("`passThroughOnException` is not supported");
}
};
const response = await app.fetch(request, env, executionContext);
if (!(response instanceof Response)) {
throw response;
}
if (options?.injectClientScript !== false && response.headers.get("content-type")?.match(/^text\/html/)) {
const nonce = response.headers.get("content-security-policy")?.match(/'nonce-([^']+)'/)?.[1];
const script = `<script${nonce ? ` nonce="${nonce}"` : ""}>import("/@vite/client")</script>`;
return injectStringToResponse(response, script) ?? response;
}
return response;
},
{
overrideGlobalObjects: false,
errorHandler: (e) => {
let err;
if (e instanceof Error) {
err = e;
server2.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(await createMiddleware(server));
server.httpServer?.on("close", async () => {
const adapter = await getAdapterFromOptions(options);
if (adapter?.onServerClose) {
await adapter.onServerClose();
}
});
},
handleHotUpdate: options?.handleHotUpdate ?? defaultOptions.handleHotUpdate,
config: () => {
return {
server: {
watch: {
ignored: options?.ignoreWatching ?? defaultOptions.ignoreWatching
}
}
};
}
};
return plugin;
}
const getAdapterFromOptions = async (options) => {
let adapter = options?.adapter;
if (typeof adapter === "function") {
adapter = adapter();
}
if (adapter instanceof Promise) {
adapter = await adapter;
}
return adapter;
};
function injectStringToResponse(response, content) {
const stream = response.body;
const newContent = new TextEncoder().encode(content);
if (!stream) {
return null;
}
const reader = stream.getReader();
const newContentReader = new ReadableStream({
start(controller) {
controller.enqueue(newContent);
controller.close();
}
}).getReader();
const combinedStream = new ReadableStream({
async start(controller) {
for (; ; ) {
const [existingResult, newContentResult] = await Promise.all([
reader.read(),
newContentReader.read()
]);
if (existingResult.done && newContentResult.done) {
controller.close();
break;
}
if (!existingResult.done) {
controller.enqueue(existingResult.value);
}
if (!newContentResult.done) {
controller.enqueue(newContentResult.value);
}
}
}
});
const headers = new Headers(response.headers);
headers.delete("content-length");
return new Response(combinedStream, {
headers,
status: response.status
});
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
defaultOptions,
devServer
});
+46
View File
@@ -0,0 +1,46 @@
import { Plugin } from 'vite';
import { Env, EnvFunc, LoadModule, Adapter } from './types.cjs';
type DevServerOptions = {
entry?: string;
export?: string;
injectClientScript?: boolean;
exclude?: (string | RegExp)[];
ignoreWatching?: (string | RegExp)[];
env?: Env | EnvFunc;
loadModule?: LoadModule;
/**
* This can be used to inject environment variables into the worker from your wrangler.toml for example,
* by making use of the helper function `getPlatformProxy` from `wrangler`.
*
* @example
*
* ```ts
* import { defineConfig } from 'vite'
* import devServer from '@hono/vite-dev-server'
* import getPlatformProxy from 'wrangler'
*
* export default defineConfig(async () => {
* const { env, dispose } = await getPlatformProxy()
* return {
* plugins: [
* devServer({
* adapter: {
* env,
* onServerClose: dispose
* },
* }),
* ],
* }
* })
* ```
*
*
*/
adapter?: Adapter | Promise<Adapter> | (() => Adapter | Promise<Adapter>);
handleHotUpdate?: Plugin['handleHotUpdate'];
};
declare const defaultOptions: Required<Omit<DevServerOptions, 'env' | 'adapter' | 'loadModule'>>;
declare function devServer(options?: DevServerOptions): Plugin;
export { DevServerOptions, defaultOptions, devServer };
+46
View File
@@ -0,0 +1,46 @@
import { Plugin } from 'vite';
import { Env, EnvFunc, LoadModule, Adapter } from './types.js';
type DevServerOptions = {
entry?: string;
export?: string;
injectClientScript?: boolean;
exclude?: (string | RegExp)[];
ignoreWatching?: (string | RegExp)[];
env?: Env | EnvFunc;
loadModule?: LoadModule;
/**
* This can be used to inject environment variables into the worker from your wrangler.toml for example,
* by making use of the helper function `getPlatformProxy` from `wrangler`.
*
* @example
*
* ```ts
* import { defineConfig } from 'vite'
* import devServer from '@hono/vite-dev-server'
* import getPlatformProxy from 'wrangler'
*
* export default defineConfig(async () => {
* const { env, dispose } = await getPlatformProxy()
* return {
* plugins: [
* devServer({
* adapter: {
* env,
* onServerClose: dispose
* },
* }),
* ],
* }
* })
* ```
*
*
*/
adapter?: Adapter | Promise<Adapter> | (() => Adapter | Promise<Adapter>);
handleHotUpdate?: Plugin['handleHotUpdate'];
};
declare const defaultOptions: Required<Omit<DevServerOptions, 'env' | 'adapter' | 'loadModule'>>;
declare function devServer(options?: DevServerOptions): Plugin;
export { DevServerOptions, defaultOptions, devServer };
+211
View File
@@ -0,0 +1,211 @@
import { getRequestListener } from "@hono/node-server";
import { minimatch } from "minimatch";
import fs from "fs";
import path from "path";
const defaultOptions = {
entry: "./src/index.ts",
export: "default",
injectClientScript: true,
exclude: [
/.*\.css$/,
/.*\.ts$/,
/.*\.tsx$/,
/^\/@.+$/,
/\?t\=\d+$/,
/^\/favicon\.ico$/,
/^\/static\/.+/,
/^\/node_modules\/.*/
],
ignoreWatching: [/\.wrangler/, /\.mf/],
handleHotUpdate: ({ server, modules }) => {
const isSSR = modules.some((mod) => mod._ssrModule);
if (isSSR) {
server.hot.send({ type: "full-reload" });
return [];
}
}
};
function devServer(options) {
let publicDirPath = "";
const entry = options?.entry ?? defaultOptions.entry;
const plugin = {
name: "@hono/vite-dev-server",
configResolved(config) {
publicDirPath = config.publicDir;
},
configureServer: async (server) => {
async function createMiddleware(server2) {
return async function(req, res, next) {
if (req.url) {
const filePath = path.join(publicDirPath, req.url);
try {
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
return next();
}
} catch {
}
}
const exclude = options?.exclude ?? defaultOptions.exclude;
for (const pattern of exclude) {
if (req.url) {
if (pattern instanceof RegExp) {
if (pattern.test(req.url)) {
return next();
}
} else if (minimatch(req.url?.toString(), pattern)) {
return next();
}
}
}
let loadModule;
if (options?.loadModule) {
loadModule = options.loadModule;
} else {
loadModule = async (server3, entry2) => {
let appModule;
try {
appModule = await server3.ssrLoadModule(entry2);
} catch (e) {
if (e instanceof Error) {
server3.ssrFixStacktrace(e);
}
throw e;
}
const exportName = options?.export ?? defaultOptions.export;
const app2 = appModule[exportName];
if (!app2) {
throw new Error(`Failed to find a named export "${exportName}" from ${entry2}`);
}
return app2;
};
}
let app;
try {
app = await loadModule(server2, entry);
} catch (e) {
return next(e);
}
getRequestListener(
async (request) => {
let env = {};
if (options?.env) {
if (typeof options.env === "function") {
env = { ...env, ...await options.env() };
} else {
env = { ...env, ...options.env };
}
}
const adapter = await getAdapterFromOptions(options);
if (adapter?.env) {
env = { ...env, ...adapter.env };
}
const executionContext = adapter?.executionContext ?? {
waitUntil: async (fn) => fn,
passThroughOnException: () => {
throw new Error("`passThroughOnException` is not supported");
}
};
const response = await app.fetch(request, env, executionContext);
if (!(response instanceof Response)) {
throw response;
}
if (options?.injectClientScript !== false && response.headers.get("content-type")?.match(/^text\/html/)) {
const nonce = response.headers.get("content-security-policy")?.match(/'nonce-([^']+)'/)?.[1];
const script = `<script${nonce ? ` nonce="${nonce}"` : ""}>import("/@vite/client")</script>`;
return injectStringToResponse(response, script) ?? response;
}
return response;
},
{
overrideGlobalObjects: false,
errorHandler: (e) => {
let err;
if (e instanceof Error) {
err = e;
server2.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(await createMiddleware(server));
server.httpServer?.on("close", async () => {
const adapter = await getAdapterFromOptions(options);
if (adapter?.onServerClose) {
await adapter.onServerClose();
}
});
},
handleHotUpdate: options?.handleHotUpdate ?? defaultOptions.handleHotUpdate,
config: () => {
return {
server: {
watch: {
ignored: options?.ignoreWatching ?? defaultOptions.ignoreWatching
}
}
};
}
};
return plugin;
}
const getAdapterFromOptions = async (options) => {
let adapter = options?.adapter;
if (typeof adapter === "function") {
adapter = adapter();
}
if (adapter instanceof Promise) {
adapter = await adapter;
}
return adapter;
};
function injectStringToResponse(response, content) {
const stream = response.body;
const newContent = new TextEncoder().encode(content);
if (!stream) {
return null;
}
const reader = stream.getReader();
const newContentReader = new ReadableStream({
start(controller) {
controller.enqueue(newContent);
controller.close();
}
}).getReader();
const combinedStream = new ReadableStream({
async start(controller) {
for (; ; ) {
const [existingResult, newContentResult] = await Promise.all([
reader.read(),
newContentReader.read()
]);
if (existingResult.done && newContentResult.done) {
controller.close();
break;
}
if (!existingResult.done) {
controller.enqueue(existingResult.value);
}
if (!newContentResult.done) {
controller.enqueue(newContentResult.value);
}
}
}
});
const headers = new Headers(response.headers);
headers.delete("content-length");
return new Response(combinedStream, {
headers,
status: response.status
});
}
export {
defaultOptions,
devServer
};
+31
View File
@@ -0,0 +1,31 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var src_exports = {};
__export(src_exports, {
default: () => src_default,
defaultOptions: () => import_dev_server2.defaultOptions
});
module.exports = __toCommonJS(src_exports);
var import_dev_server = require("./dev-server.js");
var import_dev_server2 = require("./dev-server.js");
var src_default = import_dev_server.devServer;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
defaultOptions
});
+8
View File
@@ -0,0 +1,8 @@
import { devServer } from './dev-server.cjs';
export { DevServerOptions, defaultOptions } from './dev-server.cjs';
import 'vite';
import './types.cjs';
export { devServer as default };
+8
View File
@@ -0,0 +1,8 @@
import { devServer } from './dev-server.js';
export { DevServerOptions, defaultOptions } from './dev-server.js';
import 'vite';
import './types.js';
export { devServer as default };
+7
View File
@@ -0,0 +1,7 @@
import { devServer } from "./dev-server.js";
import { defaultOptions } from "./dev-server.js";
var src_default = devServer;
export {
src_default as default,
defaultOptions
};
+16
View File
@@ -0,0 +1,16 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var types_exports = {};
module.exports = __toCommonJS(types_exports);
+32
View File
@@ -0,0 +1,32 @@
import { ViteDevServer } from 'vite';
type Env = Record<string, unknown> | Promise<Record<string, unknown>>;
type EnvFunc = () => Env | Promise<Env>;
type GetEnv<Options> = (options: Options) => EnvFunc;
interface ExecutionContext {
waitUntil(promise: Promise<unknown>): void;
passThroughOnException(): void;
}
type Fetch = (request: Request, env: {}, executionContext: ExecutionContext) => Promise<Response>;
type LoadModule = (server: ViteDevServer, entry: string) => Promise<{
fetch: Fetch;
}>;
interface Adapter {
/**
* Environment variables to be injected into the worker
*/
env?: Env;
/**
* Function called when the vite dev server is closed
*/
onServerClose?: () => Promise<void>;
/**
* Implementation of waitUntil and passThroughOnException
*/
executionContext?: {
waitUntil(promise: Promise<unknown>): void;
passThroughOnException(): void;
};
}
export { Adapter, Env, EnvFunc, ExecutionContext, Fetch, GetEnv, LoadModule };
+32
View File
@@ -0,0 +1,32 @@
import { ViteDevServer } from 'vite';
type Env = Record<string, unknown> | Promise<Record<string, unknown>>;
type EnvFunc = () => Env | Promise<Env>;
type GetEnv<Options> = (options: Options) => EnvFunc;
interface ExecutionContext {
waitUntil(promise: Promise<unknown>): void;
passThroughOnException(): void;
}
type Fetch = (request: Request, env: {}, executionContext: ExecutionContext) => Promise<Response>;
type LoadModule = (server: ViteDevServer, entry: string) => Promise<{
fetch: Fetch;
}>;
interface Adapter {
/**
* Environment variables to be injected into the worker
*/
env?: Env;
/**
* Function called when the vite dev server is closed
*/
onServerClose?: () => Promise<void>;
/**
* Implementation of waitUntil and passThroughOnException
*/
executionContext?: {
waitUntil(promise: Promise<unknown>): void;
passThroughOnException(): void;
};
}
export { Adapter, Env, EnvFunc, ExecutionContext, Fetch, GetEnv, LoadModule };
View File
+111
View File
@@ -0,0 +1,111 @@
{
"name": "@hono/vite-dev-server",
"description": "Vite dev-server plugin for Hono",
"version": "0.19.1",
"types": "dist/index.d.ts",
"module": "dist/index.js",
"type": "module",
"scripts": {
"test:unit": "vitest --run ./src",
"test:e2e": "playwright test -c e2e/playwright.config.ts e2e/e2e.test.ts",
"test:e2e:bun": "playwright test -c e2e-bun/playwright.config.ts e2e-bun/e2e.test.ts",
"test": "yarn test:unit && yarn test:e2e",
"build": "rimraf dist && tsup --format esm,cjs --dts && publint",
"watch": "tsup --watch",
"prerelease": "yarn build && yarn test",
"release": "yarn publish"
},
"files": [
"dist"
],
"exports": {
".": {
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
},
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"./cloudflare": {
"types": "./dist/adapter/cloudflare.d.ts",
"require": "./dist/adapter/cloudflare.cjs",
"import": "./dist/adapter/cloudflare.js"
},
"./node": {
"types": "./dist/adapter/node.d.ts",
"require": "./dist/adapter/node.cjs",
"import": "./dist/adapter/node.js"
},
"./bun": {
"types": "./dist/adapter/bun.d.ts",
"require": "./dist/adapter/bun.cjs",
"import": "./dist/adapter/bun.js"
}
},
"typesVersions": {
"*": {
"types": [
"./dist/types"
],
"cloudflare": [
"./dist/adapter/cloudflare.d.ts"
],
"node": [
"./dist/adapter/node.d.ts"
],
"bun": [
"./dist/adapter/bun.d.ts"
]
}
},
"author": "Yusuke Wada <yusuke@kamawada.com> (https://github.com/yusukebe)",
"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": {
"@playwright/test": "^1.37.1",
"glob": "^10.3.10",
"hono": "^4.4.11",
"miniflare": "^3.20240701.0",
"playwright": "^1.39.0",
"publint": "^0.2.5",
"rimraf": "^5.0.1",
"tsup": "^7.2.0",
"vite": "^6.1.1",
"vitest": "^0.34.6",
"wrangler": "^3.63.1"
},
"peerDependencies": {
"hono": "*",
"miniflare": "*",
"wrangler": "*"
},
"peerDependenciesMeta": {
"hono": {
"optional": false
},
"miniflare": {
"optional": true
},
"wrangler": {
"optional": true
}
},
"engines": {
"node": ">=18.14.1"
},
"dependencies": {
"@hono/node-server": "^1.14.2",
"minimatch": "^9.0.3"
}
}