@modern-js/plugin-bff 2.69.7 → 3.0.0-alpha.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.
- package/dist/cjs/cli.js +256 -272
- package/dist/cjs/constants.js +43 -34
- package/dist/cjs/index.js +55 -19
- package/dist/cjs/loader.js +69 -65
- package/dist/cjs/runtime/create-request/index.js +39 -29
- package/dist/cjs/runtime/hono/adapter.js +128 -126
- package/dist/cjs/runtime/hono/index.js +78 -30
- package/dist/cjs/runtime/hono/operators.js +64 -67
- package/dist/cjs/server.js +159 -165
- package/dist/cjs/utils/clientGenerator.js +204 -206
- package/dist/cjs/utils/createHonoRoutes.js +128 -144
- package/dist/cjs/utils/crossProjectApiPlugin.js +91 -81
- package/dist/cjs/utils/pluginGenerator.js +66 -54
- package/dist/cjs/utils/runtimeGenerator.js +67 -45
- package/dist/esm/cli.mjs +214 -0
- package/dist/esm/constants.mjs +11 -0
- package/dist/esm/loader.mjs +39 -0
- package/dist/esm/runtime/create-request/{index.js → index.mjs} +1 -5
- package/dist/esm/runtime/hono/adapter.mjs +95 -0
- package/dist/{esm-node/runtime/hono/index.js → esm/runtime/hono/index.mjs} +2 -4
- package/dist/esm/runtime/hono/operators.mjs +31 -0
- package/dist/esm/server.mjs +122 -0
- package/dist/esm/utils/clientGenerator.mjs +175 -0
- package/dist/esm/utils/createHonoRoutes.mjs +91 -0
- package/dist/esm/utils/crossProjectApiPlugin.mjs +34 -0
- package/dist/esm/utils/pluginGenerator.mjs +29 -0
- package/dist/esm/utils/runtimeGenerator.mjs +43 -0
- package/dist/esm-node/cli.mjs +214 -0
- package/dist/esm-node/constants.mjs +11 -0
- package/dist/esm-node/index.mjs +1 -0
- package/dist/esm-node/loader.mjs +39 -0
- package/dist/esm-node/runtime/create-request/{index.js → index.mjs} +1 -5
- package/dist/esm-node/runtime/hono/adapter.mjs +95 -0
- package/dist/{esm/runtime/hono/index.js → esm-node/runtime/hono/index.mjs} +3 -5
- package/dist/esm-node/runtime/hono/operators.mjs +31 -0
- package/dist/esm-node/server.mjs +122 -0
- package/dist/esm-node/utils/clientGenerator.mjs +175 -0
- package/dist/esm-node/utils/createHonoRoutes.mjs +91 -0
- package/dist/esm-node/utils/crossProjectApiPlugin.mjs +34 -0
- package/dist/esm-node/utils/pluginGenerator.mjs +29 -0
- package/dist/esm-node/utils/runtimeGenerator.mjs +43 -0
- package/dist/types/loader.d.ts +2 -2
- package/dist/types/runtime/hono/adapter.d.ts +3 -3
- package/dist/types/server.d.ts +2 -2
- package/dist/types/utils/runtimeGenerator.d.ts +2 -1
- package/package.json +55 -33
- package/rslib.config.mts +4 -0
- package/dist/cjs/helper.js +0 -48
- package/dist/esm/cli.js +0 -425
- package/dist/esm/constants.js +0 -14
- package/dist/esm/helper.js +0 -13
- package/dist/esm/index.js +0 -1
- package/dist/esm/loader.js +0 -75
- package/dist/esm/runtime/hono/adapter.js +0 -243
- package/dist/esm/runtime/hono/operators.js +0 -79
- package/dist/esm/server.js +0 -258
- package/dist/esm/utils/clientGenerator.js +0 -517
- package/dist/esm/utils/createHonoRoutes.js +0 -319
- package/dist/esm/utils/crossProjectApiPlugin.js +0 -49
- package/dist/esm/utils/pluginGenerator.js +0 -94
- package/dist/esm/utils/runtimeGenerator.js +0 -55
- package/dist/esm-node/cli.js +0 -246
- package/dist/esm-node/constants.js +0 -14
- package/dist/esm-node/helper.js +0 -14
- package/dist/esm-node/loader.js +0 -49
- package/dist/esm-node/runtime/hono/adapter.js +0 -103
- package/dist/esm-node/runtime/hono/operators.js +0 -46
- package/dist/esm-node/server.js +0 -142
- package/dist/esm-node/utils/clientGenerator.js +0 -192
- package/dist/esm-node/utils/createHonoRoutes.js +0 -120
- package/dist/esm-node/utils/crossProjectApiPlugin.js +0 -47
- package/dist/esm-node/utils/pluginGenerator.js +0 -31
- package/dist/esm-node/utils/runtimeGenerator.js +0 -35
- package/dist/types/helper.d.ts +0 -2
- package/types.d.ts +0 -3
- /package/dist/{esm-node/index.js → esm/index.mjs} +0 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Hono } from "@modern-js/server-core";
|
|
2
|
+
import { isProd, logger } from "@modern-js/utils";
|
|
3
|
+
import createHonoRoutes from "../../utils/createHonoRoutes.mjs";
|
|
4
|
+
const before = [
|
|
5
|
+
'custom-server-hook',
|
|
6
|
+
'custom-server-middleware',
|
|
7
|
+
'render'
|
|
8
|
+
];
|
|
9
|
+
class HonoAdapter {
|
|
10
|
+
wrapInArray(handler) {
|
|
11
|
+
if (Array.isArray(handler)) return handler;
|
|
12
|
+
return [
|
|
13
|
+
handler
|
|
14
|
+
];
|
|
15
|
+
}
|
|
16
|
+
constructor(api){
|
|
17
|
+
this.apiMiddleware = [];
|
|
18
|
+
this.apiServer = null;
|
|
19
|
+
this.isHono = true;
|
|
20
|
+
this.setHandlers = async ()=>{
|
|
21
|
+
if (!this.isHono) return;
|
|
22
|
+
const { apiHandlerInfos } = this.api.getServerContext();
|
|
23
|
+
const honoHandlers = createHonoRoutes(apiHandlerInfos);
|
|
24
|
+
this.apiMiddleware = honoHandlers.map(({ path, method, handler })=>({
|
|
25
|
+
name: 'hono-bff-api',
|
|
26
|
+
path,
|
|
27
|
+
method,
|
|
28
|
+
handler,
|
|
29
|
+
order: 'post',
|
|
30
|
+
before
|
|
31
|
+
}));
|
|
32
|
+
};
|
|
33
|
+
this.registerApiRoutes = async ()=>{
|
|
34
|
+
if (!this.isHono) return;
|
|
35
|
+
this.apiServer = new Hono();
|
|
36
|
+
this.apiMiddleware.forEach(({ path = '*', method = 'all', handler })=>{
|
|
37
|
+
const handlers = this.wrapInArray(handler);
|
|
38
|
+
if (0 === handlers.length) return;
|
|
39
|
+
const firstHandler = handlers[0];
|
|
40
|
+
const restHandlers = handlers.slice(1);
|
|
41
|
+
const m = method;
|
|
42
|
+
const server = this.apiServer;
|
|
43
|
+
if (!server) return;
|
|
44
|
+
const register = server[m];
|
|
45
|
+
register.call(server, path, firstHandler, ...restHandlers);
|
|
46
|
+
});
|
|
47
|
+
this.apiServer.onError(async (err, c)=>{
|
|
48
|
+
try {
|
|
49
|
+
const serverConfig = this.api.getServerConfig();
|
|
50
|
+
const onErrorHandler = serverConfig?.onError;
|
|
51
|
+
if (onErrorHandler) {
|
|
52
|
+
const result = await onErrorHandler(err, c);
|
|
53
|
+
if (result instanceof Response) return result;
|
|
54
|
+
} else logger.error(err);
|
|
55
|
+
} catch (configError) {
|
|
56
|
+
logger.error(`Error in serverConfig.onError handler: ${configError}`);
|
|
57
|
+
}
|
|
58
|
+
return c.json({
|
|
59
|
+
message: err?.message || '[BFF] Internal Server Error'
|
|
60
|
+
}, err?.status || 500);
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
this.registerMiddleware = async (options)=>{
|
|
64
|
+
const { prefix } = options;
|
|
65
|
+
const { bffRuntimeFramework } = this.api.getServerContext();
|
|
66
|
+
if ('hono' !== bffRuntimeFramework) {
|
|
67
|
+
this.isHono = false;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const { middlewares: globalMiddlewares } = this.api.getServerContext();
|
|
71
|
+
await this.setHandlers();
|
|
72
|
+
if (isProd()) globalMiddlewares.push(...this.apiMiddleware);
|
|
73
|
+
else {
|
|
74
|
+
await this.registerApiRoutes();
|
|
75
|
+
const dynamicApiMiddleware = {
|
|
76
|
+
name: 'dynamic-bff-handler',
|
|
77
|
+
path: `${prefix}/*`,
|
|
78
|
+
method: 'all',
|
|
79
|
+
order: 'post',
|
|
80
|
+
before,
|
|
81
|
+
handler: async (c, next)=>{
|
|
82
|
+
if (this.apiServer) {
|
|
83
|
+
const response = await this.apiServer.fetch(c.req.raw, c.env);
|
|
84
|
+
if (404 !== response.status) return new Response(response.body, response);
|
|
85
|
+
}
|
|
86
|
+
await next();
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
globalMiddlewares.push(dynamicApiMiddleware);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
this.api = api;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export { HonoAdapter };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useHonoContext } from "@modern-js/server-core";
|
|
2
|
+
const Pipe = (func)=>({
|
|
3
|
+
name: 'pipe',
|
|
4
|
+
async execute (executeHelper, next) {
|
|
5
|
+
const { inputs } = executeHelper;
|
|
6
|
+
const ctx = useHonoContext();
|
|
7
|
+
const { res } = ctx;
|
|
8
|
+
if ('function' == typeof func) {
|
|
9
|
+
let isPiped = true;
|
|
10
|
+
const end = (value)=>{
|
|
11
|
+
isPiped = false;
|
|
12
|
+
if ('function' == typeof value) return void value(res);
|
|
13
|
+
return value;
|
|
14
|
+
};
|
|
15
|
+
const output = await func(inputs, end);
|
|
16
|
+
if (!isPiped) if (output) return executeHelper.result = output;
|
|
17
|
+
else return;
|
|
18
|
+
executeHelper.inputs = output;
|
|
19
|
+
await next();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
const Middleware = (middleware)=>({
|
|
24
|
+
name: 'middleware',
|
|
25
|
+
metadata (helper) {
|
|
26
|
+
const middlewares = helper.getMetadata('pipe') || [];
|
|
27
|
+
middlewares.push(middleware);
|
|
28
|
+
helper.setMetadata('middleware', middlewares);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
export { Middleware, Pipe };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { ApiRouter } from "@modern-js/bff-core";
|
|
3
|
+
import { API_DIR, isFunction, isProd, isWebOnly, requireExistModule } from "@modern-js/utils";
|
|
4
|
+
import { API_APP_NAME } from "./constants.mjs";
|
|
5
|
+
import { HonoAdapter } from "./runtime/hono/adapter.mjs";
|
|
6
|
+
class Storage {
|
|
7
|
+
reset() {
|
|
8
|
+
this.middlewares = [];
|
|
9
|
+
}
|
|
10
|
+
constructor(){
|
|
11
|
+
this.middlewares = [];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const createTransformAPI = (storage)=>({
|
|
15
|
+
addMiddleware (fn) {
|
|
16
|
+
storage.middlewares.push(fn);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
const server = ()=>({
|
|
20
|
+
name: '@modern-js/plugin-bff',
|
|
21
|
+
setup: (api)=>{
|
|
22
|
+
const storage = new Storage();
|
|
23
|
+
const transformAPI = createTransformAPI(storage);
|
|
24
|
+
let apiAppPath = '';
|
|
25
|
+
let apiRouter;
|
|
26
|
+
const honoAdapter = new HonoAdapter(api);
|
|
27
|
+
api.onPrepare(async ()=>{
|
|
28
|
+
const appContext = api.getServerContext();
|
|
29
|
+
const { appDirectory, distDirectory, render } = appContext;
|
|
30
|
+
const root = isProd() ? distDirectory : appDirectory;
|
|
31
|
+
const apiPath = path.resolve(root || process.cwd(), API_DIR);
|
|
32
|
+
apiAppPath = path.resolve(apiPath, API_APP_NAME);
|
|
33
|
+
const apiMod = await requireExistModule(apiAppPath);
|
|
34
|
+
if (apiMod && 'function' == typeof apiMod) apiMod(transformAPI);
|
|
35
|
+
const { middlewares } = storage;
|
|
36
|
+
api.updateServerContext({
|
|
37
|
+
...appContext,
|
|
38
|
+
apiMiddlewares: middlewares
|
|
39
|
+
});
|
|
40
|
+
const config = api.getServerConfig();
|
|
41
|
+
const prefix = config?.bff?.prefix || '/api';
|
|
42
|
+
const enableHandleWeb = config?.bff?.enableHandleWeb;
|
|
43
|
+
const httpMethodDecider = config?.bff?.httpMethodDecider;
|
|
44
|
+
const { distDirectory: pwd, middlewares: globalMiddlewares } = api.getServerContext();
|
|
45
|
+
const webOnly = await isWebOnly();
|
|
46
|
+
let handler;
|
|
47
|
+
if (webOnly) handler = async (c, next)=>{
|
|
48
|
+
c.body('');
|
|
49
|
+
await next();
|
|
50
|
+
};
|
|
51
|
+
else {
|
|
52
|
+
const runner = api.getHooks();
|
|
53
|
+
const renderHandler = enableHandleWeb ? render : null;
|
|
54
|
+
handler = await runner.prepareApiServer.call({
|
|
55
|
+
pwd: pwd,
|
|
56
|
+
prefix,
|
|
57
|
+
render: renderHandler,
|
|
58
|
+
httpMethodDecider
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (handler && isFunction(handler)) globalMiddlewares.push({
|
|
62
|
+
name: 'bind-bff',
|
|
63
|
+
handler: (c, next)=>{
|
|
64
|
+
if (!c.req.path.startsWith(prefix) && !enableHandleWeb) return next();
|
|
65
|
+
return handler(c, next);
|
|
66
|
+
},
|
|
67
|
+
order: 'post',
|
|
68
|
+
before: [
|
|
69
|
+
'custom-server-hook',
|
|
70
|
+
'custom-server-middleware',
|
|
71
|
+
'render'
|
|
72
|
+
]
|
|
73
|
+
});
|
|
74
|
+
honoAdapter.registerMiddleware({
|
|
75
|
+
prefix,
|
|
76
|
+
enableHandleWeb
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
api.onReset(async ({ event })=>{
|
|
80
|
+
storage.reset();
|
|
81
|
+
const appContext = api.getServerContext();
|
|
82
|
+
const newApiModule = await requireExistModule(apiAppPath);
|
|
83
|
+
if (newApiModule && 'function' == typeof newApiModule) newApiModule(transformAPI);
|
|
84
|
+
const { middlewares } = storage;
|
|
85
|
+
api.updateServerContext({
|
|
86
|
+
...appContext,
|
|
87
|
+
apiMiddlewares: middlewares
|
|
88
|
+
});
|
|
89
|
+
if ('file-change' === event.type) {
|
|
90
|
+
const apiHandlerInfos = await apiRouter.getApiHandlers();
|
|
91
|
+
const appContext = api.getServerContext();
|
|
92
|
+
api.updateServerContext({
|
|
93
|
+
...appContext,
|
|
94
|
+
apiHandlerInfos
|
|
95
|
+
});
|
|
96
|
+
await honoAdapter.setHandlers();
|
|
97
|
+
await honoAdapter.registerApiRoutes();
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
api.prepareApiServer(async (input, next)=>{
|
|
101
|
+
const { pwd, prefix, httpMethodDecider } = input;
|
|
102
|
+
const apiDir = path.resolve(pwd, API_DIR);
|
|
103
|
+
const appContext = api.getServerContext();
|
|
104
|
+
const { apiDirectory, lambdaDirectory } = appContext;
|
|
105
|
+
apiRouter = new ApiRouter({
|
|
106
|
+
appDir: pwd,
|
|
107
|
+
apiDir: apiDirectory || apiDir,
|
|
108
|
+
lambdaDir: lambdaDirectory,
|
|
109
|
+
prefix,
|
|
110
|
+
httpMethodDecider
|
|
111
|
+
});
|
|
112
|
+
const apiHandlerInfos = await apiRouter.getApiHandlers();
|
|
113
|
+
api.updateServerContext({
|
|
114
|
+
...appContext,
|
|
115
|
+
apiRouter,
|
|
116
|
+
apiHandlerInfos
|
|
117
|
+
});
|
|
118
|
+
return next(input);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
export { server as default };
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { generateClient } from "@modern-js/bff-core";
|
|
3
|
+
import { fs, logger } from "@modern-js/utils";
|
|
4
|
+
const API_DIR = 'api';
|
|
5
|
+
const PLUGIN_DIR = 'plugin';
|
|
6
|
+
const RUNTIME_DIR = 'runtime';
|
|
7
|
+
const CLIENT_DIR = 'client';
|
|
8
|
+
const EXPORT_PREFIX = `./${API_DIR}/`;
|
|
9
|
+
const TYPE_PREFIX = `${API_DIR}/`;
|
|
10
|
+
const toPosixPath = (p)=>p.replace(/\\/g, '/');
|
|
11
|
+
const posixJoin = (...args)=>toPosixPath(path.join(...args));
|
|
12
|
+
async function readDirectoryFiles(appDirectory, directory, relativeDistPath) {
|
|
13
|
+
const filesList = [];
|
|
14
|
+
async function readFiles(currentPath) {
|
|
15
|
+
const entries = await fs.readdir(currentPath, {
|
|
16
|
+
withFileTypes: true
|
|
17
|
+
});
|
|
18
|
+
for (const entry of entries){
|
|
19
|
+
if ('_app.ts' === entry.name) continue;
|
|
20
|
+
const resourcePath = path.join(currentPath, entry.name);
|
|
21
|
+
if (entry.isDirectory()) await readFiles(resourcePath);
|
|
22
|
+
else {
|
|
23
|
+
const source = await fs.readFile(resourcePath, 'utf8');
|
|
24
|
+
const relativePath = path.relative(directory, resourcePath);
|
|
25
|
+
const parsedPath = path.parse(relativePath);
|
|
26
|
+
const targetDir = posixJoin(`./${relativeDistPath}/${CLIENT_DIR}`, parsedPath.dir, `${parsedPath.name}.js`);
|
|
27
|
+
const name = parsedPath.name;
|
|
28
|
+
const absTargetDir = path.resolve(targetDir);
|
|
29
|
+
const relativePathFromAppDirectory = path.relative(appDirectory, currentPath);
|
|
30
|
+
const typesFilePath = posixJoin(`./${relativeDistPath}`, relativePathFromAppDirectory, `${name}.d.ts`);
|
|
31
|
+
const relativeTargetDistDir = `./${typesFilePath}`;
|
|
32
|
+
const exportKey = toPosixPath(path.join(parsedPath.dir, name));
|
|
33
|
+
filesList.push({
|
|
34
|
+
resourcePath,
|
|
35
|
+
source,
|
|
36
|
+
targetDir,
|
|
37
|
+
name,
|
|
38
|
+
absTargetDir,
|
|
39
|
+
relativeTargetDistDir,
|
|
40
|
+
exportKey
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
await readFiles(directory);
|
|
46
|
+
return filesList;
|
|
47
|
+
}
|
|
48
|
+
function mergePackageJson(packageJson, files, typesVersion, exports) {
|
|
49
|
+
packageJson.files = [
|
|
50
|
+
...new Set([
|
|
51
|
+
...packageJson.files || [],
|
|
52
|
+
...files
|
|
53
|
+
])
|
|
54
|
+
];
|
|
55
|
+
packageJson.typesVersions ??= {};
|
|
56
|
+
const starTypes = packageJson.typesVersions['*'] || {};
|
|
57
|
+
Object.keys(starTypes).forEach((k)=>k.startsWith(TYPE_PREFIX) && delete starTypes[k]);
|
|
58
|
+
packageJson.typesVersions['*'] = {
|
|
59
|
+
...starTypes,
|
|
60
|
+
...typesVersion['*'] || {}
|
|
61
|
+
};
|
|
62
|
+
packageJson.exports ??= {};
|
|
63
|
+
Object.keys(packageJson.exports).forEach((k)=>k.startsWith(EXPORT_PREFIX) && delete packageJson.exports[k]);
|
|
64
|
+
Object.assign(packageJson.exports, exports);
|
|
65
|
+
}
|
|
66
|
+
async function writeTargetFile(absTargetDir, content) {
|
|
67
|
+
await fs.mkdir(path.dirname(absTargetDir), {
|
|
68
|
+
recursive: true
|
|
69
|
+
});
|
|
70
|
+
await fs.writeFile(absTargetDir, content);
|
|
71
|
+
}
|
|
72
|
+
async function setPackage(files, appDirectory, relativeDistPath) {
|
|
73
|
+
try {
|
|
74
|
+
const packagePath = path.resolve(appDirectory, './package.json');
|
|
75
|
+
const packageContent = await fs.readFile(packagePath, 'utf8');
|
|
76
|
+
const packageJson = JSON.parse(packageContent);
|
|
77
|
+
const addFiles = [
|
|
78
|
+
posixJoin(relativeDistPath, CLIENT_DIR, '**', '*'),
|
|
79
|
+
posixJoin(relativeDistPath, RUNTIME_DIR, '**', '*'),
|
|
80
|
+
posixJoin(relativeDistPath, PLUGIN_DIR, '**', '*')
|
|
81
|
+
];
|
|
82
|
+
const typesVersions = {
|
|
83
|
+
'*': files.reduce((acc, file)=>{
|
|
84
|
+
const typeFilePath = toPosixPath(`./${file.targetDir}`).replace('js', 'd.ts');
|
|
85
|
+
return {
|
|
86
|
+
...acc,
|
|
87
|
+
[toPosixPath(`${TYPE_PREFIX}${file.exportKey}`)]: [
|
|
88
|
+
typeFilePath
|
|
89
|
+
]
|
|
90
|
+
};
|
|
91
|
+
}, {
|
|
92
|
+
[`${API_DIR}/*`]: [
|
|
93
|
+
toPosixPath(`./${relativeDistPath}/${CLIENT_DIR}/*.d.ts`)
|
|
94
|
+
],
|
|
95
|
+
[RUNTIME_DIR]: [
|
|
96
|
+
toPosixPath(`./${relativeDistPath}/${RUNTIME_DIR}/index.d.ts`)
|
|
97
|
+
],
|
|
98
|
+
[PLUGIN_DIR]: [
|
|
99
|
+
toPosixPath(`./${relativeDistPath}/${PLUGIN_DIR}/index.d.ts`)
|
|
100
|
+
]
|
|
101
|
+
})
|
|
102
|
+
};
|
|
103
|
+
const exports = files.reduce((acc, file)=>{
|
|
104
|
+
const exportKey = `${EXPORT_PREFIX}${file.exportKey}`;
|
|
105
|
+
const jsFilePath = toPosixPath(`./${file.targetDir}`);
|
|
106
|
+
return {
|
|
107
|
+
...acc,
|
|
108
|
+
[toPosixPath(exportKey)]: {
|
|
109
|
+
import: jsFilePath,
|
|
110
|
+
types: toPosixPath(jsFilePath.replace(/\.js$/, '.d.ts'))
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}, {
|
|
114
|
+
[toPosixPath(`./${API_DIR}/*`)]: {
|
|
115
|
+
import: toPosixPath(`./${relativeDistPath}/${CLIENT_DIR}/*.js`),
|
|
116
|
+
types: toPosixPath(`./${relativeDistPath}/${CLIENT_DIR}/*.d.ts`)
|
|
117
|
+
},
|
|
118
|
+
[toPosixPath(`./${PLUGIN_DIR}`)]: {
|
|
119
|
+
import: toPosixPath(`./${relativeDistPath}/${PLUGIN_DIR}/index.js`),
|
|
120
|
+
require: toPosixPath(`./${relativeDistPath}/${PLUGIN_DIR}/index.js`),
|
|
121
|
+
types: toPosixPath(`./${relativeDistPath}/${PLUGIN_DIR}/index.d.ts`)
|
|
122
|
+
},
|
|
123
|
+
[toPosixPath(`./${RUNTIME_DIR}`)]: {
|
|
124
|
+
import: toPosixPath(`./${relativeDistPath}/${RUNTIME_DIR}/index.js`),
|
|
125
|
+
require: toPosixPath(`./${relativeDistPath}/${RUNTIME_DIR}/index.js`),
|
|
126
|
+
types: toPosixPath(`./${relativeDistPath}/${RUNTIME_DIR}/index.d.ts`)
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
mergePackageJson(packageJson, addFiles, typesVersions, exports);
|
|
130
|
+
await fs.promises.writeFile(packagePath, JSON.stringify(packageJson, null, 2));
|
|
131
|
+
} catch (error) {
|
|
132
|
+
logger.error(`package.json update failed: ${error}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function copyFiles(from, to) {
|
|
136
|
+
if (await fs.pathExists(from)) await fs.copy(toPosixPath(from), toPosixPath(to));
|
|
137
|
+
}
|
|
138
|
+
async function clientGenerator(draftOptions) {
|
|
139
|
+
const sourceList = await readDirectoryFiles(draftOptions.appDir, draftOptions.lambdaDir, draftOptions.relativeDistPath);
|
|
140
|
+
const getClitentCode = async (resourcePath, source)=>{
|
|
141
|
+
const warning = `The file ${resourcePath} is not allowd to be imported in src directory, only API definition files are allowed.`;
|
|
142
|
+
if (!draftOptions.existLambda) return void logger.warn(warning);
|
|
143
|
+
const options = {
|
|
144
|
+
prefix: Array.isArray(draftOptions.prefix) ? draftOptions.prefix[0] : draftOptions.prefix,
|
|
145
|
+
appDir: draftOptions.appDir,
|
|
146
|
+
apiDir: draftOptions.apiDir,
|
|
147
|
+
lambdaDir: draftOptions.lambdaDir,
|
|
148
|
+
port: Number(draftOptions.port),
|
|
149
|
+
source,
|
|
150
|
+
resourcePath,
|
|
151
|
+
target: 'bundle',
|
|
152
|
+
httpMethodDecider: draftOptions.httpMethodDecider,
|
|
153
|
+
requestCreator: draftOptions.requestCreator
|
|
154
|
+
};
|
|
155
|
+
const { lambdaDir } = draftOptions;
|
|
156
|
+
if (!resourcePath.startsWith(lambdaDir)) return void logger.warn(warning);
|
|
157
|
+
const result = await generateClient(options);
|
|
158
|
+
return result;
|
|
159
|
+
};
|
|
160
|
+
try {
|
|
161
|
+
for (const source of sourceList){
|
|
162
|
+
const code = await getClitentCode(source.resourcePath, source.source);
|
|
163
|
+
if (code?.value) {
|
|
164
|
+
await writeTargetFile(source.absTargetDir, code.value);
|
|
165
|
+
await copyFiles(source.relativeTargetDistDir, source.targetDir.replace("js", 'd.ts'));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
logger.info("Client bundle generate succeed");
|
|
169
|
+
} catch (error) {
|
|
170
|
+
logger.error(`Client bundle generate failed: ${error}`);
|
|
171
|
+
}
|
|
172
|
+
setPackage(sourceList, draftOptions.appDir, draftOptions.relativeDistPath);
|
|
173
|
+
}
|
|
174
|
+
const utils_clientGenerator = clientGenerator;
|
|
175
|
+
export { copyFiles, utils_clientGenerator as default, readDirectoryFiles };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { HttpMetadata, ResponseMetaType, ValidationError, isWithMetaHandler } from "@modern-js/bff-core";
|
|
2
|
+
import { parse } from "@modern-js/create-request/qs";
|
|
3
|
+
import type_is from "type-is";
|
|
4
|
+
const createHonoRoutes = (handlerInfos = [])=>handlerInfos.map(({ routePath, handler, httpMethod })=>{
|
|
5
|
+
const routeMiddlwares = Reflect.getMetadata('middleware', handler) || [];
|
|
6
|
+
const honoHandler = createHonoHandler(handler);
|
|
7
|
+
return {
|
|
8
|
+
method: httpMethod.toLowerCase(),
|
|
9
|
+
path: routePath,
|
|
10
|
+
handler: routeMiddlwares.length > 0 ? [
|
|
11
|
+
...routeMiddlwares,
|
|
12
|
+
honoHandler
|
|
13
|
+
] : honoHandler
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
const handleResponseMeta = (c, handler)=>{
|
|
17
|
+
const responseMeta = Reflect.getMetadata(HttpMetadata.Response, handler);
|
|
18
|
+
if (Array.isArray(responseMeta)) for (const meta of responseMeta)switch(meta.type){
|
|
19
|
+
case ResponseMetaType.Headers:
|
|
20
|
+
for (const [key, value] of Object.entries(meta.value))c.header(key, value);
|
|
21
|
+
break;
|
|
22
|
+
case ResponseMetaType.Redirect:
|
|
23
|
+
return c.redirect(meta.value);
|
|
24
|
+
case ResponseMetaType.StatusCode:
|
|
25
|
+
c.status(meta.value);
|
|
26
|
+
break;
|
|
27
|
+
default:
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
};
|
|
32
|
+
const createHonoHandler = (handler)=>async (c)=>{
|
|
33
|
+
const input = await getHonoInput(c);
|
|
34
|
+
if (isWithMetaHandler(handler)) try {
|
|
35
|
+
const response = handleResponseMeta(c, handler);
|
|
36
|
+
if (response) return response;
|
|
37
|
+
if (c.finalized) return;
|
|
38
|
+
const result = await handler(input);
|
|
39
|
+
if (result instanceof Response) return result;
|
|
40
|
+
return result && 'object' == typeof result ? c.json(result) : c.body(result);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error instanceof ValidationError) {
|
|
43
|
+
c.status(error.status);
|
|
44
|
+
return c.json({
|
|
45
|
+
message: error.message
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
{
|
|
51
|
+
const routePath = c.req.routePath;
|
|
52
|
+
const paramNames = routePath.match(/:\w+/g)?.map((s)=>s.slice(1)) || [];
|
|
53
|
+
const params = Object.fromEntries(paramNames.map((name)=>[
|
|
54
|
+
name,
|
|
55
|
+
input.params[name]
|
|
56
|
+
]));
|
|
57
|
+
const args = Object.values(params).concat(input);
|
|
58
|
+
const body = await handler(...args);
|
|
59
|
+
if (c.finalized) return await Promise.resolve();
|
|
60
|
+
if (void 0 !== body) {
|
|
61
|
+
if (body instanceof Response) return body;
|
|
62
|
+
return c.json(body);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const getHonoInput = async (c)=>{
|
|
67
|
+
const draft = {
|
|
68
|
+
params: c.req.param(),
|
|
69
|
+
query: parse(c.req.query()),
|
|
70
|
+
headers: c.req.header(),
|
|
71
|
+
cookies: c.req.header('cookie')
|
|
72
|
+
};
|
|
73
|
+
try {
|
|
74
|
+
const contentType = c.req.header('content-type') || '';
|
|
75
|
+
if (type_is.is(contentType, [
|
|
76
|
+
'application/json'
|
|
77
|
+
])) draft.data = await c.req.json();
|
|
78
|
+
else if (type_is.is(contentType, [
|
|
79
|
+
'multipart/form-data'
|
|
80
|
+
])) draft.formData = await c.req.parseBody();
|
|
81
|
+
else if (type_is.is(contentType, [
|
|
82
|
+
'application/x-www-form-urlencoded'
|
|
83
|
+
])) draft.formUrlencoded = await c.req.parseBody();
|
|
84
|
+
else draft.body = await c.req.json();
|
|
85
|
+
} catch (error) {
|
|
86
|
+
draft.body = null;
|
|
87
|
+
}
|
|
88
|
+
return draft;
|
|
89
|
+
};
|
|
90
|
+
const utils_createHonoRoutes = createHonoRoutes;
|
|
91
|
+
export { createHonoHandler, utils_createHonoRoutes as default };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
const PACKAGE_NAME = '{packageName}';
|
|
3
|
+
const PREFIX = '{prefix}';
|
|
4
|
+
const API_DIR = '{apiDirectory}';
|
|
5
|
+
const LAMBDA_DIR = '{lambdaDirectory}';
|
|
6
|
+
const DIST_DIR = '{distDirectory}';
|
|
7
|
+
const NODE_MODULES = 'node_modules';
|
|
8
|
+
const crossProjectApiPlugin = ()=>({
|
|
9
|
+
name: '@modern-js/plugin-independent-bff',
|
|
10
|
+
post: [
|
|
11
|
+
'@modern-js/plugin-bff'
|
|
12
|
+
],
|
|
13
|
+
setup: (api)=>{
|
|
14
|
+
api.modifyResolvedConfig((resolvedConfig)=>{
|
|
15
|
+
const { appDirectory: originAppDirectory } = api.getAppContext();
|
|
16
|
+
const sdkPath = path.join(originAppDirectory, NODE_MODULES, PACKAGE_NAME);
|
|
17
|
+
const sdkDistPath = path.join(sdkPath, DIST_DIR);
|
|
18
|
+
const apiDirectory = path.join(sdkDistPath, API_DIR);
|
|
19
|
+
const lambdaDirectory = path.resolve(sdkDistPath, LAMBDA_DIR);
|
|
20
|
+
api.updateAppContext({
|
|
21
|
+
apiDirectory,
|
|
22
|
+
lambdaDirectory
|
|
23
|
+
});
|
|
24
|
+
const config = api.getConfig();
|
|
25
|
+
if (config?.bff?.prefix) console.warn(`[WARNING] Detected bff.prefix configuration: "${config.bff.prefix}".
|
|
26
|
+
When using cross-project BFF, you should not configure bff.prefix as it may cause API path conflicts or access issues. Please remove the bff.prefix configuration.`);
|
|
27
|
+
resolvedConfig.bff.prefix = PREFIX;
|
|
28
|
+
resolvedConfig.bff.isCrossProjectServer = true;
|
|
29
|
+
return resolvedConfig;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
const utils_crossProjectApiPlugin = crossProjectApiPlugin;
|
|
34
|
+
export { API_DIR, DIST_DIR, LAMBDA_DIR, PACKAGE_NAME, PREFIX, crossProjectApiPlugin, utils_crossProjectApiPlugin as default };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { fs, logger, normalizeToPosixPath } from "@modern-js/utils";
|
|
3
|
+
import { API_DIR, DIST_DIR, LAMBDA_DIR, PACKAGE_NAME, PREFIX } from "./crossProjectApiPlugin.mjs";
|
|
4
|
+
function replaceContent(source, packageName, prefix, relativeDistPath, relativeApiPath, relativeLambdaPath) {
|
|
5
|
+
const updatedSource = source.replace(new RegExp(PACKAGE_NAME, 'g'), packageName).replace(new RegExp(PREFIX, 'g'), prefix).replace(new RegExp(DIST_DIR, 'g'), normalizeToPosixPath(relativeDistPath)).replace(new RegExp(API_DIR, 'g'), normalizeToPosixPath(relativeApiPath)).replace(new RegExp(LAMBDA_DIR, 'g'), normalizeToPosixPath(relativeLambdaPath));
|
|
6
|
+
return updatedSource;
|
|
7
|
+
}
|
|
8
|
+
async function pluginGenerator({ prefix, appDirectory, relativeDistPath, relativeApiPath, relativeLambdaPath }) {
|
|
9
|
+
try {
|
|
10
|
+
const packageContent = await fs.readFile(path.resolve(appDirectory, './package.json'), 'utf8');
|
|
11
|
+
const packageJson = JSON.parse(packageContent);
|
|
12
|
+
const pluginDir = path.resolve(appDirectory, `./${relativeDistPath}`, 'plugin');
|
|
13
|
+
const pluginPath = path.join(pluginDir, 'index.js');
|
|
14
|
+
const pluginTemplate = await fs.readFile(path.resolve(__dirname, 'crossProjectApiPlugin.js'), 'utf8');
|
|
15
|
+
const updatedPlugin = replaceContent(pluginTemplate, packageJson.name, prefix, relativeDistPath, relativeApiPath, relativeLambdaPath);
|
|
16
|
+
await fs.ensureFile(pluginPath);
|
|
17
|
+
await fs.writeFile(pluginPath, updatedPlugin);
|
|
18
|
+
const typeContent = `import type { AppTools, CliPlugin } from '@modern-js/app-tools';
|
|
19
|
+
export declare const crossProjectApiPlugin: () => CliPlugin<AppTools>`;
|
|
20
|
+
const pluginTypePath = path.join(pluginDir, 'index.d.ts');
|
|
21
|
+
await fs.ensureFile(pluginTypePath);
|
|
22
|
+
await fs.writeFile(pluginTypePath, typeContent);
|
|
23
|
+
logger.info('Api plugin generate succeed');
|
|
24
|
+
} catch (error) {
|
|
25
|
+
logger.error('Api plugin generate failed:', error);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const utils_pluginGenerator = pluginGenerator;
|
|
29
|
+
export { utils_pluginGenerator as default };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { fs } from "@modern-js/utils";
|
|
3
|
+
const getPackageName = (appDirectory)=>{
|
|
4
|
+
try {
|
|
5
|
+
const packageJsonPath = path.resolve(appDirectory, './package.json');
|
|
6
|
+
const packageJson = require(packageJsonPath);
|
|
7
|
+
return packageJson.name;
|
|
8
|
+
} catch (error) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
async function runtimeGenerator({ runtime, appDirectory, relativeDistPath, packageName }) {
|
|
13
|
+
const pluginDir = path.resolve(appDirectory, `./${relativeDistPath}`, 'runtime');
|
|
14
|
+
const requestId = packageName || getPackageName(appDirectory) || process.env.npm_package_name || 'default';
|
|
15
|
+
const source = `import { configure as _configure } from '${runtime}'
|
|
16
|
+
const configure = (options) => {
|
|
17
|
+
return _configure({
|
|
18
|
+
...options,
|
|
19
|
+
requestId: '${requestId}',
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export { configure }
|
|
23
|
+
`;
|
|
24
|
+
const pluginPath = path.join(pluginDir, 'index.js');
|
|
25
|
+
await fs.ensureFile(pluginPath);
|
|
26
|
+
await fs.writeFile(pluginPath, source);
|
|
27
|
+
const tsSource = `type IOptions<F = typeof fetch> = {
|
|
28
|
+
request?: F;
|
|
29
|
+
interceptor?: (request: F) => F;
|
|
30
|
+
allowedHeaders?: string[];
|
|
31
|
+
setDomain?: (ops?: {
|
|
32
|
+
target: 'node' | 'browser';
|
|
33
|
+
requestId: string;
|
|
34
|
+
}) => string;
|
|
35
|
+
requestId?: string;
|
|
36
|
+
};
|
|
37
|
+
export declare const configure: (options: IOptions) => void;`;
|
|
38
|
+
const pluginTypePath = path.join(pluginDir, 'index.d.ts');
|
|
39
|
+
await fs.ensureFile(pluginTypePath);
|
|
40
|
+
await fs.writeFile(pluginTypePath, tsSource);
|
|
41
|
+
}
|
|
42
|
+
const utils_runtimeGenerator = runtimeGenerator;
|
|
43
|
+
export { utils_runtimeGenerator as default };
|
package/dist/types/loader.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HttpMethodDecider } from '@modern-js/types';
|
|
2
|
-
import type {
|
|
2
|
+
import type { Rspack } from '@rsbuild/core';
|
|
3
3
|
export type APILoaderOptions = {
|
|
4
4
|
prefix: string;
|
|
5
5
|
appDir: string;
|
|
@@ -12,5 +12,5 @@ export type APILoaderOptions = {
|
|
|
12
12
|
target: string;
|
|
13
13
|
httpMethodDecider?: HttpMethodDecider;
|
|
14
14
|
};
|
|
15
|
-
declare function loader(this: LoaderContext<APILoaderOptions>, source: string): Promise<void>;
|
|
15
|
+
declare function loader(this: Rspack.LoaderContext<APILoaderOptions>, source: string): Promise<void>;
|
|
16
16
|
export default loader;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MiddlewareHandler,
|
|
1
|
+
import type { MiddlewareHandler, ServerMiddleware, ServerPluginAPI } from '@modern-js/server-core';
|
|
2
2
|
import { Hono } from '@modern-js/server-core';
|
|
3
3
|
interface MiddlewareOptions {
|
|
4
4
|
prefix: string;
|
|
@@ -7,9 +7,9 @@ interface MiddlewareOptions {
|
|
|
7
7
|
export declare class HonoAdapter {
|
|
8
8
|
apiMiddleware: ServerMiddleware[];
|
|
9
9
|
apiServer: Hono | null;
|
|
10
|
-
api:
|
|
10
|
+
api: ServerPluginAPI;
|
|
11
11
|
isHono: boolean;
|
|
12
|
-
constructor(api:
|
|
12
|
+
constructor(api: ServerPluginAPI);
|
|
13
13
|
setHandlers: () => Promise<void>;
|
|
14
14
|
registerApiRoutes: () => Promise<void>;
|
|
15
15
|
registerMiddleware: (options: MiddlewareOptions) => Promise<void>;
|
package/dist/types/server.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
declare const _default: () =>
|
|
1
|
+
import type { ServerPlugin } from '@modern-js/server-core';
|
|
2
|
+
declare const _default: () => ServerPlugin;
|
|
3
3
|
export default _default;
|