一般你会在官网上看到 Umi 的配置分为普通配置和运行时配置。在本文中我们都会详细说明,并且还会聊一聊其他的官网没有明说的配置。
普通配置是在构建时读取的,你可以理解为传给 webpack 的配置。(当然实际上,它最终也给传给了其他的工具或者组件)这意味着它是运行在 node 环境中的,所以你可以在普通配置文件中,去使用一些 node 的 api.
而运行时配置时是跑在浏览器端的,这意味着你可以在运行时配置的文件中写函数、jsx还有其他的一些浏览器端的依赖。注意在这里使用 node 的 api 会导致程序崩溃。
配置
Umi 的配置文件两个 .umirc.ts
和 config/config.ts
。
Umi 中约定的文件都支持 ts 和 js。你可以根据自己项目中使用的语言做出合理的选择,因为这个课程推荐全程使用 typescript 开发,所以在说明的时候,我会直接写明文件后缀是
ts|tsx
。
.umirc.ts
与 config/config.ts 文件功能相同,2 选 1 。.umirc.ts 文件优先级较高
配置文件,包含 Umi 内置功能和插件的配置。
config/config.ts
与 .umirc.ts 文件功能相同,2 选 1 。.umirc.ts 文件优先级较高
配置文件,包含 Umi 内置功能和插件的配置。
配置文件实践
新建 config/config.ts
export default { }; 复制代码
为了获得更好的开发体验,我们引入 Umi 导出的配置文件定义方法 defineConfig
。
import { defineConfig } from "umi"; export default defineConfig({ }); 复制代码
以上两种写法,在功能上是没有区别的,但是有个好处是,我们在编写配置项的时候,会有“联想”推荐和类型校验,比如,你输入一个字母 t
,就会自动联想推荐 Umi 支持的所有配置,这个配置项还会根据你引用的插件数量而发生变化。
import { defineConfig } from "umi"; export default defineConfig({ t // targets // (property) targets?: { // [key: string]: any; // } | undefined // terminal // (property) IConfigFromPlugins.terminal?: {} | undefined // theme // (property) IConfigFromPlugins.theme?: {} | undefined // title // (property) IConfigFromPlugins.title?: string | undefined // tmpFiles // (property) IConfigFromPlugins.tmpFiles?: boolean | undefined }); 复制代码
并且在你的值写错时 VS Code 就会直接给出标红提示,而不用等到运行构建时,才能暴露问题。这在开发前期你不熟悉 Umi 框架的时候,将会非常有用。在你觉得某个功能没有按照预期表现时,你可以很好的排查你的配置没有写错。
import { defineConfig } from "umi"; export default defineConfig({ title:[123] // 此处标红,鼠标移上去,会有详细的错误说明 // 不能将类型“number[]”分配给类型“string”。ts(2322) // pluginConfig.d.ts(9, 1): 所需类型来自属性 "title",在此处的 "ConfigType" 类型上声明该属性 }); 复制代码
将上面的 title
配置修改为正确的 string
类型:"Hello Umi"。
运行项目(pnpm start),你将在浏览器中看到也看的 title 从 http://127.0.0.1:8000/
变成了 Hello Umi
。
其他的配置,我们将会在后续用到的时候逐个说明。
运行时配置
运行时配置,光从字面上理解,那就是为框架提供一些动态的配置数据,简单的概括成一句废话就是运行时配置是运行时使用的配置。
这意味着其实我们可以在这里面执行一些异步的 Effect。
首先运行时里面支持的所有配置和普通配置一样,都是有 Umi 插件扩展而来的。所以如果你遇到在使用一个配置时,Umi 告诉你这个配置不存在,那最大的可能就是你少用了某个插件。
Umi 的运行时配置文件只有一个 src/app.ts|x
。
我们以 render
为例来简要说明,感受运行时配置的特性。
新建 src/app.ts
export function render(oldRender: any) { fetch("/api/auth").then((auth) => { if (auth.isLogin) { oldRender(); } else { history.push("/login"); oldRender(); } }); } 复制代码
render
是一个函数,会传入旧的页面 render
函数。最简单的理解这个配置,你可以把它当作一种阻断页面渲染的手段,就是当你配置了 render
你就暂停了页面的渲染,当你执行完所有你需要的代码逻辑之后,你调用 oldRender
就可以让页面继续渲染了。
如上述的伪代码中,我们先请求了 /api/auth
接口,检测用户是否已登录,如果已登录(isLogin)就继续渲染页面,如果未登录,就跳转到登录页面("/login")。
在这里你可以执行任意的逻辑,甚至如果你觉得你的页面白屏(或者loading)时间太短,你可以在这里倒数三秒再开始渲染页面。
export function render(oldRender: any) { setTimeout(() => { oldRender(); }, 3000); } 复制代码
重新访问开发服务,刷新页面,你需要等待至少 3 秒,才能看到页面被正确渲染。
其他配置
除了官网上提到的普通配置和运行配置,其实我们在使用 Umi 开发项目的时候,还会用到一些其他的配置,比如环境变量,或者在插件中配置等手段,这些内容在官网的文档中都有,但是并不是被归类到 Umi 的配置中。
环境变量
定义环境变量有两个地方,一个是 .env
文件
新建 .env
PORT=8888 复制代码
运行开发服务,服务端口号被正确修改为 8888
ready - App listening at http://127.0.0.1:8888 复制代码
另一个地方是启动 umi
命令的时候,一起传入.
如在 package.json
中配置 ANALYZE=1
{ "name": "umi4-course", "scripts": { "start": "umi dev", "build": "ANALYZE=1 umi build" }, } 复制代码
执行 pnpm build
,你可以在项目构建介绍,访问 http://127.0.0.1:8888
查看构建产物中都包含哪些依赖。
将构建产物添加到 gitignore 中,增加
dist
。
需要注意的是,在执行命令时配置环境变量,有平台的差异。
# OS X, Linux $ PORT=3000 umi dev # Windows (cmd.exe) $ set PORT=3000&&umi dev 复制代码
一般我们都使用 cross-env
来消除平台差异
$ pnpm install cross-env -D $ cross-env PORT=3000 umi dev 复制代码
{ "name": "umi4-course", "scripts": { "start": "umi dev", "build": "cross-env ANALYZE=1 umi build" }, } 复制代码
插件中配置 Umi
在插件开发中使用 modifyConfig
修改用户的配置,这是我最喜欢用的一种方式,因为可以“黑盒”的修改一些 Umi 的配置,其实也相当于修改了 Umi 的默认行为,比如在同一个团队中,我们可以将一些共同的配置,放到插件中,这样每一个项目的配置文件就会非常的干净。同团队中,会大大减少配置文件的维护工作。
像 alita 中,使用 modifyConfig
修改了 alita 项目中默认的一些配置信息,这样 alita 项目中的配置文件就可以很干净。加入说我们不使用 modifyConfig
,而是要求用户严格编写如下配置,才能正常使用。这无疑会大大的增加框架的维护成本。
const configDefaults: Record<string, any> = { history: { type: 'hash' }, title: false, // 默认内置了 Helmet targets: { ie: 9, }, hash: true, hd: api.userConfig.appType !== 'pc' ? {} : false, dva: { enableModelsReExport: true, }, model: {}, request: {}, displayName: 'alita-demo', conventionRoutes: { // 规定只有index文件会被识别成路由 exclude: [ /(?<!(index|\[index\]|404)(\.(js|jsx|ts|tsx)))$/, /model\.(j|t)sx?$/, /\.test\.(j|t)sx?$/, /service\.(j|t)sx?$/, /models\//, /components\//, /services\//, ], }, ...api.userConfig, }; api.modifyConfig((memo: any) => { Object.keys(configDefaults).forEach((key) => { memo[key] = configDefaults[key]; }); return memo; }); 复制代码
在插件开发中设置环境变量会非常的简单,直接将环境变量设置成对应的值即可。
编写项目中的插件
新建 plugin.ts
存在
plugin.ts
这个文件,会被当前项目加载为 Umi 插件,这属于 Umi 的约定行为,你可以在这里解一些需要插件级支撑的问题。出了默认加载这个插件,你也可以使用 plugins 配置,引入项目中的其他相对路径的插件文件。
import { IApi } from "umi"; export default (api: IApi) => { // 通过插件设置环境变量 process.env.COMPRESS = "none"; // 通过插件修改配置 api.modifyConfig((memo: any) => { memo.title = "Hello Umi"; return memo; }); }; 复制代码
通过上面的插件,我们定义了一个环境变量 COMPRESS = "none"
,它的作用时在执行 umi build
构建的时候不压缩代码,这在定位线上 bug 的时候非常的有用。当然我们这里只是演示,真实的项目中,肯定不能让这个构建一直保持不压缩,而是应该在线上出现 bug 时,临时使用一次这个变量,编译一次项目。
我们还将用户的配置 title
修改成了 "Hello Umi",这样我们就可以删掉本地配置文件中的 title
配置。看起来就像是 Umi 默认的 title
就是 Hello Umi
。当然我们也可以在这里写上其他的配置,修改 Umi 的其他默认行为,定制化属于自己的一个 Umi 框架。