Skip to content

Commit f6b81dd

Browse files
keepviewyimingjfe
andauthored
feat: hono bff (#6937)
Co-authored-by: Ming <527990618@163.com>
1 parent 80e92dc commit f6b81dd

File tree

52 files changed

+1258
-28
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1258
-28
lines changed

.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 运行时框架

.changeset/new-wasps-accept.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@modern-js/plugin-express': patch
3+
'@modern-js/app-tools': patch
4+
'@modern-js/types': patch
5+
'@modern-js/server-core': patch
6+
---
7+
8+
refactor: avoid only one of "req" and "request" has a request body
9+
refactor: 避免 req 和 request 只有一个有请求体

packages/cli/core/src/types/context.ts

+5
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,9 @@ export interface IAppContext {
8383
* @private
8484
*/
8585
partialsByEntrypoint?: Record<string, HtmlPartials>;
86+
/**
87+
* Identification for bff runtime framework
88+
* @private
89+
*/
90+
bffRuntimeFramework?: string;
8691
}

packages/cli/plugin-bff/package.json

+12-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@
4242
"jsnext:source": "./src/loader.ts",
4343
"default": "./dist/cjs/loader.js"
4444
},
45+
"./hono": {
46+
"types": "./dist/types/runtime/hono/index.d.ts",
47+
"jsnext:source": "./src/runtime/hono/index.ts",
48+
"default": "./dist/cjs/runtime/hono/index.js"
49+
},
4550
"./runtime/create-request": {
4651
"types": "./dist/types/create-request/index.d.ts",
4752
"jsnext:source": "./src/runtime/create-request/index.ts",
@@ -59,6 +64,9 @@
5964
"server": [
6065
"./dist/types/server.d.ts"
6166
],
67+
"hono": [
68+
"./dist/types/runtime/hono/index.d.ts"
69+
],
6270
"runtime/create-request": [
6371
"./dist/types/runtime/create-request/index.d.ts"
6472
]
@@ -78,6 +86,7 @@
7886
"@modern-js/server-core": "workspace:*",
7987
"@modern-js/server-utils": "workspace:*",
8088
"@modern-js/utils": "workspace:*",
89+
"type-is": "^1.6.18",
8190
"@swc/helpers": "0.5.13"
8291
},
8392
"devDependencies": {
@@ -92,11 +101,13 @@
92101
"@types/babel__core": "^7.20.5",
93102
"@types/jest": "^29",
94103
"@types/node": "^14",
104+
"@types/type-is": "^1.6.3",
95105
"jest": "^29",
96106
"memfs": "^3.5.1",
97107
"ts-jest": "^29.1.0",
98108
"typescript": "^5",
99-
"webpack": "^5.98.0"
109+
"webpack": "^5.98.0",
110+
"zod": "^3.22.3"
100111
},
101112
"sideEffects": false,
102113
"publishConfig": {

packages/cli/plugin-bff/src/cli.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import runtimeGenerator from './utils/runtimeGenerator';
1111
const DEFAULT_API_PREFIX = '/api';
1212
const TS_CONFIG_FILENAME = 'tsconfig.json';
1313
const RUNTIME_CREATE_REQUEST = '@modern-js/plugin-bff/runtime/create-request';
14+
const RUNTIME_HONO = '@modern-js/plugin-bff/hono';
1415

1516
export const bffPlugin = (): CliPlugin<AppTools> => ({
1617
name: '@modern-js/plugin-bff',
@@ -125,8 +126,17 @@ export const bffPlugin = (): CliPlugin<AppTools> => ({
125126
}
126127
};
127128

129+
const isHono = () => {
130+
const { bffRuntimeFramework } = api.useAppContext();
131+
return bffRuntimeFramework === 'hono';
132+
};
133+
128134
return {
129135
config() {
136+
const honoRuntimePath = isHono()
137+
? { [RUNTIME_HONO]: RUNTIME_HONO }
138+
: undefined;
139+
130140
return {
131141
tools: {
132142
bundlerChain: (chain, { CHAIN_ID, isServer }) => {
@@ -184,6 +194,9 @@ export const bffPlugin = (): CliPlugin<AppTools> => ({
184194
source: {
185195
moduleScopes: [`./${API_DIR}`, /create-request/],
186196
},
197+
output: {
198+
externals: honoRuntimePath,
199+
},
187200
};
188201
},
189202
modifyServerRoutes({ routes }) {
@@ -207,7 +220,7 @@ export const bffPlugin = (): CliPlugin<AppTools> => ({
207220
isSSR: false,
208221
})) as ServerRoute[];
209222

210-
if (bff?.enableHandleWeb) {
223+
if (!isHono() && bff?.enableHandleWeb) {
211224
return {
212225
routes: (
213226
routes.map(route => {
@@ -227,7 +240,6 @@ export const bffPlugin = (): CliPlugin<AppTools> => ({
227240
plugins.push({
228241
name: '@modern-js/plugin-bff/server',
229242
});
230-
231243
return { plugins };
232244
},
233245
async beforeDev() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import type { APIHandlerInfo } from '@modern-js/bff-core';
2+
import type {
3+
Context,
4+
MiddlewareHandler,
5+
Next,
6+
PluginAPI,
7+
ServerMiddleware,
8+
} from '@modern-js/server-core';
9+
import { Hono } from '@modern-js/server-core';
10+
11+
import { isProd } from '@modern-js/utils';
12+
import createHonoRoutes from '../../utils/createHonoRoutes';
13+
14+
const before = ['custom-server-hook', 'custom-server-middleware', 'render'];
15+
16+
interface MiddlewareOptions {
17+
prefix: string;
18+
enableHandleWeb?: boolean;
19+
}
20+
21+
export class HonoAdapter {
22+
apiMiddleware: ServerMiddleware[] = [];
23+
apiServer: Hono | null = null;
24+
api: PluginAPI;
25+
isHono = true;
26+
constructor(api: PluginAPI) {
27+
this.api = api;
28+
}
29+
30+
setHandlers = async () => {
31+
if (!this.isHono) {
32+
return;
33+
}
34+
const { apiHandlerInfos } = this.api.useAppContext();
35+
36+
const honoHandlers = createHonoRoutes(apiHandlerInfos as APIHandlerInfo[]);
37+
this.apiMiddleware = honoHandlers.map(({ path, method, handler }) => ({
38+
name: 'hono-bff-api',
39+
path,
40+
method,
41+
handler,
42+
order: 'post',
43+
before,
44+
}));
45+
};
46+
47+
registerApiRoutes = async () => {
48+
if (!this.isHono) {
49+
return;
50+
}
51+
this.apiServer = new Hono();
52+
this.apiMiddleware.forEach(({ path = '*', method = 'all', handler }) => {
53+
const handlers = this.wrapInArray(handler);
54+
this.apiServer?.[method](path, ...handlers);
55+
});
56+
};
57+
58+
registerMiddleware = async (options: MiddlewareOptions) => {
59+
const { prefix } = options;
60+
61+
const { bffRuntimeFramework } = this.api.useAppContext();
62+
63+
if (bffRuntimeFramework !== 'hono') {
64+
this.isHono = false;
65+
return;
66+
}
67+
68+
const { middlewares: globalMiddlewares } = this.api.useAppContext();
69+
70+
await this.setHandlers();
71+
72+
if (isProd()) {
73+
globalMiddlewares.push(...this.apiMiddleware);
74+
} else {
75+
await this.registerApiRoutes();
76+
/** api hot update */
77+
const dynamicApiMiddleware: ServerMiddleware = {
78+
name: 'dynamic-bff-handler',
79+
path: `${prefix}/*`,
80+
method: 'all',
81+
order: 'post',
82+
before,
83+
handler: async (c: Context, next: Next) => {
84+
if (this.apiServer) {
85+
const response = await this.apiServer.fetch(
86+
c.req as unknown as Request,
87+
c.env,
88+
);
89+
90+
if (response.status !== 404) {
91+
return new Response(response.body, response);
92+
}
93+
}
94+
await next();
95+
},
96+
};
97+
globalMiddlewares.push(dynamicApiMiddleware);
98+
}
99+
};
100+
wrapInArray(
101+
handler: MiddlewareHandler[] | MiddlewareHandler,
102+
): MiddlewareHandler[] {
103+
if (Array.isArray(handler)) {
104+
return handler;
105+
} else {
106+
return [handler];
107+
}
108+
}
109+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from '@modern-js/bff-core';
2+
export { useHonoContext } from '@modern-js/server-core';
3+
export * from './operators';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import type { Operator } from '@modern-js/bff-core';
2+
import {
3+
type Context,
4+
type Next,
5+
useHonoContext,
6+
} from '@modern-js/server-core';
7+
8+
export type EndFunction = ((func: (res: Response) => void) => void) &
9+
((data: unknown) => void);
10+
11+
type MaybeAsync<T> = T | Promise<T>;
12+
type PipeFunction<T> = (
13+
value: T,
14+
end: EndFunction,
15+
) => MaybeAsync<void> | MaybeAsync<T>;
16+
17+
export const Pipe = <T>(func: PipeFunction<T>): Operator<T> => {
18+
return {
19+
name: 'pipe',
20+
async execute(executeHelper, next) {
21+
const { inputs } = executeHelper;
22+
const ctx = useHonoContext();
23+
const { res } = ctx;
24+
if (typeof func === 'function') {
25+
let isPiped = true;
26+
const end: EndFunction = value => {
27+
isPiped = false;
28+
if (typeof value === 'function') {
29+
value(res);
30+
return;
31+
}
32+
return value;
33+
};
34+
const output = await func(inputs, end);
35+
if (!isPiped) {
36+
if (output) {
37+
return (executeHelper.result = output);
38+
} else {
39+
return;
40+
}
41+
}
42+
executeHelper.inputs = output as T;
43+
await next();
44+
}
45+
},
46+
};
47+
};
48+
49+
export type Pipe = typeof Pipe;
50+
51+
export const Middleware = (
52+
middleware: (c: Context, next: Next) => void,
53+
): Operator<void> => {
54+
return {
55+
name: 'middleware',
56+
metadata(helper) {
57+
const middlewares = helper.getMetadata('pipe') || [];
58+
middlewares.push(middleware);
59+
helper.setMetadata('middleware', middlewares);
60+
},
61+
};
62+
};
63+
64+
export type Middleware = typeof Middleware;

packages/cli/plugin-bff/src/server.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import {
88
isWebOnly,
99
requireExistModule,
1010
} from '@modern-js/utils';
11+
import { isFunction } from '@modern-js/utils';
1112
import { API_APP_NAME } from './constants';
13+
import { HonoAdapter } from './runtime/hono/adapter';
1214

1315
type SF = (args: any) => void;
1416
class Storage {
@@ -32,6 +34,9 @@ export default (): ServerPluginLegacy => ({
3234
const transformAPI = createTransformAPI(storage);
3335
let apiAppPath = '';
3436
let apiRouter: ApiRouter;
37+
38+
const honoAdapter = new HonoAdapter(api);
39+
3540
return {
3641
async prepare() {
3742
const appContext = api.useAppContext();
@@ -83,7 +88,7 @@ export default (): ServerPluginLegacy => ({
8388
);
8489
}
8590

86-
if (handler) {
91+
if (handler && isFunction(handler)) {
8792
globalMiddlewares.push({
8893
name: 'bind-bff',
8994
handler: (c, next) => {
@@ -101,6 +106,11 @@ export default (): ServerPluginLegacy => ({
101106
],
102107
});
103108
}
109+
110+
honoAdapter.registerMiddleware({
111+
prefix,
112+
enableHandleWeb,
113+
});
104114
},
105115
async reset({ event }) {
106116
storage.reset();
@@ -123,6 +133,9 @@ export default (): ServerPluginLegacy => ({
123133
...appContext,
124134
apiHandlerInfos,
125135
});
136+
137+
await honoAdapter.setHandlers();
138+
await honoAdapter.registerApiRoutes();
126139
}
127140
},
128141

0 commit comments

Comments
 (0)