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
文件收集。