@hono/vite-build 1.10.1 → 1.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +37 -0
  3. package/dist/adapter/bun/{index.d.ts → index.d.mts} +6 -6
  4. package/dist/adapter/bun/index.mjs +47 -0
  5. package/dist/adapter/cloudflare-pages/{index.d.ts → index.d.mts} +5 -5
  6. package/dist/adapter/cloudflare-pages/index.mjs +41 -0
  7. package/dist/adapter/cloudflare-workers/{index.d.ts → index.d.mts} +6 -5
  8. package/dist/adapter/cloudflare-workers/index.mjs +33 -0
  9. package/dist/adapter/deno/index.d.mts +10 -0
  10. package/dist/adapter/deno/index.mjs +21 -0
  11. package/dist/adapter/netlify-functions/{index.d.ts → index.d.mts} +5 -5
  12. package/dist/adapter/netlify-functions/index.mjs +16 -0
  13. package/dist/adapter/node/index.d.mts +19 -0
  14. package/dist/adapter/node/index.mjs +37 -0
  15. package/dist/adapter/vercel/index.d.mts +22 -0
  16. package/dist/adapter/vercel/index.mjs +170 -0
  17. package/dist/adapter/vercel/types.d.mts +276 -0
  18. package/dist/adapter/vercel/types.mjs +1 -0
  19. package/dist/base.d.mts +30 -0
  20. package/dist/base.mjs +99 -0
  21. package/dist/entry/index.d.mts +27 -0
  22. package/dist/entry/index.mjs +63 -0
  23. package/dist/entry/{serve-static.d.ts → serve-static.d.mts} +5 -4
  24. package/dist/entry/serve-static.mjs +8 -0
  25. package/dist/index.d.mts +2 -0
  26. package/dist/index.mjs +5 -0
  27. package/package.json +21 -53
  28. package/dist/adapter/bun/index.js +0 -55
  29. package/dist/adapter/cloudflare-pages/index.js +0 -53
  30. package/dist/adapter/cloudflare-workers/index.js +0 -37
  31. package/dist/adapter/deno/index.d.ts +0 -10
  32. package/dist/adapter/deno/index.js +0 -28
  33. package/dist/adapter/netlify-functions/index.js +0 -18
  34. package/dist/adapter/node/index.d.ts +0 -19
  35. package/dist/adapter/node/index.js +0 -50
  36. package/dist/adapter/vercel/index.d.ts +0 -14
  37. package/dist/adapter/vercel/index.js +0 -85
  38. package/dist/adapter/vercel/types.d.ts +0 -368
  39. package/dist/adapter/vercel/types.js +0 -0
  40. package/dist/base.d.ts +0 -29
  41. package/dist/base.js +0 -112
  42. package/dist/entry/index.d.ts +0 -26
  43. package/dist/entry/index.js +0 -69
  44. package/dist/entry/serve-static.js +0 -11
  45. package/dist/index.d.ts +0 -8
  46. package/dist/index.js +0 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @hono/vite-build
2
2
 
3
+ ## 1.11.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#340](https://github.com/honojs/vite-plugins/pull/340) [`1f2b2fc308fe39b3bf940b948199f5fe1c184d65`](https://github.com/honojs/vite-plugins/commit/1f2b2fc308fe39b3bf940b948199f5fe1c184d65) Thanks [@josiahwiebe](https://github.com/josiahwiebe)! - feat: configure multiple function output for Vercel
8
+
3
9
  ## 1.10.1
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -10,6 +10,7 @@ Here are the modules included:
10
10
  - `@hono/vite-build/bun`
11
11
  - `@hono/vite-build/node`
12
12
  - `@hono/vite-build/netlify-functions`
13
+ - `@hono/vite-build/vercel`
13
14
 
14
15
  ## Usage
15
16
 
@@ -125,6 +126,42 @@ export const defaultOptions = {
125
126
 
126
127
  This plugin generates `_routes.json` automatically. The automatic generation can be overridden by creating a `public/_routes.json`. See [Create a `_routes.json` file](https://developers.cloudflare.com/pages/functions/routing/#create-a-_routesjson-file) on Cloudflare Docs for more details.
127
128
 
129
+ ### Vercel
130
+
131
+ By default, the Vercel adapter emits a single function named `__hono` and a catch-all route.
132
+
133
+ To emit multiple functions, add the Vercel adapter multiple times in `plugins`.
134
+
135
+ ```ts
136
+ import { defineConfig } from 'vite'
137
+ import build from '@hono/vite-build/vercel'
138
+
139
+ export default defineConfig({
140
+ plugins: [
141
+ build({
142
+ entry: './src/server.ts',
143
+ vercel: {
144
+ name: 'api',
145
+ routes: [{ src: '^/api(?:/.*)?$' }],
146
+ },
147
+ }),
148
+ build({
149
+ entry: './src/auth.ts',
150
+ vercel: {
151
+ name: 'auth',
152
+ routes: [{ src: '^/auth(?:/.*)?$' }],
153
+ function: {
154
+ maxDuration: 30,
155
+ },
156
+ },
157
+ }),
158
+ ],
159
+ })
160
+ ```
161
+
162
+ If `vercel.routes` is omitted, the adapter generates `^/<function-name>(?:/.*)?$`.
163
+ If `vercel.name` is omitted, the adapter uses `__hono`.
164
+
128
165
  ## Example project
129
166
 
130
167
  `src/index.tsx`:
@@ -1,11 +1,11 @@
1
- import { Plugin } from 'vite';
2
- import { BuildOptions } from '../../base.js';
3
- import '../../entry/index.js';
1
+ import { BuildOptions } from "../../base.mjs";
2
+ import { Plugin } from "vite";
4
3
 
4
+ //#region src/adapter/bun/index.d.ts
5
5
  type BunBuildOptions = {
6
- staticRoot?: string | undefined;
6
+ staticRoot?: string | undefined;
7
7
  } & BuildOptions;
8
8
  declare const defaultOptions: BunBuildOptions;
9
9
  declare const bunBuildPlugin: (pluginOptions?: BunBuildOptions) => Plugin;
10
-
11
- export { BunBuildOptions, bunBuildPlugin as default, defaultOptions };
10
+ //#endregion
11
+ export { BunBuildOptions, bunBuildPlugin as default, defaultOptions };
@@ -0,0 +1,47 @@
1
+ import buildPlugin, { defaultOptions as defaultOptions$1 } from "../../base.mjs";
2
+ import { serveStaticHook } from "../../entry/serve-static.mjs";
3
+ //#region src/adapter/bun/index.ts
4
+ const defaultOptions = {
5
+ ...defaultOptions$1,
6
+ entryContentAfterHooks: [() => `
7
+ let websocket
8
+ for (const [, app] of Object.entries(modules)) {
9
+ if (
10
+ app &&
11
+ typeof app === 'object' &&
12
+ 'websocket' in app &&
13
+ app.websocket !== undefined
14
+ ) {
15
+ if (websocket !== undefined) {
16
+ throw new Error(
17
+ \`Handler "websocket" is defined in multiple entry files. Please ensure each handler is defined only once.\`
18
+ )
19
+ }
20
+ websocket = app.websocket
21
+ }
22
+ }
23
+ `],
24
+ entryContentDefaultExportHook: (appName) => `export default websocket !== undefined ? { fetch: ${appName}.fetch.bind(${appName}), websocket } : ${appName}`
25
+ };
26
+ const bunBuildPlugin = (pluginOptions) => {
27
+ return {
28
+ ...buildPlugin({
29
+ ssrTarget: "node",
30
+ entryContentBeforeHooks: [async (appName, options) => {
31
+ let code = "import { serveStatic } from 'hono/bun'\n";
32
+ code += serveStaticHook(appName, {
33
+ filePaths: options?.staticPaths,
34
+ root: pluginOptions?.staticRoot
35
+ });
36
+ return code;
37
+ }],
38
+ ...pluginOptions,
39
+ external: ["bun", ...pluginOptions?.external ?? []],
40
+ entryContentAfterHooks: pluginOptions?.entryContentAfterHooks ?? defaultOptions.entryContentAfterHooks,
41
+ entryContentDefaultExportHook: pluginOptions?.entryContentDefaultExportHook ?? defaultOptions.entryContentDefaultExportHook
42
+ }),
43
+ name: "@hono/vite-build/bun"
44
+ };
45
+ };
46
+ //#endregion
47
+ export { bunBuildPlugin as default, defaultOptions };
@@ -1,8 +1,8 @@
1
- import { Plugin } from 'vite';
2
- import { BuildOptions } from '../../base.js';
3
- import '../../entry/index.js';
1
+ import { BuildOptions } from "../../base.mjs";
2
+ import { Plugin } from "vite";
4
3
 
4
+ //#region src/adapter/cloudflare-pages/index.d.ts
5
5
  type CloudflarePagesBuildOptions = BuildOptions;
6
6
  declare const cloudflarePagesBuildPlugin: (pluginOptions?: CloudflarePagesBuildOptions) => Plugin;
7
-
8
- export { CloudflarePagesBuildOptions, cloudflarePagesBuildPlugin as default };
7
+ //#endregion
8
+ export { CloudflarePagesBuildOptions, cloudflarePagesBuildPlugin as default };
@@ -0,0 +1,41 @@
1
+ import buildPlugin, { defaultOptions } from "../../base.mjs";
2
+ import { resolve } from "node:path";
3
+ import { readdir, writeFile } from "node:fs/promises";
4
+ //#region src/adapter/cloudflare-pages/index.ts
5
+ const WORKER_JS_NAME = "_worker.js";
6
+ const ROUTES_JSON_NAME = "_routes.json";
7
+ const cloudflarePagesBuildPlugin = (pluginOptions) => {
8
+ let config;
9
+ const staticPaths = [];
10
+ return {
11
+ ...buildPlugin({
12
+ ...pluginOptions,
13
+ output: WORKER_JS_NAME
14
+ }),
15
+ configResolved: async (resolvedConfig) => {
16
+ config = resolvedConfig;
17
+ },
18
+ writeBundle: async () => {
19
+ const paths = await readdir(resolve(config.root, config.build.outDir), { withFileTypes: true });
20
+ if (paths.some((p) => p.name === ROUTES_JSON_NAME)) return;
21
+ else {
22
+ paths.forEach((p) => {
23
+ if (p.isDirectory()) staticPaths.push(`/${p.name}/*`);
24
+ else {
25
+ if (p.name === WORKER_JS_NAME) return;
26
+ staticPaths.push(`/${p.name}`);
27
+ }
28
+ });
29
+ const staticRoutes = {
30
+ version: 1,
31
+ include: ["/*"],
32
+ exclude: staticPaths
33
+ };
34
+ await writeFile(resolve(config.root, pluginOptions?.outputDir ?? defaultOptions.outputDir, "_routes.json"), JSON.stringify(staticRoutes));
35
+ }
36
+ },
37
+ name: "@hono/vite-build/cloudflare-pages"
38
+ };
39
+ };
40
+ //#endregion
41
+ export { cloudflarePagesBuildPlugin as default };
@@ -1,9 +1,10 @@
1
- import { Plugin } from 'vite';
2
- import { BuildOptions } from '../../base.js';
3
- import { GetEntryContentOptions } from '../../entry/index.js';
1
+ import { GetEntryContentOptions } from "../../entry/index.mjs";
2
+ import { BuildOptions } from "../../base.mjs";
3
+ import { Plugin } from "vite";
4
4
 
5
+ //#region src/adapter/cloudflare-workers/index.d.ts
5
6
  type CloudflareWorkersBuildOptions = BuildOptions & Pick<GetEntryContentOptions, 'entryContentAfterHooks' | 'entryContentDefaultExportHook'>;
6
7
  declare const defaultOptions: CloudflareWorkersBuildOptions;
7
8
  declare const cloudflareWorkersBuildPlugin: (pluginOptions?: CloudflareWorkersBuildOptions) => Plugin;
8
-
9
- export { CloudflareWorkersBuildOptions, cloudflareWorkersBuildPlugin as default, defaultOptions };
9
+ //#endregion
10
+ export { CloudflareWorkersBuildOptions, cloudflareWorkersBuildPlugin as default, defaultOptions };
@@ -0,0 +1,33 @@
1
+ import buildPlugin, { defaultOptions as defaultOptions$1 } from "../../base.mjs";
2
+ //#region src/adapter/cloudflare-workers/index.ts
3
+ const defaultOptions = {
4
+ ...defaultOptions$1,
5
+ entryContentAfterHooks: [() => `
6
+ const merged = {}
7
+ const definedHandlers = new Set()
8
+ for (const [file, app] of Object.entries(modules)) {
9
+ for (const [key, handler] of Object.entries(app)) {
10
+ if (key !== 'fetch') {
11
+ if (definedHandlers.has(key)) {
12
+ throw new Error(\`Handler "\${key}" is defined in multiple entry files. Please ensure each handler (except fetch) is defined only once.\`);
13
+ }
14
+ definedHandlers.add(key)
15
+ merged[key] = handler
16
+ }
17
+ }
18
+ }
19
+ `],
20
+ entryContentDefaultExportHook: (appName) => `export default { ...merged, fetch: ${appName}.fetch }`
21
+ };
22
+ const cloudflareWorkersBuildPlugin = (pluginOptions) => {
23
+ return {
24
+ ...buildPlugin({
25
+ ...pluginOptions,
26
+ entryContentAfterHooks: pluginOptions?.entryContentAfterHooks ?? defaultOptions.entryContentAfterHooks,
27
+ entryContentDefaultExportHook: pluginOptions?.entryContentDefaultExportHook ?? defaultOptions.entryContentDefaultExportHook
28
+ }),
29
+ name: "@hono/vite-build/cloudflare-workers"
30
+ };
31
+ };
32
+ //#endregion
33
+ export { cloudflareWorkersBuildPlugin as default, defaultOptions };
@@ -0,0 +1,10 @@
1
+ import { BuildOptions } from "../../base.mjs";
2
+ import { Plugin } from "vite";
3
+
4
+ //#region src/adapter/deno/index.d.ts
5
+ type DenoBuildOptions = {
6
+ staticRoot?: string | undefined;
7
+ } & BuildOptions;
8
+ declare const denoBuildPlugin: (pluginOptions?: DenoBuildOptions) => Plugin;
9
+ //#endregion
10
+ export { DenoBuildOptions, denoBuildPlugin as default };
@@ -0,0 +1,21 @@
1
+ import buildPlugin from "../../base.mjs";
2
+ import { serveStaticHook } from "../../entry/serve-static.mjs";
3
+ //#region src/adapter/deno/index.ts
4
+ const denoBuildPlugin = (pluginOptions) => {
5
+ return {
6
+ ...buildPlugin({
7
+ entryContentBeforeHooks: [async (appName, options) => {
8
+ let code = `import { serveStatic } from '${pluginOptions?.preset ?? "hono"}/deno'\n`;
9
+ code += serveStaticHook(appName, {
10
+ filePaths: options?.staticPaths,
11
+ root: pluginOptions?.staticRoot
12
+ });
13
+ return code;
14
+ }],
15
+ ...pluginOptions
16
+ }),
17
+ name: "@hono/vite-build/deno"
18
+ };
19
+ };
20
+ //#endregion
21
+ export { denoBuildPlugin as default };
@@ -1,8 +1,8 @@
1
- import { Plugin } from 'vite';
2
- import { BuildOptions } from '../../base.js';
3
- import '../../entry/index.js';
1
+ import { BuildOptions } from "../../base.mjs";
2
+ import { Plugin } from "vite";
4
3
 
4
+ //#region src/adapter/netlify-functions/index.d.ts
5
5
  type NetlifyFunctionsBuildOptions = BuildOptions;
6
6
  declare function netlifyFunctionsBuildPlugin(pluginOptions?: NetlifyFunctionsBuildOptions): Plugin;
7
-
8
- export { NetlifyFunctionsBuildOptions, netlifyFunctionsBuildPlugin as default };
7
+ //#endregion
8
+ export { NetlifyFunctionsBuildOptions, netlifyFunctionsBuildPlugin as default };
@@ -0,0 +1,16 @@
1
+ import buildPlugin from "../../base.mjs";
2
+ //#region src/adapter/netlify-functions/index.ts
3
+ function netlifyFunctionsBuildPlugin(pluginOptions) {
4
+ return {
5
+ ...buildPlugin({
6
+ ssrTarget: "node",
7
+ entryContentBeforeHooks: [() => "import { handle } from \"hono/netlify\""],
8
+ entryContentAfterHooks: [() => "export const config = { path: \"/*\", preferStatic: true }"],
9
+ entryContentDefaultExportHook: (appName) => `export default handle(${appName})`,
10
+ ...pluginOptions
11
+ }),
12
+ name: "@hono/vite-build/netlify-functions"
13
+ };
14
+ }
15
+ //#endregion
16
+ export { netlifyFunctionsBuildPlugin as default };
@@ -0,0 +1,19 @@
1
+ import { BuildOptions } from "../../base.mjs";
2
+ import { Plugin } from "vite";
3
+
4
+ //#region src/adapter/node/index.d.ts
5
+ type NodeBuildOptions = {
6
+ staticRoot?: string | undefined;
7
+ port?: number | undefined;
8
+ /**
9
+ * Enable graceful shutdown on SIGINT and SIGTERM signals.
10
+ * Set to a number to specify the timeout in milliseconds before forcing shutdown.
11
+ * Set to 0 to wait indefinitely for connections to close.
12
+ * Leave undefined to disable graceful shutdown.
13
+ * @default undefined
14
+ */
15
+ shutdownTimeoutMs?: number | undefined;
16
+ } & BuildOptions;
17
+ declare const nodeBuildPlugin: (pluginOptions?: NodeBuildOptions) => Plugin;
18
+ //#endregion
19
+ export { NodeBuildOptions, nodeBuildPlugin as default };
@@ -0,0 +1,37 @@
1
+ import buildPlugin from "../../base.mjs";
2
+ import { serveStaticHook } from "../../entry/serve-static.mjs";
3
+ //#region src/adapter/node/index.ts
4
+ const nodeBuildPlugin = (pluginOptions) => {
5
+ const port = pluginOptions?.port ?? 3e3;
6
+ const shutdownTimeoutMs = pluginOptions?.shutdownTimeoutMs;
7
+ return {
8
+ ...buildPlugin({
9
+ ssrTarget: "node",
10
+ entryContentBeforeHooks: [async (appName, options) => {
11
+ let code = "import { serveStatic } from '@hono/node-server/serve-static'\n";
12
+ code += serveStaticHook(appName, {
13
+ filePaths: options?.staticPaths,
14
+ root: pluginOptions?.staticRoot
15
+ });
16
+ return code;
17
+ }],
18
+ entryContentAfterHooks: [async (appName) => {
19
+ let code = "import { serve } from '@hono/node-server'\n";
20
+ if (shutdownTimeoutMs !== void 0) {
21
+ code += `const server = serve({ fetch: ${appName}.fetch, port: ${port.toString()} })\n`;
22
+ code += "const gracefulShutdown = () => {\n";
23
+ code += " server.close(() => process.exit(0))\n";
24
+ if (shutdownTimeoutMs > 0) code += ` setTimeout(() => process.exit(1), ${shutdownTimeoutMs}).unref()\n`;
25
+ code += "}\n";
26
+ code += "process.on('SIGINT', gracefulShutdown)\n";
27
+ code += "process.on('SIGTERM', gracefulShutdown)";
28
+ } else code += `serve({ fetch: ${appName}.fetch, port: ${port.toString()} })`;
29
+ return code;
30
+ }],
31
+ ...pluginOptions
32
+ }),
33
+ name: "@hono/vite-build/node"
34
+ };
35
+ };
36
+ //#endregion
37
+ export { nodeBuildPlugin as default };
@@ -0,0 +1,22 @@
1
+ import { BuildOptions } from "../../base.mjs";
2
+ import { VercelBuildConfigV3, VercelNodejsServerlessFunctionConfig } from "./types.mjs";
3
+ import { Plugin } from "vite";
4
+
5
+ //#region src/adapter/vercel/index.d.ts
6
+ type VercelSourceRoute = Extract<NonNullable<VercelBuildConfigV3['routes']>[number], {
7
+ src: string;
8
+ }>;
9
+ type VercelRouteConfig = Array<Omit<VercelSourceRoute, 'dest'> & {
10
+ dest?: string;
11
+ }>;
12
+ type VercelBuildOptions = {
13
+ vercel?: {
14
+ config?: VercelBuildConfigV3;
15
+ function?: Partial<VercelNodejsServerlessFunctionConfig>;
16
+ name?: string;
17
+ routes?: VercelRouteConfig;
18
+ };
19
+ } & Omit<BuildOptions, 'output' | 'outputDir'>;
20
+ declare const vercelBuildPlugin: (pluginOptions?: VercelBuildOptions) => Plugin;
21
+ //#endregion
22
+ export { VercelBuildOptions, vercelBuildPlugin as default };
@@ -0,0 +1,170 @@
1
+ import { getEntryContent } from "../../entry/index.mjs";
2
+ import { defaultOptions } from "../../base.mjs";
3
+ import { builtinModules } from "module";
4
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
5
+ import { resolve } from "node:path";
6
+ import { cp, writeFile } from "node:fs/promises";
7
+ //#region src/adapter/vercel/index.ts
8
+ const BUNDLE_NAME = "index.js";
9
+ const DEFAULT_FUNCTION_NAME = "__hono";
10
+ const VIRTUAL_ENTRY_PREFIX = "virtual:build-entry-module-vercel-";
11
+ const functionEntryHooks = {
12
+ entryContentAfterHooks: [() => "import { handle } from '@hono/node-server/vercel'"],
13
+ entryContentDefaultExportHook: (appName) => `export default handle(${appName})`
14
+ };
15
+ const configWriteQueues = /* @__PURE__ */ new Map();
16
+ const writeJSON = (path, data) => {
17
+ const dir = resolve(path, "..");
18
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
19
+ return writeFile(path, JSON.stringify(data));
20
+ };
21
+ const readJSON = (path) => {
22
+ if (!existsSync(path)) return;
23
+ return JSON.parse(readFileSync(path, "utf-8"));
24
+ };
25
+ const enqueueConfigWrite = async (key, writeTask) => {
26
+ const previousTask = configWriteQueues.get(key);
27
+ if (previousTask) await previousTask;
28
+ const nextTask = writeTask();
29
+ configWriteQueues.set(key, nextTask);
30
+ try {
31
+ await nextTask;
32
+ } finally {
33
+ if (configWriteQueues.get(key) === nextTask) configWriteQueues.delete(key);
34
+ }
35
+ };
36
+ const getRuntimeVersion = () => {
37
+ try {
38
+ const systemNodeVersion = process.versions.node.split(".")[0];
39
+ return `nodejs${Number(systemNodeVersion)}.x`;
40
+ } catch {
41
+ return "nodejs22.x";
42
+ }
43
+ };
44
+ const escapeRouteSegment = (value) => {
45
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
46
+ };
47
+ const getDefaultRoutePattern = (functionName) => {
48
+ return `^/${escapeRouteSegment(functionName)}(?:/.*)?$`;
49
+ };
50
+ const appendRouteIfMissing = (target, seen, route) => {
51
+ const key = JSON.stringify(route);
52
+ if (seen.has(key)) return;
53
+ seen.add(key);
54
+ target.push(route);
55
+ };
56
+ const getFunctionConfig = (config, functionConfig) => {
57
+ return {
58
+ ...functionConfig,
59
+ runtime: getRuntimeVersion(),
60
+ launcherType: "Nodejs",
61
+ handler: BUNDLE_NAME,
62
+ shouldAddHelpers: Boolean(functionConfig?.shouldAddHelpers),
63
+ shouldAddSourcemapSupport: Boolean(config.build.sourcemap),
64
+ supportsResponseStreaming: true
65
+ };
66
+ };
67
+ const getRoutesForFunction = (functionName, configuredRoutes) => {
68
+ if (configuredRoutes && configuredRoutes.length > 0) return configuredRoutes;
69
+ if (functionName === DEFAULT_FUNCTION_NAME) return [{
70
+ src: "/(.*)",
71
+ dest: `/${DEFAULT_FUNCTION_NAME}`
72
+ }];
73
+ return [{
74
+ src: getDefaultRoutePattern(functionName),
75
+ dest: `/${functionName}`
76
+ }];
77
+ };
78
+ const mergeVercelConfig = async (configPath, routesToAdd, configOverride, functionName) => {
79
+ const existingConfig = readJSON(configPath);
80
+ const mergedRoutes = [];
81
+ const seenRoutes = /* @__PURE__ */ new Set();
82
+ for (const route of existingConfig?.routes ?? []) appendRouteIfMissing(mergedRoutes, seenRoutes, route);
83
+ for (const route of configOverride?.routes ?? []) appendRouteIfMissing(mergedRoutes, seenRoutes, route);
84
+ appendRouteIfMissing(mergedRoutes, seenRoutes, { handle: "filesystem" });
85
+ for (const route of routesToAdd) appendRouteIfMissing(mergedRoutes, seenRoutes, {
86
+ ...route,
87
+ dest: route.dest ?? `/${functionName}`
88
+ });
89
+ await writeJSON(configPath, {
90
+ ...existingConfig,
91
+ ...configOverride,
92
+ version: 3,
93
+ routes: mergedRoutes
94
+ });
95
+ };
96
+ const copyStaticFiles = async (publicDirPath, outputDir) => {
97
+ if (!existsSync(publicDirPath)) return;
98
+ try {
99
+ await cp(publicDirPath, resolve(outputDir, "static"), {
100
+ recursive: true,
101
+ force: true
102
+ });
103
+ } catch (error) {
104
+ if (error.code !== "EEXIST") throw error;
105
+ }
106
+ };
107
+ const vercelBuildPlugin = (pluginOptions) => {
108
+ let config;
109
+ const functionName = pluginOptions?.vercel?.name ?? DEFAULT_FUNCTION_NAME;
110
+ if (!functionName) throw new Error("`vercel.name` is required and cannot be empty.");
111
+ const virtualEntryId = `${VIRTUAL_ENTRY_PREFIX}${functionName}`;
112
+ const resolvedVirtualEntryId = `\0${virtualEntryId}`;
113
+ return {
114
+ name: "@hono/vite-build/vercel",
115
+ apply: pluginOptions?.apply ?? defaultOptions.apply,
116
+ resolveId(id) {
117
+ if (id === virtualEntryId) return resolvedVirtualEntryId;
118
+ },
119
+ async load(id) {
120
+ if (id !== resolvedVirtualEntryId) return;
121
+ const entry = pluginOptions?.entry ?? defaultOptions.entry;
122
+ return await getEntryContent({
123
+ entry: Array.isArray(entry) ? entry : [entry],
124
+ entryContentBeforeHooks: pluginOptions?.entryContentBeforeHooks,
125
+ entryContentAfterHooks: pluginOptions?.entryContentAfterHooks ?? functionEntryHooks.entryContentAfterHooks,
126
+ entryContentDefaultExportHook: pluginOptions?.entryContentDefaultExportHook ?? functionEntryHooks.entryContentDefaultExportHook,
127
+ staticPaths: pluginOptions?.staticPaths,
128
+ preset: pluginOptions?.preset
129
+ });
130
+ },
131
+ configResolved: (resolvedConfig) => {
132
+ config = resolvedConfig;
133
+ },
134
+ config: async () => {
135
+ return {
136
+ ssr: {
137
+ external: pluginOptions?.external ?? defaultOptions.external,
138
+ noExternal: true,
139
+ target: "node"
140
+ },
141
+ build: {
142
+ outDir: ".vercel/output",
143
+ emptyOutDir: pluginOptions?.emptyOutDir ?? defaultOptions.emptyOutDir,
144
+ minify: pluginOptions?.minify ?? defaultOptions.minify,
145
+ ssr: true,
146
+ rollupOptions: {
147
+ external: [...builtinModules, /^node:/],
148
+ input: { [functionName]: virtualEntryId },
149
+ output: { entryFileNames: `functions/[name].func/${BUNDLE_NAME}` }
150
+ }
151
+ }
152
+ };
153
+ },
154
+ writeBundle: async () => {
155
+ const outputDir = resolve(config.root, config.build.outDir);
156
+ const functionDir = resolve(outputDir, "functions", `${functionName}.func`);
157
+ const configPath = resolve(outputDir, "config.json");
158
+ const publicDirPath = resolve(config.root, config.publicDir);
159
+ const routesToAdd = getRoutesForFunction(functionName, pluginOptions?.vercel?.routes);
160
+ const functionConfig = getFunctionConfig(config, pluginOptions?.vercel?.function);
161
+ await copyStaticFiles(publicDirPath, outputDir);
162
+ await Promise.all([writeJSON(resolve(functionDir, ".vc-config.json"), functionConfig), writeJSON(resolve(functionDir, "package.json"), { type: "module" })]);
163
+ await enqueueConfigWrite(configPath, async () => {
164
+ await mergeVercelConfig(configPath, routesToAdd, pluginOptions?.vercel?.config, functionName);
165
+ });
166
+ }
167
+ };
168
+ };
169
+ //#endregion
170
+ export { vercelBuildPlugin as default };