logo
  • 指南
  • 配置
  • 插件
  • API
  • 示例
  • 社区
  • Modern.js 2.x 文档
  • 简体中文
    • 简体中文
    • English
    • 开始
      介绍
      快速上手
      版本升级
      名词解释
      技术栈
      核心概念
      页面入口
      构建工具
      Web 服务器
      基础功能
      路由
      路由基础
      配置式路由
      数据管理
      数据获取
      数据写入
      数据缓存
      渲染
      服务端渲染(SSR)
      服务端流式渲染(Streaming SSR)
      渲染缓存
      静态站点生成(SSG)
      渲染预处理 (Render Preprocessing)
      样式开发
      引入 CSS
      使用 CSS Modules
      使用 CSS-in-JS
      使用 Tailwind CSS
      HTML 模板
      引用静态资源
      引用 JSON 文件
      引用 SVG 资源
      引用 Wasm 资源
      调试
      数据模拟(Mock)
      网络代理
      使用 Rsdoctor
      使用 Storybook
      测试
      Playwright
      Vitest
      Jest
      Cypress
      路径别名
      环境变量
      构建产物目录
      部署应用
      进阶功能
      使用 Rspack
      使用 BFF
      基础用法
      运行时框架
      扩展 BFF Server
      扩展一体化调用 SDK
      文件上传
      跨项目调用
      优化页面性能
      代码分割
      静态资源内联
      产物体积优化
      React Compiler
      提升构建性能
      浏览器兼容性
      配置底层工具
      源码构建模式
      服务端监控
      Monitors
      日志事件
      指标事件
      国际化
      基础概念
      快速开始
      配置说明
      语言检测
      资源加载
      路由集成
      API 参考
      高级用法
      最佳实践
      自定义 Web Server
      专题详解
      模块联邦
      简介
      开始使用
      应用级别模块
      服务端渲染
      部署
      集成国际化能力
      常见问题
      依赖安装问题
      命令行问题
      构建相关问题
      热更新问题
      已下线功能
      📝 编辑此页面
      上一页使用 BFF下一页运行时框架

      #基础用法

      在 Modern.js 应用中,开发者可以在 api/lambda 目录下定义接口文件,并导出接口函数。在前端代码中,可以用文件引用的方式,直接调用这些接口函数,发起接口请求。

      这种调用方式我们称为一体化调用,开发者无需编写前后端胶水层代码,并天然地保证前后端类型安全。

      #启用 BFF

      1. 执行 new 命令:
      npm
      yarn
      pnpm
      bun
      deno
      npm run new
      yarn run new
      pnpm run new
      bun run new
      deno run npm:new
      1. 按照提示,选择启用 BFF 功能:
      ? 请选择你想要的操作 启用可选功能
      ? 请选择功能名称 启用「BFF」功能
      1. 将下面的代码添加到 modern.config.[tj]s 中:
      modern.config.ts
      import { bffPlugin } from '@modern-js/plugin-bff';
      
      export default defineConfig({
        plugins: [bffPlugin()],
      });

      #BFF 函数

      允许使用一体化调用的函数,我们称为 BFF 函数。这里写一个最简单的 BFF 函数,首先创建 api/lambda/hello.ts 文件:

      api/lambda/hello.ts
      export const get = async () => 'Hello Modern.js';

      接着在 src/routes/page.tsx 中直接引入函数并调用:

      src/routes/page.tsx
      import { useState, useEffect } from 'react';
      import { get as hello } from '@api/hello';
      
      export default () => {
        const [text, setText] = useState('');
      
        useEffect(() => {
          hello().then(setText);
        }, []);
        return <div>{text}</div>;
      };
      Tip

      在调用 new 命令后,Modern.js 生成器会自动在 tsconfig.json 中配置 @api 别名,因此可以直接通过别名的方式引入函数。

      在 src/routes/page.tsx 中引入的函数,会自动转换成接口调用,不需要再通过请求 SDK 或 Web Fetch 调用接口。

      执行 pnpm run dev 后,打开 http://localhost:8080/ 可以看到页面已经展示了 BFF 函数返回的内容,在 Network 中可以看到页面向 http://localhost:8080/api/hello 发送了请求:

      Network

      #函数路由

      Modern.js 中,BFF 函数对应的路由系统是基于文件系统实现的,也是一种约定式路由。api/lambda 下的所有文件中的每个 BFF 函数都会映射为一个接口,下面介绍几种路由的约定。

      Info

      所有 BFF 函数生成的路由都带有统一的前缀,默认值为 /api。可以通过 bff.prefix 设置公共路由的前缀。

      #默认路由

      以 index.[jt]s 命名的文件会被映射到上一层目录。

      • api/lambda/index.ts -> {prefix}/
      • api/lambda/user/index.ts -> {prefix}/user

      #多层路由

      支持解析嵌套的文件,如果创建嵌套文件夹结构,文件仍会以相同方式自动解析路由。

      • api/lambda/hello.ts -> {prefix}/hello
      • api/lambda/user/list.ts -> {prefix}/user/list

      #动态路由

      同样的,创建命名带有 [xxx] 的文件夹或者文件,支持动态的命名路由参数。动态路由的函数参数规则可以看 dynamac-path。

      • api/lambda/user/[username]/info.ts -> {prefix}/user/:username/info
      • api/lambda/user/username/[action].ts -> {prefix}/user/username/:action

      #白名单

      默认 api/lambda/ 目录下所有文件都会当作 BFF 函数文件去解析,但以下文件不会被解析:

      • 命名以 _ 开头的文件。例如:_utils.ts。
      • 命名以 _ 开头的文件夹下所有文件。例如:_utils/index.ts、_utils/cp.ts。
      • 测试文件。例如:foo.test.ts。
      • TypeScript 类型文件。例如:hello.d.ts。
      • node_module 下的文件。

      #RESTful API

      Modern.js 的 BFF 函数需要遵循 RESTful API 标准来定义,开发者需要按照一系列规则来定义 BFF 函数。

      设计原则

      BFF 函数不仅会在项目中被调用,也应该允许其他项目通过请求 SDK 或 Web fetch 调用。因此 Modern.js 没有在一体化调用时定义私有协议,而是通过标准的 HTTP Method,以及 params、query、body 等通用的 HTTP 请求参数来定义函数。

      #函数导出规则

      #HTTP Method 具名函数

      Modern.js BFF 函数的导出名决定了函数对应接口的 HTTP Method,如 get,post 等。例如导出一个 GET 接口:

      export const get = async () => {
        return {
          name: 'Modern.js',
          desc: '现代 web 工程方案',
        };
      };

      按照以下例子,则可导出一个 POST 接口:

      export const post = async () => {
        return {
          name: 'Modern.js',
          desc: '现代 web 工程方案',
        };
      };
      • 对应 HTTP Method,Modern.js 也支持了 9 种定义,即:GET、POST、PUT、DELETE、CONNECT、TRACE、PATCH、OPTIONS、HEAD,即可以用这些 Method 作为函数导出的名字。

      • 名字是大小不敏感的,如果是 GET,写成 get、Get、GEt、GET,都可以准确识别。而默认导出,即 export default xxx 则会被映射为 Get。

      #使用 Async 函数

      Modern.js 推荐将 BFF 函数定义为 Async 异步函数,即使函数中不存在异步流程,例如:

      export const get = async () => {
        return {
          name: 'Modern.js',
          desc: '现代 web 工程方案',
        };
      };

      这是因为在前端调用时,BFF 函数会自动转换成 HTTP 接口调用,而 HTTP 接口调用时异步的,在前端通常会这样使用:

      src/routes/page.tsx
      import { useState, useEffect } from 'react';
      import { get as hello } from '@api/hello';
      
      export default () => {
        const [text, setText] = useState('');
      
        useEffect(() => {
          hello().then(setText);
        }, []);
        return <div>{text}</div>;
      };

      因此,为了保持类型定义与实际调用体验统一,我们推荐在定义 BFF 函数时将它设置为异步函数。

      #函数参数规则

      函数参数规则分为两块,分别是请求路径中的动态路由(Dynamic Path)和请求选项(RequestOption)。

      #Dynamic Path

      动态路由会作为 BFF 函数第一部分的入参,每个入参对应一段动态路由。例如以下示例,level 和 id 会作为前两个参数传递到函数中:

      api/lambda/[level]/[id].ts
      export default async (level: number, id: number) => {
        const userData = await queryUser(level, uid);
        return userData;
      };

      在调用时直接传入动态参数:

      src/routes/page.tsx
      import { useState, useEffect } from 'react';
      import { get as getUser } from '@api/[level]/[id]';
      
      export default () => {
        const [name, setName] = useState('');
      
        useEffect(() => {
          getUser(6, 001).then(userData => setName(userData.name));
        }, []);
      
        return <div>{name}</div>;
      };

      #RequestOption

      Dynamic Path 之后的参数是包含 querystring、request body 的对象 RequestOption,这个字段用来定义 data 和 query 的类型。

      在不存在动态路由的普通函数中,可以从第一个入参中获取传入的 data 和 query,例如:

      api/lambda/hello.ts
      import type { RequestOption } from '@modern-js/server-runtime';
      
      export async function post({
        query,
        data,
      }: RequestOption<Record<string, string>, Record<string, string>>) {
        // do somethings
      }

      这里你也可以使用自定义类型:

      api/lambda/hello.ts
      import type { RequestOption } from '@modern-js/server-runtime';
      
      type IQuery = {
        // some types
      };
      type IData = {
        // some types
      };
      
      export async function post({ query, data }: { query: IQuery; data: IData }) {
        // do somethings
      }

      当函数文件使用动态路由规则时,动态路由会在 RequestOption 对象参数前。

      api/lambda/[sku]/[id]/item.ts
      export async function post(
        sku: string,
        id: string,
        {
          data,
          query,
        }: RequestOption<Record<string, string>, Record<string, string>>,
      ) {
        // do somethings
      }

      调用时也按照函数定义,传入对应的参数即可:

      src/routes/page.tsx
      import { post } from '@api/[sku]/[id]/item';
      
      export default () => {
        const addSku = () => {
          post('0001' /* sku */, '1234' /* id */, {
            query: {
              /* ... */
            },
            data: {
              /* ... */
            },
          });
        };
      
        return <div onClick={addSku}>添加 SKU</div>;
      };

      #扩展 BFF 函数

      普通的 BFF 函数写法有时并不能满足需求,我们正在设计一套更强大的 BFF 函数写法,让开发者更方便地扩展 BFF 函数。

      Note

      敬请期待

      #代码共享

      除 api/ 目录下的 BFF 函数可在 src/ 中通过一体化调用方式引用,项目中 src/ 和 api/ 目录默认不能直接引用对方代码。为实现代码共享,可在项目根目录创建 shared 目录,供 src/ 和 api/ 共同引用。