@modern-js/plugin-bff 2.69.4 → 3.0.0-alpha.0
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 +54 -32
- 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,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";
|
|
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 };
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { ApiRouter } from "@modern-js/bff-core";
|
|
3
|
+
import { compile } from "@modern-js/server-utils";
|
|
4
|
+
import { API_DIR, DEFAULT_API_PREFIX, SHARED_DIR, fs, normalizeOutputPath } from "@modern-js/utils";
|
|
5
|
+
import clientGenerator from "./utils/clientGenerator.mjs";
|
|
6
|
+
import pluginGenerator from "./utils/pluginGenerator.mjs";
|
|
7
|
+
import runtimeGenerator from "./utils/runtimeGenerator.mjs";
|
|
8
|
+
const TS_CONFIG_FILENAME = 'tsconfig.json';
|
|
9
|
+
const RUNTIME_CREATE_REQUEST = '@modern-js/plugin-bff/client';
|
|
10
|
+
const RUNTIME_HONO = '@modern-js/plugin-bff/server';
|
|
11
|
+
const bffPlugin = ()=>({
|
|
12
|
+
name: '@modern-js/plugin-bff',
|
|
13
|
+
setup: (api)=>{
|
|
14
|
+
const compileApi = async ()=>{
|
|
15
|
+
const { appDirectory, distDirectory, apiDirectory, sharedDirectory, moduleType } = api.getAppContext();
|
|
16
|
+
const modernConfig = api.getNormalizedConfig();
|
|
17
|
+
const distDir = path.resolve(distDirectory);
|
|
18
|
+
const apiDir = apiDirectory || path.resolve(appDirectory, API_DIR);
|
|
19
|
+
const sharedDir = sharedDirectory || path.resolve(appDirectory, SHARED_DIR);
|
|
20
|
+
const tsconfigPath = path.resolve(appDirectory, TS_CONFIG_FILENAME);
|
|
21
|
+
const sourceDirs = [];
|
|
22
|
+
if (await fs.pathExists(apiDir)) sourceDirs.push(apiDir);
|
|
23
|
+
if (await fs.pathExists(sharedDir)) sourceDirs.push(sharedDir);
|
|
24
|
+
const { server } = modernConfig;
|
|
25
|
+
const { alias } = modernConfig.source;
|
|
26
|
+
const { alias: resolveAlias } = modernConfig.resolve;
|
|
27
|
+
const { babel } = modernConfig.tools;
|
|
28
|
+
if (sourceDirs.length > 0) {
|
|
29
|
+
const combinedAlias = [].concat(alias ?? []).concat(resolveAlias ?? []);
|
|
30
|
+
await compile(appDirectory, {
|
|
31
|
+
server,
|
|
32
|
+
alias: combinedAlias,
|
|
33
|
+
babelConfig: babel
|
|
34
|
+
}, {
|
|
35
|
+
sourceDirs,
|
|
36
|
+
distDir,
|
|
37
|
+
tsconfigPath,
|
|
38
|
+
moduleType,
|
|
39
|
+
throwErrorInsteadOfExit: true
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const generator = async ()=>{
|
|
44
|
+
const { appDirectory, apiDirectory, lambdaDirectory, port } = api.getAppContext();
|
|
45
|
+
const modernConfig = api.getNormalizedConfig();
|
|
46
|
+
const relativeDistPath = modernConfig?.output?.distPath?.root || 'dist';
|
|
47
|
+
const { bff } = modernConfig || {};
|
|
48
|
+
const prefix = bff?.prefix || DEFAULT_API_PREFIX;
|
|
49
|
+
const httpMethodDecider = bff?.httpMethodDecider;
|
|
50
|
+
const apiRouter = new ApiRouter({
|
|
51
|
+
apiDir: apiDirectory,
|
|
52
|
+
appDir: appDirectory,
|
|
53
|
+
lambdaDir: lambdaDirectory,
|
|
54
|
+
prefix,
|
|
55
|
+
httpMethodDecider,
|
|
56
|
+
isBuild: true
|
|
57
|
+
});
|
|
58
|
+
const lambdaDir = apiRouter.getLambdaDir();
|
|
59
|
+
const existLambda = apiRouter.isExistLambda();
|
|
60
|
+
const runtime = bff?.runtimeCreateRequest || RUNTIME_CREATE_REQUEST;
|
|
61
|
+
const relativeApiPath = path.relative(appDirectory, apiDirectory);
|
|
62
|
+
const relativeLambdaPath = path.relative(appDirectory, lambdaDir);
|
|
63
|
+
await pluginGenerator({
|
|
64
|
+
prefix,
|
|
65
|
+
appDirectory,
|
|
66
|
+
relativeDistPath,
|
|
67
|
+
relativeApiPath,
|
|
68
|
+
relativeLambdaPath
|
|
69
|
+
});
|
|
70
|
+
await clientGenerator({
|
|
71
|
+
prefix,
|
|
72
|
+
appDir: appDirectory,
|
|
73
|
+
apiDir: apiDirectory,
|
|
74
|
+
lambdaDir,
|
|
75
|
+
existLambda,
|
|
76
|
+
port,
|
|
77
|
+
requestCreator: bff?.requestCreator,
|
|
78
|
+
httpMethodDecider,
|
|
79
|
+
relativeDistPath,
|
|
80
|
+
relativeApiPath
|
|
81
|
+
});
|
|
82
|
+
await runtimeGenerator({
|
|
83
|
+
runtime,
|
|
84
|
+
appDirectory,
|
|
85
|
+
relativeDistPath
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
const handleCrossProjectInvocation = async (isBuild = false)=>{
|
|
89
|
+
const { bff } = api.getNormalizedConfig();
|
|
90
|
+
if (bff?.crossProject) {
|
|
91
|
+
if (!isBuild) await compileApi();
|
|
92
|
+
await generator();
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const isHono = ()=>{
|
|
96
|
+
const { bffRuntimeFramework } = api.getAppContext();
|
|
97
|
+
return 'hono' === bffRuntimeFramework;
|
|
98
|
+
};
|
|
99
|
+
const createCompressConfig = (devServer, prefix)=>{
|
|
100
|
+
if (!devServer || 'object' != typeof devServer || Array.isArray(devServer)) return;
|
|
101
|
+
const { compress } = devServer;
|
|
102
|
+
if (void 0 === compress || true === compress) return {
|
|
103
|
+
filter: (req)=>!req.url?.includes(prefix)
|
|
104
|
+
};
|
|
105
|
+
if (false === compress) return false;
|
|
106
|
+
return compress;
|
|
107
|
+
};
|
|
108
|
+
api.config(async ()=>{
|
|
109
|
+
const honoRuntimePath = isHono() ? {
|
|
110
|
+
[RUNTIME_HONO]: RUNTIME_HONO
|
|
111
|
+
} : void 0;
|
|
112
|
+
const devServer = api.getConfig()?.tools?.devServer;
|
|
113
|
+
const prefix = api.getConfig()?.bff?.prefix || DEFAULT_API_PREFIX;
|
|
114
|
+
const compress = createCompressConfig(devServer, prefix);
|
|
115
|
+
return {
|
|
116
|
+
tools: {
|
|
117
|
+
devServer: {
|
|
118
|
+
compress
|
|
119
|
+
},
|
|
120
|
+
bundlerChain: (chain, { CHAIN_ID, isServer })=>{
|
|
121
|
+
const { port, appDirectory, apiDirectory, lambdaDirectory } = api.getAppContext();
|
|
122
|
+
const modernConfig = api.getNormalizedConfig();
|
|
123
|
+
const { bff } = modernConfig || {};
|
|
124
|
+
const prefix = bff?.prefix || DEFAULT_API_PREFIX;
|
|
125
|
+
const httpMethodDecider = bff?.httpMethodDecider;
|
|
126
|
+
const apiRouter = new ApiRouter({
|
|
127
|
+
apiDir: apiDirectory,
|
|
128
|
+
appDir: appDirectory,
|
|
129
|
+
lambdaDir: lambdaDirectory,
|
|
130
|
+
prefix,
|
|
131
|
+
httpMethodDecider,
|
|
132
|
+
isBuild: true
|
|
133
|
+
});
|
|
134
|
+
const lambdaDir = apiRouter.getLambdaDir();
|
|
135
|
+
const existLambda = apiRouter.isExistLambda();
|
|
136
|
+
const apiRegexp = new RegExp(normalizeOutputPath(`${apiDirectory}${path.sep}.*(.[tj]s)$`));
|
|
137
|
+
const name = isServer ? 'server' : 'client';
|
|
138
|
+
const loaderPath = require.resolve('./loader');
|
|
139
|
+
chain.module.rule(CHAIN_ID.RULE.JS).exclude.add(apiRegexp);
|
|
140
|
+
chain.module.rule('js-bff-api').test(apiRegexp).use('custom-loader').loader(loaderPath.replace(/\\/g, '/')).options({
|
|
141
|
+
prefix,
|
|
142
|
+
appDir: appDirectory,
|
|
143
|
+
apiDir: apiDirectory,
|
|
144
|
+
lambdaDir,
|
|
145
|
+
existLambda,
|
|
146
|
+
port,
|
|
147
|
+
target: name,
|
|
148
|
+
requestCreator: bff?.requestCreator,
|
|
149
|
+
httpMethodDecider
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
output: {
|
|
154
|
+
externals: honoRuntimePath
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
api.modifyServerRoutes(({ routes })=>{
|
|
159
|
+
const modernConfig = api.getNormalizedConfig();
|
|
160
|
+
const { bff } = modernConfig || {};
|
|
161
|
+
const prefix = bff?.prefix || '/api';
|
|
162
|
+
const prefixList = [];
|
|
163
|
+
if (Array.isArray(prefix)) prefixList.push(...prefix);
|
|
164
|
+
else prefixList.push(prefix);
|
|
165
|
+
const apiServerRoutes = prefixList.map((pre)=>({
|
|
166
|
+
urlPath: pre,
|
|
167
|
+
isApi: true,
|
|
168
|
+
entryPath: '',
|
|
169
|
+
isSPA: false,
|
|
170
|
+
isSSR: false
|
|
171
|
+
}));
|
|
172
|
+
if (!isHono() && bff?.enableHandleWeb) return {
|
|
173
|
+
routes: routes.map((route)=>({
|
|
174
|
+
...route,
|
|
175
|
+
isApi: true
|
|
176
|
+
})).concat(apiServerRoutes)
|
|
177
|
+
};
|
|
178
|
+
return {
|
|
179
|
+
routes: routes.concat(apiServerRoutes)
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
api._internalServerPlugins(({ plugins })=>{
|
|
183
|
+
plugins.push({
|
|
184
|
+
name: '@modern-js/plugin-bff/server-plugin'
|
|
185
|
+
});
|
|
186
|
+
return {
|
|
187
|
+
plugins
|
|
188
|
+
};
|
|
189
|
+
});
|
|
190
|
+
api.onBeforeDev(async ()=>{
|
|
191
|
+
await handleCrossProjectInvocation();
|
|
192
|
+
});
|
|
193
|
+
api.onAfterBuild(async ()=>{
|
|
194
|
+
await compileApi();
|
|
195
|
+
await handleCrossProjectInvocation(true);
|
|
196
|
+
});
|
|
197
|
+
api.addWatchFiles(async ()=>{
|
|
198
|
+
const appContext = api.getAppContext();
|
|
199
|
+
const config = api.getNormalizedConfig();
|
|
200
|
+
if (config?.bff?.crossProject) return [
|
|
201
|
+
appContext.apiDirectory
|
|
202
|
+
];
|
|
203
|
+
return [];
|
|
204
|
+
});
|
|
205
|
+
api.onFileChanged(async (e)=>{
|
|
206
|
+
const { filename, eventType, isPrivate } = e;
|
|
207
|
+
const { appDirectory, apiDirectory } = api.getAppContext();
|
|
208
|
+
const relativeApiPath = path.relative(appDirectory, apiDirectory);
|
|
209
|
+
if (!isPrivate && ('change' === eventType || 'unlink' === eventType) && filename.startsWith(`${relativeApiPath}/`) && (filename.endsWith('.ts') || filename.endsWith('.js'))) await handleCrossProjectInvocation();
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
const cli = bffPlugin;
|
|
214
|
+
export { bffPlugin, cli as default };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./constants.mjs";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { generateClient } from "@modern-js/bff-core";
|
|
2
|
+
import { logger } from "@modern-js/utils";
|
|
3
|
+
async function loader(source) {
|
|
4
|
+
this.cacheable();
|
|
5
|
+
const { resourcePath } = this;
|
|
6
|
+
delete require.cache[resourcePath];
|
|
7
|
+
const callback = this.async();
|
|
8
|
+
const draftOptions = this.getOptions();
|
|
9
|
+
const warning = `The file ${resourcePath} is not allowd to be imported in src directory, only API definition files are allowed.`;
|
|
10
|
+
if (!draftOptions.existLambda) {
|
|
11
|
+
logger.warn(warning);
|
|
12
|
+
callback(null, `throw new Error('${warning}')`);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const options = {
|
|
16
|
+
prefix: Array.isArray(draftOptions.prefix) ? draftOptions.prefix[0] : draftOptions.prefix,
|
|
17
|
+
appDir: draftOptions.appDir,
|
|
18
|
+
apiDir: draftOptions.apiDir,
|
|
19
|
+
lambdaDir: draftOptions.lambdaDir,
|
|
20
|
+
target: draftOptions.target,
|
|
21
|
+
port: Number(draftOptions.port),
|
|
22
|
+
source,
|
|
23
|
+
resourcePath,
|
|
24
|
+
httpMethodDecider: draftOptions.httpMethodDecider
|
|
25
|
+
};
|
|
26
|
+
const { lambdaDir } = draftOptions;
|
|
27
|
+
if (!resourcePath.startsWith(lambdaDir)) {
|
|
28
|
+
logger.warn(warning);
|
|
29
|
+
callback(null, `throw new Error('${warning}')`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (draftOptions.fetcher) options.fetcher = draftOptions.fetcher;
|
|
33
|
+
if (draftOptions.requestCreator) options.requestCreator = draftOptions.requestCreator;
|
|
34
|
+
options.requireResolve = require.resolve;
|
|
35
|
+
const result = await generateClient(options);
|
|
36
|
+
callback(void 0, result.isOk ? result.value : `throw new Error('${result.value}')`);
|
|
37
|
+
}
|
|
38
|
+
const src_loader = loader;
|
|
39
|
+
export { src_loader as default };
|