Skip to content

Commit de13c8d

Browse files
committed
feat: add indep bff doc
1 parent b1d036f commit de13c8d

File tree

24 files changed

+516
-118
lines changed

24 files changed

+516
-118
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@modern-js/create-request': patch
3+
'@modern-js/main-doc': patch
4+
'@modern-js/plugin-bff': patch
5+
---
6+
7+
feat: BFF 跨项目调用支持配置域名,补充文档
8+
feat: BFF cross-project-invocation supports configuration of domain, add doc

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ export const bffPlugin = (): CliPlugin<AppTools> => ({
117117

118118
const handleCrossProjectInvocation = async (isBuild = false) => {
119119
const { bff } = api.useResolvedConfigContext();
120-
if (bff?.enableCrossProjectInvocation) {
120+
if (bff?.crossProject) {
121121
if (!isBuild) {
122122
await compileApi();
123123
}
@@ -242,7 +242,7 @@ export const bffPlugin = (): CliPlugin<AppTools> => ({
242242
const appContext = api.useAppContext();
243243
const config = api.useResolvedConfigContext();
244244

245-
if (config?.bff?.enableCrossProjectInvocation) {
245+
if (config?.bff?.crossProject) {
246246
return [appContext.apiDirectory];
247247
} else {
248248
return [];

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

Lines changed: 130 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,63 @@ interface FileDetails {
2525
relativeTargetDistDir: string;
2626
exportKey: string;
2727
}
28+
const API_DIR = 'api';
29+
const PLUGIN_DIR = 'plugin';
30+
const RUNTIME_DIR = 'runtime';
31+
const CLIENT_DIR = 'client';
32+
33+
const EXPORT_PREFIX = `./${API_DIR}/`;
34+
const TYPE_PREFIX = `${API_DIR}/`;
35+
36+
function deepMerge<T extends Record<string, any>>(
37+
target: T | undefined,
38+
source: Partial<T>,
39+
strategy?: {
40+
array?: 'append' | 'prepend' | 'replace';
41+
dedupe?: boolean;
42+
},
43+
): T {
44+
const base = (target || {}) as T;
45+
const merged = { ...base };
46+
47+
for (const [key, value] of Object.entries(source)) {
48+
if (Array.isArray(value) && Array.isArray(base[key])) {
49+
merged[key as keyof T] = [
50+
...(strategy?.array === 'prepend' ? value : base[key]),
51+
...(strategy?.array === 'append' ? value : []),
52+
...(strategy?.array !== 'replace' && strategy?.array !== 'prepend'
53+
? base[key]
54+
: []),
55+
].filter((v, i, a) =>
56+
strategy?.dedupe
57+
? a.findIndex(e => JSON.stringify(e) === JSON.stringify(v)) === i
58+
: true,
59+
) as T[keyof T];
60+
continue;
61+
}
62+
63+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
64+
merged[key as keyof T] = deepMerge(base[key], value, strategy);
65+
continue;
66+
}
67+
68+
if (!(key in base)) {
69+
merged[key as keyof T] = value;
70+
}
71+
}
72+
73+
return merged;
74+
}
75+
76+
const filterObjectKeys = <T extends Record<string, any>>(
77+
obj: T | undefined,
78+
predicate: (key: string) => boolean,
79+
): T => {
80+
return Object.fromEntries(
81+
Object.entries(obj || {}).filter(([key]) => predicate(key)),
82+
) as T;
83+
};
84+
2885
export async function readDirectoryFiles(
2986
appDirectory: string,
3087
directory: string,
@@ -48,7 +105,7 @@ export async function readDirectoryFiles(
48105
const parsedPath = path.parse(relativePath);
49106

50107
const targetDir = path.join(
51-
`./${relativeDistPath}/client`,
108+
`./${relativeDistPath}/${CLIENT_DIR}`,
52109
parsedPath.dir,
53110
`${parsedPath.name}.js`,
54111
);
@@ -96,61 +153,90 @@ async function setPackage(
96153
}[],
97154
appDirectory: string,
98155
relativeDistPath: string,
99-
relativeApiPath: string,
100156
) {
101157
try {
102158
const packagePath = path.resolve(appDirectory, './package.json');
103159
const packageContent = await fs.readFile(packagePath, 'utf8');
104160
const packageJson = JSON.parse(packageContent);
105161

106-
packageJson.exports = packageJson.exports || {};
107-
packageJson.typesVersions = packageJson.typesVersions || { '*': {} };
108-
109-
files.forEach(file => {
110-
const exportKey = `./api/${file.exportKey}`;
111-
const jsFilePath = `./${file.targetDir}`;
112-
const typePath = file.relativeTargetDistDir;
113-
114-
packageJson.exports[exportKey] = {
115-
import: jsFilePath,
116-
types: typePath,
117-
};
118-
119-
packageJson.typesVersions['*'][`api/${file.exportKey}`] = [typePath];
120-
});
162+
packageJson.exports = filterObjectKeys(
163+
packageJson.exports,
164+
key => !key.startsWith(EXPORT_PREFIX),
165+
);
121166

122-
packageJson.exports['./plugin'] = {
123-
require: `./${relativeDistPath}/plugin/index.js`,
124-
types: `./${relativeDistPath}/plugin/index.d.ts`,
125-
};
167+
if (packageJson.typesVersions?.['*']) {
168+
packageJson.typesVersions['*'] = filterObjectKeys(
169+
packageJson.typesVersions['*'],
170+
key => !key.startsWith(TYPE_PREFIX),
171+
);
172+
}
126173

127-
packageJson.exports['./runtime'] = {
128-
import: `./${relativeDistPath}/runtime/index.js`,
129-
types: `./${relativeDistPath}/runtime/index.d.ts`,
130-
};
131-
packageJson.typesVersions['*'].runtime = [
132-
`./${relativeDistPath}/runtime/index.d.ts`,
133-
];
134-
packageJson.typesVersions['*'].plugin = [
135-
`./${relativeDistPath}/plugin/index.d.ts`,
136-
];
137-
138-
packageJson.files = [
139-
`${relativeDistPath}/client/**/*`,
140-
`${relativeDistPath}/${relativeApiPath}/**/*`,
141-
`${relativeDistPath}/runtime/**/*`,
142-
`${relativeDistPath}/plugin/**/*`,
143-
];
174+
const mergedPackage = deepMerge(
175+
packageJson,
176+
{
177+
exports: files.reduce(
178+
(acc, file) => {
179+
const exportKey = `${EXPORT_PREFIX}${file.exportKey}`;
180+
const jsFilePath = `./${file.targetDir}`;
181+
return deepMerge(acc, {
182+
[exportKey]: {
183+
import: jsFilePath,
184+
types: jsFilePath.replace('js', 'd.ts'),
185+
},
186+
});
187+
},
188+
{
189+
'./plugin': {
190+
require: `./${relativeDistPath}/${PLUGIN_DIR}/index.js`,
191+
types: `./${relativeDistPath}/${PLUGIN_DIR}/index.d.ts`,
192+
},
193+
'./runtime': {
194+
import: `./${relativeDistPath}/${RUNTIME_DIR}/index.js`,
195+
types: `./${relativeDistPath}/${RUNTIME_DIR}/index.d.ts`,
196+
},
197+
},
198+
),
199+
typesVersions: {
200+
'*': files.reduce(
201+
(acc, file) =>
202+
deepMerge(acc, {
203+
[`${TYPE_PREFIX}${file.exportKey}`]: [
204+
file.relativeTargetDistDir,
205+
],
206+
}),
207+
{
208+
runtime: [`./${relativeDistPath}/${RUNTIME_DIR}/index.d.ts`],
209+
plugin: [`./${relativeDistPath}/${PLUGIN_DIR}/index.d.ts`],
210+
},
211+
),
212+
},
213+
files: [
214+
`${relativeDistPath}/${CLIENT_DIR}/**/*`,
215+
`${relativeDistPath}/${RUNTIME_DIR}/**/*`,
216+
`${relativeDistPath}/${PLUGIN_DIR}/**/*`,
217+
],
218+
},
219+
{
220+
array: 'append',
221+
dedupe: true,
222+
},
223+
);
144224

145225
await fs.promises.writeFile(
146226
packagePath,
147-
JSON.stringify(packageJson, null, 2),
227+
JSON.stringify(mergedPackage, null, 2),
148228
);
149229
} catch (error) {
150230
logger.error(`package.json update failed: ${error}`);
151231
}
152232
}
153233

234+
export async function copyFiles(from: string, to: string) {
235+
if (await fs.pathExists(from)) {
236+
await fs.copy(from, to);
237+
}
238+
}
239+
154240
async function clientGenerator(draftOptions: APILoaderOptions) {
155241
const sourceList = await readDirectoryFiles(
156242
draftOptions.appDir,
@@ -197,19 +283,18 @@ async function clientGenerator(draftOptions: APILoaderOptions) {
197283
const code = await getClitentCode(source.resourcePath, source.source);
198284
if (code?.value) {
199285
await writeTargetFile(source.absTargetDir, code.value);
286+
await copyFiles(
287+
source.relativeTargetDistDir,
288+
source.targetDir.replace(`js`, 'd.ts'),
289+
);
200290
}
201291
}
202292
logger.info(`Client bundle generate succeed`);
203293
} catch (error) {
204294
logger.error(`Client bundle generate failed: ${error}`);
205295
}
206296

207-
setPackage(
208-
sourceList,
209-
draftOptions.appDir,
210-
draftOptions.relativeDistPath,
211-
draftOptions.relativeApiPath,
212-
);
297+
setPackage(sourceList, draftOptions.appDir, draftOptions.relativeDistPath);
213298
}
214299

215300
export default clientGenerator;

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ async function runtimeGenerator({
2929
request?: F;
3030
interceptor?: (request: F) => F;
3131
allowedHeaders?: string[];
32+
setDomain?: (ops?: {
33+
target: 'node' | 'browser';
34+
requestId: string;
35+
}) => string;
3236
requestId?: string;
3337
};
3438
export declare const configure: (options: IOptions) => void;`;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
1. run `@modern-js/create` command:
2+
3+
```bash
4+
npx @modern-js/create@latest myapi
5+
```
6+
7+
2. interactive Q & A interface to initialize the project based on the results, with initialization performed according to the default settings:
8+
9+
```bash
10+
? Please select the programming language: TS
11+
? Please select the package manager: pnpm
12+
```
13+
14+
3. Execute the `new` command,enable BFF:
15+
16+
```bash
17+
? Please select the operation you want to perform Enable optional features
18+
? Please select the feature to enable Enable "BFF"
19+
? Please select BFF type Framework mode
20+
```
21+
22+
23+
4. Execute【[Existing BFF-enabled Projects](/en/guides/advanced-features/bff/cross-project.html#existing-bff-enabled-projects)】to turn on the cross-project call switch.
24+
25+
**Note:** When a project serves solely as a BFF producer, its runtime does not depend on the `/src` source directory. Removing the `/src` directory can help optimize the project's build efficiency.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
["function", "frameworks", "extend-server", "sdk", "upload"]
1+
["function", "frameworks", "extend-server", "sdk", "upload", "cross-project"]

0 commit comments

Comments
 (0)