Skip to content

Commit

Permalink
feat: add indep bff doc
Browse files Browse the repository at this point in the history
  • Loading branch information
keepview committed Feb 7, 2025
1 parent f0ea9a4 commit 81cc0d0
Show file tree
Hide file tree
Showing 17 changed files with 368 additions and 45 deletions.
8 changes: 8 additions & 0 deletions .changeset/bright-apricots-complain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@modern-js/create-request': patch
'@modern-js/main-doc': patch
'@modern-js/plugin-bff': patch
---

feat: BFF 跨项目调用支持配置域名,补充文档
feat: BFF cross-project-invocation supports configuration of domain, add doc
13 changes: 11 additions & 2 deletions packages/cli/plugin-bff/src/utils/clientGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ async function setPackage(

packageJson.exports[exportKey] = {
import: jsFilePath,
types: typePath,
types: jsFilePath.replace(`js`, 'd.ts'),
};

packageJson.typesVersions['*'][`api/${file.exportKey}`] = [typePath];
Expand All @@ -137,7 +137,6 @@ async function setPackage(

packageJson.files = [
`${relativeDistPath}/client/**/*`,
`${relativeDistPath}/${relativeApiPath}/**/*`,
`${relativeDistPath}/runtime/**/*`,
`${relativeDistPath}/plugin/**/*`,
];
Expand All @@ -151,6 +150,12 @@ async function setPackage(
}
}

export async function copyFiles(from: string, to: string) {
if (await fs.pathExists(from)) {
await fs.copy(from, to);
}
}

async function clientGenerator(draftOptions: APILoaderOptions) {
const sourceList = await readDirectoryFiles(
draftOptions.appDir,
Expand Down Expand Up @@ -197,6 +202,10 @@ async function clientGenerator(draftOptions: APILoaderOptions) {
const code = await getClitentCode(source.resourcePath, source.source);
if (code?.value) {
await writeTargetFile(source.absTargetDir, code.value);
await copyFiles(
source.relativeTargetDistDir,
source.targetDir.replace(`js`, 'd.ts'),
);
}
}
logger.info(`Client bundle generate succeed`);
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/plugin-bff/src/utils/runtimeGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ async function runtimeGenerator({
request?: F;
interceptor?: (request: F) => F;
allowedHeaders?: string[];
setDomain?: (ops?: {
target: 'node' | 'browser';
requestId: string;
}) => string;
requestId?: string;
};
export declare const configure: (options: IOptions) => void;`;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
1. run `@modern-js/create` command:

```bash
npx @modern-js/create@latest myapi
```

2. interactive Q & A interface to initialize the project based on the results, with initialization performed according to the default settings:

```bash
? Please select the programming language: TS
? Please select the package manager: pnpm
```
3. Execute the `new` command,enable BFF:
```bash
? Please select the operation you want to perform Enable optional features
? Please select the feature to enable Enable "BFF"
? Please select BFF type Framework mode
```
4. Execute【[BFF-Enabled Projects](/en/guides/advanced-features/bff/cross-project.html#bff-enabled-projects)】to turn on the cross-project call switch.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
["function", "frameworks", "extend-server", "sdk", "upload"]
["function", "frameworks", "extend-server", "sdk", "upload", "cross-project"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { PackageManagerTabs } from '@theme';

# Cross-Project Invocation

Based on the BFF architecture, Modern.js provides the capability of cross-project invocation,
where BFF APIs created in one project can be integrated and called by other projects,
achieving API sharing and functionality reuse across projects.
Cross-project invocation is divided into the **producer** and **consumer** sides.
The producer side is responsible for creating and providing API services,
generating integrated invocation SDKs, while the consumer side makes interface requests through calling the SDK.

## API Producer Side

Upgrade EdenX related dependencies to version 1.63.7 or above, and cross-project invocation can be enabled through configuration. You can consider a project with BFF capabilities enabled as the API producer side, or you can create a standalone API service.
When executing `dev` or `build`, the following artifacts for consumer use are automatically generated:
- Interface functions under the `dist/client` directory
- Runtime configuration functions under the `dist/runtime` directory
- Interface function exports defined in `package.json` `exports`
- File list specified in `package.json` `files` for publication to npm packages

### BFF-Enabled Projects

1. Enable cross-project invocation

Ensure that the current project has BFF capabilities enabled, and define interface files under the api/lambda directory. Configure as follows:

```ts title="modern.config.ts"
export default defineConfig({
bff: {
enableCrossProjectInvocation: true,
}
});
```


2. Generate SDK type files

To provide type hints for the integrated invocation SDK, enable the declaration option in the `TypeScript` `tsconfig.json` file, `configured` as follows:
```ts title="tsconfig.json"
"compilerOptions": {
"declaration": true,
}
```

### Creating an API Project

import CreateApi from "@site-docs-en/components/create-bff-api-app"

<CreateApi/>

## API Consumer Side

:::info
You can initiate interface requests to the production side by calling the SDK in projects of any framework.
:::

### Invocation within the Same Monorepo

If the producer and consumer sides are in the same Monorepo, you can directly import the SDK. The interface functions are located in the `${package_name}/api` directory, as shown below:

```ts title="src/routes/page.tsx"
import { useState, useEffect } from 'react';
import { get as hello } from '${package_name}/api/hello';

export default () => {
const [text, setText] = useState('');

useEffect(() => {
hello().then(setText);
}, []);
return <div>{text}</div>;
};
```

### Calling Between Independent Projects

When the producer and consumer sides are not in the same Monorepo, the producer side needs to publish the API project as a package through `npm publish`. The calling method is the same as within the Monorepo.

### Configure Domain and Extended Features

In real-world scenarios, cross-project invocation needs to specify the API service's domain. This can be achieved through the following configuration function:

```ts title="src/routes/page.tsx"
import { configure } from '${package_name}/runtime';

configure({
setDomain() {
return 'https://your-bff-api.com';
},
});
```

The configure function in the `/runtime` directory supports setting the domain through `setDomain`, and `configure` also supports adding interceptors and custom SDK capabilities.

If you need to use both the API of this project and the cross-project API on the same page, you can `configure` as follows:

```ts title="src/routes/page.tsx"
import { configure } from '${package_name}/runtime';
import { configure as innerConfigure } from '@edenx/runtime/bff';
import axios from 'axios';

configure({
setDomain() {
return 'https://your-bff-api.com';
},
});

innerConfigure({
async request(...config: Parameters<typeof fetch>) {
const [url, params] = config;
const res = await axios({
url: url as string,
method: params?.method as Method,
data: params?.body,
headers: {
'x-header': 'innerConfigure',
},
});
return res.data;
},
});


```
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

1. 执行 `@modern-js/create` 命令:

```bash
npx @modern-js/create@latest myapi
```

2. 可交互的问答界面中,按照默认的选择进行初始化:

```bash
? 请选择开发语言 TS
? 请选择包管理工具 pnpm
```

3. 执行 `new` 命令,启用 BFF:

```bash
? 请选择你想要的操作 启用可选功能
? 请选择功能名称 启用「BFF」功能
? 请选择 BFF 类型 框架模式
```


4. 执行【[已启用 BFF 项目](/guides/advanced-features/bff/cross-project.html#已启用-bff-项目)】步骤,即可打开跨项目调用开关
Original file line number Diff line number Diff line change
@@ -1 +1 @@
["function", "frameworks", "extend-server", "sdk", "upload"]
["function", "frameworks", "extend-server", "sdk", "upload", "cross-project"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { PackageManagerTabs } from '@theme';

# 跨项目调用

基于 BFF 架构,Modern.js 提供了跨项目调用的能力,即在一个项目中创建的 BFF API 可以被其他项目进行一体化调用,实现项目间的 API 共享和功能复用。
跨项目调用分为 API 的**生产端****消费端**。生产端负责创建和提供 API 服务、生成一体化调用 SDK,而消费端通过调用 SDK 发起接口请求。

## API 生产端

升级 Modern.js 相关依赖到 x.63.7 及以上版本,通过配置即可启用跨项目调用能力。你可以将已启用 BFF 能力的项目视为 API 生产端,也可以单独创建独立的 API 服务。
当执行 `dev``build`,都会自动生成供消费端使用的产物,包括:
- `dist/client` 目录下的接口函数
- `dist/runtime` 目录下的运行时配置函数
- `package.json``exports` 定义接口函数出口
- `package.json``files` 指定发布到 npm 包的文件列表

### 已启用 BFF 项目

1. 开启跨项目调用

确保当前项目已启动 BFF 能力,并在 api/lambda 目录下定义接口文件。配置如下:

```ts title="modern.config.ts"
export default defineConfig({
bff: {
enableCrossProjectInvocation: true,
}
});
```


2. 生成 SDK 类型文件

为一体化调用 SDK 提供类型提示,需要在 `TypeScript``tsconfig.json` 文件中打开 `declaration` 选项,配置如下:

```ts title="tsconfig.json"
"compilerOptions": {
"declaration": true,
}
```

### 创建 API 项目

import CreateApi from "@site-docs/components/create-bff-api-app"

<CreateApi/>

## API 消费端

:::info
你可以在任意框架的项目中通过调用 SDK 向生产端发起接口请求。
:::

### 同一 Monorepo 内调用

如果生产端和消费端在同一 Monorepo 中,可以直接引入 SDK。接口函数位于 `${package_name}/api` 目录下,示例如下:

```ts title="src/routes/page.tsx"
import { useState, useEffect } from 'react';
import { get as hello } from '${package_name}/api/hello';

export default () => {
const [text, setText] = useState('');

useEffect(() => {
hello().then(setText);
}, []);
return <div>{text}</div>;
};
```

### 独立项目间调用

当生产端和消费端不在同一个 Monorepo 中时,生产端需要通过 `npm publish` 将 API 项目发布为包,调用方式与 Monorepo 内相同。

### 配置域名及扩展功能

实际应用场景中,跨项目调用需要指定 API 服务的域名。可以通过以下配置函数实现:

```ts title="src/routes/page.tsx"
import { configure } from '${package_name}/runtime';

configure({
setDomain() {
return 'https://your-bff-api.com';
},
});
```

`/runtime` 目录下的 `configure` 函数,支持通过 `setDomain` 配置域名,`configure` 同样支持添加拦截器和自定义个 SDK 能力。
如果需要在同一个页面中同时使用本项目的 API 和跨项目的 API,可以进行如下配置:

```ts title="src/routes/page.tsx"
import { configure } from '${package_name}/runtime';
import { configure as innerConfigure } from '@modern-js/runtime/bff';
import axios from 'axios';

configure({
setDomain() {
return 'https://your-bff-api.com';
},
});

innerConfigure({
async request(...config: Parameters<typeof fetch>) {
const [url, params] = config;
const res = await axios({
url: url as string,
method: params?.method as Method,
data: params?.body,
headers: {
'x-header': 'innerConfigure',
},
});
return res.data;
},
});


```
Loading

0 comments on commit 81cc0d0

Please sign in to comment.