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,输出路径,默认是./distabsPagesPath,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 文件收集。