 UMI插件-使用插件实现模块与模块的传递
UMI插件-使用插件实现模块与模块的传递
  # 0.前言
在 umi3 升级 umi4 的过程中遇到一个网络请求相关的问题。在开发通用功能页面时,经常会出现不同模块间组件相互引用的情况。
# 1.通用功能 api 加载方式
 每一个通用功能模块下都有一个 apis 文件夹用于存放 接口文件,配置字段如下:
// apis.ts 文件
export default {
  alias: {
    name: "xxxxxx",
    code: "",
    method: "",
    url: () => "/api/unit/list",
  },
};
2
3
4
5
6
7
8
9
在通用模块的入口文件中,会将此 apis.ts 注册到 @xxx/request 依赖中,代码如下:
import { addApis } from "@xxx/request";
import apis from "./apis";
addApis(apis);
2
3
由上可见,api 网络请求模块的加载时机是:当前模块被加载后,才会将 apis 文件注入到 @xxx/request 的 api 变量中。
# 2. umi4 升级报错及解决方案
 这套执行机制在 umi3 中运行良好,升级到 umi4 后在某一场景下出现问题:
- 当通用功能模块不使用其余模块页面时,运行正常。
- 当通用功能模块调用其余模块,且其余模块此前未被点击时,会出现 api报错。
猜测原因:umi4 升级后,umi 插件采用按需运行的方式执行,只有当模块加载后,被加载模块的 api 接口才可以使用。
解决方案:利用 umi 插件机制在 @xxx/pages 中提前收集所有模块的 api 文件,并在 @xxx/platform 中插件中编译生成 requestData.ts 依赖文件,再通过 createRequest 传递给 @xxx/request 模块。
# 3. Umi 中的插件机制
 如何将 xxx-pages 工程下收集的内容传递给 xxx-core 工程?
可通过 umi 提供的 hook 钩子函数实现(底层采用的是和 webpack 相同的 tapable 库)
在 umi 中提供两种 hook 函数注册方式:register 和 registerMethods 函数。
umi 新官网这块写的不错,后续补充进去:https://umijs.org/docs/api/plugin-api#register
# 4.实际解决方案
在 xxx-pages 工程下的 plugin-xxx-pages 插件中主要实现两个功能:
- 在 - config.ts中暴露出一个- xxxPages属性,支持对路由页面的加载控制:- export default defineConfig({ xxxPages: { excludes: ["xxx1", "xxx2", "xxx3"], includes: ["yyy1", "yyy2", "yyy3"], }, });1
 2
 3
 4
 5
 6
- 功能二:收集所有通用功能页面的 - apis文件夹信息。
这里着重介绍功能二的写法:
export default function (api:IApi){
    .........,
    api.register({
      key: "addApi", /* 特别注意:在 umi 中 hook 以 `addXxx` 开头时,默认为 add 模式 */
      fn: ()=>{
        const filterRoutes = includeRoutes(excludeRoutes(customeRoutes));
        const apis = filterRoutes.map((item)=>{
          /* 收集所有 packages 下构建 lib 文件夹下所有的 apis 文件*/
          let api = winPath(join(xxxx,"./lib","apis"));
          /* 检测此文件夹路径是否存在 */
          if(existsSync(api)){
            /* 将 api 文件夹路径返回 */
            return {
              name: item.access,
              path: api
            }
            return false;
          }
        }).filter(Boolean);
        return [
          /* 此文件夹本来用于收集公共 api 文件,现在感觉可以删掉了 */
          {
            name: "customApis",
            path: "@umijs/plugin-xxx-pages/lib/apis"
          },
          ...apis
        ]
      }
    })
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
在 xxx-platform 下的 plugin-xxx-request 模块下,会构建生成 requestData 文件
在此 umi 插件中,此功能模块的示例代码如下:
import { winPath, Mustache } from "@umijs/utils"; /* 使用 Mustache 作为模板解析引擎*/
export default function (api){
  api.onGenerateFiles(async ()=>{
    const {paths} = api;
    /* 此函数用于收集 pc-local-demo 下的 apis 文件 */
    function getApiList(){
      /* 可根据配置读 api 文件夹还是 apis 文件夹 */
      const apiFolder = api.config?.singular ? 'api' : 'apis';
      const apiFiles = glob.sync('*.{ts,js,json}',{
        cwd: winPath(join(paths.absSrcPath!, apiFolder))
      }).map(()=>{
        ..... // 此部分对数据做一层 Format 操作,不是很重要
      })
      return apiFiles;
    }
    /*
       api.applyPlugins 触发 `addApi` 这个 Hook 钩子,并是指定一个初始值 getApiList()
       `apiFiles` 在 add 模式下是一个数组,会将 getApiList 的结果作为数组的第一项,
       将 `xxx-pages` 中收集的 apis 数组项 push 到数组后面。
    */
    const apiFiles = await api.applyPlugins({
      key: "addApi",
      type: api.ApplyPluginsType.add /* 也可以显式指明为 add 模式*/,
      initialValue: getApiList();
    })
    api.writeTmpFile({
      path: `requestData.ts`,
      content: Mustache.render(requestDataTpl,{apiFiles,xxxx,xxx})
    })
  })
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
在
umi插件中,可通过const { path } = api;方式暴露出许多实用的地址:
path
absNodeModulesPath,node_modules目录绝对路径
absOutputPath,输出路径,默认是./dist
absPagesPath,pages目录绝对路径
absSrcPath,src目录绝对路径,需注意src目录是可选的,如果没有src目录,absSrcPath等同于cwd。
absTmpPath,临时目录绝对路径
cwd,当前路径注意: 注册阶段不能获取到。因此不能在插件里直接获取,要在
hook里使用。
根据 Mustache 模块解析引擎,解析如下代码:
// .tpl 文件
{{#apiFiles}}
import {{name}} from "{{{path}}}";
{{/apiFiles}}
{{#apiFiles}}
const apis = {
	...defaultApis,
	{{#apiFiles}}
  ...{{name}}
  {{/apiFiles}}
}
{{/apiFiles}}
2
3
4
5
6
7
8
9
10
11
12
13
最终结果会在 src/.umi/plugin-xxxRequest 文件夹下生成 requestData.ts 文件
import index from "@/apis/index"; //  此部分为 getApiList() 获取的结果
import customApis from "@umijs/plugin-xxx-pages/lib/apis"; // 此部分为自定义 api 文件
import xx1 from "xxx:xxxxx/@xxx/page-xxx1/lib/apis";
import xx2 from "xxx:xxxxx/@xxx/page-xxx2/lib/apis";
import xx3 from "xxx:xxxxx/@xxx/page-xxx3/lib/apis";
const apis = {
  ...index,
  ...cunstomApis,
  ...xxx1,
  ...xxx2,
  ...xxx3,
};
2
3
4
5
6
7
8
9
10
11
12
最后通过此配置文件被 @xxx/request 的一个参数传入创建 requestInstance 对象,最后经过一系列转化,转化为 umi-request(url,options) 中的 url 以及 option 。
# 5. 扩展性考虑
通过上述方案,已实现 api 文件的注册,通用功能模块中的 addApi(apis) 就显的非常冗余了,实则不然,api 文件依赖的收集需要借助xxx-platform 工程下的 plugin-request 插件实现,若有项目组在使用时不想使用团队提供的 xxxRequest 方法,项目组可自行实现 api 文件收集。
