Skip to content

Commit bc49b88

Browse files
committed
feat: hono bff
1 parent 7899005 commit bc49b88

File tree

37 files changed

+1305
-13
lines changed

37 files changed

+1305
-13
lines changed

Diff for: .changeset/dirty-eels-refuse.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@modern-js/create-request': patch
3+
'@modern-js/plugin-express': patch
4+
'@modern-js/plugin-koa': patch
5+
'@modern-js/plugin-bff': patch
6+
'@modern-js/server-core': patch
7+
---
8+
9+
feat: bff supports hono runtime framework
10+
feat: bff 支持 hono 运行时框架

Diff for: packages/cli/plugin-bff/package.json

+12-1
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@
2121
"main": "./dist/cjs/cli.js",
2222
"module": "./dist/esm/cli.js",
2323
"exports": {
24-
"./types": "./types.d.ts",
2524
".": {
2625
"types": "./dist/types/cli.d.ts",
2726
"jsnext:source": "./src/cli.ts",
2827
"default": "./dist/cjs/cli.js"
2928
},
29+
"./types": "./types/index.d.ts",
30+
"./types/runtime": "./types/runtime.d.ts",
3031
"./cli": {
3132
"types": "./dist/types/cli.d.ts",
3233
"jsnext:source": "./src/cli.ts",
@@ -42,6 +43,11 @@
4243
"jsnext:source": "./src/loader.ts",
4344
"default": "./dist/cjs/loader.js"
4445
},
46+
"./runtime": {
47+
"types": "./dist/types/index.d.ts",
48+
"jsnext:source": "./src/runtime/index.ts",
49+
"default": "./dist/cjs/runtime/index.js"
50+
},
4551
"./runtime/create-request": {
4652
"types": "./dist/types/create-request/index.d.ts",
4753
"jsnext:source": "./src/runtime/create-request/index.ts",
@@ -78,6 +84,9 @@
7884
"@modern-js/server-core": "workspace:*",
7985
"@modern-js/server-utils": "workspace:*",
8086
"@modern-js/utils": "workspace:*",
87+
"type-is": "^1.6.18",
88+
"hono": "^3.12.2",
89+
"formidable": "^1.2.2",
8190
"@swc/helpers": "0.5.13"
8291
},
8392
"devDependencies": {
@@ -91,6 +100,8 @@
91100
"@types/babel__core": "^7.20.5",
92101
"@types/jest": "^29",
93102
"@types/node": "^14",
103+
"@types/type-is": "^1.6.3",
104+
"@types/formidable": "^1.2.3",
94105
"jest": "^29",
95106
"memfs": "^3.5.1",
96107
"ts-jest": "^29.1.0",

Diff for: packages/cli/plugin-bff/src/cli.ts

+21-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ const RUNTIME_CREATE_REQUEST = '@modern-js/plugin-bff/runtime/create-request';
1515
export const bffPlugin = (): CliPlugin<AppTools> => ({
1616
name: '@modern-js/plugin-bff',
1717
setup: api => {
18+
const useConfig = api.useConfigContext();
19+
20+
useConfig.bff ??= {};
21+
(useConfig.bff as any).runtimeFramework = 'hono';
22+
1823
const compileApi = async () => {
1924
const {
2025
appDirectory,
@@ -127,6 +132,18 @@ export const bffPlugin = (): CliPlugin<AppTools> => ({
127132

128133
return {
129134
config() {
135+
const useConfig = api.useConfigContext();
136+
const { bff } = useConfig ?? {};
137+
const isHono = (bff as any)?.runtimeFramework === 'hono';
138+
const isDev = process.env.NODE_ENV === 'development';
139+
const useLocalRuntime = isDev && !useConfig?.bff?.crossProject;
140+
141+
const runtimePath = isHono
142+
? useLocalRuntime
143+
? require.resolve('@modern-js/plugin-bff/runtime')
144+
: '@modern-js/plugin-bff/runtime'
145+
: undefined;
146+
130147
return {
131148
tools: {
132149
bundlerChain: (chain, { CHAIN_ID, isServer }) => {
@@ -183,6 +200,9 @@ export const bffPlugin = (): CliPlugin<AppTools> => ({
183200
},
184201
source: {
185202
moduleScopes: [`./${API_DIR}`, /create-request/],
203+
alias: runtimePath
204+
? { '@modern-js/runtime/server': runtimePath }
205+
: undefined,
186206
},
187207
};
188208
},
@@ -207,7 +227,7 @@ export const bffPlugin = (): CliPlugin<AppTools> => ({
207227
isSSR: false,
208228
})) as ServerRoute[];
209229

210-
if (bff?.enableHandleWeb) {
230+
if ((bff as any).runtimeFramework !== 'hono' && bff?.enableHandleWeb) {
211231
return {
212232
routes: (
213233
routes.map(route => {
@@ -227,7 +247,6 @@ export const bffPlugin = (): CliPlugin<AppTools> => ({
227247
plugins.push({
228248
name: '@modern-js/plugin-bff/server',
229249
});
230-
231250
return { plugins };
232251
},
233252
async beforeDev() {

Diff for: packages/cli/plugin-bff/src/runtime/hono.ts

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import type { APIHandlerInfo } from '@modern-js/bff-core';
2+
import type { PluginAPI, ServerMiddleware } from '@modern-js/server-core';
3+
import { CustomServer } from '@modern-js/server-core';
4+
import { isProd } from '@modern-js/utils';
5+
import { Hono } from 'hono';
6+
import type { Context, MiddlewareHandler, Next } from 'hono';
7+
import createHonoRoutes from '../utils/createHonoRoutes';
8+
9+
const before = ['custom-server-hook', 'custom-server-middleware', 'render'];
10+
11+
type SF = (args: any) => void;
12+
13+
interface MiddlewareOptions {
14+
prefix: string;
15+
enableHandleWeb?: boolean;
16+
customMiddlewares: SF[];
17+
}
18+
19+
export class HonoRuntime {
20+
apiMiddleware: ServerMiddleware[] = [];
21+
apiServer: Hono | null = null;
22+
api: PluginAPI;
23+
isHono = true;
24+
constructor(api: PluginAPI) {
25+
this.api = api;
26+
}
27+
28+
setHandlers = async () => {
29+
if (!this.isHono) {
30+
return;
31+
}
32+
const { apiHandlerInfos } = this.api.useAppContext();
33+
34+
const honoHandlers = createHonoRoutes(apiHandlerInfos as APIHandlerInfo[]);
35+
this.apiMiddleware = honoHandlers.map(({ path, method, handler }) => ({
36+
name: `bff-api-${method}-${path}`,
37+
path,
38+
method,
39+
handler,
40+
order: 'post',
41+
before,
42+
}));
43+
};
44+
45+
registerApiRoutes = async () => {
46+
if (!this.isHono) {
47+
return;
48+
}
49+
this.apiServer = new Hono();
50+
this.apiMiddleware.forEach(({ path = '*', method = 'all', handler }) => {
51+
const handlers = this.wrapInArray(handler);
52+
this.apiServer?.[method](path, ...handlers);
53+
});
54+
};
55+
56+
registerMiddleware = async (options: MiddlewareOptions) => {
57+
const { prefix, enableHandleWeb, customMiddlewares } = options;
58+
59+
const { bff } = this.api.useConfigContext();
60+
if ((bff as any)?.runtimeFramework !== 'hono') {
61+
this.isHono = false;
62+
return;
63+
}
64+
65+
const { serverBase } = this.api.useAppContext();
66+
const runner = this.api.useHookRunners();
67+
const { distDirectory: pwd, middlewares: globalMiddlewares } =
68+
this.api.useAppContext();
69+
70+
const customServer = new CustomServer(runner, serverBase!, pwd);
71+
72+
const customServerMiddleware = await customServer.getServerMiddleware();
73+
74+
customServerMiddleware &&
75+
globalMiddlewares.push({
76+
name: 'bff-custom-server-middleware',
77+
path: `${prefix}/*`,
78+
handler: customServerMiddleware,
79+
order: 'post',
80+
before,
81+
});
82+
83+
(customMiddlewares as unknown as MiddlewareHandler[]).forEach(handler => {
84+
globalMiddlewares.push({
85+
name: 'bff-custom-middleware',
86+
handler: (c: Context, next: Next) => {
87+
if (c.req.path.startsWith(prefix || '/api') || enableHandleWeb) {
88+
return handler(c, next);
89+
} else {
90+
return next();
91+
}
92+
},
93+
order: 'post',
94+
before,
95+
});
96+
});
97+
98+
await this.setHandlers();
99+
100+
if (isProd()) {
101+
globalMiddlewares.push(...this.apiMiddleware);
102+
} else {
103+
await this.registerApiRoutes();
104+
/** api hot update */
105+
const dynamicApiMiddleware: ServerMiddleware = {
106+
name: 'dynamic-bff-handler',
107+
path: `${prefix}/*`,
108+
method: 'all',
109+
order: 'post',
110+
before,
111+
handler: async (c: Context, next: Next) => {
112+
if (this.apiServer) {
113+
const response = await this.apiServer.fetch(c.req.raw, c.env);
114+
115+
if (response.status !== 404) {
116+
return new Response(response.body, response);
117+
}
118+
}
119+
await next();
120+
},
121+
};
122+
globalMiddlewares.push(dynamicApiMiddleware);
123+
}
124+
};
125+
wrapInArray(
126+
handler: MiddlewareHandler[] | MiddlewareHandler,
127+
): MiddlewareHandler[] {
128+
if (Array.isArray(handler)) {
129+
return handler;
130+
} else {
131+
return [handler];
132+
}
133+
}
134+
}

Diff for: packages/cli/plugin-bff/src/runtime/hook.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import type { Context, Next } from 'hono';
2+
3+
export const hook = (
4+
attacher: (c: Context, next: Next) => void | Promise<void>,
5+
) => attacher;

Diff for: packages/cli/plugin-bff/src/runtime/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from '@modern-js/bff-core';
2+
export { useContext } from '@modern-js/server-core';
3+
export { hook } from './hook';
4+
export * from './operators';

Diff for: packages/cli/plugin-bff/src/runtime/operators.ts

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type { Operator } from '@modern-js/bff-core';
2+
import { useContext } from '@modern-js/server-core';
3+
import type { Context, Next } from 'hono';
4+
5+
export type EndFunction = ((func: (res: Response) => void) => void) &
6+
((data: unknown) => void);
7+
8+
type MaybeAsync<T> = T | Promise<T>;
9+
type PipeFunction<T> = (
10+
value: T,
11+
end: EndFunction,
12+
) => MaybeAsync<void> | MaybeAsync<T>;
13+
14+
export const Pipe = <T>(func: PipeFunction<T>): Operator<T> => {
15+
return {
16+
name: 'pipe',
17+
async execute(executeHelper, next) {
18+
const { inputs } = executeHelper;
19+
const ctx = useContext();
20+
const { res } = ctx;
21+
if (typeof func === 'function') {
22+
let isPiped = true;
23+
const end: EndFunction = value => {
24+
isPiped = false;
25+
if (typeof value === 'function') {
26+
value(res);
27+
return;
28+
}
29+
return value;
30+
};
31+
const output = await func(inputs, end);
32+
if (!isPiped) {
33+
if (output) {
34+
return (executeHelper.result = output);
35+
} else {
36+
return;
37+
}
38+
}
39+
executeHelper.inputs = output as T;
40+
await next();
41+
}
42+
},
43+
};
44+
};
45+
46+
export type Pipe = typeof Pipe;
47+
48+
export const Middleware = (
49+
middleware: (c: Context, next: Next) => void,
50+
): Operator<void> => {
51+
return {
52+
name: 'middleware',
53+
metadata(helper) {
54+
const middlewares = helper.getMetadata('pipe') || [];
55+
middlewares.push(middleware);
56+
helper.setMetadata('middleware', middlewares);
57+
},
58+
};
59+
};
60+
61+
export type Middleware = typeof Middleware;

Diff for: packages/cli/plugin-bff/src/server.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import path from 'path';
22
import { ApiRouter } from '@modern-js/bff-core';
3-
import type { ServerPlugin } from '@modern-js/server-core';
3+
import type { PluginAPI, ServerPlugin } from '@modern-js/server-core';
44
import type { ServerNodeMiddleware } from '@modern-js/server-core/node';
55
import {
66
API_DIR,
@@ -9,6 +9,7 @@ import {
99
requireExistModule,
1010
} from '@modern-js/utils';
1111
import { API_APP_NAME } from './constants';
12+
import { HonoRuntime } from './runtime/hono';
1213

1314
type SF = (args: any) => void;
1415
class Storage {
@@ -32,6 +33,9 @@ export default (): ServerPlugin => ({
3233
const transformAPI = createTransformAPI(storage);
3334
let apiAppPath = '';
3435
let apiRouter: ApiRouter;
36+
37+
const honoRuntime = new HonoRuntime(api);
38+
3539
return {
3640
async prepare() {
3741
const appContext = api.useAppContext();
@@ -101,6 +105,12 @@ export default (): ServerPlugin => ({
101105
],
102106
});
103107
}
108+
109+
honoRuntime.registerMiddleware({
110+
customMiddlewares: middlewares,
111+
prefix,
112+
enableHandleWeb,
113+
});
104114
},
105115
async reset({ event }) {
106116
storage.reset();
@@ -123,6 +133,9 @@ export default (): ServerPlugin => ({
123133
...appContext,
124134
apiHandlerInfos,
125135
});
136+
137+
await honoRuntime.setHandlers();
138+
await honoRuntime.registerApiRoutes();
126139
}
127140
},
128141

0 commit comments

Comments
 (0)