Technical Documentation: umijs/umi
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (README.md)
umi
A framework in react community ✨
Please consider following this project's author, sorrycc, and consider starring the project to show your ❤️ and support.
[🚀 Read the launch post →](https://umijs.org/blog/umi-4-rc)
[📚 Learn Umi →](https://umijs.org/)
Contribution
See Contributing Guide.
Core Maintainers
Core Maintainers are community members who have contributed significantly to the project through addressing issues, fixing bugs, and implementing enhancements/features.
Maintainers
Maintainers are community members who have had 10 or more PRs merged in umi or have spent a lot of time contributing to the umi community or addressing issues.
Contributors
Contributors are community members who have had 1 or more PRs merged in umi. If you are a contributor, you can contact me [sorrycc] to join the Contributor Group.
Community
LICENSE
README (`docs/README.md`)
UmiJS
A static site base on dumi.
Development
# install dependencies
$ pnpm install
# start dev server
$ pnpm start
# build docs
$ pnpm run buildLICENSE
MIT
Docs Docs Guides Getting Started (`docs/docs/docs/guides/getting-started.md`)
order: -1 toc: content
快速上手
环境准备
首先得有 node,并确保 node 版本是 22 或以上。(推荐用 nvm 来管理 node 版本,windows 下推荐用 nvm-windows)
mac 或 linux 下安装 nvm。
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
$ nvm -v
0.40.3安装 node。
$ nvm install 22
$ nvm use 22
$ node -v
v22.21.1然后需要包管理工具。node 默认包含 npm,但也可以选择其他方案,
安装 pnpm。
curl -fsSL https://get.pnpm.io/install.sh | sh -
$ pnpm -v
7.3.0创建项目
通过官方工具创建项目,
PNPM
$ pnpm dlx create-umi@latest
✔ Install the following package: create-umi? (Y/n) · true
✔ Pick Npm Client › pnpm
✔ Pick Npm Registry › taobao
Write: .gitignore
Write: .npmrc
Write: .umirc.ts
Copy: layouts/index.tsx
Write: package.json
Copy: pages/index.tsx
Copy: pages/users.tsx
Copy: pages/users/foo.tsx
> @ postinstall /private/tmp/sorrycc-vylwuW
> umi setup
info - generate filesBUN
$ bunx create-umi
✔ Pick Umi App Template › Simple App
✔ Pick Npm Client › pnpm
✔ Pick Npm Registry › npm
Write: .gitignore
Write: .npmrc
Write: .umirc.ts
Write: package.json
Copy: src/assets/yay.jpg
Copy: src/layouts/index.less
Write: src/layouts/index.tsx
Copy: src/pages/docs.tsx
Copy: src/pages/index.tsx
Write: tsconfig.json
Copy: typings.d.ts
ready - Git initialized successfullyNPM
$ npx create-umi@latest
Need to install the following packages:
create-umi@latest
Ok to proceed? (y) y
✔ Pick Umi App Template › Simple App
✔ Pick Npm Client › npm
✔ Pick Npm Registry › taobao
Write: .gitignore
Write: .npmrc
Write: .umirc.ts
Write: package.json
Copy: src/assets/yay.jpg
Copy: src/layouts/index.less
Write: src/layouts/index.tsx
Copy: src/pages/docs.tsx
Copy: src/pages/index.tsx
Write: tsconfig.json
Copy: typings.d.ts
> postinstall
> umi setupYARN
$ yarn create umi
success Installed "[email protected]" with binaries:
- create-umi
✔ Pick Umi App Template › Simple App
✔ Pick Npm Client › yarn
✔ Pick Npm Registry › taobao
Write: .gitignore
Write: .npmrc
Write: .umirc.ts
Write: package.json
Copy: src/assets/yay.jpg
Copy: src/layouts/index.less
Write: src/layouts/index.tsx
Copy: src/pages/docs.tsx
Copy: src/pages/index.tsx
Write: tsconfig.json
Copy: typings.d.ts
yarn install v1.22.18
success Saved lockfile.
$ umi setup
info - generate files注:使用 bun 初始化项目会更快,需要 bun >= 0.4.0 版本。
国内建议选 pnpm + taobao 源,速度提升明显。这一步会自动安装依赖,同时安装成功后会自动执行 umi setup 做一些文件预处理等工作。
从模板创建项目
# 从 @umijs/electron-template 创建一个 electron 模板
pnpm create umi --template electron参数选项
使用 create-umi 创建项目时,可用的参数如下:
| option | description |
|---|---|
--no-git |
创建项目,但不初始化 Git |
--no-install |
创建项目,但不自动安装依赖 |
启动项目
执行 pnpm dev 命令,
$ pnpm dev
╔═════════════════════════════════════════════════════╗
║ App listening at: ║
║ > Local: https://127.0.0.1:8000 ║
ready - ║ > Network: https://192.168.1.1:8000 ║
║ ║
║ Now you can open browser with the above addresses👆 ║
╚═════════════════════════════════════════════════════╝
event - compiled successfully in 1121 ms (388 modules)
event - MFSU compiled successfully in 1308 ms (875 modules)在浏览器里打开 http://localhost:8000/,能看到以下界面,
启用 Prettier(可选)
如果需要用 prettier 做项目代码的自动格式化,执行 pnpm umi g,
$ pnpm umi g
✔ Pick generator type › Enable Prettier -- Enable Prettier
info - Write package.json
info - Write .prettierrc
info - Write .prettierignore
info - Install dependencies with pnpm部署发布
执行 pnpm build 命令,
> umi build
event - compiled successfully in 1179 ms (567 modules)
event - build index.html产物默认会生成到 ./dist 目录下,
./dist
├── index.html
├── umi.css
└── umi.js完成构建后,就可以把 dist 目录部署到服务器上了。
Docs Blog Code Splitting.En US (`docs/docs/blog/code-splitting.en-US.md`)
toc: content order: 5 group: title: Blog
代码拆分指南
Umi 4 默认 按页拆包、按需加载(这近似等同于 Umi 3 中的 dynamicImport),通过 loading.tsx 来自定义加载动画。
使用分包策略
Umi 4 内置了不同的代码拆分策略 ( codeSplitting ) ,通过配置开启:
// .umirc.ts
export default {
codeSplitting: {
jsStrategy: 'granularChunks',
},
};这会按照一定的优化策略进行自动分包,若需手动进行更细致的分包,请参见下文。
手动拆分
当你的产物体积变大时,可进一步手动拆包:
import { lazy, Suspense } from 'react'
// './Page' 该组件将被自动拆出去
const Page = lazy(() => import('./Page'))
export default function() {
return (
<Suspense fallback={<div>loading...</div}>
<Page />
</Suspense>
)
}通常情况下,我们会手动拆分引用了较大第三方库的组件,实现按需加载。
分析产物构成
通过指定 ANALYZE 环境变量可以分析产物构成,根据分析结果来修改代码和进一步决策。
Docs Blog Code Splitting (`docs/docs/blog/code-splitting.md`)
toc: content order: 5 group: title: Blog
代码拆分指南
Umi 4 默认 按页拆包、按需加载(这近似等同于 Umi 3 中的 dynamicImport),通过 loading.tsx 来自定义加载动画。
使用分包策略
Umi 4 内置了不同的代码拆分策略 ( codeSplitting ) ,通过配置开启:
// .umirc.ts
export default {
codeSplitting: {
jsStrategy: 'granularChunks',
},
};这会按照一定的优化策略进行自动分包,若需手动进行更细致的分包,请参见下文。
手动拆分
当你的产物体积变大时,可进一步手动拆包:
import { lazy, Suspense } from 'react'
// './Page' 该组件将被自动拆出去
const Page = lazy(() => import('./Page'))
export default function() {
return (
<Suspense fallback={<div>loading...</div}>
<Page />
</Suspense>
)
}通常情况下,我们会手动拆分引用了较大第三方库的组件,实现按需加载。
分析产物构成
通过指定 ANALYZE 环境变量可以分析产物构成,根据分析结果来修改代码和进一步决策。
Docs Blog Develop Blog Using Umi.En US (`docs/docs/blog/develop-blog-using-umi.en-US.md`)
order: 1 toc: content group: title: Blog
使用 Umi 开发一个 Blog
这篇文章将带领你使用 Umi.js 搭配 PlanetScale, Prisma 和 Tailwindcss等服务与技术,开发一个简单的博客网站,并部署到 Vercel 服务。
成果展示
成果看起来是这样的:你会有一个博客首页展示你的文章 https://umi-blog-example.vercel.app/
点击某一篇文章可以看到这篇文章的完整内容:https://umi-blog-example.vercel.app/posts/5
当然,你还可以在博客中发表新文章:https://umi-blog-example.vercel.app/posts/create
前提是你有登入:https://umi-blog-example.vercel.app/login
准备好了吗,让我们马上开始吧!
环境准备
首先,你必须确保你的本地环境已经准备好进行一个 Umi.js 项目的开发。如果你目前还没有开发过 Umi.js 项目,也没有在本地搭建过开发环境,建议你先阅读 开发环境 这篇教学。
配置完本地环境以后,你已经准备好开始开发 Umi.js 项目了!跟着 脚手架 这篇文档的教学,快速初始化一个 Umi.js 项目吧。
调整目录结构
因为我们的博客网站会使用到 Umi 4 的 API 路由功能,所以我们需要对脚手架自动产生的目录结构进行一些调整。你现在的目录结构应该看起来是这样的:
.
├── assets
│ └── yay.jpg
├── layouts
│ ├── index.less
│ └── index.tsx
├── node_modules
├── package.json
├── pages
│ ├── docs.tsx
│ └── index.tsx
├── pnpm-lock.yaml
├── tsconfig.json
└── typings.d.ts我们需要把 assets, layouts, pages 目录从根目录移动到 src 目录下,移动之后他看起来是这样的:
.
├── src
│ ├── assets
│ │ └── yay.jpg
│ ├── layouts
│ │ ├── index.less
│ │ └── index.tsx
│ └──── pages
│ ├── docs.tsx
│ └── index.tsx
├── node_modules
├── package.json
├── pnpm-lock.yaml
├── tsconfig.json
└── typings.d.ts:::info
为什么要这样做呢?这是因为根目录下的 api 目录会被我们作为 API 路由生成构建产物的地方,如果我们没有多一层 src 目录的话,我们的 API 路由目录就会和构建产物目录冲突啦~
:::
注册 PlanetScale 服务
我们的博客将会把用户和文章的数据保存在 MySQL 数据库中。然而,我们不需要真的自己准备一台服务器来运行数据库,我们可以使用免费的 PlanetScale来一键部署一个开箱即用的数据库!
首先从 https://auth.planetscale.com/sign-in登入你的账号,如果你没有注册过,可以选择使用 GitHub 一键登入或是点击 Sign up for an account 注册一个账号。
登入之后,在你的 PlanetScale 账号建立一个数据库(如果你是第一次注册,则可以看到他的教学步骤带领你一步步建立一个数据库):
建立完成后,点击数据库页面右上角的 Connect 按钮:
你会在弹窗里面看到一个 Connect With 的下拉选单,选择 Prisma,然后就能获得一串这个格式的字符串:
DATABASE_URL='mysql://************:************@************.ap-southeast-2.psdb.cloud/umi-blog-example?sslaccept=strict'这个字符串就是我们要用来让 Prisma 连接数据库的连线信息,暂时先把他记录起来就可以了 👍
安装依赖
接下来,帮我们的 Umi 项目安装这次教程会用到的依赖:
pnpm i -d prisma @types/bcryptjs @types/jsonwebtoken
pnpm i @prisma/client bcryptjs jsonwebtoken这两行命令帮我们安装了这些包:
- prisma和 @prisma/client:这两个包让我们可以用 Prisma来方便地串接数据库,不需要担心任何复杂的 SQL 命令。
- bcryptjs:这个包让我们将用户注册后的密码使用 Bcrypt哈希加密算法来安全的存储在数据库中。
- jsonwebtoken:这个包让我们可以方便地使用用 JWT(Json Web Token) 实现用户鉴权。
然后将 package.json 中 scripts 里面的 build 脚本从
"scripts": {
"dev": "umi dev",
"build": "umi build",
"postinstall": "umi setup",
"start": "npm run dev"
},改成
"scripts": {
"dev": "umi dev",
"build": "prisma generate && umi build",
"postinstall": "umi setup",
"start": "npm run dev"
},这可以确保每次开始构建以前都已经生成好 Prisma 客户端。
安装 Tailwindcss
使用 Umi 提供的微生成器来在项目中启用 Tailwindcss :
npx umi g tailwindcss他会帮我们安装 Tailwindcss 所需要的依赖,然后生成需要的文件。
初始化页面组件
接下来,当你使用 pnpm dev启动项目后,可能会看到错误讯息并且启动失败了。这是因为我们在配置中声明了一些页面,但并没有帮他建立对应的页面组件!
我们可以使用 Umi 的微生成器来自动生成这些页面:login.tsx, posts/post.tsx, posts/create.tsx:
npx umi g page login posts/post posts/create新增后的目录结构是这样的:
src
├── assets
│ └── yay.jpg
├── layouts
│ ├── index.less
│ └── index.tsx
└── pages
├── index.less
├── index.tsx
├── login.less
├── login.tsx
└── posts
├── create.less
├── create.tsx
├── post.less
└── post.tsx现在再输入一次 pnpm dev 就可以看到我们的网站正常启动了。
配置 umi 项目
最后一步,就是要来对 Umi 项目进行配置,完整的配置可以参考 配置 这篇教学文档,在本次的教学中,只要按照以下配置即可:
// .umirc.ts
export default {
npmClient: 'pnpm',
apiRoute: {
platform: 'vercel',
},
routes: [
{ path: '/', component: 'index' },
{ path: '/posts/create', component: 'posts/create' },
{ path: '/login', component: 'login' },
{ path: '/posts/:postId', component: 'posts/post' },
],
plugins: [require.resolve('@umijs/plugins/dist/tailwindcss')],
tailwindcss: {},
};其中,apiRoute 这个配置项告诉 Umi 我们的项目启用了 API 路由 这个功能,而 platform: 'vercel' 代表我们要部署到Vercel 平台,在 umi build 的时候会针对这个平台来将 API 路由进行打包。
为了顺利部署项目到 Vercel ,你需要在项目根目录下加入一个 vercel.json 配置文件:
{
"build": {
"env": {
"ENABLE_FILE_SYSTEM_API": "1"
}
},
"rewrites": [
{
"source": "/api/:match*",
"destination": "api/:match*"
}
]
}route 这个配置项则声明了我们网站的路由架构,可以看到我们的博客网站有这些页面:
/: 首页/posts/create: 建立文章/login: 登入/posts/:postId: 某篇文章
plugins 配置项代表我们启用了哪些 Umi 插件,其中 @umijs/plugins/dist/tailwindcss 是在 Umi 中使用 Tailwindcss
的插件。下面一项 tailwindcss: {} 则是从配置来启用该插件的意思。
API 路由设计
我们的整个博客网站由两大部分构成,一半是运行在浏览器内的前端代码,另一半则是运行在 Serverless Function 中的服务端代码。
为什么需要分成两边呢?这是因为有些代码我们不能让他在浏览器内运行,比如说用户鉴权、串接数据库等等的功能,这些必须作为一个服务然后以 API 的形式暴露给前端页面调用,这个部分可以透过 Umi 4 的 API 路由功能来实现。
(这里好像放一张图可以比较清楚地解释)
因为我们已经在 .umirc.ts 配置文件中声明了我们要启用 API 路由功能,现在可以直接在 src 目录下添加一个 api目录,这个目录下以约定式路由的设计来提供 API 路由的开发。
作为一个博客的 API 服务,不难想到我们会需要这些接口来供用户调用:
- 用户注册:
POST /api/register - 用户登入:
POST /api/login - 发表文章:
POST /api/posts - 查询所有文章:
GET /api/posts - 查询一篇文章:
GET /api/posts/{postId}
所以我们可以在 src/api 目录下建立这些新文件:
src
├── api
│ ├── login.ts
│ ├── register.ts
│ └── posts
│ ├── [postId].ts
│ └── index.ts
...:::info
你可能注意到了,这里有一个文件叫做 [postId].ts,这个写法代表这个路由可以动态匹配不同的值。例如 /api/posts/1 和 /api/posts/2 两个请求都会交给 src/api/posts/[postId].ts处理,但他们的 req.params 分别是 { postId: 1 } 和 { postId: 2 }。
:::
这里的每个 .ts 文件就是一个 API Handler,他们默认导出一个函数用来处理发送到该路径的请求,我们可以暂时先这样写:
import type { UmiApiRequest, UmiApiResponse } from 'umi';
export default async function (req: UmiApiRequest, res: UmiApiResponse) {
res.status(400).json({ error: 'This API is not implemented yet.' });
}然后你可以试着用浏览器或 Postman 访问看看这些 API 路由(例如 http://localhost:8000/api/login ),就可以看到你刚刚写的响应数据了 🎉
我们等一下再回来实作这些 API 路由的实际功能,因为有一个更重要的事情要先做。
定义 Schema
现在必须要先确定一件事情:我们要保存哪些数据、以怎么样的形式保存在数据库,又是以怎么样的形式响应给前端的?
文章数据
文章数据(Post)每笔数据就代表了一篇博客里面的文章,我们可以按自己的系统需求来设计他需要保存的内容,例如我们的范例保存了这些数据:
id: 文章 IDtitle: 文章标题authorId: 作者的 IDtags: 文章的标签(以逗号隔开)imageUrl: 文章封面图片的链接content: 文章的内文(markdown 格式)
用户数据
用户数据 (User) 每笔数据代表一个在我们博客注册的用户数据,我们可以按照自己的系统需求来设计他需要保存的内容,例如我们的范例保存了这些数据:
id: 用户 IDname: 名称email: 邮箱avatarUrl: 头像链接passwordHash: 加密过的密码
生成配置
这个章节可以考虑阅读 Prisma 官方的教学文档
定义好数据格式以后,我们要让 Prisma 帮我们根据 Schema 设计来生成对应的客户端,并且自动的将数据库迁移至为我们设计的格式,
连线到数据库
第一步,我们在根目录建立一个 .env 文件,并且在里面加入刚刚在 注册 PlanetScale 服务 章节拿到的连线信息。
# .env
DATABASE_URL='mysql://************:************@************.ap-southeast-2.psdb.cloud/umi-blog-example?sslaccept=strict'编写 Prisma 配置
第二步,在根目录下建立一个 prisma/schema.prisma 文件,并把我们设计的 Schema 按照 Prisma 语法 写进去文件中:
generator client {
provider = "prisma-client-js"
previewFeatures = ["referentialIntegrity"]
}
datasource db {
provider = "mysql"
referentialIntegrity = "prisma"
url = env("DATABASE_URL")
}
model Post {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String @db.VarChar(255)
content String?
author User @relation(fields: [authorId], references: [id])
authorId Int
imageUrl String?
tags String
@@index(authorId)
}
model User {
id Int @id @default(autoincrement())
email String @unique
passwordHash String
name String?
posts Post[]
avatarUrl String?
}完成后,在命令行输入
npx prisma migrate dev --name init他会帮我们将 MySQL 数据库迁移为我们定义的格式。 接下来,在命令行输入
npx prisma generate他会帮我们生成一个按照我们的 Schema 设计量身定制的客户端包。
至此,我们已经顺利处理完数据库的部分,接下来只要专注于如何在 API 路由中使用 Prisma 客户端包来获取与更新数据即可。
实现 API 路由
我们现在要回头来实现刚刚建立的那些 api 目录下的 .ts 文件了,只要我们自己清楚:
- API 会如何被调用 (path, request header, request body)
- 我们应该在 API 内做什么事
- 响应什么内容回去 (status, response header, response body)
那么 API 路由的开发就像写编写一个简单的函数一样。
用户注册
当用户对 /api/register 发起 POST 请求时,代表他们想要在我们的博客网站注册一个账号。
:::info
你可以在 https://github.com/umijs/umi-blog-example/blob/main/src/api/register.ts 找到这个示范的源代码!
:::
/* Detailed source-code truncated for AI context efficiency. */完成开发后,可以使用 Postman 对这个 API 发起请求,测试功能是否正常运作。
用户登入
当用户对 /api/login 发起 POST 请求时,代表他们想要登入我们的博客网站并取得一个 JWT 令牌,这可以让他用于建立新文章。
:::info
这个部分留给读者练习,你可以在 https://github.com/umijs/umi-blog-example/blob/main/src/api/login.ts 找到这个示范的源代码!
:::
发表文章
当用户对 /api/posts 发起 POST 请求时,代表他们想要在我们的博客网站发表一篇文章。
:::info
这个部分留给读者练习,你可以在 https://github.com/umijs/umi-blog-example/blob/main/src/api/posts/index.ts 找到这个示范的源代码!
:::
查询所有文章
当用户对 /api/posts 发起 GET 请求时,代表他们想要查询所有文章的数据。
:::info
这个部分留给读者练习,你可以在 https://github.com/umijs/umi-blog-example/blob/main/src/api/posts/index.ts 找到这个示范的源代码!
:::
查询某篇文章
当用户对 /api/posts/{postId} 发起 GET 请求时,代表他们想要查询某篇文章的数据。
:::info
这个部分留给读者练习,你可以在 https://github.com/umijs/umi-blog-example/blob/main/src/api/posts/%5BpostId%5D.ts 找到这个示范的源代码!
:::
实作页面组件
在这个章节,我们主要要学习如何在页面组件调用 API,来实现获取文章或注册等前后端交互的行为:
// pages/index.tsx
import React, { useEffect, useState } from 'react';
import { history } from "umi";
export default function HomePage() {
const [posts, setPosts] = useState<any[]>();
return (
<div>
{!posts && <p>Loading...</p>}
{posts && <div>
{posts.map(post => <div key={post.id}>
<div onClick={() => history.push(`/posts/${post.id}`)}>
<p>{post.title}</p>
</div>
</div>)}
</div>}
</div>
);
}可以看到我们在首页组件维护了一个 posts 状态,当 posts 是 undefined 时,我们认为是数据尚未加载完成。所以我们可以加入一个 useEffect让他在组件加载后对 API 路由发起一个请求,去查询目前所有的文章列表:
// pages/index.tsx
import React, { useEffect, useState } from 'react';
import { history } from 'umi';
export default function HomePage() {
const [posts, setPosts] = useState<any[]>();
async function refresh() {
try {
const res = await fetch('/api/posts');
if (res.status !== 200) {
console.error(await res.text());
}
setPosts(await res.json());
} catch (err) {
console.error(err);
}
}
useEffect(() => {
refresh();
}, []);
return (
<div>
{!posts && <p>Loading...</p>}
{posts && (
<div>
{posts.map((post) => (
<div key={post.id}>
<div onClick={() => history.push(`/posts/${post.id}`)}>
<p>{post.title}</p>
</div>
</div>
))}
</div>
)}
</div>
);
}可以看到我们加入了一个 refresh 函数,他会帮我们从 API 路由查询目前的文章列表。若你现在访问这个页面,应该可以看到一开始是 Loading ...
等一阵子就会有全部文章的标题被渲染出来的效果。
最后只要帮他加一点样式:
其他页面留给读者实作,可以加入自己的想法及样式的设计来实现,源代码可参考:https://github.com/umijs/umi-blog-example/blob/main/src/pages
部署
最后,将你的项目提交到 git 服务上,然后登入 Vercel:
如果你的项目代码是提交到 GitHub,那么建议你选择 GitHub 登入,这样你就可以在 Vercel 直接导入现有的代码仓库了 👍
导入以后,他会自动检测到这个项目是使用 Umi.js 框架搭建的,并且自动化完成相关的配置,因此直接点击 Deploy 即可开始部署!
等他部署完成以后,你的博客就正式上线啦 👍
但这个时候你会发现,网站内的 API 路由没有办法正常工作,这是因为他缺少了连线到数据库需要的环境变量。我们需要帮他配置一下:
在项目配置页面,下面有可以设置环境变量的地方:
你可以把 DATABASE_URL, JWT_KEY 等等你用到的环境变量从这里传入。
加入环境变量以后,点击 Redeploy 重新部署一次版本,你的博客就能正常运作啦~
Docs Blog Develop Blog Using Umi (`docs/docs/blog/develop-blog-using-umi.md`)
order: 1 toc: content group: title: Blog
使用 Umi 开发一个 Blog
这篇文章将带领你使用 Umi.js 搭配 PlanetScale, Prisma 和 Tailwindcss等服务与技术,开发一个简单的博客网站,并部署到 Vercel 服务。
成果展示
成果看起来是这样的:你会有一个博客首页展示你的文章 https://umi-blog-example.vercel.app/
点击某一篇文章可以看到这篇文章的完整内容:https://umi-blog-example.vercel.app/posts/5
当然,你还可以在博客中发表新文章:https://umi-blog-example.vercel.app/posts/create
前提是你有登入:https://umi-blog-example.vercel.app/login
准备好了吗,让我们马上开始吧!
环境准备
首先,你必须确保你的本地环境已经准备好进行一个 Umi.js 项目的开发。如果你目前还没有开发过 Umi.js 项目,也没有在本地搭建过开发环境,建议你先阅读 开发环境 这篇教学。
配置完本地环境以后,你已经准备好开始开发 Umi.js 项目了!跟着 脚手架 这篇文档的教学,快速初始化一个 Umi.js 项目吧。
调整目录结构
因为我们的博客网站会使用到 Umi 4 的 API 路由功能,所以我们需要对脚手架自动产生的目录结构进行一些调整。你现在的目录结构应该看起来是这样的:
.
├── assets
│ └── yay.jpg
├── layouts
│ ├── index.less
│ └── index.tsx
├── node_modules
├── package.json
├── pages
│ ├── docs.tsx
│ └── index.tsx
├── pnpm-lock.yaml
├── tsconfig.json
└── typings.d.ts我们需要把 assets, layouts, pages 目录从根目录移动到 src 目录下,移动之后他看起来是这样的:
.
├── src
│ ├── assets
│ │ └── yay.jpg
│ ├── layouts
│ │ ├── index.less
│ │ └── index.tsx
│ └──── pages
│ ├── docs.tsx
│ └── index.tsx
├── node_modules
├── package.json
├── pnpm-lock.yaml
├── tsconfig.json
└── typings.d.ts:::info
为什么要这样做呢?这是因为根目录下的 api 目录会被我们作为 API 路由生成构建产物的地方,如果我们没有多一层 src 目录的话,我们的 API 路由目录就会和构建产物目录冲突啦~
:::
注册 PlanetScale 服务
我们的博客将会把用户和文章的数据保存在 MySQL 数据库中。然而,我们不需要真的自己准备一台服务器来运行数据库,我们可以使用免费的 PlanetScale来一键部署一个开箱即用的数据库!
首先从 https://auth.planetscale.com/sign-in登入你的账号,如果你没有注册过,可以选择使用 GitHub 一键登入或是点击 Sign up for an account 注册一个账号。
登入之后,在你的 PlanetScale 账号建立一个数据库(如果你是第一次注册,则可以看到他的教学步骤带领你一步步建立一个数据库):
建立完成后,点击数据库页面右上角的 Connect 按钮:
你会在弹窗里面看到一个 Connect With 的下拉选单,选择 Prisma,然后就能获得一串这个格式的字符串:
DATABASE_URL='mysql://************:************@************.ap-southeast-2.psdb.cloud/umi-blog-example?sslaccept=strict'这个字符串就是我们要用来让 Prisma 连接数据库的连线信息,暂时先把他记录起来就可以了 👍
安装依赖
接下来,帮我们的 Umi 项目安装这次教程会用到的依赖:
pnpm i -d prisma @types/bcryptjs @types/jsonwebtoken
pnpm i @prisma/client bcryptjs jsonwebtoken这两行命令帮我们安装了这些包:
- prisma和 @prisma/client:这两个包让我们可以用 Prisma来方便地串接数据库,不需要担心任何复杂的 SQL 命令。
- bcryptjs:这个包让我们将用户注册后的密码使用 Bcrypt哈希加密算法来安全的存储在数据库中。
- jsonwebtoken:这个包让我们可以方便地使用用 JWT(Json Web Token) 实现用户鉴权。
然后将 package.json 中 scripts 里面的 build 脚本从
"scripts": {
"dev": "umi dev",
"build": "umi build",
"postinstall": "umi setup",
"start": "npm run dev"
},改成
"scripts": {
"dev": "umi dev",
"build": "prisma generate && umi build",
"postinstall": "umi setup",
"start": "npm run dev"
},这可以确保每次开始构建以前都已经生成好 Prisma 客户端。
安装 Tailwindcss
使用 Umi 提供的微生成器来在项目中启用 Tailwindcss :
npx umi g tailwindcss他会帮我们安装 Tailwindcss 所需要的依赖,然后生成需要的文件。
初始化页面组件
接下来,当你使用 pnpm dev启动项目后,可能会看到错误讯息并且启动失败了。这是因为我们在配置中声明了一些页面,但并没有帮他建立对应的页面组件!
我们可以使用 Umi 的微生成器来自动生成这些页面:login.tsx, posts/post.tsx, posts/create.tsx:
npx umi g page login posts/post posts/create新增后的目录结构是这样的:
src
├── assets
│ └── yay.jpg
├── layouts
│ ├── index.less
│ └── index.tsx
└── pages
├── index.less
├── index.tsx
├── login.less
├── login.tsx
└── posts
├── create.less
├── create.tsx
├── post.less
└── post.tsx现在再输入一次 pnpm dev 就可以看到我们的网站正常启动了。
配置 umi 项目
最后一步,就是要来对 Umi 项目进行配置,完整的配置可以参考 配置 这篇教学文档,在本次的教学中,只要按照以下配置即可:
// .umirc.ts
export default {
npmClient: 'pnpm',
apiRoute: {
platform: 'vercel',
},
routes: [
{ path: '/', component: 'index' },
{ path: '/posts/create', component: 'posts/create' },
{ path: '/login', component: 'login' },
{ path: '/posts/:postId', component: 'posts/post' },
],
plugins: [require.resolve('@umijs/plugins/dist/tailwindcss')],
tailwindcss: {},
};其中,apiRoute 这个配置项告诉 Umi 我们的项目启用了 API 路由 这个功能,而 platform: 'vercel' 代表我们要部署到Vercel 平台,在 umi build 的时候会针对这个平台来将 API 路由进行打包。
为了顺利部署项目到 Vercel ,你需要在项目根目录下加入一个 vercel.json 配置文件:
{
"build": {
"env": {
"ENABLE_FILE_SYSTEM_API": "1"
}
},
"rewrites": [
{
"source": "/api/:match*",
"destination": "api/:match*"
}
]
}route 这个配置项则声明了我们网站的路由架构,可以看到我们的博客网站有这些页面:
/: 首页/posts/create: 建立文章/login: 登入/posts/:postId: 某篇文章
plugins 配置项代表我们启用了哪些 Umi 插件,其中 @umijs/plugins/dist/tailwindcss 是在 Umi 中使用 Tailwindcss
的插件。下面一项 tailwindcss: {} 则是从配置来启用该插件的意思。
API 路由设计
我们的整个博客网站由两大部分构成,一半是运行在浏览器内的前端代码,另一半则是运行在 Serverless Function 中的服务端代码。
为什么需要分成两边呢?这是因为有些代码我们不能让他在浏览器内运行,比如说用户鉴权、串接数据库等等的功能,这些必须作为一个服务然后以 API 的形式暴露给前端页面调用,这个部分可以透过 Umi 4 的 API 路由功能来实现。
(这里好像放一张图可以比较清楚地解释)
因为我们已经在 .umirc.ts 配置文件中声明了我们要启用 API 路由功能,现在可以直接在 src 目录下添加一个 api目录,这个目录下以约定式路由的设计来提供 API 路由的开发。
作为一个博客的 API 服务,不难想到我们会需要这些接口来供用户调用:
- 用户注册:
POST /api/register - 用户登入:
POST /api/login - 发表文章:
POST /api/posts - 查询所有文章:
GET /api/posts - 查询一篇文章:
GET /api/posts/{postId}
所以我们可以在 src/api 目录下建立这些新文件:
src
├── api
│ ├── login.ts
│ ├── register.ts
│ └── posts
│ ├── [postId].ts
│ └── index.ts
...:::info
你可能注意到了,这里有一个文件叫做 [postId].ts,这个写法代表这个路由可以动态匹配不同的值。例如 /api/posts/1 和 /api/posts/2 两个请求都会交给 src/api/posts/[postId].ts处理,但他们的 req.params 分别是 { postId: 1 } 和 { postId: 2 }。
:::
这里的每个 .ts 文件就是一个 API Handler,他们默认导出一个函数用来处理发送到该路径的请求,我们可以暂时先这样写:
import type { UmiApiRequest, UmiApiResponse } from 'umi';
export default async function (req: UmiApiRequest, res: UmiApiResponse) {
res.status(400).json({ error: 'This API is not implemented yet.' });
}然后你可以试着用浏览器或 Postman 访问看看这些 API 路由(例如 http://localhost:8000/api/login ),就可以看到你刚刚写的响应数据了 🎉
我们等一下再回来实作这些 API 路由的实际功能,因为有一个更重要的事情要先做。
定义 Schema
现在必须要先确定一件事情:我们要保存哪些数据、以怎么样的形式保存在数据库,又是以怎么样的形式响应给前端的?
文章数据
文章数据(Post)每笔数据就代表了一篇博客里面的文章,我们可以按自己的系统需求来设计他需要保存的内容,例如我们的范例保存了这些数据:
id: 文章 IDtitle: 文章标题authorId: 作者的 IDtags: 文章的标签(以逗号隔开)imageUrl: 文章封面图片的链接content: 文章的内文(markdown 格式)
用户数据
用户数据 (User) 每笔数据代表一个在我们博客注册的用户数据,我们可以按照自己的系统需求来设计他需要保存的内容,例如我们的范例保存了这些数据:
id: 用户 IDname: 名称email: 邮箱avatarUrl: 头像链接passwordHash: 加密过的密码
生成配置
这个章节可以考虑阅读 Prisma 官方的教学文档
定义好数据格式以后,我们要让 Prisma 帮我们根据 Schema 设计来生成对应的客户端,并且自动的将数据库迁移至为我们设计的格式,
连线到数据库
第一步,我们在根目录建立一个 .env 文件,并且在里面加入刚刚在 注册 PlanetScale 服务 章节拿到的连线信息。
# .env
DATABASE_URL='mysql://************:************@************.ap-southeast-2.psdb.cloud/umi-blog-example?sslaccept=strict'编写 Prisma 配置
第二步,在根目录下建立一个 prisma/schema.prisma 文件,并把我们设计的 Schema 按照 Prisma 语法 写进去文件中:
generator client {
provider = "prisma-client-js"
previewFeatures = ["referentialIntegrity"]
}
datasource db {
provider = "mysql"
referentialIntegrity = "prisma"
url = env("DATABASE_URL")
}
model Post {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String @db.VarChar(255)
content String?
author User @relation(fields: [authorId], references: [id])
authorId Int
imageUrl String?
tags String
@@index(authorId)
}
model User {
id Int @id @default(autoincrement())
email String @unique
passwordHash String
name String?
posts Post[]
avatarUrl String?
}完成后,在命令行输入
npx prisma migrate dev --name init他会帮我们将 MySQL 数据库迁移为我们定义的格式。 接下来,在命令行输入
npx prisma generate他会帮我们生成一个按照我们的 Schema 设计量身定制的客户端包。
至此,我们已经顺利处理完数据库的部分,接下来只要专注于如何在 API 路由中使用 Prisma 客户端包来获取与更新数据即可。
实现 API 路由
我们现在要回头来实现刚刚建立的那些 api 目录下的 .ts 文件了,只要我们自己清楚:
- API 会如何被调用 (path, request header, request body)
- 我们应该在 API 内做什么事
- 响应什么内容回去 (status, response header, response body)
那么 API 路由的开发就像写编写一个简单的函数一样。
用户注册
当用户对 /api/register 发起 POST 请求时,代表他们想要在我们的博客网站注册一个账号。
:::info
你可以在 https://github.com/umijs/umi-blog-example/blob/main/src/api/register.ts 找到这个示范的源代码!
:::
/* Detailed source-code truncated for AI context efficiency. */完成开发后,可以使用 Postman 对这个 API 发起请求,测试功能是否正常运作。
用户登入
当用户对 /api/login 发起 POST 请求时,代表他们想要登入我们的博客网站并取得一个 JWT 令牌,这可以让他用于建立新文章。
:::info
这个部分留给读者练习,你可以在 https://github.com/umijs/umi-blog-example/blob/main/src/api/login.ts 找到这个示范的源代码!
:::
发表文章
当用户对 /api/posts 发起 POST 请求时,代表他们想要在我们的博客网站发表一篇文章。
:::info
这个部分留给读者练习,你可以在 https://github.com/umijs/umi-blog-example/blob/main/src/api/posts/index.ts 找到这个示范的源代码!
:::
查询所有文章
当用户对 /api/posts 发起 GET 请求时,代表他们想要查询所有文章的数据。
:::info
这个部分留给读者练习,你可以在 https://github.com/umijs/umi-blog-example/blob/main/src/api/posts/index.ts 找到这个示范的源代码!
:::
查询某篇文章
当用户对 /api/posts/{postId} 发起 GET 请求时,代表他们想要查询某篇文章的数据。
:::info
这个部分留给读者练习,你可以在 https://github.com/umijs/umi-blog-example/blob/main/src/api/posts/%5BpostId%5D.ts 找到这个示范的源代码!
:::
实作页面组件
在这个章节,我们主要要学习如何在页面组件调用 API,来实现获取文章或注册等前后端交互的行为:
// pages/index.tsx
import React, { useEffect, useState } from 'react';
import { history } from "umi";
export default function HomePage() {
const [posts, setPosts] = useState<any[]>();
return (
<div>
{!posts && <p>Loading...</p>}
{posts && <div>
{posts.map(post => <div key={post.id}>
<div onClick={() => history.push(`/posts/${post.id}`)}>
<p>{post.title}</p>
</div>
</div>)}
</div>}
</div>
);
}可以看到我们在首页组件维护了一个 posts 状态,当 posts 是 undefined 时,我们认为是数据尚未加载完成。所以我们可以加入一个 useEffect让他在组件加载后对 API 路由发起一个请求,去查询目前所有的文章列表:
// pages/index.tsx
import React, { useEffect, useState } from 'react';
import { history } from 'umi';
export default function HomePage() {
const [posts, setPosts] = useState<any[]>();
async function refresh() {
try {
const res = await fetch('/api/posts');
if (res.status !== 200) {
console.error(await res.text());
}
setPosts(await res.json());
} catch (err) {
console.error(err);
}
}
useEffect(() => {
refresh();
}, []);
return (
<div>
{!posts && <p>Loading...</p>}
{posts && (
<div>
{posts.map((post) => (
<div key={post.id}>
<div onClick={() => history.push(`/posts/${post.id}`)}>
<p>{post.title}</p>
</div>
</div>
))}
</div>
)}
</div>
);
}可以看到我们加入了一个 refresh 函数,他会帮我们从 API 路由查询目前的文章列表。若你现在访问这个页面,应该可以看到一开始是 Loading ...
等一阵子就会有全部文章的标题被渲染出来的效果。
最后只要帮他加一点样式:
其他页面留给读者实作,可以加入自己的想法及样式的设计来实现,源代码可参考:https://github.com/umijs/umi-blog-example/blob/main/src/pages
部署
最后,将你的项目提交到 git 服务上,然后登入 Vercel:
如果你的项目代码是提交到 GitHub,那么建议你选择 GitHub 登入,这样你就可以在 Vercel 直接导入现有的代码仓库了 👍
导入以后,他会自动检测到这个项目是使用 Umi.js 框架搭建的,并且自动化完成相关的配置,因此直接点击 Deploy 即可开始部署!
等他部署完成以后,你的博客就正式上线啦 👍
但这个时候你会发现,网站内的 API 路由没有办法正常工作,这是因为他缺少了连线到数据库需要的环境变量。我们需要帮他配置一下:
在项目配置页面,下面有可以设置环境变量的地方:
你可以把 DATABASE_URL, JWT_KEY 等等你用到的环境变量从这里传入。
加入环境变量以后,点击 Redeploy 重新部署一次版本,你的博客就能正常运作啦~
Docs Blog Legacy Browser.En US (`docs/docs/blog/legacy-browser.en-US.md`)
toc: content order: 6 group: title: Blog
非现代浏览器兼容
默认兼容说明
Umi 4 默认不支持 IE ,编译兼容目标 targets 为 chrome: 80 ,如需调整,请指定明确的 targets :
// .umirc.ts
export default {
targets: { chrome: 67 },
};若想反馈更多关于兼容性的问题,或参与讨论,请前往:issue / 8656
兼容非现代浏览器
如果你并不需要兼容至 IE ,只为了提升项目对非现代浏览器的兼容性,可调整兼容目标 targets 。
Umi 4 默认使用现代构建工具,产物生成至 es6 ,如果你有要打包为 es5 产物的考量,请调整配置:
// .umirc.ts
export default {
jsMinifier: 'terser',
cssMinifier: 'cssnano',
};兼容旧时代浏览器 ( IE 11 )
由于 IE 已经淘汰不再主流,当需要兼容至 IE 时,请阅读以下对策。
框架自带的 legacy mode
Umi 4 自带提供一个 legacy 配置用于构建降级(使用限制等详见 legacy ):
// .umirc.ts
export default {
legacy: {},
};默认仅在构建时生效,将尝试构建能使 IE 兼容的产物。
legacy mode 的更多自定义
legacy 开启时,默认会转译全部 node_modules ,这在大型项目中,会极大的增加构建时间。
若你了解当前项目使用的第三方依赖情况(知道哪些不再提供 es5 产物了),可以关闭 node_modules 的转换,改为使用 extraBabelIncludes 定点配置那些需要额外纳入转换范围的包。
一个例子:
// .umirc.ts
export default {
legacy: {
nodeModulesTransform: false,
},
extraBabelIncludes: ['some-es6-pkg', /@scope\//],
};提高兼容的鲁棒性
legacy 选项并不能 100% 保证产物 没有边界情况 的运行在被淘汰的浏览器内,你可能还需要添加 前置的 全量 polyfill 来增强项目的 鲁棒性 。
// .umirc.ts
export default {
headScripts: [
'http://polyfill.alicdn.com/v3/polyfill.min.js', // or https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js
],
legacy: {},
};参考的思路有:
| 方案 | 说明 |
|---|---|
| CDN 引入 | 以 cdn 形式引入 script 形式且前置的 、目标浏览器环境缺少的 polyfill js 文件,如 es6-shim 。 |
| 人工 core-js | 利用 core-js 系工具,如通过 core-js-builder 构建自己需要的 polyfill 产物,再以 前置 script 脚本 形式引入项目。 |
| 动态 polyfill 服务 | 使用根据当前浏览器请求 UA 动态下发所需 polyfill 的服务,比如 polyfill.io (alicdn) 或 polyfill.io (CloudFlare) 服务。另外,你还可以使用 polyfill-service 自建相同的动态 polyfill 下发服务。 |
注:
当你处于内外网隔离开发环境时,可以考虑将全部 polyfill 的 js 内容传入内网,在内网的 CDN 使用,或放入 public 目录等方式使用。
使用 script 前置引入的意义在于,在项目 js 资源运行前就准备好一个完整的、被 polyfill 过 api 的环境。
在开发环境验证
推荐的做法是:构建后在本地通过 umi preview 或 serve 、nginx 等启动服务,来验证产物的 IE 11 运行可行性。
当你需要在开发环境验证时:
将
legacy.buildOnly置为false。由于 react fresh 、hmr 等开发注入的 es6 代码始终在第一位运行,你需要以 script 形式添加一个前置的 polyfill ,提前准备好环境。
// .umirc.ts
const isProd = process.env.NODE_ENV === 'production';
export default {
legacy: {
buildOnly: false,
},
headScripts: isProd ? [] : ['http://polyfill.alicdn.com/v3/polyfill.min.js'],
};注:IE 11 并不能完整支持开发时的热更新,且缓存可能需要人为在控制台进行清除后才能看到最新的页面,请做好准备。
Docs Blog Legacy Browser (`docs/docs/blog/legacy-browser.md`)
toc: content order: 6 group: title: Blog
非现代浏览器兼容
默认兼容说明
Umi 4 默认不支持 IE ,编译兼容目标 targets 为 chrome: 80 ,如需调整,请指定明确的 targets :
// .umirc.ts
export default {
targets: { chrome: 67 },
};若想反馈更多关于兼容性的问题,或参与讨论,请前往:issue / 8656
兼容非现代浏览器
如果你并不需要兼容至 IE ,只为了提升项目对非现代浏览器的兼容性,可调整兼容目标 targets 。
Umi 4 默认使用现代构建工具,产物生成至 es6 ,如果你有要打包为 es5 产物的考量,请调整配置:
// .umirc.ts
export default {
jsMinifier: 'terser',
cssMinifier: 'cssnano',
};兼容旧时代浏览器 ( IE 11 )
由于 IE 已经淘汰不再主流,当需要兼容至 IE 时,请阅读以下对策。
框架自带的 legacy mode
Umi 4 自带提供一个 legacy 配置用于构建降级(使用限制等详见 legacy ):
// .umirc.ts
export default {
legacy: {},
};默认仅在构建时生效,将尝试构建能使 IE 兼容的产物。
legacy mode 的更多自定义
legacy 开启时,默认会转译全部 node_modules ,这在大型项目中,会极大的增加构建时间。
若你了解当前项目使用的第三方依赖情况(知道哪些不再提供 es5 产物了),可以关闭 node_modules 的转换,改为使用 extraBabelIncludes 定点配置那些需要额外纳入转换范围的包。
一个例子:
// .umirc.ts
export default {
legacy: {
nodeModulesTransform: false,
},
extraBabelIncludes: ['some-es6-pkg', /@scope\//],
};提高兼容的鲁棒性
legacy 选项并不能 100% 保证产物 没有边界情况 的运行在被淘汰的浏览器内,你可能还需要添加 前置的 全量 polyfill 来增强项目的 鲁棒性 。
// .umirc.ts
export default {
headScripts: [
'http://polyfill.alicdn.com/v3/polyfill.min.js',
],
legacy: {},
};参考的思路有:
| 方案 | 说明 |
|---|---|
| CDN 引入 | 以 cdn 形式引入 script 形式且前置的 、目标浏览器环境缺少的 polyfill js 文件,如 es6-shim 。 |
| 人工 core-js | 利用 core-js 系工具,如通过 core-js-builder 构建自己需要的 polyfill 产物,再以 前置 script 脚本 形式引入项目。 |
| 动态 polyfill 服务 | 使用根据当前浏览器请求 UA 动态下发所需 polyfill 的服务,比如 polyfill.io (alicdn) 或 polyfill.io (CloudFlare) 服务。另外,你还可以使用 polyfill-service 自建相同的动态 polyfill 下发服务。 |
注:
当你处于内外网隔离开发环境时,可以考虑将全部 polyfill 的 js 内容传入内网,在内网的 CDN 使用,或放入 public 目录等方式使用。
使用 script 前置引入的意义在于,在项目 js 资源运行前就准备好一个完整的、被 polyfill 过 api 的环境。
在开发环境验证
推荐的做法是:构建后在本地通过 umi preview 或 serve 、nginx 等启动服务,来验证产物的 IE 11 运行可行性。
当你需要在开发环境验证时:
将
legacy.buildOnly置为false。由于 react fresh 、hmr 等开发注入的 es6 代码始终在第一位运行,你需要以 script 形式添加一个前置的 polyfill ,提前准备好环境。
// .umirc.ts
const isProd = process.env.NODE_ENV === 'production';
export default {
legacy: {
buildOnly: false,
},
headScripts: isProd ? [] : ['http://polyfill.alicdn.com/v3/polyfill.min.js'],
};注:IE 11 并不能完整支持开发时的热更新,且缓存可能需要人为在控制台进行清除后才能看到最新的页面,请做好准备。
Docs Blog Mfsu Faster Than Vite.En US (`docs/docs/blog/mfsu-faster-than-vite.en-US.md`)
order: 3 toc: content group: title: Blog
比 Vite 还快的 MFSU
编者按:Change the code, don't Workaround! Webpack
慢就去改他,优化到位后,Bundle 也可以很快。此方案会在 Umi 4
中默认开启,适用于既要 Webpack 功能与生态,又想要 Vite 速度的同学们。
Umi 4 中同时支持 webpack 和 vite 两种构建方式,跑通了后,迫不及待对比了 Vite 和 Webpack + MFSU 的效果,结果有点意外。关于什么是 MFSU,我在《SEE Conf: Umi 4 设计思路文字稿》中有一段详细介绍。
两个示例、四种模式、四个维度的对比。两个示例分别是大型的全量 ant-design-pro 和小型的 libs example;四种模式分别是 webpack、webpack + MFSU、webpack + MFSU with esbuild mode、Vite in umi;四个维度分别是无缓存的冷启动、有缓存的热启动、修改代码后的热更新、页面打开速度。
多说几点和统计相关的。上述 webpack 相关模式全部开启物理缓存;Vite 是 Umi 中集成后的 Vite,也有担心是不是 Umi 对于 Vite 的误用,经开发者确认,基本排除误用的可能性,大段时间消耗在预编译依赖上;Ant Design Pro 中包含 less 的使用,这是使用 esbuild 无法加速的部分,这有影响,但对于不同模式应该是公平的;下图数据是本地用 13-inch M1 2022 重启电脑后跑 5 次后平均取值的结果;Vite 的热更速度没统计是因为由于 esm 的特性,改完后要等请求过来后处理完才算结束,无法统计,但肯定是很快的。
直接上结果。有兴趣手动验证的同学可到 umijs/umi 仓库的不同 example 目录下执行 npm run dev 验证。
图:全量 ant-design-pro 速度对比图
图:libs example 速度对比图
可以看到,在这几个场景下,MFSU with esbuild 数据领先。 四个模式的页面打开速度差不多,所以对比数据没在图中列出,这也是让我意外的点,原以为 Vite 请求多会让页面打开速度变慢,也有可能项目还不够复杂?
Docs Blog Mfsu Faster Than Vite (`docs/docs/blog/mfsu-faster-than-vite.md`)
order: 3 toc: content group: title: Blog
比 Vite 还快的 MFSU
编者按:Change the code, don't Workaround! Webpack
慢就去改他,优化到位后,Bundle 也可以很快。此方案会在 Umi 4
中默认开启,适用于既要 Webpack 功能与生态,又想要 Vite 速度的同学们。
Umi 4 中同时支持 webpack 和 vite 两种构建方式,跑通了后,迫不及待对比了 Vite 和 Webpack + MFSU 的效果,结果有点意外。关于什么是 MFSU,我在《SEE Conf: Umi 4 设计思路文字稿》中有一段详细介绍。
两个示例、四种模式、四个维度的对比。两个示例分别是大型的全量 ant-design-pro 和小型的 libs example;四种模式分别是 webpack、webpack + MFSU、webpack + MFSU with esbuild mode、Vite in umi;四个维度分别是无缓存的冷启动、有缓存的热启动、修改代码后的热更新、页面打开速度。
多说几点和统计相关的。上述 webpack 相关模式全部开启物理缓存;Vite 是 Umi 中集成后的 Vite,也有担心是不是 Umi 对于 Vite 的误用,经开发者确认,基本排除误用的可能性,大段时间消耗在预编译依赖上;Ant Design Pro 中包含 less 的使用,这是使用 esbuild 无法加速的部分,这有影响,但对于不同模式应该是公平的;下图数据是本地用 13-inch M1 2022 重启电脑后跑 5 次后平均取值的结果;Vite 的热更速度没统计是因为由于 esm 的特性,改完后要等请求过来后处理完才算结束,无法统计,但肯定是很快的。
直接上结果。有兴趣手动验证的同学可到 umijs/umi 仓库的不同 example 目录下执行 npm run dev 验证。
图:全量 ant-design-pro 速度对比图
图:libs example 速度对比图
可以看到,在这几个场景下,MFSU with esbuild 数据领先。 四个模式的页面打开速度差不多,所以对比数据没在图中列出,这也是让我意外的点,原以为 Vite 请求多会让页面打开速度变慢,也有可能项目还不够复杂?
Docs Blog Mfsu Independent Usage.En US (`docs/docs/blog/mfsu-independent-usage.en-US.md`)
toc: content order: 4 group: title: Blog
独立使用 MFSU
MFSU 支持独立在非 umijs 项目中使用,本文将会介绍如何将 MFSU 接入你的 webpack 项目。
示例项目
如何接入 MFSU ?提供以下几个 示例项目 配置供参考:
Webpack 配置示例:examples/mfsu-independent
CRA v5 配置示例:cra-v5-with-mfsu-example
安装
首先安装 mfsu 的依赖:
pnpm add -D @umijs/mfsu配置 MFSU
配置 MFSU 一共需要简单的四步操作,请确保以下所有行为都只在开发环境生效。
1. 初始化实例
第一步,初始化一个 MFSU 实例,这是 MFSU 的基础:
// webpack.config.js
const { MFSU } = require('@umijs/mfsu');
const webpack = require('webpack');
// [mfsu] 1. init instance
const mfsu = new MFSU({
implementor: webpack,
buildDepWithESBuild: true,
});2. 添加中间件
第二步,添加 MFSU 的 devServer 中间件到 webpack-dev-server 中,他将为你提供 MFSU 所需打包后的资源:
webpack 5
// webpack.config.js
module.exports = {
devServer: {
// [mfsu] 2. add mfsu middleware
setupMiddlewares(middlewares, devServer) {
middlewares.unshift(...mfsu.getMiddlewares());
return middlewares;
},
},
};webpack 4
// webpack.config.js
module.exports = {
devServer: {
// [mfsu] 2. add mfsu middleware
onBeforeSetupMiddleware(devServer) {
for (const middleware of mfsu.getMiddlewares()) {
devServer.app.use(middleware);
}
},
},
};3. 配置转换器
第三步,你需要配置一种源码转换器,他的作用是用来收集、转换依赖导入路径,替换为 MFSU 的模块联邦地址(中间件所提供的)。
此处提供两种方案:babel plugins 或 esbuild handler ,一般情况下选择 babel plugins 即可。
Babel Plugins
向 babel-loader 添加 MFSU 的 babel plugins 即可。
// webpack.config.js
module.exports = {
module: {
rules: [
// handle javascript source loader
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
plugins: [
// [mfsu] 3. add mfsu babel plugins
...mfsu.getBabelPlugins(),
],
},
},
},
],
},
};Esbuild handler
另一种方案是使用内置提供的 esbuild-loader 来处理 js/ts 资源,仅用于开发环境 。
:::info
使用这种方案的好处是:在开发环境获得比 babel 更快的编译和启动速度
:::
// webpack.config.js
const { esbuildLoader } = require('@umijs/mfsu');
const esbuild = require('esbuild');
module.exports = {
module: {
rules: [
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: {
loader: esbuildLoader,
options: {
handler: [
// [mfsu] 3. add mfsu esbuild loader handlers
...mfsu.getEsbuildLoaderHandler(),
],
target: 'esnext',
implementation: esbuild,
},
},
},
],
},
};:::warning
**什么时候我不应该使用 esbuild 方案?**1. 我有自定义的 babel plugins 必须在开发环境使用2. 我需要显示 css-in-js 的开发环境友好类名(一般由 babel plugin 提供支持)3. 在开发环境多适配一套 esbuild-loader 的成本大于配置 babel plugins 的成本
:::
4. 设定 webpack 配置
第四步,调用 MFSU 提供的方法改变你的 webpack 配置,在这里只有增量行为,你无需担心会影响到你原来的配置内容。
如下代码所示,mfsu.setWebpackConfig 是一个异步方法,为了调用他你需要将原来的 webpack 配置单独抽为一个对象 config 之后,再将调用此方法的返回值导出。
// webpack.config.js
const config = {
// origin webpack config
};
const depConfig = {
// webpack config for dependencies
};
// [mfsu] 4. inject mfsu webpack config
const getConfig = async () => {
await mfsu.setWebpackConfig({
config,
depConfig,
});
return config;
};
module.exports = getConfig();到此为止,MFSU 完全配置完毕,下面可以开始启动项目使用。
使用
进行完 4 步配置后,启动你的项目,你可以从项目根目录得到 .mfsu 文件夹,即 MFSU 缓存文件夹,请将其添加到 git 的忽略列表(这些缓存文件你不应该提交他们):
# .gitignore
.mfsu符合预期时,你已经可以享受 MFSU 带来的好处,包括 esbuild 快速的打包和二次热启动的提速。
其他配置
以下是其他你可能会用到的 MFSU 实例配置:
const mfsu = new MFSU({
cwd: process.cwd(),
});其他 Options:
| option | default | description |
|---|---|---|
cwd |
process.cwd() |
项目根目录 |
getCacheDependency |
() => {} |
用返回值来对比,使 MFSU cache 无效的函数 |
tmpBase |
${process.cwd()}/.mfsu |
MFSU 缓存存放目录 |
unMatchLibs |
[] |
手动排除某些不需要被 MFSU 处理的依赖 |
runtimePublicPath |
undefined |
同 umijs > runtimePublicPath |
implementor |
undefined |
webpack 实例,需要和项目内使用的唯一实例一致 |
buildDepWithESBuild |
false |
是否使用 esbuild 打包依赖 |
onMFSUProgress |
undefined |
获取 MFSU 编译进度的回调 |
常见问题
如何保证我的 MFSU 配置只在开发环境生效?
使用环境标识避免所有 MFSU 在生产环境构建时的配置侵入:
const isDev = process.env.NODE_ENV === 'development'
const mfsu = isDev
? new MFSU({
implementor: webpack,
buildDepWithESBuild: true,
})
: undefined
// e.g.
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
plugins: [
...(isDev ? [] : mfsu.getBabelPlugins())
]
}
}
}Docs Blog Mfsu Independent Usage (`docs/docs/blog/mfsu-independent-usage.md`)
toc: content order: 4 group: title: Blog
独立使用 MFSU
MFSU 支持独立在非 umijs 项目中使用,本文将会介绍如何将 MFSU 接入你的 webpack 项目。
示例项目
如何接入 MFSU ?提供以下几个 示例项目 配置供参考:
Webpack 配置示例:examples/mfsu-independent
CRA v5 配置示例:cra-v5-with-mfsu-example
安装
首先安装 mfsu 的依赖:
pnpm add -D @umijs/mfsu配置 MFSU
配置 MFSU 一共需要简单的四步操作,请确保以下所有行为都只在开发环境生效。
1. 初始化实例
第一步,初始化一个 MFSU 实例,这是 MFSU 的基础:
// webpack.config.js
const { MFSU } = require('@umijs/mfsu');
const webpack = require('webpack');
// [mfsu] 1. init instance
const mfsu = new MFSU({
implementor: webpack,
buildDepWithESBuild: true,
});2. 添加中间件
第二步,添加 MFSU 的 devServer 中间件到 webpack-dev-server 中,他将为你提供 MFSU 所需打包后的资源:
webpack 5
// webpack.config.js
module.exports = {
devServer: {
// [mfsu] 2. add mfsu middleware
setupMiddlewares(middlewares, devServer) {
middlewares.unshift(...mfsu.getMiddlewares());
return middlewares;
},
},
};webpack 4
// webpack.config.js
module.exports = {
devServer: {
// [mfsu] 2. add mfsu middleware
onBeforeSetupMiddleware(devServer) {
for (const middleware of mfsu.getMiddlewares()) {
devServer.app.use(middleware);
}
},
},
};3. 配置转换器
第三步,你需要配置一种源码转换器,他的作用是用来收集、转换依赖导入路径,替换为 MFSU 的模块联邦地址(中间件所提供的)。
此处提供两种方案:babel plugins 或 esbuild handler ,一般情况下选择 babel plugins 即可。
Babel Plugins
向 babel-loader 添加 MFSU 的 babel plugins 即可。
// webpack.config.js
module.exports = {
module: {
rules: [
// handle javascript source loader
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
plugins: [
// [mfsu] 3. add mfsu babel plugins
...mfsu.getBabelPlugins(),
],
},
},
},
],
},
};Esbuild handler
另一种方案是使用内置提供的 esbuild-loader 来处理 js/ts 资源,仅用于开发环境 。
:::info
使用这种方案的好处是:在开发环境获得比 babel 更快的编译和启动速度
:::
// webpack.config.js
const { esbuildLoader } = require('@umijs/mfsu');
const esbuild = require('esbuild');
module.exports = {
module: {
rules: [
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: {
loader: esbuildLoader,
options: {
handler: [
// [mfsu] 3. add mfsu esbuild loader handlers
...mfsu.getEsbuildLoaderHandler(),
],
target: 'esnext',
implementation: esbuild,
},
},
},
],
},
};:::warning
**什么时候我不应该使用 esbuild 方案?**1. 我有自定义的 babel plugins 必须在开发环境使用2. 我需要显示 css-in-js 的开发环境友好类名(一般由 babel plugin 提供支持)3. 在开发环境多适配一套 esbuild-loader 的成本大于配置 babel plugins 的成本
:::
4. 设定 webpack 配置
第四步,调用 MFSU 提供的方法改变你的 webpack 配置,在这里只有增量行为,你无需担心会影响到你原来的配置内容。
如下代码所示,mfsu.setWebpackConfig 是一个异步方法,为了调用他你需要将原来的 webpack 配置单独抽为一个对象 config 之后,再将调用此方法的返回值导出。
// webpack.config.js
const config = {
// origin webpack config
};
const depConfig = {
// webpack config for dependencies
};
// [mfsu] 4. inject mfsu webpack config
const getConfig = async () => {
await mfsu.setWebpackConfig({
config,
depConfig,
});
return config;
};
module.exports = getConfig();到此为止,MFSU 完全配置完毕,下面可以开始启动项目使用。
使用
进行完 4 步配置后,启动你的项目,你可以从项目根目录得到 .mfsu 文件夹,即 MFSU 缓存文件夹,请将其添加到 git 的忽略列表(这些缓存文件你不应该提交他们):
# .gitignore
.mfsu符合预期时,你已经可以享受 MFSU 带来的好处,包括 esbuild 快速的打包和二次热启动的提速。
其他配置
以下是其他你可能会用到的 MFSU 实例配置:
const mfsu = new MFSU({
cwd: process.cwd(),
});其他 Options:
| option | default | description |
|---|---|---|
cwd |
process.cwd() |
项目根目录 |
getCacheDependency |
() => {} |
用返回值来对比,使 MFSU cache 无效的函数 |
tmpBase |
${process.cwd()}/.mfsu |
MFSU 缓存存放目录 |
unMatchLibs |
[] |
手动排除某些不需要被 MFSU 处理的依赖 |
runtimePublicPath |
undefined |
同 umijs > runtimePublicPath |
implementor |
undefined |
webpack 实例,需要和项目内使用的唯一实例一致 |
buildDepWithESBuild |
false |
是否使用 esbuild 打包依赖 |
onMFSUProgress |
undefined |
获取 MFSU 编译进度的回调 |
常见问题
如何保证我的 MFSU 配置只在开发环境生效?
使用环境标识避免所有 MFSU 在生产环境构建时的配置侵入:
const isDev = process.env.NODE_ENV === 'development'
const mfsu = isDev
? new MFSU({
implementor: webpack,
buildDepWithESBuild: true,
})
: undefined
// e.g.
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
plugins: [
...(isDev ? [] : mfsu.getBabelPlugins())
]
}
}
}Docs Blog Umi 4 Rc.En US (`docs/docs/blog/umi-4-rc.en-US.md`)
toc: content order: 2 group: title: Blog
Umi 4 RC 发布
大家好,Umi 4 经过几个月的开发,终于要和大家见面了。相比 Umi 2 到 3,3 到 4 的变化是巨大的,开发时间也更长,但我们尽量把对于开发者的影响降低到最小。按捺住激动的心情,在此先和大家分享下都有哪些变化。
🎉 新官网和文档 🚀 MFSU V3 & 默认开启 🎭 双构建引擎和 ESMi 🕸 Webpack 5 ⛹🏾♂️ React Router 6 & 新路由 🐹 最佳实践迭代 🛡️ 依赖预打包 🤺 Umi Max 🐛 Low Import 研发模式 ⚠️ 强约束功能集成 🎈 Import All From Umi 迭代 🍀 srcTranspiler 和 depTranspiler 🌼 jsMinifier 和 cssMinifier 🌸 应用元数据 ❄️ 微生成器 🧪 贴心小改进
新官网和文档。
下图是新官网的首页,包括重新梳理的文档、信息结构、以及新写的文档插件。目前包含基础的配置、API、升级和快速上手等基础文档,剩余文档还在紧张编写中。有个变化是之前插件的文档集成到
Umi 官网中,成为 Umi Max 的一部分,之后无需跳转。
MFSU V3 & 默认开启。
MFSU 更新了他的第三个大版本,如果你有用 Umi 3 内置的 MFSU 并遇到问题,建议重新尝试,这个版本有很多改进,解决基本所有之前可能会遇到的诡异问题,并且编译速度和页面打开速度都更快。昨天我还有写一篇
《比 Vite 更快的 MFSU》。基于此,我们自信地把这个功能在
Umi 4 中默认开启。还有值得一提的是,MFSU 可脱离 Umi 运行。
双构建引擎和 ESMi。
Umi 4 提供 Vite 和 Webpack 两种构建模式供开发者选择,并尽可能保证他们之间功能的一致性,可能有些同学会喜欢
dev 用 vite,build 用 webpack 这样的组合。同时基于 Vite 模式实现了 ESMi 的 Client
端,ESMi 依赖服务端,在外网还无法使用。
Webpack 5。Umi 4
默认使用 webpack 5 并开启物理缓存。
React Router 6 & 新路由。 Umi 4 的路由基于 React Router 6 实现,个人非常喜欢这个版本,因为 Remix 的原因,React
Router 6 从设计上考虑了配置式路由的场景,让我得以删除大量 Umi 3 中关于路由渲染的代码。同时基于此,设计了新的路由结构,方便扩展和在未来处理路由的约定式请求。
最佳实践迭代。
针对之前 umijs/plugins 仓库中的插件进行了重写、升级,并整合到主仓库。这么做是为了更好的顶层设计,让官方插件之间的风格更一致。
依赖预打包。
由于服务企业内部,安全和稳定是其中很重要点,加上最近 colors 和 faker.js 闹得社区沸沸扬扬,谁都不希望睡一觉醒来,自己负责的业务挂了,还背个故障。Umi
4 接着 Umi 3 继续做依赖预打包的事,并且更彻底,不仅是 node 侧的依赖,部分运行时的依赖也会做锁定,比如
core-js 和 @babel/runtime。
Umi Max。Umi Max
是内部 Bigfish
框架的对外版本,解我们自己的问题,同时也给社区另一个集中化框架的选择。
Low Import 研发模式。 这是 Umi 4 的试验性功能之一,目前已开发完成,解的问题是让开发者少些或不写 import
语句。项目中大量的 import 其实都可以通过工程化的方式自动处理。Umi 4 里通过 lowImport:{' '}
{} 开启,然后就可以无 import 直接用路由相关的 Link、useLocation 等,数据流相关的
connect、useModel,antd 组件 Button、Calendar 等,以及其他更多。
强约束功能集成。Umi
4 提供 API 让强约束和代码校验变得非常容易。API 包括
api.onCheck、api.onCheckConfig、api.onCheckPkgJSON 和
api.onCheckCode,顾名思义,非常好理解他们分别是干嘛的,可以分别对依赖类、代码类和配置类的内容做校验和卡点,适用于团队。
Import All From Umi 迭代。 这是两年前 Umi 3 加的功能,最近发现 Remix、prisma、vitekit 等框架和工具都有类似实现。这种方式有好有坏。好处是通过
umi 将大量依赖管理起来,用户无需手动安装。坏处是更黑盒,同时有点 Hack。Umi 4 不能解其黑盒问题,但解了
Hack 问题,让实现无副作用,可以和 Vite、MFSU 等方案无缝结合。
srcTranspiler 和 depTranspiler。 提供针对源码编译和依赖编译更多选择。源码编译可选 babel、swc 和 esbuild,目前没有银弹,合适场景做合适的选择。比如
swc 由于不支持 top level await,和 mfsu 会有些冲突,但他适用于 build,因为有补丁可以兼容到
es7;比如 esbuild 适用于 dev,因为快。数据方面以 ant-design-pro 项目为例,源码编译用
esbuild 相比 babel 在 M1 2020 无缓存情况下会快 3s。
jsMinifier 和 cssMinifier。 js 压缩和 css 压缩 Umi 4 默认都用的 esbuild,因为快。同时也提供更多选择,js 压缩还支持
swc、terser 和 uglifyJs,css 压缩还支持 cssnano。
应用元数据。Umi 4
有通过 api.appData
收集各种项目数据,从配置、路由、package.json、tsconfig.json、npmClient
到数据流、国际化、antd 用了哪个版本、react 和 react-dom
的版本等,应有尽有,这对于插件开发者会非常实用,也适用于有统计需求的场景。
微生成器。没错,就是
modern.js 的微生成器,这功能从 modern.js
里学习了不少,名字就不改了。举个例子,比如 prettier
功能,可能不是每个项目都需要,就比较适用于微生成器,按需启用、添加配置、安装依赖。
贴心小改进。
还有不少贴心小改进,举两个例子。1 是项目中新增 plugin.ts,会默认作为插件添加,方便项目进行一些插件级的扩展;2
是调试问题时通常需要修改编译后的代码看看有没有改对,你把 umi.js 下下来存到项目根目录,umi
会优先使用这份代码。
以上是 Umi 4 目前的新功能。
除此之外,还有一些计划在正式版发布之前做的事情。包括 api route、umi server and adapter、route loader、稳定的 lint、更多命令、组件研发 father 4、文档工具 dumi 2 等,会在之后的 RC 版本中与大家见面。
欢迎大家尝鲜 Umi 4,官方文档有准备 ant-design-pro 从 Umi 3 到 4 的升级文档。同时 RC 阶段,还准备了一个手把手升级的微信交流群,欢迎 Umi 4 的先行者们加入,祝大家升级顺利,也提前祝大家新年快乐 🧨,🐯 年吉祥。
Docs Blog Umi 4 Rc (`docs/docs/blog/umi-4-rc.md`)
toc: content order: 2 group: title: Blog
Umi 4 RC 发布
大家好,Umi 4 经过几个月的开发,终于要和大家见面了。相比 Umi 2 到 3,3 到 4 的变化是巨大的,开发时间也更长,但我们尽量把对于开发者的影响降低到最小。按捺住激动的心情,在此先和大家分享下都有哪些变化。
🎉 新官网和文档 🚀 MFSU V3 & 默认开启 🎭 双构建引擎和 ESMi 🕸 Webpack 5 ⛹🏾♂️ React Router 6 & 新路由 🐹 最佳实践迭代 🛡️ 依赖预打包 🤺 Umi Max 🐛 Low Import 研发模式 ⚠️ 强约束功能集成 🎈 Import All From Umi 迭代 🍀 srcTranspiler 和 depTranspiler 🌼 jsMinifier 和 cssMinifier 🌸 应用元数据 ❄️ 微生成器 🧪 贴心小改进
新官网和文档。
下图是新官网的首页,包括重新梳理的文档、信息结构、以及新写的文档插件。目前包含基础的配置、API、升级和快速上手等基础文档,剩余文档还在紧张编写中。有个变化是之前插件的文档集成到
Umi 官网中,成为 Umi Max 的一部分,之后无需跳转。
MFSU V3 & 默认开启。
MFSU 更新了他的第三个大版本,如果你有用 Umi 3 内置的 MFSU 并遇到问题,建议重新尝试,这个版本有很多改进,解决基本所有之前可能会遇到的诡异问题,并且编译速度和页面打开速度都更快。昨天我还有写一篇
《比 Vite 更快的 MFSU》。基于此,我们自信地把这个功能在Umi 4 中默认开启。还有值得一提的是,MFSU 可脱离 Umi 运行。
双构建引擎和 ESMi。
Umi 4 提供 Vite 和 Webpack 两种构建模式供开发者选择,并尽可能保证他们之间功能的一致性,可能有些同学会喜欢
dev 用 vite,build 用 webpack 这样的组合。同时基于 Vite 模式实现了 ESMi 的 Client
端,ESMi 依赖服务端,在外网还无法使用。
Webpack 5。Umi 4
默认使用 webpack 5 并开启物理缓存。
React Router 6 & 新路由。 Umi 4 的路由基于 React Router 6 实现,个人非常喜欢这个版本,因为 Remix 的原因,React
Router 6 从设计上考虑了配置式路由的场景,让我得以删除大量 Umi 3 中关于路由渲染的代码。同时基于此,设计了新的路由结构,方便扩展和在未来处理路由的约定式请求。
最佳实践迭代。
针对之前 umijs/plugins 仓库中的插件进行了重写、升级,并整合到主仓库。这么做是为了更好的顶层设计,让官方插件之间的风格更一致。
依赖预打包。
由于服务企业内部,安全和稳定是其中很重要点,加上最近 colors 和 faker.js 闹得社区沸沸扬扬,谁都不希望睡一觉醒来,自己负责的业务挂了,还背个故障。Umi
4 接着 Umi 3 继续做依赖预打包的事,并且更彻底,不仅是 node 侧的依赖,部分运行时的依赖也会做锁定,比如
core-js 和 @babel/runtime。
Umi Max。Umi Max
是内部 Bigfish
框架的对外版本,解我们自己的问题,同时也给社区另一个集中化框架的选择。
Low Import 研发模式。 这是 Umi 4 的试验性功能之一,目前已开发完成,解的问题是让开发者少些或不写 import
语句。项目中大量的 import 其实都可以通过工程化的方式自动处理。Umi 4 里通过 lowImport:{' '}
{} 开启,然后就可以无 import 直接用路由相关的 Link、useLocation 等,数据流相关的
connect、useModel,antd 组件 Button、Calendar 等,以及其他更多。
强约束功能集成。Umi
4 提供 API 让强约束和代码校验变得非常容易。API 包括
api.onCheck、api.onCheckConfig、api.onCheckPkgJSON 和
api.onCheckCode,顾名思义,非常好理解他们分别是干嘛的,可以分别对依赖类、代码类和配置类的内容做校验和卡点,适用于团队。
Import All From Umi 迭代。 这是两年前 Umi 3 加的功能,最近发现 Remix、prisma、vitekit 等框架和工具都有类似实现。这种方式有好有坏。好处是通过
umi 将大量依赖管理起来,用户无需手动安装。坏处是更黑盒,同时有点 Hack。Umi 4 不能解其黑盒问题,但解了
Hack 问题,让实现无副作用,可以和 Vite、MFSU 等方案无缝结合。
srcTranspiler 和 depTranspiler。 提供针对源码编译和依赖编译更多选择。源码编译可选 babel、swc 和 esbuild,目前没有银弹,合适场景做合适的选择。比如
swc 由于不支持 top level await,和 mfsu 会有些冲突,但他适用于 build,因为有补丁可以兼容到
es7;比如 esbuild 适用于 dev,因为快。数据方面以 ant-design-pro 项目为例,源码编译用
esbuild 相比 babel 在 M1 2020 无缓存情况下会快 3s。
jsMinifier 和 cssMinifier。 js 压缩和 css 压缩 Umi 4 默认都用的 esbuild,因为快。同时也提供更多选择,js 压缩还支持
swc、terser 和 uglifyJs,css 压缩还支持 cssnano。
应用元数据。Umi 4
有通过 api.appData
收集各种项目数据,从配置、路由、package.json、tsconfig.json、npmClient
到数据流、国际化、antd 用了哪个版本、react 和 react-dom
的版本等,应有尽有,这对于插件开发者会非常实用,也适用于有统计需求的场景。
微生成器。没错,就是
modern.js 的微生成器,这功能从 modern.js
里学习了不少,名字就不改了。举个例子,比如 prettier
功能,可能不是每个项目都需要,就比较适用于微生成器,按需启用、添加配置、安装依赖。
贴心小改进。
还有不少贴心小改进,举两个例子。1 是项目中新增 plugin.ts,会默认作为插件添加,方便项目进行一些插件级的扩展;2
是调试问题时通常需要修改编译后的代码看看有没有改对,你把 umi.js 下下来存到项目根目录,umi
会优先使用这份代码。
以上是 Umi 4 目前的新功能。
除此之外,还有一些计划在正式版发布之前做的事情。包括 api route、umi server and adapter、route loader、稳定的 lint、更多命令、组件研发 father 4、文档工具 dumi 2 等,会在之后的 RC 版本中与大家见面。
欢迎大家尝鲜 Umi 4,官方文档有准备 ant-design-pro 从 Umi 3 到 4 的升级文档。同时 RC 阶段,还准备了一个手把手升级的微信交流群,欢迎 Umi 4 的先行者们加入,祝大家升级顺利,也提前祝大家新年快乐 🧨,🐯 年吉祥。
Docs Blog Webpack 5 Prod Cache.En US (`docs/docs/blog/webpack-5-prod-cache.en-US.md`)
toc: content order: 7 group: title: Blog
物理构建缓存
在 umi build 构建生产环境产物时,Umi 4 默认没有配置 webpack 5 的物理缓存,这是因为 webpack 的物理缓存失效时机问题,需要依赖用户项目的实际情况,所以没有很好的通用解决方案。
所以,当你不明确哪些依赖会让项目物理缓存失效时,很容易产生构建缓存不失效,导致产物是旧的问题,极大影响研发效率。
缓存场景
当你的项目需要构建缓存时,是有原因的,我们粗略把场景分成两类:普通项目、Monorepo 中的项目。
普通项目
构建比较慢,如何复用上次的物理缓存,做到多次构建提速?
首选解决思路
此时首选的优化思路应该是:考虑使用其他更快的现代转译器,比如调整 srcTranspiler 、cssMinifier 。
CI 中的问题
物理缓存一般存在于 node_modules/.cache ,这就意味着如果你在 CI 中构建,构建的基建必须要支持恢复上次的缓存文件,如果构建容器不支持恢复缓存,同样也无法享受好处。
选择依据
所以,当你:
多次构建:确实有多次反复构建的需求。
能恢复缓存:在本地构建,或在 CI 有手段能恢复上次的物理缓存文件。
时间长:项目构建时间比较长、开启其他转译器仍无法提速(或有强诉求无法切换转译器)。
满足这些条件后,你才应该考虑开启物理缓存。
配置方法
// .umirc.ts
import { join } from 'path';
import { defineConfig } from 'umi';
import { createHash } from 'crypto';
export default defineConfig({
chainWebpack(config, { env }) {
if (env === 'production') {
config.cache({
type: 'filesystem',
store: 'pack',
// 🟡 假如你的项目在 CI 中构建每次环境变量都不一样,请挑选或者排除
version: createEnvironmentHash(process.env),
buildDependencies: {
config: [__filename],
tsconfig: [join(__dirname, 'tsconfig.json')],
packagejson: [join(__dirname, 'package.json')],
umirc: [join(__dirname, '.umirc.ts')],
// 🟡 其他可能会影响项目的配置文件路径,其内容变更会使缓存失效
},
});
}
},
});
function createEnvironmentHash(env: Record<string, any>) {
const hash = createHash('md5');
hash.update(JSON.stringify(env));
const result = hash.digest('hex');
return result;
}请格外注意:
你的项目有哪些文件、依赖会影响项目,配置他们作为依赖,变更时可以使得缓存失效。
因为
process.env包括了所有的 nodejs 环境变量,这非常多,如果环境变量在 CI 中每次构建都存在差异,请挑选所需的环境变量,或者排除掉会变化的。ts// 如挑选可能会影响项目内容的环境变量 createEnvironmentHash({ NODE_ENV: process.env.NODE_ENV, // ... });
Monorepo 中的项目
在 monorepo 中,如何缓存需要前置构建的其他子包,比如构建 apps/project-umi 需要先构建好他依赖的子包 libs/component ,但是下次 libs/component 没有代码改动,如何跳过这部分前置依赖的构建?
此时推荐你使用 Turborepo 来做 monorepo 构建方案,具体使用方法请参见 官方文档 和 examples 。
注:如果在 CI 中构建,同样需要容器支持恢复上次的 turbo 缓存,可以通过 --cache-dir 选项更改缓存位置。
Docs Blog Webpack 5 Prod Cache (`docs/docs/blog/webpack-5-prod-cache.md`)
toc: content order: 7 group: title: Blog
物理构建缓存
在 umi build 构建生产环境产物时,Umi 4 默认没有配置 webpack 5 的物理缓存,这是因为 webpack 的物理缓存失效时机问题,需要依赖用户项目的实际情况,所以没有很好的通用解决方案。
所以,当你不明确哪些依赖会让项目物理缓存失效时,很容易产生构建缓存不失效,导致产物是旧的问题,极大影响研发效率。
缓存场景
当你的项目需要构建缓存时,是有原因的,我们粗略把场景分成两类:普通项目、Monorepo 中的项目。
普通项目
构建比较慢,如何复用上次的物理缓存,做到多次构建提速?
首选解决思路
此时首选的优化思路应该是:考虑使用其他更快的现代转译器,比如调整 srcTranspiler 、cssMinifier 。
CI 中的问题
物理缓存一般存在于 node_modules/.cache ,这就意味着如果你在 CI 中构建,构建的基建必须要支持恢复上次的缓存文件,如果构建容器不支持恢复缓存,同样也无法享受好处。
选择依据
所以,当你:
多次构建:确实有多次反复构建的需求。
能恢复缓存:在本地构建,或在 CI 有手段能恢复上次的物理缓存文件。
时间长:项目构建时间比较长、开启其他转译器仍无法提速(或有强诉求无法切换转译器)。
满足这些条件后,你才应该考虑开启物理缓存。
配置方法
// .umirc.ts
import { join } from 'path';
import { defineConfig } from 'umi';
import { createHash } from 'crypto';
export default defineConfig({
chainWebpack(config, { env }) {
if (env === 'production') {
config.cache({
type: 'filesystem',
store: 'pack',
// 🟡 假如你的项目在 CI 中构建每次环境变量都不一样,请挑选或者排除
version: createEnvironmentHash(process.env),
buildDependencies: {
config: [__filename],
tsconfig: [join(__dirname, 'tsconfig.json')],
packagejson: [join(__dirname, 'package.json')],
umirc: [join(__dirname, '.umirc.ts')],
// 🟡 其他可能会影响项目的配置文件路径,其内容变更会使缓存失效
},
});
}
},
});
function createEnvironmentHash(env: Record<string, any>) {
const hash = createHash('md5');
hash.update(JSON.stringify(env));
const result = hash.digest('hex');
return result;
}请格外注意:
你的项目有哪些文件、依赖会影响项目,配置他们作为依赖,变更时可以使得缓存失效。
因为
process.env包括了所有的 nodejs 环境变量,这非常多,如果环境变量在 CI 中每次构建都存在差异,请挑选所需的环境变量,或者排除掉会变化的。ts// 如挑选可能会影响项目内容的环境变量 createEnvironmentHash({ NODE_ENV: process.env.NODE_ENV, // ... });
Monorepo 中的项目
在 monorepo 中,如何缓存需要前置构建的其他子包,比如构建 apps/project-umi 需要先构建好他依赖的子包 libs/component ,但是下次 libs/component 没有代码改动,如何跳过这部分前置依赖的构建?
此时推荐你使用 Turborepo 来做 monorepo 构建方案,具体使用方法请参见 官方文档 和 examples 。
注:如果在 CI 中构建,同样需要容器支持恢复上次的 turbo 缓存,可以通过 --cache-dir 选项更改缓存位置。
Docs Docs Api Api.En US (`docs/docs/docs/api/api.en-US.md`)
order: 1 toc: content translated_at: '2024-03-17T10:47:17.550Z'
API
For ease of search, the following content is sorted alphabetically.
umi
createBrowserHistory
Creates a BrowserHistory that uses the browser's built-in history to track application navigation. It is recommended for use in modern web browsers that support the HTML5 history API.
Type definition is as follows:
function createBrowserHistory(options?: { window?: Window }) => BrowserHistory;Usage example:
// create a BrowserHistory
import { createBrowserHistory } from 'umi';
const history = createBrowserHistory();
// or a iframe BrowserHistory
import { createBrowserHistory } from 'umi';
const history = createBrowserHistory({
window: iframe.contentWindow,
});createHashHistory
createHashHistory returns a HashHistory instance. The default window is the current document's defaultView.
The main difference between HashHistory and BrowserHistory is that HashHistory stores the current location in the hash part of the URL, which means it does not send a request to the server when switching routes. If you are hosting your site on a server that you cannot fully control, or in a single-page Electron application, HashHistory is recommended.
Usage example:
// create a HashHistory
import { createHashHistory } from 'umi';
const history = createHashHistory();createMemoryHistory
MemoryHistory is not operated or read from the address bar. It is also very suitable for testing and other rendering environments.
const history = createMemoryHistory(location)createSearchParams
A utility function that wraps new URLSearchParams(init), supports creating with arrays and objects
import { createSearchParams } from 'umi';
// Assuming the path http://a.com?foo=1&bar=2
createSearchParams(location.search);
createSearchParams("foo=1&bar=2");
createSearchParams("?foo=1&bar=2");
// Key-value pair object
createSearchParams({ foo: 'bar', qux: 'qoo'}).toString()
// foo=bar&qux=qoo
// Key-value tuple array
createSearchParams([["foo", "1"], ["bar", "2"]]).toString()
// foo=1&bar=2generatePath
Generates the actual route to be accessed using the given path with parameters and the corresponding params.
import { generatePath } from 'umi';
generatePath("/users/:id", { id: "42" }); // "/users/42"
generatePath("/files/:type/*", {
type: "img",
"*": "cat.jpg",
}); // "/files/img/cat.jpg"Helmet
The Helmet component provided by react-helmet-async, used to dynamically configure tags in the head of the page, such as the title.
Note: To ensure Helmet still works during SSR, be sure to use the Helmet provided by Umi instead of installing react-helmet separately
import { Helmet } from 'umi';
export default function Page() {
return (
<Helmet>
<title>Hello World</title>
</Helmet>
);
}history
Operations related to history, used to obtain current route information, execute route jumps, and listen for route changes.
// Recommended to use useLocation in components or hooks
import { useLocation } from 'umi';
export default function Page() {
let location = useLocation();
return (
<div>
{ location.pathname }
{ location.search }
{ location.hash }
</div>
);
}To get the current route information outside of React components and Hooks.
// location object, contains pathname, search, and hash
window.location.pathname;
window.location.search;
window.location.hash;Imperative route navigation.
import { history } from 'umi';
// Jump to a specific route
history.push('/list');
// Jump to a specific route with parameters
history.push('/list?a=b&c=d#anchor', state);
history.push({
pathname: '/list',
search: '?a=b&c=d',
hash: 'anchor',
},
{
some: 'state-data',
}
);
// Jump to the current path and refresh state
history.push({}, state)
// Jump back to the previous route
history.back();
history.go(-1);:::info{title=🚨}
Note: history.push and history.replace require using state as the second parameter passed to these two APIs
:::
Route listening.
import { history } from 'umi';
const unlisten = history.listen(({ location, action }) => {
console.log(location.pathname);
});
unlisten();Link
<Link> is a React component, an <a> element with routing jump functionality.
Type definition is as follows:
declare function Link(props: {
prefetch?: boolean | 'intent' | 'render' | 'viewport' | 'none';
prefetchTimeout?: number;
to: string | Partial<{ pathname: string; search: string; hash: string }>;
replace?: boolean;
state?: any;
reloadDocument?: boolean;
}): React.ReactElement;Example:
import { Link } from 'umi';
function IndexPage({ user }) {
return <Link to={user.id}>{user.name}</Link>;
}<Link to> supports relative path navigation; <Link reloadDocument> does not do routing navigation and is equivalent to the jump behavior of <a href>.
If prefetch is enabled, then when the user hovers over the component, Umi will automatically start preloading the component js files and data for the routing jump. (Note: Use this feature when routePrefetch is enabled)
matchPath
matchPath can match a given path with a known route format and return the match result.
Type definition is as follows:
declare function matchPath<ParamKey extends string = string>(
pattern: PathPattern | string,
pathname: string
): PathMatch<ParamKey> | null;
interface PathMatch<ParamKey extends string = string> {
params: Params<ParamKey>;
pathname: string;
pattern: PathPattern;
}
interface PathPattern {
path: string;
caseSensitive?: boolean;
end?: boolean;
}Example:
import { matchPath } from 'umi';
const match = matchPath(
{ path: "/users/:id" },
"/users/123",
);
// {
// "params": { "id": "123" },
// "pathname": "/users/123",
// "pathnameBase": "/users/123",
// "pattern": { "path": "/users/:id" }
// }matchRoutes
matchRoutes can match a given path with multiple potential route choices and return the match result.
Type definition is as follows:
declare function matchRoutes(
routes: RouteObject[],
location: Partial<Location> | string,
basename?: string
): RouteMatch[] | null;
interface RouteMatch<ParamKey extends string = string> {
params: Params<ParamKey>;
pathname: string;
route: RouteObject;
}Example:
import { matchRoutes } from 'umi';
const match = matchRoutes(
[
{
path: "/users/:id",
},
{
path: "/users/:id/posts/:postId",
},
],
"/users/123/posts/456",
);
// [
// {
// "params": {
// "id": "123",
// "postId": "456"
// },
// "pathname": "/users/123/posts/456",
// "pathnameBase": "/users/123/posts/456",
// "route": {
// "path": "/users/:id/posts/:postId"
// }
// }
// ]NavLink
<NavLink> is a special form of <Link>, aware of whether it is in an active routing state. Often used in navigation menus, breadcrumbs, Tabs to display the current selection status.
Type definition is as follows:
declare function NavLink(props: LinkProps & {
caseSensitive?: boolean;
children?: React.ReactNode | ((props: { isActive: boolean }) => React.ReactNode);
className?: string | ((props: { isActive: boolean }) => string | undefined);
end?: boolean;
style?: React.CSSProperties | ((props: { isActive: boolean }) => string | React.CSSProperties);
}): React.ReactElement;The examples below use style, className, and children to render the active state.
import { NavLink } from 'umi';
function Navs() {
return <ul>
<li><NavLink to="message" style={({ isActive }) => isActive ? { color: 'red' } : undefined}>Messages</NavLink></li>
<li><NavLink to="tasks" className={({ isActive }) => isActive ? 'active' : undefined}>Tasks</NavLink></li>
<li><NavLink to="blog">{({ isActive }) => <span className={isActive ? 'active' : undefined}>Blog</span>}</NavLink></li>
</ul>;
}Outlet
<Outlet> is used to render child routes within a parent route. If the parent route is an exact match, it will render the index route of the child routes (if any).
Type definition is as follows:
interface OutletProps {
context?: unknown;
}
declare function Outlet(
props: OutletProps
): React.ReactElement | null;Example:
import { Outlet } from 'umi';
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<Outlet />
</div>
);
}
function DashboardWithContext() {
return (
<div>
<h1>Dashboard</h1>
<Outlet context={{ prop: 'a' }}/>
</div>
);
}The context of the Outlet component can be retrieved in the child component using the API useOutletContext.
resolvePath
Used to resolve front-end routing jump paths on the client side.
Type definition is as follows:
declare function resolvePath(
to: Partial<Location> | string,
fromPathname?: string
): {
pathname: string;
search: string;
hash: string;
};Example:
// Same-level relative jump, returns { pathname: '/parent/child', search: '', hash: '' }
resolvePath('child', '/parent');
resolvePath('./child', '/parent');
resolvePath('', '/parent/child');
resolvePath('.', '/parent/child');
// Ancestor level relative jump, returns { pathname: '/parent/sibling', search: '', hash: '' }
resolvePath('../sibling', '/parent/child');
resolvePath('../../parent/sibling', '/other/child');
// Absolute jump, returns { pathname: '/target', search: '', hash: '' }
resolvePath('/target', '/parent');
resolvePath('/target', '/parent/child');
// Jump with search and hash, returns { pathname: '/params', search: '?a=b', hash: '#c' }
resolvePath('/params?a=b#c', '/prev');terminal
terminal is a tool for logging messages from the browser to the node terminal during the development stage.
Example:
import {terminal} from 'umi';
// The following three commands will print logs in different colors on the umi startup terminal
terminal.log('i am log level');
terminal.warn('i am warn level');
terminal.error('i am error level');Note that terminal only takes effect when the environment variable NODE_ENV is not production; the corresponding log call functions in Umi's build output will not have any effect, so you can leave the calls to terminal in your code.
useAppData
useAppData returns global application data.
Type definition is as follows:
declare function useAppData(): {
routes: Record<id, Route>;
routeComponents: Record<id, Promise<React.ReactComponent>>;
clientRoutes: ClientRoute[];
pluginManager: any;
rootElement: string;
basename: string;
clientLoaderData: { [routeKey: string]: any };
preloadRoute: (to: string) => void;
};Note: This API might still be adjusted.
useLocation
useLocation returns the current location object.
Type definition is as follows:
declare function useLocation(): {
pathname: string;
search: string;
state: unknown;
key: Key;
};One scenario is to perform some side effect operations when location changes, such as page view statistics.
import { useLocation } from 'umi';
function App() {
const location = useLocation();
React.useEffect(() => {
ga('send', 'pageview');
}, [location]);
// ...
}useMatch
useMatch returns match information for the given path; if no match, it will return null
Type definition is as follows:
declare function useMatch(pattern: {
path: string;
caseSensitive?: boolean;
end?: boolean;
} | string): {
params: Record<string, string>;
pathname: string;
pattern: {
path: string;
caseSensitive?: boolean;
end?: boolean;
};
};Example:
import { useMatch } from 'umi';
// when url = '/events/12'
const match = useMatch('/events/:eventId');
console.log(match?.pathname, match?.params.eventId);
// '/events/12 12'useNavigate
useNavigate hook function returns a function that can control jumping; for example, it can be used after submitting a form to jump to another page.
declare function useNavigate(): NavigateFunction;
interface NavigateFunction {
(
to: To,
options?: { replace?: boolean; state?: any }
): void;
(delta: number): void;
}Example:
- Jump to path
import { useNavigate } from 'umi';
let navigate = useNavigate();
navigate("../success", { replace: true });- Go back to the previous page
import { useNavigate } from 'umi';
let navigate = useNavigate();
navigate(-1);useOutlet
useOutlet returns the child route element currently matched, used internally by the <Outlet>.
Type definition is as follows:
declare function useOutlet(): React.ReactElement | null;Example:
import { useOutlet } from 'umi';
const Layout = ()=>{
const outlet = useOutlet()
return <div className="fancyLayout">
{outlet}
</div>
}useOutletContext
useOutletContext is used to return the context mounted on the Outlet component.
Type definition is as follows:
declare function useOutletContext<Context = unknown>(): Context;Example:
import { useOutletContext, Outlet } from 'umi';
const Layout = () => {
return <div className="fancyLayout">
<Outlet context={{ prop: 'from Layout'}} />
</div>
}
const SomeRouteComponentUnderLayout = () => {
const layoutContext = useOutletContext();
return JSON.stringify(layoutContext) // {"prop":"from Layout"}
}useParams
The useParams hook function returns a read-only key-value pair object of dynamic route matching parameters; child routes will inherit dynamic parameters from parent routes.
Type definition is as follows:
declare function useParams<
K extends string = string
>(): Readonly<Params<K>>;Example:
import { useParams } from 'umi';
// Assuming a route configuration user/:uId/repo/:rId
// Current path user/abc/repo/def
const params = useParams()
/* params
{ uId: 'abc', rId: 'def'}
*/useResolvedPath
useResolvedPath resolves the complete routing information for the target address based on the current path.
Type definition is as follows:
declare function useResolvedPath(to: To): Path;Example:
import { useResolvedPath } from 'umi';
const path = useResolvedPath('docs')
/* path
{ pathname: '/a/new/page/docs', search: '', hash: '' }
*/useRouteData
useRouteData returns the route data of the currently matched route.
Type definition is as follows:
declare function useRouteData(): {
route: Route;
};Note: This API might still be adjusted.
Example:
import { useRouteData } from 'umi';
const route = useRouteData();
/* route
{
route: {
path: 'a/page',
id: 'a/page/index',
parentId: '@@/global-layout',
file: 'a/page/index.tsx'
}
}
*/useRoutes
useRoutes is a hook function to render routes, pass in route configuration and optional parameter location to get the rendering result; if there is no matched route, the result is null.
Type definition is as follows:
declare function useRoutes(
routes: RouteObject[],
location?: Partial<Location> | string;
): React.ReactElement | null;Example:
import * as React from "react";
import { useRoutes } from "umi";
function App() {
let element = useRoutes([
{
path: "/",
element: <Dashboard />,
children: [
{
path: "messages",
element: <DashboardMessages />,
},
{ path: "tasks", element: <DashboardTasks /> },
],
},
{ path: "team", element: <AboutPage /> },
]);
return element;
}useRouteProps
Read the props attribute of the current route in the route configuration using this hook. You can use this hook to obtain additional information in the route configuration.
// .umirc.ts
routes: [
{
path: '/',
custom_key: '1',
}
]import { useRouteProps } from 'umi'
export default function Page() {
const routeProps = useRouteProps()
// use `routeProps.custom_key`
}Note: Also applicable to convention-based routing.
useSelectedRoutes
Used to read all the route information hit by the current path. For example, in a layout, it is possible to obtain information on all the subroutes hit, and also to obtain parameters configured in routes, which is very useful.
Example:
// layouts/index.tsx
import { useSelectedRoutes } from 'umi'
export default function Layout() {
const routes = useSelectedRoutes()
const lastRoute = routes.at(-1)
if (lastRoute?.pathname === '/some/path') {
return <div>1 : <Outlet /></div>
}
if (
---
### Docs Docs Api Api (`docs/docs/docs/api/api.md`)
---
order: 1
toc: content
---
# API
为方便查找,以下内容通过字母排序。
## umi
### createBrowserHistory
创建使用浏览器内置 `history` 来跟踪应用的 `BrowserHistory`。推荐在支持 HTML5 `history` 接口的 现代 Web 浏览器中使用。
类型定义如下:
```ts
function createBrowserHistory(options?: { window?: Window }) => BrowserHistory;使用范例:
// create a BrowserHistory
import { createBrowserHistory } from 'umi';
const history = createBrowserHistory();
// or a iframe BrowserHistory
import { createBrowserHistory } from 'umi';
const history = createBrowserHistory({
window: iframe.contentWindow,
});createHashHistory
createHashHistory 返回一个 HashHistory 实例。window 默认为当前 document 的 defaultView。
HashHistory 与 BrowserHistory 的主要区别在于,HashHistory 将当前位置存储在 URL 的哈希部分中,这意味着它在路由切换时不会发送请求到服务器。如果您将站点托管在您无法完全控制服务器上,或者在只提供同单页面的 Electron 应用程序中,推荐使用 HashHistory。
使用范例:
// create a HashHistory
import { createHashHistory } from 'umi';
const history = createHashHistory();createMemoryHistory
MemoryHistory 不会在地址栏被操作或读取。它也非常适合测试和其他的渲染环境。
const history = createMemoryHistory(location)createSearchParams
包装 new URLSearchParams(init) 的工具函数,支持使用数组和对象创建
import { createSearchParams } from 'umi';
// 假设路径 http://a.com?foo=1&bar=2
createSearchParams(location.search);
createSearchParams("foo=1&bar=2");
createSearchParams("?foo=1&bar=2");
// 键值对对象
createSearchParams({ foo: 'bar', qux: 'qoo'}).toString()
// foo=bar&qux=qoo
// 键值元组数组
createSearchParams([["foo", "1"], ["bar", "2"]]).toString()
// foo=1&bar=2generatePath
使用给定的带参数的 path 和对应的 params 生成实际要访问的路由。
import { generatePath } from 'umi';
generatePath("/users/:id", { id: "42" }); // "/users/42"
generatePath("/files/:type/*", {
type: "img",
"*": "cat.jpg",
}); // "/files/img/cat.jpg"Helmet
即 react-helmet-async 提供的 Helmet 组件,用于在页面中动态配置 head 中的标签,例如 title。
注意:为了确保 SSR 时 Helmet 仍能正常工作,请务必使用 Umi 提供的 Helmet 而不是单独安装 react-helmet
import { Helmet } from 'umi';
export default function Page() {
return (
<Helmet>
<title>Hello World</title>
</Helmet>
);
}history
和 history 相关的操作,用于获取当前路由信息、执行路由跳转、监听路由变更。
// 建议组件或 hooks 里用 useLocation 取
import { useLocation } from 'umi';
export default function Page() {
let location = useLocation();
return (
{ location.pathname }
{ location.search }
{ location.hash }
);
}如果在 React 组件和 Hooks 之外获取当前路由信息。
// location 对象,包含 pathname、search 和 hash
window.location.pathname;
window.location.search;
window.location.hash;命令式路由跳转。
import { history } from 'umi';
// 跳转到指定路由
history.push('/list');
// 带参数跳转到指定路由
history.push('/list?a=b&c=d#anchor', state);
history.push({
pathname: '/list',
search: '?a=b&c=d',
hash: 'anchor',
},
{
some: 'state-data',
}
);
// 跳转当前路径,并刷新 state
history.push({}, state)
// 跳转到上一个路由
history.back();
history.go(-1);:::info{title=🚨}
注意:history.push 和 history.replace 需要使用 state 需将 state 作为这两个 API 的第二个参数传递
:::
路由监听。
import { history } from 'umi';
const unlisten = history.listen(({ location, action }) => {
console.log(location.pathname);
});
unlisten();Link
<Link> 是 React 组件,是带路由跳转功能的 <a> 元素。
类型定义如下:
declare function Link(props: {
prefetch?: boolean | 'intent' | 'render' | 'viewport' | 'none';
prefetchTimeout?: number;
to: string | Partial<{ pathname: string; search: string; hash: string }>;
replace?: boolean;
state?: any;
reloadDocument?: boolean;
}): React.ReactElement;示例:
import { Link } from 'umi';
function IndexPage({ user }) {
return <Link to={user.id}>{user.name}</Link>;
}<Link to> 支持相对路径跳转;<Link reloadDocument> 不做路由跳转,等同于 <a href> 的跳转行为。
若开启了 prefetch 则当用户将鼠标放到该组件上方时,Umi 就会自动开始进行跳转路由的组件 js 文件和数据预加载。(注:使用此功能请开启 routePrefetch 配置)
matchPath
matchPath 可以将给定的路径以及一个已知的路由格式进行匹配,并且返回匹配结果。
类型定义如下:
declare function matchPath(
pattern: PathPattern | string,
pathname: string
): PathMatch | null;
interface PathMatch {
params: Params;
pathname: string;
pattern: PathPattern;
}
interface PathPattern {
path: string;
caseSensitive?: boolean;
end?: boolean;
}示例:
import { matchPath } from 'umi';
const match = matchPath(
{ path: "/users/:id" },
"/users/123",
);
// {
// "params": { "id": "123" },
// "pathname": "/users/123",
// "pathnameBase": "/users/123",
// "pattern": { "path": "/users/:id" }
// }matchRoutes
matchRoutes 可以将给定的路径以及多个可能的路由选择进行匹配,并且返回匹配结果。
类型定义如下:
declare function matchRoutes(
routes: RouteObject[],
location: Partial<Location> | string,
basename?: string
): RouteMatch[] | null;
interface RouteMatch {
params: Params;
pathname: string;
route: RouteObject;
}示例:
import { matchRoutes } from 'umi';
const match = matchRoutes(
[
{
path: "/users/:id",
},
{
path: "/users/:id/posts/:postId",
},
],
"/users/123/posts/456",
);
// [
// {
// "params": {
// "id": "123",
// "postId": "456"
// },
// "pathname": "/users/123/posts/456",
// "pathnameBase": "/users/123/posts/456",
// "route": {
// "path": "/users/:id/posts/:postId"
// }
// }
// ]NavLink
<NavLink> 是 <Link> 的特殊形态,他知道当前是否为路由激活状态。通常在导航菜单、面包屑、Tabs 中会使用,用于显示当前的选中状态。
类型定义如下:
declare function NavLink(props: LinkProps & {
caseSensitive?: boolean;
children?: React.ReactNode | ((props: { isActive: boolean }) => React.ReactNode);
className?: string | ((props: { isActive: boolean }) => string | undefined);
end?: boolean;
style?: React.CSSProperties | ((props: { isActive: boolean }) => string | React.CSSProperties);
}): React.ReactElement;下方示例分别用了 style、className 和 children 来渲染 active 状态。
import { NavLink } from 'umi';
function Navs() {
return
<li><NavLink to="message" style={({ isActive }) => isActive ? { color: 'red' } : undefined}>Messages</NavLink></li>
<li><NavLink to="tasks" className={({ isActive }) => isActive ? 'active' : undefined}>Tasks</NavLink></li>
<li><NavLink to="blog">{({ isActive }) => Blog}</NavLink></li>
;
}Outlet
<Outlet> 用于渲染父路由中渲染子路由。如果父路由被严格匹配,会渲染子路由中的 index 路由(如有)。
类型定义如下:
interface OutletProps {
context?: unknown;
}
declare function Outlet(
props: OutletProps
): React.ReactElement | null;示例:
import { Outlet } from 'umi';
function Dashboard() {
return (
<h1>Dashboard</h1>
<Outlet />
);
}
function DashboardWithContext() {
return (
<h1>Dashboard</h1>
<Outlet context={{ prop: 'a' }}/>
);
}Outlet 组件的 context 可以使用 API useOutletContext 在子组件中获取。
resolvePath
用于在客户端解析前端路由跳转路径。
类型定义如下:
declare function resolvePath(
to: Partial<Location> | string,
fromPathname?: string
): {
pathname: string;
search: string;
hash: string;
};示例:
// 同级相对跳转,返回 { pathname: '/parent/child', search: '', hash: '' }
resolvePath('child', '/parent');
resolvePath('./child', '/parent');
resolvePath('', '/parent/child');
resolvePath('.', '/parent/child');
// 祖先层级相对跳转,返回 { pathname: '/parent/sibling', search: '', hash: '' }
resolvePath('../sibling', '/parent/child');
resolvePath('../../parent/sibling', '/other/child');
// 绝对跳转,返回 { pathname: '/target', search: '', hash: '' }
resolvePath('/target', '/parent');
resolvePath('/target', '/parent/child');
// 携带 search 和 hash 跳转,返回 { pathname: '/params', search: '?a=b', hash: '#c' }
resolvePath('/params?a=b#c', '/prev');terminal
terminal 用于在开发阶段在浏览器向 node 终端输出日志的工具。
示例:
import {terminal} from 'umi';
// 下面三条命令会在 umi 启动终端上打出用不同颜色代表的日志
terminal.log('i am log level');
terminal.warn('i am warn level');
terminal.error('i am error level');注意 terminal 只在环境变量 NODE_ENV 非 production 时生效;在 Umi 的构建产物中对应的日志调用函数不会有任何作用,所以可以不必删除调用 terminal 的代码。
useAppData
useAppData 返回全局的应用数据。
类型定义如下:
declare function useAppData(): {
routes: Record;
routeComponents: Record>;
clientRoutes: ClientRoute[];
pluginManager: any;
rootElement: string;
basename: string;
clientLoaderData: { [routeKey: string]: any };
preloadRoute: (to: string) => void;
};注意:此处 API 可能还会调整。
useLocation
useLocation 返回当前 location 对象。
类型定义如下:
declare function useLocation(): {
pathname: string;
search: string;
state: unknown;
key: Key;
};一个场景是在 location change 时做一些 side effect 操作,比如 page view 统计。
import { useLocation } from 'umi';
function App() {
const location = useLocation();
React.useEffect(() => {
ga('send', 'pageview');
}, [location]);
// ...
}useMatch
useMatch 返回传入 path 的匹配信息;如果匹配失败将返回 null
类型定义如下:
declare function useMatch(pattern: {
path: string;
caseSensitive?: boolean;
end?: boolean;
} | string): {
params: Record<string, string>;
pathname: string;
pattern: {
path: string;
caseSensitive?: boolean;
end?: boolean;
};
};示例:
import { useMatch } from 'umi';
// when url = '/events/12'
const match = useMatch('/events/:eventId');
console.log(match?.pathname, match?.params.eventId);
// '/events/12 12'useNavigate
useNavigate 钩子函数返回一个可以控制跳转的函数;比如可以用在提交完表单后跳转到其他页面。
declare function useNavigate(): NavigateFunction;
interface NavigateFunction {
(
to: To,
options?: { replace?: boolean; state?: any }
): void;
(delta: number): void;
}示例:
- 跳转路径
import { useNavigate } from 'umi';
let navigate = useNavigate();
navigate("../success", { replace: true });- 返回上一页
import { useNavigate } from 'umi';
let navigate = useNavigate();
navigate(-1);useOutlet
useOutlet 返回当前匹配的子路由元素,<Outlet> 内部使用的就是此 hook 。
类型定义如下:
declare function useOutlet(): React.ReactElement | null;示例:
import { useOutlet } from 'umi';
const Layout = ()=>{
const outlet = useOutlet()
return
{outlet}
}useOutletContext
useOutletContext 用于返回 Outlet 组件上挂载的 context 。
类型定义如下:
declare function useOutletContext<Context = unknown>(): Context;示例:
import { useOutletContext, Outlet } from 'umi';
const Layout = () => {
return
<Outlet context={{ prop: 'from Layout'}} />
}
const SomeRouteComponentUnderLayout = () => {
const layoutContext = useOutletContext();
return JSON.stringify(layoutContext) // {"prop":"from Layout"}
}useParams
useParams 钩子函数返回动态路由的匹配参数键值对对象;子路由中会集成父路由的动态参数。
类型定义如下:
declare function useParams<
K extends string = string
>(): Readonly>;示例:
import { useParams } from 'umi';
// 假设有路由配置 user/:uId/repo/:rId
// 当前路径 user/abc/repo/def
const params = useParams()
/* params
{ uId: 'abc', rId: 'def'}
*/useResolvedPath
useResolvedPath 根据当前路径将目标地址解析出完整的路由信息。
类型定义如下:
declare function useResolvedPath(to: To): Path;示例:
import { useResolvedPath } from 'umi';
const path = useResolvedPath('docs')
/* path
{ pathname: '/a/new/page/docs', search: '', hash: '' }
*/useRouteData
useRouteData 返回当前匹配路由的数据的钩子函数。
类型定义如下:
declare function useRouteData(): {
route: Route;
};注意:此处 API 可能还会调整。
示例:
import { useRouteData } from 'umi';
const route = useRouteData();
/* route
{
route: {
path: 'a/page',
id: 'a/page/index',
parentId: '@@/global-layout',
file: 'a/page/index.tsx'
}
}
*/useRoutes
useRoutes 渲染路由的钩子函数,传入路由配置和可选参数 location, 即可得到渲染结果;如果没有匹配的路由,结果为 null。
类型定义如下:
declare function useRoutes(
routes: RouteObject[],
location?: Partial<Location> | string;
): React.ReactElement | null;示例:
import * as React from "react";
import { useRoutes } from "umi";
function App() {
let element = useRoutes([
{
path: "/",
element: <Dashboard />,
children: [
{
path: "messages",
element: <DashboardMessages />,
},
{ path: "tasks", element: <DashboardTasks /> },
],
},
{ path: "team", element: <AboutPage /> },
]);
return element;
}useRouteProps
读取当前路由在路由配置里的 props 属性,你可以用此 hook 来获取路由配置中的额外信息。
// .umirc.ts
routes: [
{
path: '/',
custom_key: '1',
}
]import { useRouteProps } from 'umi'
export default function Page() {
const routeProps = useRouteProps()
// use `routeProps.custom_key`
}注:同样适用于约定式路由。
useSelectedRoutes
用于读取当前路径命中的所有路由信息。比如在 layout 布局中可以获取到当前命中的所有子路由信息,同时可以获取到在 routes 配置中的参数,这格外有用。
实例:
// layouts/index.tsx
import { useSelectedRoutes } from 'umi'
export default function Layout() {
const routes = useSelectedRoutes()
const lastRoute = routes.at(-1)
if (lastRoute?.pathname === '/some/path') {
return 1 : <Outlet />
}
if (lastRoute?.extraProp) {
return 2 : <Outlet />
}
return <Outlet />
}useSearchParams
useSearchParams 用于读取和修改当前 URL 的 query string。类似 React 的 useState,其返回包含两个值的数组,当前 URL 的 search 参数和用于更新 search 参数的函数。
类型定义如下:
declare function useSearchParams(defaultInit?: URLSearchParamsInit): [
URLSearchParams,
(
nextInit?: URLSearchParamsInit,
navigateOpts?: : { replace?: boolean; state?: any }
) => void
];
type URLSearchParamsInit =
| string
| ParamKeyValuePair[]
| Record<string, string | string[]>
| URLSearchParams;示例:
import React from 'react';
import { useSearchParams } from 'umi';
function App() {
let [searchParams, setSearchParams] = useSearchParams();
function handleSubmit(event) {
event.preventDefault();
setSearchParams(serializeFormQuery(event.target));
}
return <form onSubmit={handleSubmit}>{/* ... */}</form>;
}withRouter
withRouter 参考 react-router faq 实现的版本, 仅实现了部分能力, 请参考类型定义按需使用, 建议迁移到 React Hook API。
类型定义如下:
export interface RouteComponentProps<T = ReturnType<typeof useParams>> {
history: {
back: () => void;
goBack: () => void;
location: ReturnType<typeof useLocation>;
push: (url: string, state?: any) => void;
};
location: ReturnType<typeof useLocation>;
match: {
params: T;
};
params: T;
navigate: ReturnType<typeof useNavigate>;
}示例:
import React from 'react';
import { withRouter } from 'umi';
class HelloWorld extends React.Component<any> {
render() {
return (
Hello World {this.props.location.pathname}
<h2>params: {JSON.stringify(this.props.match.params)}</h2>
{
this.props.history.push('/users');
}}
>
To Users
);
}
}
export default withRouter(HelloWorld);Docs Docs Api Commands.En US (`docs/docs/docs/api/commands.en-US.md`)
order: 4 toc: content translated_at: '2024-03-17T10:43:51.227Z'
Command Line
umi offers many built-in command line interfaces to start and build projects, as well as some auxiliary development commands, such as generators.
To get a list of available commands, you can run the help command in the project directory:
umi helpYou should see logs similar to the following:
Usage: umi <command> [options]
Commands:
build build app for production
config umi config cli
dev dev server for development
help show commands help
lint lint source code using eslint and stylelint
setup setup project
deadcode check dead code
version show umi version
v show umi version
plugin inspect umi plugins
verify-commit verify the commit message, which is usually used with husky.
preview locally preview production build
run run the script commands, support for ts and zx
generate generate code snippets quickly
g generate code snippets quickly
Run `umi help <command>` for more information of specific commands.
Visit https://umijs.org/ to learn more about Umi.For ease of lookup, the commands below are sorted alphabetically.
build
Build the project for deployment in a production environment.
$ umi buildconfig
Quickly view and modify configuration via the command line.
Viewing configuration, you can use list or get.
$ umi config list
- [key: polyfill] false
- [key: externals] { esbuild: true }
$ umi config get mfsu
- [key: externals] { esbuild: true }Modifying configuration, you can use set or remove.
$ umi config set polyfill false
set config:polyfill on /private/tmp/sorrycc-wsYpty/.umirc.ts
$ umi config remove polyfill
remove config:polyfill on /private/tmp/sorrycc-wsYpty/.umirc.tsdev
Start a local development server for project development and debugging.
$ umi dev
╔═════════════════════════════════════════════════════╗
║ App listening at: ║
║ > Local: https://127.0.0.1:8001 ║
ready - ║ > Network: https://192.168.1.1:8001 ║
║ ║
║ Now you can open browser with the above addresses👆 ║
╚═════════════════════════════════════════════════════╝
event - compiled successfully in 1051 ms (416 modules)generate
Used for incrementally generating files or enabling features, the command line alias is g.
When used without any parameters, it will opt for an interactive generator selection.
$ umi g
# or
$ umi generate
? Pick generator type › - Use arrow-keys. Return to submit.
❯ Create Pages -- Create a umi page by page name
Enable Prettier -- Enable PrettierYou can also specify parameters.
# Generate route files
$ umi g page index --typescript --lesshelp
View help.
$ umi help
Usage: umi <command> [options]
Commands:
build build app for production
config umi config cli
dev dev server for development
help show commands help
setup setup project
version show umi version
plugin inspect umi plugins
generate generate code snippets quickly
Run `umi help <command>` for more information of specific commands.
Visit https://umijs.org/ to learn more about Umi.You can also specify a command to see detailed help for that command.
$ umi help build
Usage: umi build [options]
build app for production.
Details:
umi build
# build without compression
COMPRESS=none umi build
# clean and build
umi build --cleanlint
Used to check and correct code to match rules.
$ umi lint
Usage: umi lint
Support for validation of js, ts, tsx, jsx type files only: umi lint --eslint-only
Support for validation of css, less and other style files only: umi lint --stylelint-only
Support for cssinjs mode validation: umi lint --stylelint-only --cssinjs
Correct the code: --fixplugin
Plugin related operations, currently only supports the list subcommand.
List all plugins.
$ umi plugin list
- @umijs/core/dist/service/servicePlugin
- @umijs/preset-umi (from preset)
- @umijs/preset-umi/dist/registerMethods (from preset)
- @umijs/preset-umi/dist/features/appData/appData (from preset)
- @umijs/preset-umi/dist/features/check/check (from preset)
- @umijs/preset-umi/dist/features/configPlugins/configPlugins (from preset)
- virtual: config-styles
- virtual: config-scripts
- virtual: config-routes
- virtual: config-plugins
...preview
The umi preview command will launch a local static web server, running the dist folder at http://127.0.0.1:4172, for previewing the production build, supporting proxy, mock, etc. settings.
You can use the --port parameter to configure the service's running port.
$ umi preview --port 9527Now the preview command will run the server at http://127.0.0.1:9527.
Use the --host parameter to specify the service's running hostname.
The following user configurations will also take effect during preview
Note that the dist directory will change with the configuration of outputPath.
run
The umi run command allows you to run TypeScript and ESM files like running js with node. You can pair it with zx for better script command usability.
$ umi run ./script.tssetup
Initialize the project, doing operations like generating temporary files. Usually set in scripts.postinstall in package.json.
{
"scripts": { "postinstall": "umi setup" }
}deadcode
Used to find files in the src directory that are not referenced, and output to the root directory.
$ umi deadcode
- Preparing...
- begin check deadCode
- write file /examples/umi-run/DeadCodeList-{timeStamp}.txt
- check dead code end, please be careful if you want to remove themmfsu
The umi mfsu command can be used to view MFSU dependency information, rebuild MFSU dependencies, and clear MFSU dependencies.
$ umi mfsu$ umi mfsu ls
[email protected]
regenerator-runtime/[email protected]
react/[email protected]
[email protected]
react-error-overlay/lib/[email protected]
[email protected]
[email protected]
lodash/[email protected]
lodash/[email protected]
lodash/[email protected]
...$ umi mfsu build
info - Preparing...
info - MFSU eager strategy enabled
warn - Invalidate webpack cache since mfsu cache is missing
info - [MFSU] buildDeps since cacheDependency has changed
...
info - [plugin: @umijs/preset-umi/dist/commands/mfsu/mfsu] [MFSU][eager] build success$ # Delete dependency information list
$ umi mfsu remove
$ # Delete dependency information list and product files
$ umi mfsu remove --allverifyCommit
Verify commit message information, usually used in conjunction with husky.
For example, configure the following in .husky/commit-msg,
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install umi verify-commit $1version
View the umi version, equivalent to umi -v.
$ umi version
4.0.0Docs Docs Api Commands (`docs/docs/docs/api/commands.md`)
order: 4 toc: content
命令行
umi 提供了很多内置的命令行用于启动,构建项目,另外还有一些辅助开发的命令,如生成器等。
要获取可用的命令列表,你可以在项目目录中运行 help 命令:
umi help你应该能看到类似如下的日志:
Usage: umi <command> [options]
Commands:
build build app for production
config umi config cli
dev dev server for development
help show commands help
lint lint source code using eslint and stylelint
setup setup project
deadcode check dead code
version show umi version
v show umi version
plugin inspect umi plugins
verify-commit verify the commit message, which is usually used with husky.
preview locally preview production build
run run the script commands, support for ts and zx
generate generate code snippets quickly
g generate code snippets quickly
Run `umi help <command>` for more information of specific commands.
Visit https://umijs.org/ to learn more about Umi.为方便查找,以下命令通过字母排序。
build
构建项目,适用于生产环境的部署。
$ umi buildconfig
通过命令行快速查看和修改配置。
查看配置,可以用 list 或 get。
$ umi config list
- [key: polyfill] false
- [key: externals] { esbuild: true }
$ umi config get mfsu
- [key: externals] { esbuild: true }修改配置,可以用 set 或 remove。
$ umi config set polyfill false
set config:polyfill on /private/tmp/sorrycc-wsYpty/.umirc.ts
$ umi config remove polyfill
remove config:polyfill on /private/tmp/sorrycc-wsYpty/.umirc.tsdev
启动本地开发服务器,进行项目的开发与调试。
$ umi dev
╔═════════════════════════════════════════════════════╗
║ App listening at: ║
║ > Local: https://127.0.0.1:8001 ║
ready - ║ > Network: https://192.168.1.1:8001 ║
║ ║
║ Now you can open browser with the above addresses👆 ║
╚═════════════════════════════════════════════════════╝
event - compiled successfully in 1051 ms (416 modules)generate
用于增量生成文件或启用功能,命令行别名是 g。
不加任何参数时会给交互式的生成器选择。
$ umi g
# 或
$ umi generate
? Pick generator type › - Use arrow-keys. Return to submit.
❯ Create Pages -- Create a umi page by page name
Enable Prettier -- Enable Prettier也可以指定参数。
# 生成路由文件
$ umi g page index --typescript --lesshelp
查看帮助。
$ umi help
Usage: umi <command> [options]
Commands:
build build app for production
config umi config cli
dev dev server for development
help show commands help
setup setup project
version show umi version
plugin inspect umi plugins
generate generate code snippets quickly
Run `umi help <command>` for more information of specific commands.
Visit https://umijs.org/ to learn more about Umi.也可指定命令,查看特定命令的详细帮助。
$ umi help build
Usage: umi build [options]
build app for production.
Details:
umi build
# build without compression
COMPRESS=none umi build
# clean and build
umi build --cleanlint
用于检查及修正代码是否符合规则。
$ umi lint
Usage: umi lint
支持只校验 js、ts、tsx、jsx 类型文件: umi lint --eslint-only
支持只校验 css、less 等样式文件: umi lint --stylelint-only
支持校验 cssinjs 模式校验: umi lint --stylelint-only --cssinjs
修正代码: --fixplugin
插件相关操作,目前只支持 list 子命令。
列出所有插件。
$ umi plugin list
- @umijs/core/dist/service/servicePlugin
- @umijs/preset-umi (from preset)
- @umijs/preset-umi/dist/registerMethods (from preset)
- @umijs/preset-umi/dist/features/appData/appData (from preset)
- @umijs/preset-umi/dist/features/check/check (from preset)
- @umijs/preset-umi/dist/features/configPlugins/configPlugins (from preset)
- virtual: config-styles
- virtual: config-scripts
- virtual: config-routes
- virtual: config-plugins
...preview
umi preview 命令会在本地启动一个静态 Web 服务器,将 dist 文件夹运行在 http://127.0.0.1:4172, 用于预览构建后产物, 支持 proxy、mock 等设置。
你可以通过 --port 参数来配置服务的运行端口。
$ umi preview --port 9527现在 preview 命令会将服务器运行在 http://127.0.0.1:9527.
通过 --host 参数来指定 配置服务运行的 hostname。
以下用户配置在 preview 时也会生效
注意 dist 目录会随着配置 outputPath 的变更而变更。
run
umi run 命令可以让你像 node 运行 js 一样来运行 TypeScript 和 ESM 文件。你可以搭配 zx 来更好的使用脚本命令。
$ umi run ./script.tssetup
初始化项目,会做临时文件的生成等操作。通常在 package.json 的 scripts.postinstall 里设置。
{
"scripts": { "postinstall": "umi setup" }
}deadcode
用于查找 src 目录下未被引用的文件,并在根目录输出文件。
$ umi deadcode
- Preparing...
- begin check deadCode
- write file /examples/umi-run/DeadCodeList-{timeStamp}.txt
- check dead code end, please be careful if you want to remove themmfsu
umi mfsu 命令可以查看 MFSU 依赖信息、重新构建 MFSU 依赖和清除 MFSU 依赖。
$ umi mfsu$ umi mfsu ls
[email protected]
regenerator-runtime/[email protected]
react/[email protected]
[email protected]
react-error-overlay/lib/[email protected]
[email protected]
[email protected]
lodash/[email protected]
lodash/[email protected]
lodash/[email protected]
...$ umi mfsu build
info - Preparing...
info - MFSU eager strategy enabled
warn - Invalidate webpack cache since mfsu cache is missing
info - [MFSU] buildDeps since cacheDependency has changed
...
info - [plugin: @umijs/preset-umi/dist/commands/mfsu/mfsu] [MFSU][eager] build success$ # 删除依赖信息列表
$ umi mfsu remove
$ # 删除依赖信息列表和产物文件
$ umi mfsu remove --allverifyCommit
验证 commit message 信息,通常和 husky 搭配使用。
比如在 .husky/commit-msg 做如下配置,
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install umi verify-commit $1version
查看 umi 版本,等同于 umi -v。
$ umi version
4.0.0Docs Docs Api Config.En US (`docs/docs/docs/api/config.en-US.md`)
order: 2 toc: content translated_at: '2024-03-17T10:42:29.295Z'
Configuration
For the custom configurations available in umi, you can use the .umirc.ts file or config/config.ts in the project root directory. It's noteworthy that both files have the same functionality, just residing in different directories. You have to choose one of them, with .umirc.ts having a higher priority.
For more information about the directory structure, you can learn more here.
umi's configuration file is a regular node module, which is used when the umi command line is executed and is not included in the browser-side build.
For some configurations that are required for the browser-side build, and some configurations that affect the style presentation, in umi they are collectively referred to as "runtime configuration", you can see more about it in runtime configuration.
Here is the simplest example of an umi configuration file:
import { defineConfig } from 'umi';
export default defineConfig({
outputPath: 'dist',
});Wrapping configurations with defineConfig is to get better spell check support when writing the configuration file. If you don't need it, export default {} works too.
It's important to note that when using umi, you don't need to understand the purpose of each configuration. You can briefly browse through all the configurations that umi supports below, and then, when needed, come back to see how to enable and modify the configurations you need.
For convenience, the following configurations are sorted alphabetically.
alias
- Type:
Record<string, string> - Default value:
{}
Configure aliases, to map the source in import statements.
For example:
{
alias: {
foo: '/tmp/to/foo',
}
}Then in the code, import 'foo' will actually become import '/tmp/to/foo'.
Some tips:
- It's better to use absolute paths as the value of alias, especially when pointing to a dependency, remember to add
require.resolve, for instance,
// ❌
{
alias: {
foo: 'foo',
}
}
// ✅
{
alias: {
foo: require.resolve('foo'),
}
}- If you don't want subpaths to be mapped as well, remember to add a
$suffix, for example
// import 'foo/bar' will be mapped to import '/tmp/to/foo/bar'
{
alias: {
foo: '/tmp/to/foo',
}
}
// import 'foo/bar' will still be import 'foo/bar', and won't be modified
{
alias: {
foo$: '/tmp/to/foo',
}
}autoprefixer
- Type:
object - Default value:
{ flexbox: 'no-2009' }
Used for parsing CSS and adds vendor prefixes to CSS rules using values from Can I Use. It enables autoprefixing, such as automatically adding -webkit- prefix.
For more configurations, please consult autoprefixer's options.
analyze
- Type:
object - Default value:
{}
Specific configuration options for the analyzer plugin when analyzing product composition through the ANALYZE environment variable. See webpack-bundle-analyzer
When using Vite mode, in addition to customizing the configuration of rollup-plugin-visualizer, excludeAssets, generateStatsFile, openAnalyzer, reportFilename, reportTitle options will automatically adapt.
base
- Type:
string - Default value:
/
To deploy the umi project in a non-root directory, you can use the base configuration.
The base configuration allows you to set a routing prefix for your application. For example, with routes / and /users, after setting base to /foo/, you can access the previous routes through /foo/ and /foo/users.
Note: The base configuration must be set at build time and can't be changed without rebuilding, because this value is inlined in the client-side package.
cacheDirectoryPath
- Type:
string - Default value:
node_modules/.cache
By default, Umi stores some cache files generated during the build process in the node_modules/.cache directory, such as logger logs, webpack cache, mfsu cache, etc. You can modify Umi's cache file directory using the cacheDirectoryPath configuration.
Example,
// Change the cache file path to the node_modules/.cache1 folder
cacheDirectoryPath: 'node_modules/.cache1',chainWebpack
- Type:
(memo, args) => void - Default value:
null
To extend Umi's built-in webpack configuration, we provide a way to modify the webpack configuration in a chained programming style, based on webpack-chain. See the documentation of webpack-api for specific APIs.
As shown below:
export default {
chainWebpack(memo, args) {
return memo;
},
};This function has two parameters:
memois the existing webpack configurationargscontains some additional information and auxiliary objects, currently includingenvandwebpack.envis the current environment, with values ofdevelopmentorproduction;webpackis the webpack object, from which webpack's built-in plugins can be obtained.
Usage example:
export default {
chainWebpack(memo, { env, webpack }) {
// Set alias
memo.resolve.alias.set('foo', '/tmp/to/foo');
// Add extra plugins
memo.plugin('hello').use(Plugin, [...args]);
// Delete Umi's built-in plugin
memo.plugins.delete('hmr');
},
};clickToComponent
- Type:
{ editor?: string } - Default value:
false
Currently, only React projects are supported.
When this feature is enabled, you can jump to the source code location in the editor by Option+Click/Alt+Click on the component. Option+Right-click/Alt+Right-click opens the context to view the parent component.
About parameters. The editor parameter defaults to 'vscode', supporting vscode, vscode-insiders and cursor.
Configure the behavior of clickToComponent, for more details see click-to-component.
Example:
// .umirc.ts
export default {
clickToComponent: {},
};clientLoader
- Type:
{} - Default value:
false
When enabled, you can declare a data loading function clientLoader for each route. Extracting the data loading process required by the page into clientLoader allows Umi to preload the data before the page components are fully loaded, avoiding the issue of waterfall requests. For details, see Route Data Preloading.
Example:
// .umirc.ts
export default {
clientLoader: {},
};Configuration enabled, use in the route component:
// pages/.../some_page.tsx
import { useClientLoaderData } from 'umi';
export default function SomePage() {
const { data } = useClientLoaderData();
return {data};
}
export async function clientLoader() {
const data = await fetch('/api/data');
return data;
}codeSplitting
- Type:
{ jsStrategy: 'bigVendors' | 'depPerChunk' | 'granularChunks'; jsStrategyOptions: {} } - Default value:
null
Provide code splitting strategy solutions.
bigVendors is the big vendors scheme, which packs the files under node_modules in the async chunk together, which can avoid repetition. At the same time, the disadvantages are, 1) the size of a single file is too large, 2) there is no caching efficiency to speak of.
depPerChunk is similar to bigVendors, but different in that dependencies are split by package name + version, solving bigVendors’ issues with size and caching efficiency. However, it also brings potential problems, possibly leading to more requests. My understanding is that for non-large projects its actually fine because, 1) a single page's requests will not include a very large number of dependencies, 2) based on HTTP/2, dozens of requests are not a problem. But, for large or huge projects, a more suitable solution is needed.
granularChunks take a middle value between bigVendors and depPerChunk, while also having better utilization of caching efficiency. Unless there are special circumstances, the granularChunks strategy is recommended.
conventionLayout
- Type:
boolean - Default value:
undefined
src/layouts/index.[tsx|vue|jsx|js] is conventionally set as layout, enabled by default. It can be disabled by setting conventionLayout: false.
conventionRoutes
- Type:
{ base: string; exclude: RegExp[] } - Default value:
null
Modify the default convention-based route rules, only valid when using umi's convention-based routing. Convention-based routing, also known as file-based routing, means that you don’t need to write configurations manually, as the file system is the routes, and the routing configuration is analyzed through directories and files and their naming.
When using convention-based routing, it's conventioned that all (j|t)sx? files under src/pages are routes.
You can see more details in Convention-based Routing.
base
base is used to set the base path for the routes by convention, by default reading from src/pages. If it is a site for documentation, you might need to change it to ./docs;
exclude
You can use exclude to filter some files that are not needed, such as you can use it to exclude files under components, models, etc.
Example,
// Do not recognize files under components and models directories as routes
conventionRoutes: {
exclude: [/\/components\//, /\/models\//],
}copy
- Type:
Array<string | { from: string; to: string; }> - Default value:
[]
Configure the files or folders to be copied to the output directory.
When configuring as a string, it is copied to the product directory by default, such as:
copy: ['foo.json', 'src/bar.json']Will produce the following product structure:
+ dist
- bar.json
- foo.json
+ src
- bar.json
- foo.jsonYou can also configure the specific copy location through objects, where the relative path starts from the project root directory:
copy: [
{ from: 'from', to: 'dist/output' },
{ from: 'file.json', to: 'dist' }
]This time will produce the following product structure:
+ dist
+ output
- foo.json
- file.json
+ from
- foo.json
- file.jsoncrossorigin
- Type:
{ includes?: string[] } - Default value:
false
Configure the script tag's crossorigin. If declared, the local script will have a crossorigin="anonymous" attribute.
Regarding the parameters. The parameter includes can allow extra non-local script tags to have this attribute.
For example:
crossorigin: {}Then the output HTML will have these changes,
-
<script src="/umi.js"></script>
+
<script src="/umi.js" crossorigin="anonymous"></script>cssMinifier
- Type:
string, optional values:esbuild,cssnano,parcelCSS,none - Default value:
esbuild
Configure the tool for minifying CSS during build time; none means not to minify.
Example:
{
cssMinifier: 'esbuild'
}cssMinifierOptions
- Type:
object - Default value:
{}
Options for the CSS minification tool, cssMinifier.
Example:
{
cssMinifier: 'esbuild',
cssMinifierOptions: {
minifyWhitespace: true,
minifySyntax: true,
},
}Consult the corresponding documentation for CSS minification configuration.
cssPublicPath
- Type:
string - Default value:
./
Customize the public path for external resources like images and files in CSS. Acts similar to publicPath. The default value is ./.
cssLoader
- Type:
object - Default value:
{}
Configure css-loader , see css-loader > options
cssLoaderModules
- Type:
object - Default value:
{}
Configure the behavior of CSS modules, see css-loader > modules.
For example:
cssLoaderModules: {
// Configure to use camelCase
exportLocalsConvention: 'camelCase'
}deadCode
- Type:
{ patterns?: string[]; exclude?: string[]; failOnHint?: boolean; detectUnusedFiles?: boolean; detectUnusedExport?: boolean; context?: string } - Default value:
false
Check for unused files and exports, only enabled at build phase.
For example:
deadCode: {}Then execute build, if any issues are found, a warning will be printed:
Warning: There are 1 unused files:
1. /pages/index.module.less
Please be careful if you want to remove them (¬º-°)¬.Configuration items:
patterns: Scope of code identification, such as['src/pages/**']exclude: Scope to exclude from detection, such as['src/pages/utils/**']failOnHint: Whether to stop the process if detection fails, defaultfalsenot to stopdetectUnusedFiles: Whether to detect unused files, defaulttruefor detectiondetectUnusedExport: Whether to detect unused exports, defaulttruefor detectioncontext: The directory to start matching, default as the current project root directory
define
- Type:
Record<string, string> - Default value: As shown below
{
'process.env.NODE_ENV' : process.env.NODE_ENV,
'process.env.HMR' : process.env.HMR,
'process.env.SOCKET_SERVER': process.env.ERROR_OVERLAY'
}Set available variables in the code based on define-plugin plugin.
:::warning{title=🚨}
- Property values will be converted with
JSON.stringify. - The replacement of key values is matched by their syntactic form, for instance, configuring
{'a.b.c': 'abcValue'}can't replace the code ina.b?.c.
:::
For example,
define: { FOO: 'bar' }Then the code like console.log(hello, FOO) will be compiled to console.log(hello, 'bar').
When you use these variables in a TypeScript project, you need to declare the variable type in the typings file to support ts type hints, for example:
If your typings file is global:
// typings.d.ts
declare const FOO: string;If your typings file is non-global (contains import/export):
// typings.d.ts
import './other.d.ts';
declare global {
const FOO: string;
}devtool
- Type:
string - Default value: Dev defaults to
cheap-module-source-map, no sourcemap by default for build
Sets the sourcemap generation method.
Common optional values include:
eval, the fastest type, but does not support old browserssource-map, the slowest but most comprehensive type
Example,
// Disable sourcemap generation in dev stage
devtool: false;
// Only set sourcemap for dev stage
devtool: process.env.NODE_ENV === 'development' ? 'eval' : false;classPropertiesLoose
- Type:
object - Default value:
{}
Set babel class-properties to enable loose mode
esbuildMinifyIIFE
- Type:
boolean - Default value:
false
Fix the namespace conflict caused by global variables automatically introduced by the esbuild compressor.
Since Umi 4 defaults to using esbuild as the compressor, the compressor will automatically inject global variables as polyfill, which may cause conflicts like async chunks global variables, conflicts between qiankun sub-apps and main apps' global variables, etc. This issue can be resolved by enabling this option or switching the jsMinifier compressor.
For more details, see vite#7948.
Example,
esbuildMinifyIIFE: trueexternals
- Type:
Record<string, string> | Function - Default value:
{}
Set which modules are not to be bundled, instead to be imported through <script> or other methods, usually used in conjunction with headScripts configuration.
Example,
// external react
externals: { react: 'React' },
headScripts: ['https://unpkg.com/[email protected]/umd/react.production.min.js'],Note: Do not easily set antd’s externals, as due to its many dependencies and complicated usage, many problems could occur, and it’s hard to fully explain in one or two sentences.
extraBabelIncludes
- Type:
Array<string | RegExp> - Default value:
[]
Configures extra NPM packages or directories for Babel compilation. For example:
export default {
extraBabelIncludes: [
// Support absolute path
join(__dirname, '../../common'),
// Support npm package
'react-monaco-editor',
// Transpile all paths containing @scope
/@scope/
],
};extraBabelPlugins
- Type:
string[] | Function - Default value:
[]
Configures extra babel plugins. Can pass the plugin address or plugin function.
extraBabelPresets
- Type:
string[] | Function - Default value:
[]
Configures extra babel plugin sets. Can pass the plugin set address or plugin set function.
extraPostCSSPlugins
- Type:
PostCSSPlugin[] - Default value:
[]
Configures extra postcss
exportStatic
- Type:
{ extraRoutePaths: IUserExtraRoute[] | (() => IUserExtraRoute[] | Promise<IUserExtraRoute[]>), ignorePreRenderError: boolean } - Default:
undefined
When enabled, HTML files will be generated separately for each route, typically used for static site hosting. For example, if a project has the following routes:
/
/docs
/docs/aWithout exportStatic enabled, it will output:
dist/index.htmlWith exportStatic enabled, it will output:
dist/index.html
dist/docs/index.html
dist/docs/a/index.htmlAdditional pages can be generated through the extraRoutePaths sub-configuration, typically used for static generation of dynamic routes. For example, with the following route:
/news/:idBy default, it will only output dist/news/:id/index.html, but you can staticize it through extraRoutePaths configuration:
// .umirc.ts
export default {
exportStatic: {
// Configure fixed values
extraRoutePaths: ['/news/1', '/news/2'],
// Can also configure a function for dynamic retrieval
extraRoutePaths: async () => {
const res = await fetch('https://api.example.com/news');
const data = await res.json();
return data.map((item) => `/news/${item.id}`);
},
},
}The output files will then become:
dist/news/:id/index.html
dist/news/1/index.html
dist/news/2/index.htmlIn addition to supporting string data configuration, extraRoutePaths can also be configured as an object array, used when SSR is enabled but you want to disable pre-rendering for certain routes, for example:
// .umirc.ts
export default {
exportStatic: {
// Output additional page files but skip pre-rendering
extraRoutePaths: [{ path: '/news/1', prerender: false }],
},
}When exportStatic is used with ssr, pre-rendering will be performed. If pre-rendering fails, an exception will be thrown and the build will be terminated. You can configure ignorePreRenderError to ignore pre-rendering failure errors, for example:
// .umirc.ts
export default {
exportStatic: {
// Ignore pre-rendering failure errors
ignorePreRenderError: true,
},
}favicons
- Type:
string[] - Default:
null
By default, the site will use the conventional favicon to create icon meta head tags.
You can customize it like this:
favicons: [
// Complete URL
'https://domain.com/favicon.ico',
// This will point to `/favicon.png`, make sure your project has `public/favicon.png`
'/favicon.png'
]reactCompiler
- Type:
boolean | object - Default:
null
Whether to enable React Compiler through babel-plugin-react-compiler. Reference https://react.dev/learn/react-compiler.
reactCompiler: true,React Compiler options are passed directly to babel-plugin-react-compiler:
reactCompiler: {
target: '19',
},Note:
- reactCompiler is currently incompatible with mfsu and mako. If they are enabled together, an error will be thrown; utoopack passes the React Compiler plugin config through babel-loader. To use the native Rust implementation in utoopack, configure
utoopack.reactCompilerinstead. - reactCompiler targets React 19 by default. Please install react@19 and react-dom@19 as project dependencies. To use React 17 or 18, configure the matching
targetand install react-compiler-runtime. - The old
forgetconfig is still accepted for compatibility, but it is deprecated. Please migrate toreactCompiler.
forkTSChecker
- Type:
object - Default:
null
Enable TypeScript type checking. Based on fork-ts-checker-webpack-plugin, configuration options can be found in fork-ts-checker-webpack-plugin's Options.
hash
- Type:
boolean - Default:
false
Enable hash mode to include hash suffixes in build artifacts. Typically used for incremental releases and avoiding browser cache loading.
When enabled, the output typically looks like this:
+ dist
- logo.sw892d.png
- umi.df723s.js
- umi.8sd8fw.css
- index.htmlNote: HTML files never have hash suffixes.
headScripts
- Type:
string[] | Script[] - Default:
[]
Configure additional scripts in the <head>.
For example:
headScripts: [`alert(1);`, `https://a.com/b.js`],Will generate HTML:
<script>
alert(1);
</script>
<script src="https://a.com/b.js"></script>If additional attributes are needed, switch to object format, for example:
headScripts: [
{ src: '/foo.js', defer: true },
{ content: `alert('hello');`, charset: 'utf-8' },
],helmet
- Type:
boolean - Default:
true
Configure the integration of react-helmet-async. When set to false, react-helmet-async will not be integrated, and you won't be able to import { Helmet } from the framework. The build artifacts will also be reduced by the corresponding size.
history
- Type:
{ type: 'browser' | 'hash' | 'memory' } - Default:
{ type: 'browser' }
Set the routing history type.
historyWithQuery
- Type:
{} - Default:
false
Let history carry query parameters. For navigation scenarios using useNavigate, you'll need to handle the query manually.
https
- Type:
{ cert: string; key: string; hosts: string[]; http2?: boolean } - Default:
{ hosts: ['127.0.0.1', 'localhost'] }
Enable HTTPS mode for development. Umi 4 uses mkcert by default to quickly create certificates. Please ensure it's installed.
About the parameters:
certandkeyare used to specify the cert and key files respectively.hostsspecifies which hosts should support HTTPS access, default is['127.0.0.1', 'localhost'].http2specifies whether to use HTTP 2.0 protocol, default is true (using HTTP 2.0 in Chrome or Edge browsers occasionally results inERR_HTTP2_PROTOCOL_ERROR, if encountered, it's recommended to set this to false).
Example:
https: {
}icons
- Type:
{ autoInstall: {}; alias: Record<string,string>; include: Array<string>; } - Default:
false
You can quickly reference icon sets or local icons through the Icon component exported by umi.
Using Icon Sets
In the umi configuration file, enable the icons feature and allow automatic installation of icon libraries.
icons: { autoInstall: {} },Usage in pages:
import { Icon } from 'umi';The string in icon is a combination of collection:icon, separated by :. Icon sets are recommended to be searched on the Icônes website.
Using Local Icons
In the umi configuration file, enable the icons feature.
icons: {},To use local svg icons, you need to save the svg files in the src/icons directory, then reference them using the local prefix. For example, if you have a umi.svg in the src/icons directory, you can reference it like this:
import { Icon } from 'umi';Configuration Options
autoInstallindicates whether to automatically install icon sets; tnpm/cnpm clients are not currently supported, but you can manually install corresponding icon set packages@iconify-json/collection-name. Reference: Icon Collection List, collection-name is the Icon set prefix item in the list.aliasis used to configure icon aliases. For example, after configuringalias:{home:'fa:home'}, you can use thefa:homeicon throughicon="home".includeconfigures icons that need to be forcibly used, for exampleinclude: ['fa:home', 'local:icon']. Common use cases: when icon strings are defined in a map, making them undetectable; when using theIconcomponent inmdx.
Icon Component Properties
- icon: specify the icon
- width: svg width
- height: svg height
- viewBox: svg viewBox
- style: external container style
- className: external container class name
- spin: whether to auto-rotate
- rotate: configure rotation angle, supports multiple formats like
1,"30deg","25%" - flip: supports
vertical,horizontal, or their combinationvertical,horizontal
ignoreMomentLocale
- Type:
boolean - Default:
true
Ignore moment's locale files to reduce build size.
Note: This feature is enabled by default. Configure ignoreMomentLocale: false to disable it.
inlineLimit
- Type:
number - Default:
10000(10k)
Configure the threshold for whether image files should be base64 encoded. By default, files less than 10000 bytes will be compiled to base64 encoding, otherwise, they will be generated as separate files.
jsMinifier (webpack)
- Type:
string, optional values:esbuild,terser,swc,uglifyJs,none - Default:
esbuild
Configure the tool for minifying JavaScript during build; none means no minification.
Example:
{
jsMinifier: 'esbuild'
}jsMinifierOptions
- Type:
object - Default:
{}
Options for jsMinifier; by default, code minification will remove comments in the code, you can preserve comments through the corresponding jsMinifier options.
Example:
{
jsMinifier: 'esbuild',
jsMinifierOptions: {
minifyWhitespace: true,
minifyIdentifiers: true,
minifySyntax: true,
}
}Configuration options need to correspond with the tool being used, refer to the respective documentation:
lessLoader
- Type:
object - Default:
{ modifyVars: userConfig.theme, javascriptEnabled: true }
Configure less-loader's Options. See less-loader's Options for specific details.
The default version is less@4. If you need compatibility with less@3, please configure using less-options-math.
legacy
- Type:
{ buildOnly?: boolean; nodeModulesTransform?: boolean; checkOutput?: boolean; } - Default:
false
When you need to support older browsers, you might need this option. When enabled, it will use non-modern build tools by default, which will significantly increase your build time.
legacy: {}By default, it only takes effect during build. You can disable this restriction by setting buildOnly: false.
You can enable the checkOutput: true option to automatically run es-check after each build to check if the .js files in the output are in es5 format.
When this option is enabled:
- Custom
srcTranspiler,jsMinifier,cssMinifieroptions are not supported. - All source code in
node_moduleswill be transpiled, withtargetscompatible down to ie 11. You can disable the transformation ofnode_modulesby specifyingnodeModulesTransform: false. In this case, you can useextraBabelIncludesto more precisely transform packages with compatibility issues. - Since older browsers don't support Top level await, when using
externals, make sure you're not using asynchronousexternalsTypewhile using synchronous dependency imports.
links
- Type:
Link[] - Default:
[]
Configure additional link tags.
Example:
links: [{ href: '/foo.css', rel: 'preload' }],mako 4.3.2+
- Type:
{ plugins?: Array<{ load?: ((...args: any[]) => unknown) | undefined; generateEnd?: ((...args: any[]) => unknown) | undefined; }> | undefined; px2rem?: { root?: number | undefined; propBlackList?: Array<string> | undefined; propWhiteList?: Array<string> | undefined; selectorBlackList?: Array<string> | undefined; selectorWhiteList?: Array<string> | undefined; selectorDoubleList?: Array<string> | undefined; } | undefined; experimental?: { webpackSyntaxValidate?: Array<string> | undefined; } | undefined; flexBugs?: boolean | undefined; moduleIdStrategy?: string | undefined; optimization?: { skipModules?: boolean | undefined; } | undefined; } - Default:
{}
Use mako for compilation to significantly improve build speed.
Enable this capability through configuration, which will be passed to mako. Only some common configurations are provided here; more configurations can be set in the mako.config.json file. For more information, see the mako-config documentation.
manifest
- Type:
{ fileName: string; basePath: string } - Default:
null
Enable generating an additional manifest file during build to describe the artifacts.
About the parameters: fileName is the name of the generated file, default is asset-manifest.json; basePath adds a prefix to all file paths.
Note: Only generated during build.
mdx
- Type:
{ loader: string; loaderOptions: Object } - Default:
{}
Configure mdx loader path and loaderOptions parameters.
metas
- Type:
Meta[] - Default:
[]
Configure additional meta tags.
For example:
metas: [
{ name: 'keywords', content: 'umi, umijs' },
{ name: 'description', content: 'React framework.' },
],Will generate the following HTML:
<meta name="keywords" content="umi, umijs" />
<meta name="description" content="React framework." />mfsu
- Type:
{ esbuild: boolean; mfName: string; cacheDirectory: string; strategy: 'normal' | 'eager'; include?: string[]; chainWebpack: (memo, args) => void; exclude?: Array<string | RegExp> } - Default:
{ mfName: 'mf', strategy: 'normal' }
Configure speed-up functionality based on Module Federation.
About the parameters:
esbuild: When set totrue, dependency pre-compilation will use esbuild, making first startup faster. The downside is that secondary compilation won't have physical cache and will be slightly slower; recommended for projects with stable dependencies.mfName: This is the global variable for this solution's remote library, default is mf. Usually configured in micro-frontends to avoid conflicts between main and sub applications.cacheDirectory: Can customize the cache directory, default isnode_modules/.cache/mfsuchainWebpack: Modify dependency webpack configuration using chain programming, based on webpack-chain. See webpack-api documentation for specific APIs.runtimePublicPath: Will modify the publicPath of mf loading files towindow.publicPathstrategy: Specifies when mfsu compiles dependencies; innormalmode, Module Federation remote package is built after babel compilation analysis; ineagermode, building starts simultaneously with project code using static analysis.include: Only effective instrategy: 'eager'mode, used to compensate for dependencies that static analysis cannot detect in eager mode. For example, ifreacthasn't entered Module Federation remote modules, you can configure{ include: [ 'react' ] }exclude: Manually exclude certain dependencies that don't need MFSU processing. Can be strings or regular expressions. For example, if you don't wantvantto go through MFSU processing, you can configure{ exclude: [ 'vant' ] }for exact word matching, or{ exclude: [ /vant/ ] }to exclude any dependencies whoseimportpath matches the regex.
Example:
// Use esbuild for dependency pre-compilation
mfsu: {
esbuild: true,
}
// Disable mfsu functionality
mfsu: false;// webpack configuration modification
mfsu: {mock
- Type:
{ exclude: string[], include: string[] } - Default:
{}
Configure mock functionality.
About the parameters. exclude is used to exclude unwanted mock files; include is used to add additional mock files outside the mock directory.
Example:
// Make all _mock.ts files under pages become mock files
mock: {
include: ['src/pages/**/_mock.ts'],
}Note: This feature is enabled by default. Configure mock: false to disable it.
mountElementId
- Type:
string - Default:
'root'
Configure the element id where the React component tree is rendered in HTML.
Example:
mountElementId: 'container'monorepoRedirect
- Type:
{ srcDir?: string[], exclude?: RegExp[], peerDeps?: boolean, useRootProject?: boolean } - Default:
false
When using Umi in a monorepo, you might need to import components, utility functions, etc. from other sub-packages. By enabling this option, you can redirect these sub-package imports to their source code location (default is the src folder). This can also solve the issue of sub-package changes not hot-reloading in MFSU scenarios.
The benefits of this redirection are: supports hot updates, no need to pre-build other sub-packages for development.
Use srcDir to adjust the priority location for identifying source code folders, and use exclude to set dependencies that don't need redirection.
Example:
// Default redirect to sub-package's src folder
monorepoRedirect: {}
// In sub-packages, prioritize redirecting to libs folder
monorepoRedirect: {
srcDir: ['libs', 'src'],
}
// Don't redirect @scope/* sub-packages
monorepoRedirect: {
exclude: [/^@scope\/.+/],
}In actual large business monorepos, each sub-package's dependencies are loaded by looking up node_modules from their directory upward. However, during local development, dependencies are installed in devDependencies, which behaves differently from npm installation, inevitably leading to multiple instance issues.
:::info
For example, each sub-package needs antd during local development, installed in devDependencies and specified in peerDependencies. We expect that when the package is published to npm and installed by a project, antd uses the project's own dependency, globally unique. However, in the monorepo, dependencies specified in devDependencies must exist, and when sub-package code looks for dependencies, it starts from that sub-package, causing each sub-package to use its own antd, resulting in multiple copies of antd in the output, increased bundle size, broken message queues, etc.
:::
To solve this issue, we stipulate:
When the peerDeps option is enabled, all peerDependencies specified by sub-packages will automatically be added alias redirection for uniqueness, avoiding multiple instances:
monorepoRedirect: { peerDeps: true }After redirection, dependencies become globally unique, maintaining consistency with the experience after installing packages from npm.
useRootProject: When your project is not in a monorepo subfolder but at the monorepo root, you can enable this option to make monorepoRedirect effective.
mpa
- Type:
object - Default:
false
Enable mpa mode.
outputPath
- Type:
string - Default:
dist
Configure the output path.
Note: Not allowed to be set to directories related to conventional functionality such as src, public, pages, mock, config, locales, models, etc.
phantomDependency
- Type:
{ exclude: string[] } - Default:
false
Execute phantom dependency detection.
When using dependencies not declared in package.json, and not configured through alias or externals, it will throw an error and remind you.
If you need to whitelist certain dependencies, you can do so through the exclude configuration option. The exclude items are npm dependency package names.
export default {
phantomDependency: {
exclude: ['lodash']
}
}plugins
- Type:
string[] - Default:
[]
Configure additional Umi plugins.
Array items are paths pointing to plugins, which can be npm dependencies, relative paths, or absolute paths. If it's a relative path, it will start looking from the project root directory.
Example:
plugins: [
// npm dependency
'umi-plugin-hello',
// relative path
'./plugin',
// absolute path
`${__dirname}/plugin.js`,
],polyfill
- Type:
{ imports: string[] } - Default:
{}
Configure polyfills to be imported on demand. By default, all polyfills are imported.
For example, to only import the stable part of core-js:
polyfill: {
imports: ['core-js/stable'],
}If you have more extreme performance requirements, you can consider importing on demand:
polyfill: {
imports: ['core-js/features/promise/try', 'core-js/proposals/math-extensions'],
}Note: This feature is enabled by default. Configure polyfill: false or set environment variable BABEL_POLYFILL=none to disable it.
postcssLoader
- Type:
object - Default:
{}
Configure postcss-loader options.
presets
- Type:
string[] - Default:
[]
Configure additional Umi preset collections.
Array items are paths pointing to preset collections, which can be npm dependencies, relative paths, or absolute paths. If it's a relative path, it will start looking from the project root directory.
Example:
presets: [
// npm dependency
'umi-preset-hello',
// relative path
'./preset',
// absolute path
`${__dirname}/preset.js`,
],proxy
- Type:
object - Default:
{}
Configure proxy functionality.
For example:
proxy: {
'/api': {
'target': 'http://jsonplaceholder.typicode.com/',
'changeOrigin': true,
'pathRewrite': { '^/api' : '' },
}
}Then accessing /api/users will access the data from http://jsonplaceholder.typicode.com/users.
Note: The proxy functionality is only effective during development.
publicPath
- Type:
string - Default:
/
Configure webpack's publicPath.
reactRouter5Compat
- Type:
object - Default:
false
Enable react-router 5 compatibility mode. In this mode, route component props will include location, match, history, and params properties, maintaining consistency with react-router 5.
However, note that:
- This mode will have additional re-renders
- Due to the history library update, the location still doesn't have a query property
routes
- Type:
Route[] - Default:
[]
Configure routes. For more information, please see Configuring Routes
routeLoader
- Type:
{ moduleType: 'esm' | 'cjs' } - Default:
{ moduleType: 'esm' }
Configure how routes are loaded. Setting moduleType to 'cjs' will load route components using require.
// moduleType: esm
'index': React.lazy(() => import(/* webpackChunkName: "p__index" */'../../pages/index.tsx')),
// moduleType: cjs
'index': React.lazy(() => Promise.resolve(require('../../pages/index.tsx'))),routePrefetch
- Type:
{ defaultPrefetch: 'none' | 'intent' | 'render' | 'viewport', defaultPrefetchTimeout: number } | false - Default:
false
Enable route preloading functionality.
run
- Type:
{ globals: string[] } - Default:
null
Global injection configuration for the run command. Adding ['zx/globals'] means when using umi run ./script.ts, umi will automatically inject import 'zx/globals';, eliminating the need to write import 'zx/globals'; in every script.
runtimePublicPath
- Type:
object - Default:
null
Enable runtime publicPath, which will use window.publicPath as the starting path for dynamic resource loading when enabled.
For example:
runtimePublicPath: {},sassLoader
- Type:
object - Default:
{}
Configure sass-loader, see sass-loader > options
styleLoader
- Type:
object - Default:
false
Enable style loader functionality, allowing CSS to be inlined in JS without outputting additional CSS files.
stylusLoader
- Type:
object - Default:
{}
Configure stylus-loader, see stylus-loader > options
styles
- Type:
string[] - Default:
[]
Configure additional CSS.
Configuration items support inline styles and external style paths, the latter determined by whether it starts with https?://.
Inserted styles are prepended, with lower priority than user-written styles in the project.
For example:
styles: [`body { color: red; }`, `https://a.com/b.css`],Will generate the following HTML:
<style>
body {
color: red;
}
</style>
<link rel="stylesheet" href="https://a.com/b.css" />stagewise
- Type:
Record<string, any> - Default:
undefined
Configure the Stagewise plugin. Stagewise is a development debugging tool that only takes effect in development mode.
Example:
// .umirc.ts
export default {
stagewise: {
// Stagewise toolbar configuration
theme: 'dark',
position: 'bottom-right',
},
};For more configuration options, see Stagewise Configuration.
Note: This feature needs to be enabled through configuration.
srcTranspiler
- Type:
stringoptional values:babel,swc,esbuild - Default:
babel
Configure the tool for transpiling js/ts during build.
srcTranspilerOptions
- Type:
{ swc?: SwcConfig, esbuild?: EsbuildConfig } - Default:
undefined
If you use swc / esbuild as the srcTranspiler transpiler, you can further configure the transpiler through this option. See SwcConfig and EsbuildConfig documentation for details.
For example, adding other plugins to swc:
srcTranspilerOptions: {
swc: {
jsc: {
experimental: {
plugins: [
[
'@swc/plugin-styled-components',
{
displayName: true,
ssr: true,
},
],
],
},
},
},
}svgr
- Type:
object - Default:
{}
svgr is enabled by default, supporting the following way to use React svg components:
import SmileUrl, { ReactComponent as SvgSmile } from './smile.svg';You can configure svgr's behavior, see @svgr/core > Config for configuration options.
svgo
- Type:
object - Default:
{}
Uses svgo by default to optimize svg resources, see svgo for configuration options.
targets
- Type:
object - Default:
{ chrome: 80 }
Configure the minimum browser versions to support. Umi will automatically introduce polyfills, configure autoprefixer, and perform syntax transformations based on this.
Example:
// Support ie11
targets: {
ie: 11,
}theme
- Type:
object - Default:
{}
Configure less variable theme.
Example:
theme: { '@primary-color': '#1DA57A' }title
- Type:
string - Default:
null
Configure global page title, currently only supports static Title.
transformRuntime
- Type:
{ absoluteRuntime: string, version: string } - Default:
{}
Configure transform-runtime plugin.
For example, if you want to use the latest @babel/runtime version. You can first configure it as follows:
transformRuntime: {
absoluteRuntime: process.cwd(),
}Then install @babel/runtime to the project:
$ npm install @babel/runtime --save-devutoopack 4.6.0+
- Type:
Object - Default:
{}
Use rust bundler utoopack to improve the performance of build.
This capability can be enabled through configuration, utoopack currently supports most of the framework's features.
When building a qiankun child application with utoopack, if the parent application uses qiankun 2, the qiankun version must be 2.10.17-beta.0 or later. Earlier versions do not provide document.currentScript correctly while executing entry scripts, which prevents the child application from loading. For more details about the integration, see the Utoo blog post When Turbopack Meets qiankun: Adapting Utoopack for Micro Frontends.
utoopack.reactCompiler
- Type:
boolean | { compilationMode?: 'infer' | 'annotation' | 'all'; target?: '18' | '19' } - Default:
false
Enable utoopack's native Rust implementation of React Compiler. It optimizes React components directly in the Rust compilation pipeline, without requiring you to install or configure babel-plugin-react-compiler.
Add the following to .umirc.ts or config/config.ts:
export default {
utoopack: {
reactCompiler: true,
},
};true uses the defaults compilationMode: 'infer' and target: '19', which require React 19 and React DOM 19 in your project. You can also pass an options object:
export default {
utoopack: {
reactCompiler: {
compilationMode: 'infer',
target: '19',
},
},
};The Rust implementation currently supports the following options, rather than the full set of Babel plugin options:
| Option | Type | Default |
|---|---|---|
compilationMode |
'infer' | 'annotation' | 'all' |
'infer' |
target |
'18' | '19' |
'19' |
For React 18, set utoopack.reactCompiler.target: '18' and install the compatibility runtime as a production dependency:
npm install react-compiler-runtimeUmi's top-level reactCompiler option enables the Babel implementation. When switching to the Rust implementation, remove the top-level reactCompiler, the legacy forget option, and any manually configured babel-plugin-react-compiler to avoid enabling both implementations. Set utoopack.reactCompiler: false to disable the Rust implementation.
verifyCommit
- Type:
{ scope: string[]; allowEmoji: boolean } - Default:
{ scope: ['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'workflow', 'build', 'ci', 'chore', 'types', 'wip', 'release', 'dep', 'deps', 'example', 'examples', 'merge', 'revert'] }
Configuration options for the verify-commit command.
About parameters. scope is used to configure allowed scopes, case-insensitive, configuration will override the defaults; allowEmoji when enabled will allow EMOJI prefixes, like 💥 feat(module): added a great feature.
verifyCommit: {
scope: ['feat', 'fix'],
allowEmoji: true,
}Note: Commit messages generated by git revert or git merge commands and changesets' release merge format will pass verification by default.
vite
- Type:
object - Default:
{}
Developer's configuration will be merged into vite's default configuration.
Example:
// Change temporary file path to node_modules/.bin/.vite folder
vite: {
cacheDir: 'node_modules/.bin/.vite',
}writeToDisk
- Type:
boolean - Default:
false
When enabled, it will output an additional copy of files to the dist directory in dev mode, typically used for chrome extension, electron application, sketch plugin, and other development scenarios.
Docs Docs Api Config (`docs/docs/docs/api/config.md`)
order: 2 toc: content
配置
对于 umi 中能使用的自定义配置,你可以使用项目根目录的 .umirc.ts 文件或者 config/config.ts,值得注意的是这两个文件功能一致,仅仅是存在目录不同,2 选 1 ,.umirc.ts 文件优先级较高。
更多目录相关信息介绍,你可以在目录结构了解。
umi 的配置文件是一个正常的 node 模块,它在执行 umi 命令行的时候使用,并且不包含在浏览器端构建中。
关于浏览器端构建需要用到的一些配置,还有一些在样式表现上产生作用的一些配置,在 umi 中被统一称为“运行时配置”,你可以在运行时配置看到更多关于它的说明。
这里有一个最简单的 umi 配置文件的范例:
import { defineConfig } from 'umi';
export default defineConfig({
outputPath: 'dist',
});使用 defineConfig 包裹配置是为了在书写配置文件的时候,能得到更好的拼写联想支持。如果你不需要,直接 export default {} 也可以。
值得关注的是在你使用 umi 的时候,你不需要了解每一个配置的作用。你可以大致的浏览一下以下 umi 支持的所有配置,然后在你需要的时候,再回来查看如何启用和修改你需要的内容。
为方便查找,以下配置项通过字母排序。
alias
- 类型:
Record<string, string> - 默认值:
{}
配置别名,对 import 语句的 source 做映射。
比如:
{
alias: {
foo: '/tmp/to/foo',
}
}然后代码里 import 'foo' 实际上会 import '/tmp/to/foo'。
有几个 Tip。
1、alias 的值最好用绝对路径,尤其是指向依赖时,记得加 require.resolve,比如,
// ⛔
{
alias: {
foo: 'foo',
}
}
// ✅
{
alias: {
foo: require.resolve('foo'),
}
}2、如果不需要子路径也被映射,记得加 $ 后缀,比如
// import 'foo/bar' 会被映射到 import '/tmp/to/foo/bar'
{
alias: {
foo: '/tmp/to/foo',
}
}
// import 'foo/bar' 还是 import 'foo/bar',不会被修改
{
alias: {
foo$: '/tmp/to/foo',
}
}autoprefixer
- 类型:
object - 默认值:
{ flexbox: 'no-2009' }
用于解析 CSS 并使用来自 Can I Use 的值将供应商前缀添加到 CSS 规则。如自动给 CSS 添加 -webkit- 前缀。
更多配置,请查阅 autoprefixer 的配置项。
analyze
- 类型:
object - 默认值:
{}
通过指定 ANALYZE 环境变量分析产物构成时,analyzer 插件的具体配置项,见 webpack-bundle-analyzer
使用 Vite 模式时,除了可以自定义 rollup-plugin-visualizer 的配置, excludeAssets、generateStatsFile、openAnalyzer、reportFilename、reportTitle 这些选项会自动转换适配。
base
- 类型:
string - 默认值:
/
要在非根目录下部署 umi 项目时,你可以使用 base 配置。
base 配置允许你为应用程序设置路由前缀。比如有路由 / 和 /users,设置 base 为 /foo/ 后就可通过 /foo/ 和 /foo/users 访问到之前的路由。
注意:base 配置必须在构建时设置,并且不能在不重新构建的情况下更改,因为该值内联在客户端包中。
cacheDirectoryPath
- 类型:
string - 默认值:
node_modules/.cache
默认情况下 Umi 会将构建中的一些缓存文件存放在 node_modules/.cache 目录下,比如 logger 日志,webpack 缓存,mfsu 缓存等。你可以通过使用 cacheDirectoryPath 配置来修改 Umi 的缓存文件目录。
示例,
// 更改缓存文件路径到 node_modules/.cache1 文件夹
cacheDirectoryPath: 'node_modules/.cache1',chainWebpack
- 类型:
(memo, args) => void - 默认值:
null
为了扩展 Umi 内置的 webpack 配置,我们提供了用链式编程的方式修改 webpack 配置,基于 webpack-chain,具体 API 可参考 webpack-api 的文档。
如下所示:
export default {
chainWebpack(memo, args) {
return memo;
},
};该函数具有两个参数:
memo是现有 webpack 配置args包含一些额外信息和辅助对象,目前有env和webpack。env为当前环境,值为development或production;webpack为 webpack 对象,可从中获取 webpack 内置插件等
用法示例:
export default {
chainWebpack(memo, { env, webpack }) {
// 设置 alias
memo.resolve.alias.set('foo', '/tmp/to/foo');
// 添加额外插件
memo.plugin('hello').use(Plugin, [...args]);
// 删除 Umi 内置插件
memo.plugins.delete('hmr');
},
};clickToComponent
- 类型:
{ editor?: string } - 默认值:
false
当前仅 React 项目支持。
开启后,可通过 Option+Click/Alt+Click 点击组件跳转至编辑器源码位置,Option+Right-click/Alt+Right-click 可以打开上下文,查看父组件。
关于参数。editor 为编辑器名称,默认为 'vscode',支持 vscode, vscode-insiders 和 cursor。
配置 clickToComponent 的行为,详见 click-to-component。
示例:
// .umirc.ts
export default {
clickToComponent: {},
};clientLoader
- 类型:
{} - 默认值:
false
开启后,可以为每个路由声明一个数据加载函数 clientLoader,将页面需要的加载数据程序提取到 clientLoader 可以让 Umi
提前在页面组件尚未加载好的时候提前进行数据的加载,避免瀑布流请求的问题,详细介绍请看 路由数据预加载。
示例:
// .umirc.ts
export default {
clientLoader: {},
};配置开启后,在路由组件中使用:
// pages/.../some_page.tsx
import { useClientLoaderData } from 'umi';
export default function SomePage() {
const { data } = useClientLoaderData();
return {data};
}
export async function clientLoader() {
const data = await fetch('/api/data');
return data;
}codeSplitting
- 类型:
{ jsStrategy: 'bigVendors' | 'depPerChunk' | 'granularChunks'; jsStrategyOptions: {} } - 默认值:
null
用于配置 code splitting 的策略方案,Umi 默认以路由为分界拆分 chunk,实现路由维度的 chunk 按需加载,如果在此之上希望继续提取公共 chunk,可以选择合适的策略进行配置,差异如下。
bigVendors 是大 vendors 方案,会将 async chunk 里的 node_modules 下的文件打包到一起,可以避免重复。同时缺点是,1)单文件的尺寸过大,2)毫无缓存效率可言。
depPerChunk 和 bigVendors 类似,不同的是把依赖按 package name + version 进行拆分,算是解了 bigVendors 的尺寸和缓存效率问题。但同时带来的潜在问题是,可能导致请求较多。我的理解是,对于非大型项目来说其实还好,因为,1)单个页面的请求不会包含非常多的依赖,2)基于 HTTP/2,几十个请求不算问题。但是,对于大型项目或巨型项目来说,需要考虑更合适的方案。
granularChunks 在 bigVendors 和 depPerChunk 之间取了中间值,同时又能在缓存效率上有更好的利用。无特殊场景,建议用 granularChunks 策略。
conventionLayout
- 类型:
boolean - 默认值:
undefined
src/layouts/index.[tsx|vue|jsx|js] 为约定式布局,默认开启。可通过配置 conventionLayout: false 关闭该默认行为。
conventionRoutes
- 类型:
{ base: string; exclude: RegExp[] } - 默认值:
null
修改默认的约定式路由规则,仅在使用 umi 约定式路由时有效,约定式路由也叫文件路由,就是不需要手写配置,文件系统即路由,通过目录和文件及其命名分析出路由配置。
使用约定式路由时,约定 src/pages 下所有的 (j|t)sx? 文件即路由。
你可以从约定式路由查看更多说明。
base
base 用于设置约定的路由的基础路径,默认从 src/pages 读取,如果是文档站点可能会需要将其改成 ./docs;
exclude
你可以使用 exclude 配置过滤一些不需要的文件,比如用于过滤 components、models 等。
示例,
// 不识别 components 和 models 目录下的文件为路由
conventionRoutes: {
exclude: [/\/components\//, /\/models\//],
}copy
- 类型:
Array<string | { from: string; to: string; }> - 默认值:
[]
配置要复制到输出目录的文件或文件夹。
当配置字符串时,默认拷贝到产物目录,如:
copy: ['foo.json', 'src/bar.json']会产生如下产物的结构:
+ dist
- bar.json
- foo.json
+ src
- bar.json
- foo.json你也可以通过对象配置具体的拷贝位置,其中相对路径的起点为项目根目录:
copy: [
{ from: 'from', to: 'dist/output' },
{ from: 'file.json', to: 'dist' }
]此时将产生如下产物结构:
+ dist
+ output
- foo.json
- file.json
+ from
- foo.json
- file.jsoncrossorigin
- 类型:
{ includes?: string[] } - 默认值:
false
配置 script 标签的 crossorigin。如果有声明,会为本地 script 加上 crossorigin="anonymous" 的属性。
关于参数。includes 参数可以为额外的非本地 script 标签加上此属性。
比如:
crossorigin: {}然后输出的 HTML 中会有这些变化,
-
<script src="/umi.js"></script>
+
<script src="/umi.js" crossorigin="anonymous"></script>cssMinifier
- 类型:
string可选的值:esbuild,cssnano,parcelCSS,none - 默认值:
esbuild
配置构建时使用的 CSS 压缩工具; none 表示不压缩。
示例:
{
cssMinifier: 'esbuild'
}cssMinifierOptions
- 类型:
object - 默认值:
{}
cssMinifier CSS 压缩工具配置选项。
示例:
{
cssMinifier: 'esbuild',
cssMinifierOptions: {
minifyWhitespace: true,
minifySyntax: true,
},
}对应 CSS 压缩的配置请查看对应的文档。
cssPublicPath
- 类型:
string - 默认值:
./
为 CSS 中的图片、文件等外部资源指定自定义公共路径。作用类似于 publicPath 默认值是 ./。
cssLoader
- 类型:
object - 默认值:
{}
配置 css-loader ,详见 css-loader > options
cssLoaderModules
- 类型:
object - 默认值:
{}
配置 css modules 的行为,详见 css-loader > modules。
如:
cssLoaderModules: {
// 配置驼峰式使用
exportLocalsConvention: 'camelCase'
}deadCode
- 类型:
{ patterns?: string[]; exclude?: string[]; failOnHint?: boolean; detectUnusedFiles?: boolean; detectUnusedExport?: boolean; context?: string } - 默认值:
false
检测未使用的文件和导出,仅在 build 阶段开启。
比如:
deadCode: {}然后执行 build,如有发现问题,会打印警告:
Warning: There are 1 unused files:
1. /pages/index.module.less
Please be careful if you want to remove them (¬º-°)¬.可配置项:
patterns: 识别代码的范围,如['src/pages/**']exclude: 排除检测的范围,如['src/pages/utils/**']failOnHint: 检测失败是否终止进程,默认false不终止detectUnusedFiles: 是否检测未使用的文件,默认true检测detectUnusedExport: 是否检测未使用的导出,默认true检测context: 匹配开始的目录,默认为当前项目根目录
define
- 类型:
Record<string, string> - 默认值: 如下
{
'process.env.NODE_ENV' : process.env.NODE_ENV,
'process.env.HMR' : process.env.HMR,
'process.env.SOCKET_SERVER': process.env.ERROR_OVERLAY'
}基于define-plugin 插件设置代码中的可用变量。
:::warning{title=🚨}
- 属性值会经过一次
JSON.stringify转换。 - key 值的替换是通过语法形式来匹配的,比如配置了
{'a.b.c': 'abcValue'}是无法替换代码中的a.b?.c的
:::
比如,
define: { FOO: 'bar' }然后代码里的 console.log(hello, FOO) 会被编译成 console.log(hello, 'bar')。
当你在 ts 的项目中使用这些变量时,你需要在 typings 文件中声明变量类型,以支持 ts 类型提示,比如:
如果你的 typings 文件是全局的:
// typings.d.ts
declare const FOO: string;如果你的 typings 文件是非全局的(包含了 import/export):
// typings.d.ts
import './other.d.ts';
declare global {
const FOO: string;
}devtool
- 类型:
string - 默认值:dev 时默认
cheap-module-source-map,build 时候默认无 sourcemap
设置 sourcemap 生成方式。
常见可选值有:
eval,最快的类型,缺点是不支持低版本浏览器source-map,最慢但最全的类型
示例,
// 关闭 dev 阶段的 sourcemap 生成
devtool: false;
// 只设置 dev 阶段的 sourcemap
devtool: process.env.NODE_ENV === 'development' ? 'eval' : false;classPropertiesLoose
- 类型:
object - 默认值:
{}
设置 babel class-properties 启用 loose
esbuildMinifyIIFE
- 类型:
boolean - 默认值:
false
修复 esbuild 压缩器自动引入的全局变量导致的命名冲突问题。
由于 Umi 4 默认使用 esbuild 作为压缩器,该压缩器会自动注入全局变量作为 polyfill ,这可能会引发 异步块全局变量冲突、 qiankun 子应用和主应用全局变量冲突 等问题,通过打开该选项或切换 jsMinifier 压缩器可解决此问题。
更多信息详见 vite#7948 。
示例,
esbuildMinifyIIFE: trueexternals
- 类型:
Record<string, string> | Function - 默认值:
{}
设置哪些模块不打包,转而通过 <script> 或其他方式引入,通常需要搭配 headScripts 配置使用。
示例,
// external react
externals: { react: 'React' },
headScripts: ['https://unpkg.com/[email protected]/umd/react.production.min.js'],注意:不要轻易设置 antd 的 externals,由于依赖较多,使用方式复杂,可能会遇到较多问题,并且一两句话很难解释清楚。
extraBabelIncludes
- 类型:
Array<string | RegExp> - 默认值:
[]
配置额外需要做 Babel 编译的 NPM 包或目录。比如:
export default {
extraBabelIncludes: [
// 支持绝对路径
join(__dirname, '../../common'),
// 支持 npm 包
'react-monaco-editor',
// 转译全部路径含有 @scope 的包
/@scope/
],
};extraBabelPlugins
- 类型:
string[] | Function - 默认值:
[]
配置额外的 babel 插件。可传入插件地址或插件函数。
extraBabelPresets
- 类型:
string[] | Function - 默认值:
[]
配置额外的 babel 插件集。可传入插件集地址或插件集函数。
extraPostCSSPlugins
- 类型:
PostCSSPlugin[] - 默认值:
[]
配置额外的 postcss 插件。
exportStatic
- 类型:
{ extraRoutePaths: IUserExtraRoute[] | (() => IUserExtraRoute[] | Promise<IUserExtraRoute[]>), ignorePreRenderError: boolean } - 默认值:
undefined
开启该配置后会针对每个路由单独输出 HTML 文件,通常用于静态站点托管。例如项目有如下路由:
/
/docs
/docs/a不开启 exportStatic 时会输出:
dist/index.html开启 exportStatic 时会输出:
dist/index.html
dist/docs/index.html
dist/docs/a/index.html通过 extraRoutePaths 子配置项可以产出额外的页面,通常用于动态路由静态化。例如有如下路由:
/news/:id默认情况下只会输出 dist/news/:id/index.html,但可以通过配置 extraRoutePaths 将其静态化:
// .umirc.ts
export default {
exportStatic: {
// 配置固定值
extraRoutePaths: ['/news/1', '/news/2'],
// 也可以配置函数动态获取
extraRoutePaths: async () => {
const res = await fetch('https://api.example.com/news');
const data = await res.json();
return data.map((item) => `/news/${item.id}`);
},
},
}此时输出文件会变成:
dist/news/:id/index.html
dist/news/1/index.html
dist/news/2/index.htmlextraRoutePaths 除了支持配置字符串数据,还可以配置成对象数组,用于启用 SSR 时又希望对部分路由禁用预渲染的场景,例如:
// .umirc.ts
export default {
exportStatic: {
// 输出额外页面文件但跳过预渲染
extraRoutePaths: [{ path: '/news/1', prerender: false }],
},
}exportStatic 在搭配 ssr 使用时会进行预渲染,在预渲染失败时会抛出异常并终止构建,可以通过配置 ignorePreRenderError 来忽略预渲染失败的错误,例如:
// .umirc.ts
export default {
exportStatic: {
// 忽略预渲染失败的错误
ignorePreRenderError: true,
},
}favicons
- 类型:
string[] - 默认值:
null
默认情况下,站点将使用约定 favicon 来创建图标的 meta 头标签。
通过如下方式自定义:
favicons: [
// 完整地址
'https://domain.com/favicon.ico',
// 此时将指向 `/favicon.png` ,确保你的项目含有 `public/favicon.png`
'/favicon.png'
]reactCompiler
- 类型:
boolean | object - 默认值:
null
是否开启基于 babel-plugin-react-compiler 的 React Compiler 功能。参考 https://react.dev/learn/react-compiler 。
reactCompiler: true,React Compiler 的配置项会直接透传给 babel-plugin-react-compiler:
reactCompiler: {
target: '19',
},注意:
- reactCompiler 和 mfsu、mako 暂时不兼容,如果同时打开会抛错;utoopack 会通过 babel-loader 传递 React Compiler 插件配置。使用 utoopack 内置的 Rust 版本时,请改用
utoopack.reactCompiler。 - reactCompiler 默认面向 React 19,使用时请安装 react@19 和 react-dom@19 到项目依赖。如果需要配合 React 17 或 18 使用,请配置对应的
target并安装 react-compiler-runtime。 - 旧配置项
forget仍可兼容使用,但已废弃,请迁移到reactCompiler。
forkTSChecker
- 类型:
object - 默认值:
null
开启 TypeScript 的类型检查。基于 fork-ts-checker-webpack-plugin,配置项可参考 fork-ts-checker-webpack-plugin 的 Options。
hash
- 类型:
boolean - 默认值:
false
开启 hash 模式,让 build 之后的产物包含 hash 后缀。通常用于增量发布和避免浏览器加载缓存。
启用后,产物通常是这样,
+ dist
- logo.sw892d.png
- umi.df723s.js
- umi.8sd8fw.css
- index.html注意:HTML 文件始终没有 hash 后缀。
headScripts
- 类型:
string[] | Script[] - 默认值:
[]
配置 <head> 中的额外 script。
比如,
headScripts: [`alert(1);`, `https://a.com/b.js`],会生成 HTML,
<script>
alert(1);
</script>
<script src="https://a.com/b.js"></script>如果需要额外属性,切换到对象格式,比如,
headScripts: [
{ src: '/foo.js', defer: true },
{ content: `alert('你好');`, charset: 'utf-8' },
],helmet
- 类型:
boolean - 默认值:
true
配置 react-helmet-async 的集成,当设置为 false 时,不会集成 react-helmet-async,此时无法从框架中 import { Helmet } 使用,同时构建产物也会减少相应的尺寸。
history
- 类型:
{ type: 'browser' | 'hash' | 'memory' } - 默认值:
{ type: 'browser' }
设置路由 history 类型。
historyWithQuery
- 类型:
{} - 默认值:
false
让 history 带上 query。除了通过 useNavigate 进行的跳转场景,此时还需自行处理 query。
https
- 类型:
{ cert: string; key: string; hosts: string[]; http2?: boolean } - 默认值:
{ hosts: ['127.0.0.1', 'localhost'] }
开启 dev 的 https 模式,Umi 4 默认使用 mkcert 快捷创建证书,请确保已经安装。
关于参数。
cert和key分别用于指定 cert 和 key 文件。hosts用于指定要支持 https 访问的 host,默认是['127.0.0.1', 'localhost']。http2用于指定是否使用 HTTP 2.0 协议,默认是 true(使用 HTTP 2.0 在 Chrome 或 Edge 浏览器中中有偶然出现ERR_HTTP2_PROTOCOL_ERRO报错,如有遇到,建议配置为 false)。
示例,
https: {
}icons
- 类型:
{ autoInstall: {}; alias: Record<string,string>; include: Array<string>; } - 默认值:
false
你就可以通过 umi 导出的 Icon 组件快捷地引用 icon 集或者本地的 icon。
icon 集使用
在 umi 配置文件设置,开启 icons 功能,并允许自动安装图标库。
icons: { autoInstall: {} },页面使用:
import { Icon } from 'umi';icon 里包含的字符串是 collect:icon 的组合,以 : 分割。Icon 集推荐在 Icônes 网站上搜索。
本地 icon 使用
在 umi 配置文件设置,开启 icons 功能。
icons: {},本地 svg icon 的使用需要把 svg 保存在 src/icons 目录下,然后通过 local 这个前缀引用,比如在 src/icons 目录下有个 umi.svg,然后可以这样引用。
import { Icon } from 'umi';配置项介绍
autoInstall表示是否自动安装 icon 集;tnpm/cnpm 客户端暂不支持,但可以通过手动按需安装对应 icon 集合包@iconify-json/collection-name。 参考:Icon 集合列表, collection-name 为列表中的 Icon set prefix 项。alias用于配置 icon 的别名,比如配置了alias:{home:'fa:home'}后就可以通过icon="home"使用fa:home这个 icon 了。include配置需要强制使用的 icon, 例如include: ['fa:home', 'local:icon']。常见的使用场景:将 icon 字符串定义在一个 map 中,导致无法检测到;在mdx使用了Icon组件。
Icon 组件属性
- icon,指定 icon
- width,svg 宽度
- height,svg 高度
- viewBox,svg viewBox
- style,外部容器样式
- className,外部容器样式名
- spin,是否自动旋转
- rotate,配置旋转角度,支持多种格式,比如
1,"30deg"、"25%"都可以 - flip,支持
vertical、horizontal,或者他们的组合vertical,horizontal
ignoreMomentLocale
- 类型:
boolean - 默认值:
true
忽略 moment 的 locale 文件,用于减少产物尺寸。
注意:此功能默认开。配置 ignoreMomentLocale: false 关闭。
inlineLimit
- 类型:
number - 默认值:
10000(10k)
配置图片文件是否走 base64 编译的阈值。默认是 10000 字节,少于他会被编译为 base64 编码,否则会生成单独的文件。
jsMinifier (webpack)
- 类型:
string,可选值esbuild,terser,swc,uglifyJs,none - 默认值:
esbuild
配置构建时压缩 JavaScript 的工具;none表示不压缩。
示例:
{
jsMinifier: 'esbuild'
}jsMinifierOptions
- 类型:
object - 默认值:
{}
jsMinifier 的配置项;默认情况下压缩代码会移除代码中的注释,可以通过对应的 jsMinifier 选项来保留注释。
示例:
{
jsMinifier: 'esbuild',
jsMinifierOptions: {
minifyWhitespace: true,
minifyIdentifiers: true,
minifySyntax: true,
}
}配置项需要和所使用的工具对应,具体参考对应文档:
lessLoader
- 类型:
object - 默认值:
{ modifyVars: userConfig.theme, javascriptEnabled: true }
设置 less-loader 的 Options。具体参考参考 less-loader 的 Options。
默认是用 less@4 版本,如果需要兼容 less@3 请配置使用less-options-math。
legacy
- 类型:
{ buildOnly?: boolean; nodeModulesTransform?: boolean; checkOutput?: boolean; } - 默认值:
false
当你需要兼容低版本浏览器时,可能需要该选项,开启后将默认使用 非现代 的打包工具做构建,这会显著增加你的构建时间。
legacy: {}默认只在构建时生效,通过设定 buildOnly: false 关闭该限制。
可通过打开 checkOutput: true 选项,每次构建结束后将自动运行 es-check 检查产物 .js 文件的语法是否为 es5 格式。
开启此选项后:
- 不支持自定义
srcTranspiler、jsMinifier、cssMinifier选项。 - 将转译全部
node_modules内的源码,targets兼容至 ie 11 ,通过指定nodeModulesTransform: false来取消对node_modules的转换,此时你可以通过配置extraBabelIncludes更精准的转换那些有兼容性问题的包。 - 因低版本浏览器不支持 Top level await ,当你在使用
externals时,确保你没有在使用异步性质的externalsType时又使用了同步导入依赖。
links
- 类型:
Link[] - 默认值:
[]
配置额外的 link 标签。
示例,
links: [{ href: '/foo.css', rel: 'preload' }],mako 4.3.2+
- 类型:
{ plugins?: Array<{ load?: ((...args: any[]) => unknown) | undefined; generateEnd?: ((...args: any[]) => unknown) | undefined; }> | undefined; px2rem?: { root?: number | undefined; propBlackList?: Array<string> | undefined; propWhiteList?: Array<string> | undefined; selectorBlackList?: Array<string> | undefined; selectorWhiteList?: Array<string> | undefined; selectorDoubleList?: Array<string> | undefined; } | undefined; experimental?: { webpackSyntaxValidate?: Array<string> | undefined; } | undefined; flexBugs?: boolean | undefined; moduleIdStrategy?: string | undefined; optimization?: { skipModules?: boolean | undefined; } | undefined; } - 默认值:
{}
使用 mako 用于编译以显著提高构建速度。
通过配置以启用这个能力,配置将传递给mako。这里只提供了一些常用的配置,更多的配置可以在 mako.config.json 文件中设置。有关更多信息,请参阅mako-config文档。
manifest
- 类型:
{ fileName: string; basePath: string } - 默认值:
null
开启 build 时生成额外的 manifest 文件,用于描述产物。
关于参数。fileName 是生成的文件名,默认是 asset-manifest.json;basePath 会给所有文件路径加上前缀。
注意:只在 build 时生成。
mdx
- 类型:
{ loader: string; loaderOptions: Object } - 默认值:
{}
mdx loader 配置 loader 配置路径,loaderOptions 配置参数
metas
- 类型:
Meta[] - 默认值:
[]
配置额外的 meta 标签。
比如,
metas: [
{ name: 'keywords', content: 'umi, umijs' },
{ name: 'description', content: 'React framework.' },
],会生成以下 HTML,
<meta name="keywords" content="umi, umijs" />
<meta name="description" content="React framework." />mfsu
- 类型:
{ esbuild: boolean; mfName: string; cacheDirectory: string; strategy: 'normal' | 'eager'; include?: string[]; chainWebpack: (memo, args) => void; exclude?: Array<string | RegExp> } - 默认值:
{ mfName: 'mf', strategy: 'normal' }
配置基于 Module Federation 的提速功能。
关于参数
esbuild配为true后会让依赖的预编译走 esbuild,从而让首次启动更快,缺点是二次编译不会有物理缓存,稍慢一些;推荐项目依赖比较稳定的项目使用。mfName是此方案的 remote 库的全局变量,默认是 mf,通常在微前端中为了让主应用和子应用不冲突才会进行配置cacheDirectory可以自定义缓存目录,默认是node_modules/.cache/mfsuchainWebpack用链式编程的方式修改 依赖的 webpack 配置,基于 webpack-chain,具体 API 可参考 webpack-api 的文档;runtimePublicPath会让修改 mf 加载文件的 publicPath 为window.publicPathstrategy指定 mfsu 编译依赖的时机;normal模式下,采用 babel 编译分析后,构建 Module Federation 远端包;eager模式下采用静态分析的方式,和项目代码同时发起构建。include仅在strategy: 'eager'模式下生效, 用于补偿在 eager 模式下,静态分析无法分析到的依赖,例如react未进入 Module Federation 远端模块可以这样配置{ include: [ 'react' ] }exclude手动排除某些不需要被 MFSU 处理的依赖, 字符串或者正则的形式,比如vant不希望走 MFSU 处理,可以配置{ exclude: [ 'vant' ] }匹配逻辑为全词匹配,也可以配置{ exclude: [ /vant/ ] }只要import路径中匹配该正则的依赖都不走 MFSU 处理
示例,
// 用 esbuild 做依赖预编译
mfsu: {
esbuild: true,
}
// 关闭 mfsu 功能
mfsu: false;// webpack 配置修改
mfsu: {
chainWebpack(memo, args) {
// 添加额外插件
memo.plugin('hello').use(Plugin, [...args]);
return memo;
}
}注意:此功能默认开。配置 mfsu: false 关闭。
mock
- 类型:
{ exclude: string[], include: string[] } - 默认值:
{}
配置 mock 功能。
关于参数。exclude 用于排除不需要的 mock 文件;include 用于额外添加 mock 目录之外的 mock 文件。
示例,
// 让所有 pages 下的 _mock.ts 文件成为 mock 文件
mock: {
include: ['src/pages/**/_mock.ts'],
}注意:此功能默认开。配置 mock: false 关闭。
mountElementId
- 类型:
string - 默认值:
'root'
配置 react 组件树渲染到 HTML 中的元素 id。
示例,
mountElementId: 'container'monorepoRedirect
- 类型:
{ srcDir?: string[], exclude?: RegExp[], peerDeps?: boolean, useRootProject?: boolean } - 默认值:
false
在 monorepo 中使用 Umi 时,你可能需要引入其他子包的组件、工具方法等,通过开启此选项来重定向这些子包的导入到他们的源码位置(默认为 src 文件夹),这也可以解决 MFSU 场景改动子包不热更新的问题。
这种重定向的好处是:支持热更新,无需预构建其他子包即可进行开发。
通过配置 srcDir 来调整识别源码文件夹的优先位置,通过 exclude 来设定不需要重定向的依赖范围。
示例:
// 默认重定向到子包的 src 文件夹
monorepoRedirect: {}
// 在子包中寻找,优先定向到 libs 文件夹
monorepoRedirect: {
srcDir: ['libs', 'src'],
}
// 不重定向 @scope/* 的子包
monorepoRedirect: {
exclude: [/^@scope\/.+/],
}在实际的大型业务 monorepo 中,每个子包的依赖都是从他们的目录开始向上寻找 node_modules 并加载的,但在本地开发时,依赖都安装在 devDependencies ,和从 npm 上安装表现不一致,所以不可避免会遇到多实例问题。
:::info
举个例子,每个子包在本地开发时都需要 antd ,在 devDependencies 中安装了,也在 peerDependencies 中指明了 antd ,我们预期该包发布到 npm ,被某个项目安装后, antd 是使用的项目本身的依赖,全局唯一,但由于在 monorepo 中,指定在 devDependencies 中的依赖必定存在,且子包代码寻找依赖时是从该子包进行的,导致了每个子包都用了自己的 antd ,出现了产物中有多份 antd 、产物体积增大、消息队列被破坏等情况。
:::
为了解决这种问题,我们约定:
当打开 peerDeps 选项时,所有子包指明的 peerDependencies 都会被自动添加 alias 重定向唯一化,避免多实例的存在:
monorepoRedirect: { peerDeps: true }经过重定向,依赖全局唯一,便可以在开发时保持和在 npm 上安装包后的体验一致。
useRootProject: 当你的项目不在 monorepo 子文件夹里,而在 monorepo 根的话,你可以开启这个选项,以使 monorepoRedirect 生效。
mpa
- 类型:
object - 默认值:
false
启用 mpa 模式。
outputPath
- 类型:
string - 默认值:
dist
配置输出路径。
注意:不允许设定为 src、public、pages、mock、config、locales、models 等约定式功能相关的目录。
phantomDependency
- 类型:
{ exclude: string[] } - 默认值:
false
执行幽灵依赖检测。
当使用未在 package.json 中声明的依赖,以及也没有通过 alias 或 externals 进行配置时,会抛错并提醒。
如遇到有需要需做白名单处理,可通过 exclude 配置项实现,exclude 的项是 npm 依赖的包名。
export default {
phantomDependency: {
exclude: ['lodash']
}
}plugins
- 类型:
string[] - 默认值:
[]
配置额外的 Umi 插件。
数组项为指向插件的路径,可以是 npm 依赖、相对路径或绝对路径。如果是相对路径,则会从项目根目录开始找。
示例,
plugins: [
// npm 依赖
'umi-plugin-hello',
// 相对路径
'./plugin',
// 绝对路径
`${__dirname}/plugin.js`,
],polyfill
- 类型:
{ imports: string[] } - 默认值:
{}
设置按需引入的 polyfill。默认全量引入。
比如只引入 core-js 的 stable 部分,
polyfill: {
imports: ['core-js/stable'],
}如果对于性能有更极致的要求,可以考虑按需引入,
polyfill: {
imports: ['core-js/features/promise/try', 'core-js/proposals/math-extensions'],
}注意:此功能默认开。配置 polyfill: false 或设置环境变量 BABEL_POLYFILL=none 关闭。
postcssLoader
- 类型:
object - 默认值:
{}
presets
- 类型:
string[] - 默认值:
[]
配置额外的 Umi 插件集。
数组项为指向插件集的路径,可以是 npm 依赖、相对路径或绝对路径。如果是相对路径,则会从项目根目录开始找。
示例,
presets: [
// npm 依赖
'umi-preset-hello',
// 相对路径
'./preset',
// 绝对路径
`${__dirname}/preset.js`,
],proxy
- 类型:
object - 默认值:
{}
配置代理功能。
比如,
proxy: {
'/api': {
'target': 'http://jsonplaceholder.typicode.com/',
'changeOrigin': true,
'pathRewrite': { '^/api' : '' },
}
}然后访问 /api/users 就能访问到 http://jsonplaceholder.typicode.com/users 的数据。
注意:proxy 功能仅在 dev 时有效。
publicPath
- 类型:
string - 默认值:
/
配置 webpack 的 publicPath。
reactRouter5Compat
- 类型:
object - 默认值:
false
启用 react-router 5 兼容模式。此模式下,路由组件的 props 会包含 location、match、history 和 params 属性,和 react-router 5 的保持一致。
但要注意的是,
- 此模式下会有额外的 re-render
- 由于依赖库 history 更新,location 中依旧没有 query 属性
routes
- 类型:
Route[] - 默认值:
[]
配置路由。更多信息,请查看 配置路由
routeLoader
- 类型:
{ moduleType: 'esm' | 'cjs' } - 默认值:
{ moduleType: 'esm' }
配置路由加载方式。moduleType 配置为 'cjs' 会用 require 的方式加载路由组件。
// moduleType: esm
'index': React.lazy(() => import(/* webpackChunkName: "p__index" */'../../pages/index.tsx')),
// moduleType: cjs
'index': React.lazy(() => Promise.resolve(require('../../pages/index.tsx'))),routePrefetch
- 类型:
{ defaultPrefetch: 'none' | 'intent' | 'render' | 'viewport', defaultPrefetchTimeout: number } | false - 默认值:
false
启用路由预加载功能。
run
- 类型:
{ globals: string[] } - 默认值:
null
run 命令的全局注入配置。添加['zx/globals'],在使用umi run ./script.ts的时候,umi会自动注入import 'zx/globals';,从而省略掉每个脚本都要写import 'zx/globals';。
runtimePublicPath
- 类型:
object - 默认值:
null
启用运行时 publicPath,开启后会使用 window.publicPath 作为资源动态加载的起始路径。
比如,
runtimePublicPath: {},scripts
- 类型:
string[] | Script[] - 默认值:
[]
配置 <body> 中额外的 script 标签。
比如,
scripts: [`alert(1);`, `https://a.com/b.js`],会生成 HTML,
<script>
alert(1);
</script>
<script src="https://a.com/b.js"></script>如果需要额外属性,切换到对象格式,比如,
scripts: [
{ src: '/foo.js', defer: true },
{ content: `alert('你好');`, charset: 'utf-8' },
],sassLoader
- 类型:
object - 默认值:
{}
配置 sass-loader ,详见 sass-loader > options
styleLoader
- 类型:
object - 默认值:
false
启用 style loader 功能,让 CSS 内联在 JS 中,不输出额外的 CSS 文件。
stylusLoader
- 类型:
object - 默认值:
{}
配置 stylus-loader ,详见 stylus-loader > options
styles
- 类型:
string[] - 默认值:
[]
配置额外的 CSS。
配置项支持内联样式和外联样式路径,后者通过是否以 https?:// 开头来判断。
插入的样式会前置,优先级低于项目内用户编写样式。
比如:
styles: [`body { color: red; }`, `https://a.com/b.css`],会生成以下 HTML,
<style>
body {
color: red;
}
</style>
<link rel="stylesheet" href="https://a.com/b.css" />stagewise
- 类型:
Record<string, any> - 默认值:
undefined
配置 Stagewise 插件。Stagewise 是一个开发调试工具,仅在开发模式下生效。
示例:
// .umirc.ts
export default {
stagewise: {
// Stagewise 工具栏配置
theme: 'dark',
position: 'bottom-right',
},
};更多配置选项详见 Stagewise 配置文档。
注意:此功能需要通过配置启用。
srcTranspiler
- 类型:
string可选的值:babel,swc,esbuild - 默认值:
babel
配置构建时转译 js/ts 的工具。
srcTranspilerOptions
- 类型:
{ swc?: SwcConfig, esbuild?: EsbuildConfig } - 默认值:
undefined
如果你使用了 swc / esbuild 作为 srcTranspiler 转译器,你可以通过此选项对转译器做进一步的配置,详见 SwcConfig 、 EsbuildConfig 配置文档。
如给 swc 添加其他的插件:
srcTranspilerOptions: {
swc: {
jsc: {
experimental: {
plugins: [
[
'@swc/plugin-styled-components',
{
displayName: true,
ssr: true,
},
],
],
},
},
},
}svgr
- 类型:
object - 默认值:
{}
svgr 默认开启,支持如下方式使用 React svg 组件:
import SmileUrl, { ReactComponent as SvgSmile } from './smile.svg';可配置 svgr 的行为,配置项详见 @svgr/core > Config。
svgo
- 类型:
object - 默认值:
{}
默认使用 svgo 来优化 svg 资源,配置项详见 svgo 。
targets
- 类型:
object - 默认值:
{ chrome: 80 }
配置需要兼容的浏览器最低版本。Umi 会根据这个自定引入 polyfill、配置 autoprefixer 和做语法转换等。
示例,
// 兼容 ie11
targets: {
ie: 11,
}theme
- 类型:
object - 默认值:
{}
配置 less 变量主题。
示例:
theme: { '@primary-color': '#1DA57A' }title
- 类型:
string - 默认值:
null
配置全局页面 title,暂时只支持静态的 Title。
transformRuntime
- 类型:
{ absoluteRuntime: string, version: string } - 默认值:
{}
配置 transform-runtime 插件的部分功能。
比如,如果你想用最新的 @babel/runtime 版本。可先配置如下:
transformRuntime: {
absoluteRuntime: process.cwd(),
}再安装 @babel/runtime 到项目中:
$ npm install @babel/runtime --save-devutoopack 4.6.0+
- 类型:
Object - 默认值:
{}
使用 Rust Bundler utoopack 用于提升构建性能。
通过配置来启用这个能力,目前 utoopack 对框架中大部分功能已经支持。
使用 utoopack 构建 qiankun 子应用时,如果主应用使用 qiankun 2,qiankun 版本需为 2.10.17-beta.0 或更高。更早的版本无法在执行入口脚本时正确提供 document.currentScript,会导致子应用加载失败。更多适配原理请参阅 Utoo 官网博客《当 Turbopack 遇上 qiankun:Utoopack 的微前端适配实践》。
utoopack.reactCompiler
- 类型:
boolean | { compilationMode?: 'infer' | 'annotation' | 'all'; target?: '18' | '19' } - 默认值:
false
开启 utoopack 内置的 Rust 版本 React Compiler,直接在 Rust 编译流程中优化 React 组件,无需额外安装或配置 babel-plugin-react-compiler。
在 .umirc.ts 或 config/config.ts 中配置:
export default {
utoopack: {
reactCompiler: true,
},
};true 使用默认的 compilationMode: 'infer' 和 target: '19',项目需安装 React 19 和 React DOM 19。也可以传入配置对象:
export default {
utoopack: {
reactCompiler: {
compilationMode: 'infer',
target: '19',
},
},
};Rust 版本目前支持以下选项,不能直接套用 Babel 插件的全部配置:
| 选项 | 类型 | 默认值 |
|---|---|---|
compilationMode |
'infer' | 'annotation' | 'all' |
'infer' |
target |
'18' | '19' |
'19' |
React 18 项目需设置 utoopack.reactCompiler.target: '18',并将兼容运行时安装到生产依赖:
npm install react-compiler-runtimeUmi 顶层的 reactCompiler 配置启用的是 Babel 版本。切换到 Rust 版本时,应移除顶层 reactCompiler、旧的 forget 配置,以及手动添加的 babel-plugin-react-compiler,避免同时启用两种实现。设置 utoopack.reactCompiler: false 可关闭 Rust 版本。
verifyCommit
- 类型:
{ scope: string[]; allowEmoji: boolean } - 默认值:
{ scope: ['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'workflow', 'build', 'ci', 'chore', 'types', 'wip', 'release', 'dep', 'deps', 'example', 'examples', 'merge', 'revert'] }
针对 verify-commit 命令的配置项。
关于参数。scope 用于配置允许的 scope,不区分大小写,配置后会覆盖默认的;allowEmoji 开启后会允许加 EMOJI 前缀,比如 💥 feat(模块): 添加了个很棒的功能。
verifyCommit: {
scope: ['feat', 'fix'],
allowEmoji: true,
}注意:使用 git revert 或 git merge 命令以及 changesets 的发版 merge 格式所产生的 commit message 会默认通过校验。
vite
- 类型:
object - 默认值:
{}
开发者的配置会 merge 到 vite 的 默认配置。
示例,
// 更改临时文件路径到 node_modules/.bin/.vite 文件夹
vite: {
cacheDir: 'node_modules/.bin/.vite',
}writeToDisk
- 类型:
boolean - 默认值:
false
开启后会在 dev 模式下额外输出一份文件到 dist 目录,通常用于 chrome 插件、electron 应用、sketch 插件等开发场景。
Docs Docs Api Plugin Api.En US (`docs/docs/docs/api/plugin-api.en-US.md`)
order: 5 toc: content translated_at: '2024-03-17T10:39:12.468Z'
Plugin API
The core of Umi lies in its plugin mechanism. With Umi's plugin mechanism, you can gain the ability to extend your project's compile-time and runtime capabilities. The following lists all the plugin APIs we provide for you, to help you freely write plugins.
Before using the Umi plugin API, we suggest you first read the Plugin section to understand the mechanism and principle of umi plugins, this will help you better use the plugin API.
For ease of lookup, the contents below are sorted alphabetically.
Core API
Methods defined in service and PluginAPI.
applyPlugins
api.applyPlugins({ key: string, type?: api.ApplyPluginsType, initialValue?: any, args?: any })Get the data after the execution of hooks registered by register(), this is an asynchronous function, so it will return a Promise. An example and detailed explanation of this method see register api
describe
api.describe({ key?: string, config?: { default, schema, onChange }, enableBy? })Executed in the plugin registration stage (initPresets or initPlugins stage), used to describe the key, configuration information, and enable mode of the plugin or set of plugins.
keyis the key name of the plugin configuration in the configurationconfig.defaultis the default value of the plugin configuration, when the user does not configure the key in the configuration, the default configuration will take effect.config.schemais used to declare the type of configuration, based on joi. If you want the user to configure, this is mandatory, otherwise the user's configuration is ineffectiveconfig.onChangeis the processing mechanism after the configuration is modified in dev mode. The default value isapi.ConfigChangeType.reload, indicating that the dev process will restart when the configuration item is modified in dev mode. You can also change it toapi.ConfigChangeType.regenerateTmpFiles, indicating only regenerating temporary files. You can also pass in a method to customize the processing mechanism.enableByis the enable mode of the plugin, the default isapi.EnableBy.register, indicating that it is enabled by registration, i.e., the plugin will be enabled as long as it is registered. Can be changed toapi.EnableBy.config, indicating enabled by configuration, the plugin is enabled only when the user configures the plugin's configuration items. You can also customize a method that returns a boolean value (true for enable) to decide its enable timing, this is commonly used to implement dynamic effects.
e.g.
api.describe({
key: 'foo',
config: {
schema(joi){
return joi.string();
},
onChange: api.ConfigChangeType.regenerateTmpFiles,
},
enableBy: api.EnableBy.config,
})In this example, the key of the plugin is foo, so the key name in the configuration is foo, the type of configuration is a string, when the configuration foo changes, dev will only regenerate temporary files. This plugin will only be enabled after the user configures foo.
isPluginEnable
api.isPluginEnable(key: string)Determine whether the plugin is enabled, the parameter is the key of the plugin
register
api.register({ key: string, fn, before?: string, stage?: number})Register hooks available for use by api.applyPlugins.
keyis the category name of the registered hook, you can useregistermultiple times to register hooks to the samekey, they will be executed in sequence. Thiskeyis also used when usingapplyPluginsto collect hooks data. Note: This key has no relation to the plugin's key.fnis the definition of the hook, which can be synchronous or asynchronous (just return a Promise)stageis used to adjust the execution order, the default is 0, setting it to -1 or less will execute in advance, setting it to 1 or more will execute later.beforeis also used to adjust the order of execution, the value passed in is the name of the registered hook. Note: The name of the hook registered byregisteris the id of the Umi plugin. For more usage of stage and before, refer to tapable
Note: Compared to umi@3, umi@4 has removed the pluginId parameter.
The writing method of fn needs to be determined in combination with the type parameter that will be used by applyPlugins:
api.ApplyPluginsType.addapplyPluginswill concatenate their return values into an array according to the hook order. At this point,fnneeds to have a return value,fnwill get the parameterargsofapplyPluginsas its own parameter.applyPlugins'sinitialValuemust be an array, its default value is an empty array. Whenkeystarts with'add'andtypeis not explicitly declared,applyPluginswill default to execute in this type.api.ApplyPluginsType.modifyapplyPluginswill sequentially change theapplyPluginsreceivedinitialValueaccording to the hook order, thereforeinitialValueis mandatory at this point. At this timefnneeds to receive amemoas its first parameter, and will take the parameterargsofapplyPluginsas its second parameter.memois the result of a series of hooks modifyinginitialValuebefore,fnneeds to return the modifiedmemo. Whenkeystarts with'modify'andtypeis not explicitly declared,applyPluginswill default to execute in this type.api.ApplyPluginsType.eventapplyPluginswill execute in sequence according to the hook order. At this time,initialValuedoes not need to be passed in.fndoes not need to have a return value and will take the parameterargsofapplyPluginsas its own parameter. Whenkeystarts with'on'andtypeis not explicitly declared,applyPluginswill default to execute in this type.
e.g.1 add type
api.register({
key: 'addFoo',
// Synchronous
fn: (args) => args
});
api.register({
key: 'addFoo',
// Asynchronous
fn: async (args) => args * 2
})
api.applyPlugins({
key: 'addFoo',
// key is add type, no need to explicitly declare as api.ApplyPluginsType.add
args: 1
}).then((data)=>{
console.log(data); // [1, 2]
})e.g.2 modify type
api.register({
key: 'foo',
fn: (memo, args) => ({ ...memo, a: args})
})
api.register({
key: 'foo',
fn: (memo) => ({...memo, b: 2})
})
api.applyPlugins({
key: 'foo',
type: api.ApplyPluginsType.modify,
// initialValue is required
initialValue: {
a: 0,
b: 0
},
args: 1
}).then((data) => {
console.log(data); // { a: 1, b: 2 }
});registerCommand
api.registerCommand({
name: string,
description?: string,
options?: string,
details?: string,
fn,
alias?: string | string[],
resolveConfigMode?: 'strict' | 'loose'
})Register a command.
aliasis the alias, such as the alias g for generatefn's parameter is{ args }, args' format is the same as the result of parsing by yargs, it should be noted that the command itself is removed from_, such as executingumi generate page foo,args._is['page', 'foo']resolveConfigModeparameter controls the way of configuration parsing when executing commands, understrictmode it strictly verifies the content of the Umi project's configuration file, if there is illegal content the command execution is interrupted; underloosemode, the verification check of the configuration file is not performed.
registerMethod
api.registerMethod({ name: string, fn? })Register a method named 'name' on api.
- When fn is passed, execute fn
- When fn is not passed,
registerMethodwill treatnameas thekeyforapi.registerand curry it asfn. In this situation, it is equivalent to registering a quick call method forregister, convenient for registering hook.
Note:
- Compared to
umi@3,umi@4has removed the exitsError parameter. - It is generally not recommended to register additional methods, because they will not have ts hints, directly using
api.register()is a safer approach.
e.g.1
api.registerMethod({
name: foo,
// has fn
fn: (args) => {
console.log(args);
}
})
api.foo('hello, umi!'); // hello, umi!In this example, we register a foo method on the api, which will console the parameter to the console.
e.g.2
import api from './api';
api.registerMethod({
name: 'addFoo'
// no fn
})
api.addFoo(args => args);
api.addFoo(args => args * 2);
api.applyPlugins({
key: 'addFoo',
args: 1
}).then((data)=>{
console.log(data); // [1, 2]
});In this example, we did not pass fn to api.registerMethod. At this time, we have registered a "registrar" on the api: addFoo. Each call to this method is equivalent to calling register({ key: 'addFoo', fn }). Therefore, when we use api.applyPlugins (since our method is an add type, we can not explicitly declare its type) we can get the values of the hooks we just registered.
registerPresets
api.registerPresets(presets: string[])Register a set of plugins, the parameter is an array of paths. This api must be executed in the initPresets stage, i.e. it can only register other presets in preset
e.g.
api.registerPresets([
'./preset',
require.resolve('./preset_foo')
])registerPlugins
api.registerPlugins(plugins: string[])Register plugins, the parameter is an array of paths. This api must be executed in the initPresets and initPlugins stage.
e.g.
api.registerPlugins([
'./plugin',
require.resolve('./plugin_foo')
])Note: Compared to umi@3, umi@4 no longer supports passing plugin objects directly in registerPresets and registerPlugins, now only allows passing the path of the plugin.
registerGenerator
Register a micro-generator to quickly generate template code.
Example:
import { GeneratorType } from '@umijs/core';
import { logger } from '@umijs/utils';
import { join } from 'path';
import { writeFileSync } from 'fs';
api.registerGenerator({
key: 'editorconfig',
name: 'Create .editorconfig',
type: GeneratorType.generate,
fn: () => {
const configFilePath = join(api.cwd, '.editorconfig')
if (existsSync(configFilePath)) {
logger.info(`The .editorconfig file already exists.`)
return
}
writeFileSync(
configFilePath,
`
# 🎨 http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
`.trimStart(),
'utf-8'
)
logger.info(`Generate .editorconfig file successful.`)
}
})More examples see Existing generator source code .
skipPlugins
api.skipPlugins(keys: string[])Declare which plugins need to be disabled, the parameter is an array of plugin keys
Extended Methods
Methods extended through api.registerMethod(), their role are to register some hooks for use, so they all need to receive a fn. Most of these methods are named in the manner of add- modify- on-, corresponding to the three methods of api.ApplyPluginsType, and the fns they receive are slightly different, see the section register for details.
Note: All the fns mentioned below can be synchronous or asynchronous (just return a Promise). fn can be replaced by
{
fn,
name?: string,
before?: string | string[],
stage: number,
}Where each parameter's role is detailed in tapable
addBeforeBabelPlugins
Add additional Babel plugins. The fn passed in does not need parameters and needs to return a Babel plugin or array of plugins.
api.addBeforeBabelPlugins(() => {
// Return a Babel plugin (from a Babel official website example)
return () => {
visitor: {
Identifier(path) {
const name = path.node.name;
// Reverse the name
path.node.name = name.split("").reverse().join("");
}
}
}
})addBeforeBabelPresets
Add additional Babel preset collections. The fn passed in does not need parameters and needs to return a Babel preset collection (presets) or array of preset collections.
api.addBeforeBabelPresets(() => {
// Return a Babel preset collection
return () => {
return {
plugins: ["Babel_Plugin_A","Babel_Plugin_B"]
}
}
})addBeforeMiddlewares
Add middleware before webpack-dev-middleware. The fn passed in does not need parameters and needs to return an express middleware or its array.
api.addBeforeMiddlewares(() => {
return (req, res, next) => {
if(false) {
res.end('end');
}
next();
}
})addEntryCode
Add code at the end of the entry file (after render). The fn passed in does not need parameters and needs to return a string or array of strings.
api.addEntryCode(() => `console.log('I am after render!')`);addEntryCodeAhead
Add code at the beginning of the entry file (after import, before render). The fn passed in does not need parameters and needs to return a string or array of strings.
api.addEntryCodeAhead(() => `console.log('I am before render!')`)addEntryImports
Add import statements in the entry file (at the end of import). The fn passed in does not need parameters, it needs to return a {source: string, specifier?: string} or its array.
api.addEntryImports(() => ({
source: '/modulePath/xxx.js',
specifier: 'moduleName'
}))addEntryImportsAhead
Add import statements in the entry file (at the beginning of import). The fn passed in does not need parameters, it needs to return a {source: string, specifier?: string} or its array.
api.addEntryImportsAhead(() => ({
source: 'anyPackage'
}))addExtraBabelPlugins
Add extra Babel plugins. The fn passed in does not need parameters and needs to return a Babel plugin or plugin array.
addExtraBabelPresets
Add extra Babel preset collections. The fn passed in does not need parameters and needs to return a Babel preset collection or its array.
addHTMLHeadScripts
Add scripts to the <head> element of HTML. The fn passed in does not need parameters and needs to return a string (the code to be added) or a { async?: boolean, charset?: string, crossOrigin?: string | null, defer?: boolean, src?: string, type?: string, content?: string } or its array.
api.addHTMLHeadScripts(() => `console.log('I am in HTML-head')`)addHTMLLinks
Add Link tags to HTML. The fn passed in does not need parameters, the returned object or its array interface is as follows:
{
as?: string, crossOrigin: string | null,
disabled?: boolean,
href?: string,
hreflang?: string,
imageSizes?: string,
imageSrcset?: string,
integrity?: string,
media?: string,
referrerPolicy?: string,
rel?: string,
rev?: string,
target?: string,
type?: string
}addHTMLMetas
Add Meta tags to HTML. The fn passed in does not need parameters, the returned object or its array interface is as follows:
{
content?: string,
'http-equiv'?: string,
name?: string,
scheme?: string
}addHTMLScripts
Add scripts to the end of HTML. The fn passed in does not need parameters, the returned object interface is the same as addHTMLHeadScripts
addHTMLStyles
Add Style tags to HTML. The fn passed in does not need parameters, returning a string (code in the style tag) or { type?: string, content?: string } or its array.
addLayouts
Add global layout components. The fn passed in does not need parameters, returns { id?: string, file: string }
addMiddlewares
Add middleware, after the route middleware. The fn passed in does not need parameters, returns express middleware.
addPolyfillImports
Add polyfill imports, executed at the very beginning of the application. The fn passed in does not need parameters, returns { source: string, specifier?:string }
addPrepareBuildPlugins
addRuntimePlugin
Add runtime plugin, the fn passed in does not need parameters, returns a string, indicating the path of the plugin.
addRuntimePluginKey
Add runtime plugin key, the fn passed in does not need parameters, returns a string, indicating the path of the plugin.
addTmpGenerateWatcherPaths
Add watching paths, changes will re-generate temporary files. The fn passed in does not need parameters, returns a string, indicating the path to be watched.
addOnDemandDeps
Add dependencies for on-demand installation, which will
Docs Docs Api Plugin Api (`docs/docs/docs/api/plugin-api.md`)
order: 5 toc: content
插件 API
Umi 的核心就在于它的插件机制。基于 Umi 的插件机制,你可以获得扩展项目的编译时和运行时的能力。以下罗列出我们为你提供的所有的插件API,以帮助你能自由编写插件。
在查用 Umi 插件 API 之前,我们建议你先阅读插件一节,以了解 umi 插件的机制及原理,这将帮助你更好的使用插件 API。
为方便查找,以下内容通过字母排序。
核心 API
service 和 PluginAPI 里定义的方法。
applyPlugins
api.applyPlugins({ key: string, type?: api.ApplyPluginsType, initialValue?: any, args?: any })取得 register() 注册的 hooks 执行后的数据,这是一个异步函数,因此它返回的将是一个 Promise。这个方法的例子和详解见 register api
describe
api.describe({ key?:string, config?: { default , schema, onChange }, enableBy? })在插件注册阶段( initPresets or initPlugins stage )执行,用于描述插件或者插件集的 key、配置信息和启用方式等。
key是配置中该插件配置的键名config.default是插件配置的默认值,当用户没有在配置中配置 key 时,默认配置将生效。config.schema用于声明配置的类型,基于 joi 。 如果你希望用户进行配置,这个是必须的 ,否则用户的配置无效config.onChange是 dev 模式下,配置被修改后的处理机制。默认值为api.ConfigChangeType.reload,表示在 dev 模式下,配置项被修改时会重启 dev 进程。 你也可以修改为api.ConfigChangeType.regenerateTmpFiles, 表示只重新生成临时文件。你还可以传入一个方法,来自定义处理机制。enableBy是插件的启用方式,默认是api.EnableBy.register,表示注册启用,即插件只要被注册就会被启用。可以更改为api.EnableBy.config,表示配置启用,只有配置插件的配置项才启用插件。你还可以自定义一个返回布尔值的方法( true 为启用 )来决定其启用时机,这通常用来实现动态生效。
e.g.
api.describe({
key: 'foo',
config: {
schema(joi){
return joi.string();
},
onChange: api.ConfigChangeType.regenerateTmpFiles,
},
enableBy: api.EnableBy.config,
})这个例子中,插件的 key 为 foo,因此配置中的键名为 foo,配置的类型是字符串,当配置 foo 发生变化时,dev 只会重新生成临时文件。该插件只有在用户配置了 foo 之后才会启用。
isPluginEnable
api.isPluginEnable( key:string)判断插件是否启用,传入的参数是插件的 key
register
api.register({ key: string, fn, before?: string, stage?: number})为 api.applyPlugins 注册可供其使用的 hook。
key是注册的 hook 的类别名称,可以多次使用register向同一个key注册 hook,它们将会依次执行。这个key也同样是使用applyPlugins收集 hooks 数据时使用的key。注意: 这里的 key 和 插件的 key 没有任何联系。fn是 hook 的定义,可以是同步的,也可以是异步的(返回一个 Promise 即可)stage用于调整执行顺序,默认为 0,设为 -1 或更少会提前执行,设为 1 或更多会后置执行。before同样用于调整执行的顺序,传入的值为注册的 hook 的名称。注意:register注册的 hook 的名称是所在 Umi 插件的 id。 stage 和 before 的更多用法参考 tapable
注意: 相较于 umi@3, umi@4 去除了 pluginId 参数。
fn 的写法需要结合即将使用的 applyPlugins 的 type 参数来确定:
api.ApplyPluginsType.addapplyPlugins将按照 hook 顺序来将它们的返回值拼接成一个数组。此时fn需要有返回值,fn将获取applyPlugins的参数args来作为自己的参数。applyPlugins的initialValue必须是一个数组,它的默认值是空数组。当key以'add'开头且没有显式地声明type时,applyPlugins会默认按此类型执行。api.ApplyPluginsType.modifyapplyPlugins将按照 hook 顺序来依次更改applyPlugins接收的initialValue, 因此此时initialValue是必须的 。此时fn需要接收一个memo作为自己的第一个参数,而将会把applyPlugins的参数args来作为自己的第二个参数。memo是前面一系列 hook 修改initialValue后的结果,fn需要返回修改后的memo。当key以'modify'开头且没有显式地声明type时,applyPlugins会默认按此类型执行。api.ApplyPluginsType.eventapplyPlugins将按照 hook 顺序来依次执行。此时不用传入initialValue。fn不需要有返回值,并且将会把applyPlugins的参数args来作为自己的参数。当key以'on'开头且没有显式地声明type时,applyPlugins会默认按此类型执行。
e.g.1 add 型
api.register({
key: 'addFoo',
// 同步
fn: (args) => args
});
api.register({
key: 'addFoo',
// 异步
fn: async (args) => args * 2
})
api.applyPlugins({
key: 'addFoo',
// key 是 add 型,不用显式声明为 api.ApplyPluginsType.add
args: 1
}).then((data)=>{
console.log(data); // [1,2]
})e.g.2 modify 型
api.register({
key: 'foo',
fn: (memo, args) => ({ ...memo, a: args})
})
api.register({
key: 'foo',
fn: (memo) => ({...memo, b: 2})
})
api.applyPlugins({
key: 'foo',
type: api.ApplyPluginsType.modify,
// 必须有 initialValue
initialValue: {
a: 0,
b: 0
},
args: 1
}).then((data) => {
console.log(data); // { a: 1, b: 2 }
});registerCommand
api.registerCommand({
name: string,
description? : string,
options? : string,
details? : string,
fn,
alias? : string | string[]
resolveConfigMode? : 'strict' | 'loose'
})注册命令。
alias为别名,比如 generate 的别名 gfn的参数为{ args }, args 的格式同 yargs 的解析结果,需要注意的是_里的 command 本身被去掉了,比如执行umi generate page foo,args._为['page','foo']resolveConfigMode参数控制执行命令时配置解析的方式,strict模式下强校验 Umi 项目的配置文件内容,如果有非法内容中断命令执行;loose模式下不执行配置文件的校验检查。
registerMethod
api.registerMethod({ name: string, fn? })往 api 上注册一个名为 'name' 的方法。
- 当传入了 fn 时,执行 fn
- 当没有传入 fn 时,
registerMethod会将name作为api.register的key并且将其柯里化后作为fn。这种情况下相当于注册了一个register的快捷调用方式,便于注册 hook。
注意:
- 相较于
umi@3,umi@4去除了 exitsError 参数。 - 通常不建议注册额外的方法,因为它们不会有 ts 提示,直接使用
api.register()是一个更安全的做法。
e.g.1
api.registerMethod({
name: foo,
// 有 fn
fn: (args) => {
console.log(args);
}
})
api.foo('hello, umi!'); // hello, umi!该例子中,我们往api上注册了一个 foo 方法,该方法会把参数 console 到控制台。
e.g.2
import api from './api';
api.registerMethod({
name: 'addFoo'
// 没有 fn
})
api.addFoo( args => args );
api.addFoo( args => args * 2 );
api.applyPlugins({
key: 'addFoo',
args: 1
}).then((data)=>{
console.log(data); // [ 1, 2 ]
});该例子中,我们没有向 api.registerMethod 中传入 fn。此时,我们相当于往 api 上注册了一个"注册器":addFoo。每次调用该方法都相当于调用了 register({ key: 'addFoo', fn })。因此当我们使用 api.applyPlugins 的时候(由于我们的方法是 add 型的,可以不用显式声明其 type )就可以获取刚刚注册的 hook 的值。
registerPresets
api.registerPresets( presets: string[] )注册插件集,参数为路径数组。该 api 必须在 initPresets stage 执行,即只可以在 preset 中注册其他 presets
e.g.
api.registerPresets([
'./preset',
require.resolve('./preset_foo')
])registerPlugins
api.registerPlugins( plugins: string[] )注册插件,参数为路径数组。该 api 必须在 initPresets 和 initPlugins stage 执行。
e.g.
api.registerPlugins([
'./plugin',
require.resolve('./plugin_foo')
])注意: 相较于 umi@3 ,umi@4 不再支持在 registerPresets 和 registerPlugins 中直接传入插件对象了,现在只允许传入插件的路径。
registerGenerator
注册微生成器用来快捷生成模板代码。
示例:
import { GeneratorType } from '@umijs/core';
import { logger } from '@umijs/utils';
import { join } from 'path';
import { writeFileSync } from 'fs';
api.registerGenerator({
key: 'editorconfig',
name: 'Create .editorconfig',
type: GeneratorType.generate,
fn: () => {
const configFilePath = join(api.cwd, '.editorconfig')
if (existsSync(configFilePath)) {
logger.info(`The .editorconfig file already exists.`)
return
}
writeFileSync(
configFilePath,
`
# 🎨 http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
`.trimStart(),
'utf-8'
)
logger.info(`Generate .editorconfig file successful.`)
}
})更多示例见 已有生成器源码 。
skipPlugins
api.skipPlugins( keys: string[])声明哪些插件需要被禁用,参数为插件 key 的数组
扩展方法
通过api.registerMethod() 扩展的方法,它们的作用都是注册一些 hook 以供使用,因此都需要接收一个 fn。这些方法中的大部分都按照 add- modify- on- 的方式命名,它们分别对应了 api.ApplyPluginsType的三种方式,不同方式接收的 fn 不太相同,详见 register 一节。
注意: 下文提到的所有 fn 都可以是同步的或者异步的(返回一个 Promise 即可)。fn 都可以被
{
fn,
name?: string,
before?: string | string[],
stage: number,
}代替。其中各个参数的作用详见 tapable
addBeforeBabelPlugins
增加额外的 Babel 插件。传入的 fn 不需要参数,且需要返回一个 Babel 插件或插件数组。
api.addBeforeBabelPlugins(() => {
// 返回一个 Babel 插件(来源于 Babel 官网的例子)
return () => {
visitor: {
Identifier(path) {
const name = path.node.name;
path.node.name = name.split("").reverse().join("");
}
}
}
})addBeforeBabelPresets
增加额外的 Babel 插件集。传入的 fn 不需要参数,且需要返回一个 Babel 插件集( presets )或插件集数组。
api.addBeforeBabelPresets(() => {
// 返回一个 Babel 插件集
return () => {
return {
plugins: ["Babel_Plugin_A","Babel_Plugin_B"]
}
}
})addBeforeMiddlewares
在 webpack-dev-middleware 之前添加中间件。传入的 fn 不需要参数,且需要返回一个 express 中间件或其数组。
api.addBeforeMiddlewares(() => {
return (req, res, next) => {
if(false) {
res.end('end');
}
next();
}
})addEntryCode
在入口文件的最后面添加代码(render 后)。传入的 fn 不需要参数,且需要返回一个 string 或者 string 数组。
api.addEntryCode(() => `console.log('I am after render!')`);addEntryCodeAhead
在入口文件的最前面添加代码(render 前,import 后)。传入的 fn 不需要参数,且需要返回一个 string 或者 string 数组。
api.addEntryCodeAhead(() => `console.log('I am before render!')`)addEntryImports
在入口文件中添加 import 语句 (import 最后面)。传入的 fn 不需要参数,其需要返回一个 {source: string, specifier?: string} 或其数组。
api.addEntryImports(() => ({
source: '/modulePath/xxx.js',
specifier: 'moduleName'
}))addEntryImportsAhead
在入口文件中添加 import 语句 (import 最前面)。传入的 fn 不需要参数,其需要返回一个 {source: string, specifier?: string} 或其数组。
api.addEntryImportsAhead(() => ({
source: 'anyPackage'
}))addExtraBabelPlugins
添加额外的 Babel 插件。 传入的 fn 不需要参数,且需要返回一个 Babel 插件或插件数组。
addExtraBabelPresets
添加额外的 Babel 插件集。传入的 fn 不需要参数,且需要返回一个 Babel 插件集或其数组。
addHTMLHeadScripts
往 HTML 的 <head> 元素里添加 Script。传入的 fn 不需要参数,且需要返回一个 string(想要加入的代码) 或者 { async?: boolean, charset?: string, crossOrigin?: string | null, defer?: boolean, src?: string, type?: string, content?: string } 或者它们的数组。
api.addHTMLHeadScripts(() => `console.log('I am in HTML-head')`)addHTMLLinks
往 HTML 里添加 Link 标签。 传入的 fn 不需要参数,返回的对象或其数组接口如下:
{
as?: string, crossOrigin: string | null,
disabled?: boolean,
href?: string,
hreflang?: string,
imageSizes?: string,
imageSrcset?: string,
integrity?: string,
media?: string,
referrerPolicy?: string,
rel?: string,
rev?: string,
target?: string,
type?: string
}addHTMLMetas
往 HTML 里添加 Meta 标签。 传入的 fn 不需要参数,返回的对象或其数组接口如下:
{
content?: string,
'http-equiv'?: string,
name?: string,
scheme?: string
}addHTMLScripts
往 HTML 尾部添加 Script。 传入的 fn 不需要参数,返回的对象接口同 addHTMLHeadScripts
addHTMLStyles
往 HTML 里添加 Style 标签。 传入的 fn 不需要参数,返回一个 string (style 标签里的代码)或者 { type?: string, content?: string },或者它们的数组。
addLayouts
添加全局 layout 组件。 传入的 fn 不需要参数,返回 { id?: string, file: string }
addMiddlewares
添加中间件,在 route 中间件之后。 传入的 fn 不需要参数,返回 express 中间件。
addPolyfillImports
添加补丁 import,在整个应用的最前面执行。 传入的 fn 不需要参数,返回 { source: string, specifier?:string }
addPrepareBuildPlugins
addRuntimePlugin
添加运行时插件,传入的 fn 不需要参数,返回 string ,表示插件的路径。
addRuntimePluginKey
添加运行时插件的 Key, 传入的 fn 不需要参数,返回 string ,表示插件的路径。
addTmpGenerateWatcherPaths
添加监听路径,变更时会重新生成临时文件。传入的 fn 不需要参数,返回 string,表示要监听的路径。
addOnDemandDeps
添加按需安装的依赖,他们会在项目启动时检测是否安装:
api.addOnDemandDeps(() => [{ name: '@swc/core', version: '^1.0.0', dev: true }])chainWebpack
通过 webpack-chain 的方式修改 webpack 配置。传入一个fn,该 fn 不需要返回值。它将接收两个参数:
memo对应 webpack-chain 的 configargs:{ webpack, env }arg.webpack是 webpack 实例,args.env代表当前的运行环境。
e.g.
api.chainWebpack(( memo, { webpack, env}) => {
// set alias
memo.resolve.alias.set('a','path/to/a');
// Delete progess bar plugin
memo.plugins.delete('progess');
})modifyAppData (`umi@4` 新增)
修改 app 元数据。传入的 fn 接收 appData 并且返回它。
api.modifyAppData((memo) => {
memo.foo = 'foo';
return memo;
})modifyConfig
修改配置,相较于用户的配置,这份是最终传给 Umi 使用的配置。传入的 fn 接收 config 作为第一个参数,并且返回它。另外 fn 可以接收 { paths } 作为第二个参数。paths 保存了 Umi 的各个路径。
api.modifyConfig((memo, { paths }) => {
memo.alias = {
...memo.alias,
'@': paths.absSrcPath
}
return memo;
})modifyDefaultConfig
修改默认配置。传入的 fn 接收 config 并且返回它。
modifyHTML
修改 HTML,基于 cheerio 的 ast。传入的 fn 接收 cheerioAPI 并且返回它。另外 fn 还可以接收{ path } 作为它的第二个参数,该参数代表路由的 path
api.modifyHTML(($, { path }) => {
$('h2').addClass('welcome');
return $;
})modifyHTMLFavicon
修改 HTML 的 favicon 路径。 传入的 fn 接收原本的 favicon 路径(string 类型)并且返回它。
modifyPaths
修改 paths,比如 absOutputPath、absTmpPath。传入的 fn 接收 paths 并且返回它。
paths 的接口如下:
paths:{
cwd?: string;
absSrcPath?: string;
absPagesPath?: string;
absTmpPath?: string;
absNodeModulesPath?: string;
absOutputPath?: string;
}modifyRendererPath
修改 renderer path。传入的 fn 接收原本的 path (string 类型)并且返回它。
modifyServerRendererPath
修改 server renderer path。传入的 fn 接收原本的 path (string 类型)并且返回它。
modifyRoutes
修改路由。 传入的 fn 接收 id-route 的 map 并且返回它。其中 route 的接口如下:
interface IRoute {
path: string;
file?: string;
id: string;
parentId?: string;
[key: string]: any;
}e.g.
api.modifyRoutes((memo) => {
Object.keys(memo).forEach((id) => {
const route = memo[id];
if(route.path === '/'){
route.path = '/redirect'
}
});
return memo;
})modifyTSConfig
修改临时目录下的 tsconfig 文件内容。
api.modifyTSConfig((memo) => {
memo.compilerOptions.paths['foo'] = ['bar'];
return memo;
});modifyViteConfig
修改 vite 最终配置。 传入的 fn 接收 vite 的 Config 对象作为第一个参数并且返回它。另外 fn 还可以接收 { env } 作为第二个参数,可以通过该参数获取当前的环境。
api.modifyViteConfig((memo, { env }) => {
if(env === 'development'){
// do something
}
return memo;
})modifyWebpackConfig
修改 webpack 最终配置。传入的 fn 接收 webpack 的 Config 对象作为第一个参数并且返回它。另外 fn 还可以接收 { webpack, env } 作为第二个参数,其中 webpack 是 webpack 实例,env 代表当前环境。
api.modifyWebpackConfig((memo, { webpack, env }) => {
// do something
return memo;
})onBeforeCompiler
generate 之后,webpack / vite compiler 之前。传入的 fn 不接收任何参数。
onBeforeMiddleware
提供在服务器内部执行所有其他中间件之前执行自定义中间件的能力, 这可以用来定义自定义处理程序, 例如:
api.onBeforeMiddleware(({ app }) => {
app.get('/some/path', function (req, res) {
res.json({ custom: 'response' });
});
});onBuildComplete
build 完成时。传入的 fn 接收 { isFirstCompile: boolean, stats, time: number, err?: Error } 作为参数。
onBuildHtmlComplete
build 完成且 html 完成构建之后。
onCheck
检查时,在 onStart 之前执行。传入的 fn 不接收任何参数
onCheckCode
检查代码时。传入的 fn 接收的参数接口如下:
args: {
file: string;
code: string;
isFromTmp: boolean;
imports: {
source: string;
loc: any;
default: string;
namespace: string;
kind: babelImportKind;
specifiers: Record<string, { name: string; kind: babelImportKind }>;
}[];
exports: any[];
cjsExports: string[];
}onCheckConfig
检查 config 时。传入的 fn 接收 { config, userConfig }作为参数,它们分别表示实际的配置和用户的配置。
onCheckPkgJSON
检查 package.json 时。传入的 fn 接收 {origin?, current} 作为参数。它们的类型都是 package.json 对象
onDevCompileDone
dev 完成时。传入的 fn 接收的参数接口如下:
args: {
isFirstCompile: boolean;
stats: any;
time: number;
}onGenerateFiles
生成临时文件时,随着文件变化会频繁触发,有缓存。 传入的 fn 接收的参数接口如下:
args: {
isFirstTime?: boolean;
files?: {
event: string;
path: string;
} | null;
}onPatchRoute
匹配单个路由,可以修改路由,给路由打补丁
onPkgJSONChanged
package.json 变更时。传入的 fn 接收 {origin?, current} 作为参数。它们的类型都是 package.json 对象
onPrepareBuildSuccess
onStart
启动时。传入的 fn 不接收任何参数。
writeTmpFile
api.writeTmpFile()的 type 参数的类型。
- content: 写入的文本内容,有内容就不会使用模板。
- context: 模板上下文。
- noPluginDir: 是否使用插件名做为目录。
- path: 写入文件的路径。
- tpl: 使用模板字符串,没有模板路径会使用它。
- tplPath: 使用模板文件的路径。
属性
从 api 可以直接访问到的属性,这些属性有一部分来自于 service
appData
args
命令行参数,这里去除了命令本身。
e.g.
$ umi dev --foo, args 为{ _:[], foo: true }$ umi g page index --typescript --less, args 为{ _: [ 'page', 'index''], typescript: true, less: true }
config
最终的配置(取决于你访问的时机,可能是当前收集到的最终配置)
cwd
当前路径
env
即 process.env.NODE_ENV 可能有 development、production 和 test
logger
插件日志对象,包含 { log, info, debug, error, warn, profile },他们都是方法。其中 api.logger.profile 可用于性能耗时记录。
api.logger.profile('barId');
setTimeout(() => {
api.logger.profile('barId');
})
// profile - barId Completed in 6254msname
当前命令的名称,例如 $ umi dev , name 就是 dev
paths
项目相关的路径:
absNodeModulesPath,node_modules 目录绝对路径absOutputPath,输出路径,默认是 ./distabsPagesPath,pages 目录绝对路径absSrcPath,src 目录绝对路径,需注意 src 目录是可选的,如果没有 src 目录,absSrcPath 等同于 cwdabsTmpPath,临时目录绝对路径cwd,当前路径
注意: 注册阶段不能获取到。因此不能在插件里直接获取,要在 hook 里使用。
pkg
当前项目的 package.json 对象
pkgPath
当前项目的 package.json 的绝对路径。
plugin
当前插件的对象。
type插件类型,有 preset 和 plugin 两种path插件路径id插件 idkey插件 keyconfig插件的配置enableBy插件的启用方式
注意: 注册阶段使用的 plugin 对象是你 describe 之前的对象。
service
Umi 的 Service 实例。通常不需要用到,除非你知道为什么。
userConfig
用户的配置,从 .umirc 或 config/config 中读取的内容,没有经过 defaultConfig 以及插件的任何处理。可以在注册阶段使用。
ApplyPluginsType
api.applyPlugins() 的 type 参数的类型。包含
- add
- modify
- event
ConfigChangeType
为 api.describe() 提供 config.onChange 的类型,目前包含两种:
- restart,重启 dev 进程,是默认值
- regenerateTmpFiles,重新生成临时文件
EnableBy
插件的启用方式,包含三种:
- register
- config
ServiceStage
Umi service 的运行阶段。有如下阶段:
- uninitialized
- init
- initPresets
- initPlugins
- resolveConfig
- collectAppData
- onCheck
- onStart
- runCommand
Docs Docs Api Runtime Config.En US (`docs/docs/docs/api/runtime-config.en-US.md`)
order: 3 toc: content translated_at: '2024-03-17T10:36:22.270Z'
Runtime Configuration
The difference between runtime configuration and configuration is that it runs on the browser side. Based on this, we can write functions, tsx, import browser dependencies, etc., here. Be careful not to import node dependencies.
Configuration Method
It is agreed that src/app.tsx is for runtime configuration.
TypeScript Hints
If you want hints while writing the configuration, you can define the configuration through the defineApp method of umi.
import { defineApp } from 'umi';
export default defineApp({
layout: () => {
return {
title: 'umi',
};
},
});
// or
import { RuntimeConfig } from 'umi';
export const layout: RuntimeConfig['layout'] = () => {
return {
title: 'umi',
};
};Configuration Items
The following configuration items are listed in alphabetical order.
dva
If you are using dva, you can configure the runtime configuration for the dva plugin, please refer to Plugin Configuration.
For example:
export default {
dva: {
immer: true,
extraModels: [],
},
};extraModels
- Type: string[]
- Default: [] Configure additional dva models.
immer
- Type: boolean | object
- Default: false Indicates whether to enable immer to facilitate reducer modification.
Note: To be compatible with IE11, configure { immer: { enableES5: true }}.
Data Flow
If you need to define initialization data, use getInitialState, useModel, and other data flow related functions:
You can create a
@umijs/maxproject with data flow functions, see Introduction to Umi max.Or manually enable the plugin that provides the data flow functions:
bashpnpm add -D @umijs/pluginsts// .umirc.ts export default { plugins: [ '@umijs/plugins/dist/initial-state', '@umijs/plugins/dist/model', ], initialState: {}, model: {}, };
getInitialState
- Type:
getInitialState: () => Promise<DataType extends any> | any
The return value of getInitialState() will become the global initial state. For example:
// src/app.ts
import { fetchInitialData } from "@/services/initial";
export async function getInitialState() {
const initialData = await fetchInitialData();
return initialData;
}Now, various plugins and the components you define can directly access this global initial state through useModel('@@initialState'), as shown below:
import { useModel } from "umi";
export default function Page() {
const { initialState, loading, error, refresh, setInitialState } =
useModel("@@initialState");
return <>{initialState}</>;
}| Object Property | Type | Introduction |
|---|---|---|
initialState |
any |
The return value of the exported getInitialState() method |
loading |
boolean |
Whether the getInitialState() or refresh() method is in progress. The rendering of other parts of the page will be blocked before the initial state is obtained for the first time |
error |
Error |
If an error occurs while the exported getInitialState() method is running, the error information of the error |
refresh |
() => void |
Re-execute the getInitialState method and obtain a new global initial state |
setInitialState |
(state: any) => void |
Manually set the value of initialState, and the loading will be set to false after manual setting |
layout
- Type:
RuntimeConfig | ProLayoutProps
Modify the configuration of the built-in layout, such as configuring logout, custom navigation exposed rendering areas, etc.
Note: You need to enable the layout plugin to use its runtime configuration.
import { RuntimeConfig } from 'umi';
export const layout: RuntimeConfig = {
logout: () => {}, // do something
};For more specific configurations, refer to Plugin Documentation.
onRouteChange
- type:
(args: { routes: Routes; clientRoutes: Routes; location: Location; action: Action; basename: string; isFirst: boolean }) => void
Do something during initial loading and route switching.
For example, for doing tracking statistics,
export function onRouteChange({
location,
clientRoutes,
routes,
action,
basename,
isFirst,
}) {
bacon(location.pathname);
}For example, for setting the title,
import { matchRoutes } from 'umi';
export function onRouteChange({ clientRoutes, location }) {
const route = matchRoutes(clientRoutes, location.pathname)?.pop()?.route;
if (route) {
document.title = route.title || '';
}
}patchRoutes
- type:
(args: { routes: Routes; routeComponents }) => void
export function patchRoutes({ routes, routeComponents }) {
console.log('patchRoutes', routes, routeComponents);
}routes: A flattened list of routes.routeComponents: A mapping of routes to their components.
Note: If you need to dynamically update routes, it is recommended to use patchClientRoutes(), otherwise you may need to modify both routes and routeComponents.
patchClientRoutes
- type:
(args: { routes: Routes; }) => void
Modify the tree-like route table before it is rendered by react-router, receiving the same content as useRoutes.
For example, add a /foo route at the beginning,
import Page from '@/extraRoutes/foo';
export function patchClientRoutes({ routes }) {
routes.unshift({
path: '/foo',
element: ,
});
}For example, add a redirect route at the beginning:
import { Navigate } from 'umi';
export const patchClientRoutes = ({ routes }) => {
routes.unshift({
path: '/',
element: <Navigate to="/home" replace />,
});
};For example, add a nested route:
import Page from '@/extraRoutes/foo';
export const patchClientRoutes = ({ routes }) => {
routes.push({
path: '/group',
children: [{
path: '/group/page',
element: ,
}],
});
};For example, use it in conjunction with the render configuration, requesting the server to dynamically update routes based on the response,
let extraRoutes;
export function patchClientRoutes({ routes }) {
// Modify routes based on extraRoutes
patch(routes, extraRoutes);
}
export function render(oldRender) {
fetch('/api/routes')
.then((res) => res.json())
.then((res) => {
extraRoutes = res.routes;
oldRender();
});
}Note:
- Modify routes directly, no need to return
qiankun
Umi has a built-in qiankun plugin to provide microfrontend capabilities, please refer to Plugin Configuration.
render
- Type:
(oldRender: Function)=>void
Override render.
For example, for doing authorization check before rendering,
export function render(oldRender) {
fetch('/api/auth').then(auth => {
if (auth.isLogin) { oldRender() }
else {
location.href = '/login';
oldRender()
}
});
}request
If you are using import { request } from 'umi'; to request data, then you can customize middleware, interceptors, error handling adapters, etc., through this configuration. Please refer to request plugin configuration.
rootContainer
- Type:
(container: JSX.Element,args: { routes: Routes; plugin; history: History }) => JSX.Element;
Modify the root component handed over to react-dom for rendering.
For example, to wrap a Provider around the outside,
export function rootContainer(container, args) {
return React.createElement(ThemeProvider, null, container);
}args include:
- routes, full route configuration
- plugin, runtime plugin mechanism
- history, history instance
More Configurations
Umi allows plugins to register runtime configurations. If you are using plugins, you will definitely find more runtime configuration items in the plugins.
Docs Docs Api Runtime Config (`docs/docs/docs/api/runtime-config.md`)
order: 3 toc: content
运行时配置
运行时配置和配置的区别是他跑在浏览器端,基于此,我们可以在这里写函数、tsx、import 浏览器端依赖等等,注意不要引入 node 依赖。
配置方式
约定 src/app.tsx 为运行时配置。
TypeScript 提示
如果你想在写配置时也有提示,可以通过 umi 的 defineApp 方法定义配置。
import { defineApp } from 'umi';
export default defineApp({
layout: () => {
return {
title: 'umi',
};
},
});
// or
import { RuntimeConfig } from 'umi';
export const layout: RuntimeConfig['layout'] = () => {
return {
title: 'umi',
};
};配置项
以下配置项按字母排序。
dva
如果你使用的 dva,那么支持配置 dva 插件的运行时配置,具体参考插件配置。
比如:
export default {
dva: {
immer: true,
extraModels: [],
},
};extraModels
- Type: string[]
- Default: [] 配置额外到 dva model。
immer
- Type: boolean | object
- Default: false 表示是否启用 immer 以方便修改 reducer。
注:如需兼容 IE11,需配置 { immer: { enableES5: true }}。
数据流
若你需要定义初始化数据,使用 getInitialState 、useModel 等 数据流 相关功能:
你可以创建自带数据流功能的
@umijs/max项目,详见 Umi max 简介 。或者手动开启数据流功能的插件使用该功能:
bashpnpm add -D @umijs/pluginsts// .umirc.ts export default { plugins: [ '@umijs/plugins/dist/initial-state', '@umijs/plugins/dist/model', ], initialState: {}, model: {}, };
getInitialState
- Type:
getInitialState: () => Promise<DataType extends any> | any
getInitialState() 的返回值将成为全局初始状态。例如:
// src/app.ts
import { fetchInitialData } from "@/services/initial";
export async function getInitialState() {
const initialData = await fetchInitialData();
return initialData;
}现在,各种插件和您定义的组件都可以通过 useModel('@@initialState') 直接获取到这份全局的初始状态,如下所示:
import { useModel } from "umi";
export default function Page() {
const { initialState, loading, error, refresh, setInitialState } =
useModel("@@initialState");
return <>{initialState}</>;
}| 对象属性 | 类型 | 介绍 |
|---|---|---|
initialState |
any |
导出的 getInitialState() 方法的返回值 |
loading |
boolean |
getInitialState() 或 refresh() 方法是否正在进行中。在首次获取到初始状态前,页面其他部分的渲染都会被阻止 |
error |
Error |
如果导出的 getInitialState() 方法运行时报错,报错的错误信息 |
refresh |
() => void |
重新执行 getInitialState 方法,并获取新的全局初始状态 |
setInitialState |
(state: any) => void |
手动设置 initialState 的值,手动设置完毕会将 loading 置为 false |
layout
- Type:
RuntimeConfig | ProLayoutProps
修改内置布局的配置,比如配置退出登陆、自定义导航暴露的渲染区域等。
注意:需要开启 layout 插件,才能使用它的运行时配置。
import { RuntimeConfig } from 'umi';
export const layout: RuntimeConfig = {
logout: () => {}, // do something
};更多具体配置参考插件文档。
onRouteChange
- type:
(args: { routes: Routes; clientRoutes: Routes; location: Location; action: Action; basename: string; isFirst: boolean }) => void
在初始加载和路由切换时做一些事情。
比如用于做埋点统计,
export function onRouteChange({
location,
clientRoutes,
routes,
action,
basename,
isFirst,
}) {
bacon(location.pathname);
}比如用于设置标题,
import { matchRoutes } from 'umi';
export function onRouteChange({ clientRoutes, location }) {
const route = matchRoutes(clientRoutes, location.pathname)?.pop()?.route;
if (route) {
document.title = route.title || '';
}
}patchRoutes
- type:
(args: { routes: Routes; routeComponents }) => void
export function patchRoutes({ routes, routeComponents }) {
console.log('patchRoutes', routes, routeComponents);
}routes: 打平的路由列表。routeComponents: 路由对应的组件映射。
注:如需动态更新路由,建议使用 patchClientRoutes() ,否则你可能需要同时修改 routes 和 routeComponents。
patchClientRoutes
- type:
(args: { routes: Routes; }) => void
修改被 react-router 渲染前的树状路由表,接收内容同 useRoutes。
比如在最前面添加一个 /foo 路由,
import Page from '@/extraRoutes/foo';
export function patchClientRoutes({ routes }) {
routes.unshift({
path: '/foo',
element: ,
});
}比如在最前面添加一个重定向路由:
import { Navigate } from 'umi';
export const patchClientRoutes = ({ routes }) => {
routes.unshift({
path: '/',
element: <Navigate to="/home" replace />,
});
};比如添加一个嵌套路由:
import Page from '@/extraRoutes/foo';
export const patchClientRoutes = ({ routes }) => {
routes.push({
path: '/group',
children: [{
path: '/group/page',
element: ,
}],
});
};比如和 render 配置配合使用,请求服务端根据响应动态更新路由,
let extraRoutes;
export function patchClientRoutes({ routes }) {
// 根据 extraRoutes 对 routes 做一些修改
patch(routes, extraRoutes);
}
export function render(oldRender) {
fetch('/api/routes')
.then((res) => res.json())
.then((res) => {
extraRoutes = res.routes;
oldRender();
});
}注意:
- 直接修改 routes,不需要返回
qiankun
Umi 内置了 qiankun 插件来提供微前端的能力,具体参考插件配置。
render
- Type:
(oldRender: Function)=>void
覆写 render。
比如用于渲染之前做权限校验,
export function render(oldRender) {
fetch('/api/auth').then(auth => {
if (auth.isLogin) { oldRender() }
else {
location.href = '/login';
oldRender()
}
});
}request
如果你使用了 import { request } from 'umi'; 来请求数据,那么你可以通过该配置来自定义中间件、拦截器、错误处理适配等。具体参考 request 插件配置。
rootContainer
- Type:
(container: JSX.Element,args: { routes: Routes; plugin; history: History }) => JSX.Element;
修改交给 react-dom 渲染时的根组件。
比如用于在外面包一个 Provider,
export function rootContainer(container, args) {
return React.createElement(ThemeProvider, null, container);
}args 包含:
- routes,全量路由配置
- plugin,运行时插件机制
- history,history 实例
更多配置
Umi 允许插件注册运行时配置,如果你使用插件,肯定会在插件里找到更多运行时的配置项。
Docs Docs Guides Boilerplate.En US (`docs/docs/docs/guides/boilerplate.en-US.md`)
order: 11 toc: content translated_at: '2024-03-17T10:35:15.206Z'
Scaffolding
Umi officially provides a scaffold, which allows you to easily and quickly create a project:
# Input the path to the project directory when prompted by the wizard
pnpm create umi
# Create a project under the my-umi-app folder in the current directory
pnpm create umi my-umi-appThis command installs the create-umi scaffold and runs it automatically. After running, it offers two options:
- Pick Npm Client - Choose an Npm client
You can select your preferred Node dependency management tool from the following options:
- Pick Npm Registry - Choose an Npm source
After choosing, it will automatically generate the most basic Umi project and install dependencies based on the selected client and registry source:
.
├── package.json
├── pnpm-lock.yaml
├── src
│ ├── assets
│ │ └── yay.jpg
│ ├── layouts
│ │ ├── index.less
│ │ └── index.tsx
│ └── pages
│ ├── docs.tsx
│ └── index.tsx
├── tsconfig.json
└── typings.d.tsThis way, the initialization of the Umi project is completed with a single click.
Docs Docs Guides Boilerplate (`docs/docs/docs/guides/boilerplate.md`)
order: 11 toc: content
脚手架
Umi 官方提供了一个脚手架 ,可以轻松快速创建一个项目:
# 在向导中输入文件夹名称
pnpm create umi
# 在当前目录的 my-umi-app 文件夹下创建项目
pnpm create umi my-umi-app这个命令会安装 create-umi 脚手架并自动运行,运行后提供了两个可选项:
- Pick Npm Client - 选择 Npm 客户端
你可以从以下几个选项中选择习惯的 Node 依赖管理工具:
- Pick Npm Registry - 选择 Npm 源
选择后会自动生成一个最基本的 Umi 项目,并根据选中的客户端和镜像源安装依赖:
.
├── package.json
├── pnpm-lock.yaml
├── src
│ ├── assets
│ │ └── yay.jpg
│ ├── layouts
│ │ ├── index.less
│ │ └── index.tsx
│ └── pages
│ ├── docs.tsx
│ └── index.tsx
├── tsconfig.json
└── typings.d.ts这样就一键完成 Umi 项目的初始化了。
Docs Docs Guides Client Loader.En US (`docs/docs/docs/guides/client-loader.en-US.md`)
order: 8 toc: content translated_at: '2024-03-17T10:34:59.913Z'
Route Data Loading
Umi provides an out-of-the-box solution for data pre-loading, which can solve the waterfall requests of page components and data dependencies under multi-layer nested routing. Umi automatically initiates their data requests in parallel according to the current route or the route being prepared for transition. Therefore, when the route component is loaded, there is already data available for immediate use.
How to Enable
Configuration to enable:
// .umirc.ts
export default {
clientLoader: {}
}How to Use
In the routing file, in addition to the default exported page component, another clientLoader function is exported, and the logic for route data loading is completed within this function.
// pages/.../some_page.tsx
import { useClientLoaderData } from 'umi';
export default function SomePage() {
const { data } = useClientLoaderData();
return {data};
}
export async function clientLoader() {
const data = await fetch('/api/data');
return data;
}As shown in the code above, the data returned by the clientLoader function can be obtained inside the component by calling useClientLoaderData.
Optimization Effect
Consider a scenario with three layers of nested routing:
- We need to wait for the first layer route's component to load, then the first layer route's component makes a data request.
- After the data request of the first layer route is completed, it starts to request the second layer route's component. After the second layer route's component is loaded, it requests the data needed for the second layer route.
- After the data request of the second layer route is completed, it starts to request the third layer route's component. After the third layer route's component is loaded, it requests the data needed for the third layer route.
- After the data request of the third layer route is completed, the entire page is finally rendered.
Such waterfall requests can severely impact the user experience, as shown in the figure below:
If the component's data request is extracted into clientLoader, then Umi can request these data in parallel:
Docs Docs Guides Client Loader (`docs/docs/docs/guides/client-loader.md`)
order: 8 toc: content
路由数据加载
Umi 提供了开箱即用的数据预加载方案,能够解决在多层嵌套路由下,页面组件和数据依赖的瀑布流请求。Umi
会自动根据当前路由或准备跳转的路由,并行地发起他们的数据请求,因此当路由组件加载完成后,已经有马上可以使用的数据了。
启用方式
配置开启:
// .umirc.ts
export default {
clientLoader: {}
}使用方式
在路由文件中,除了默认导出的页面组件外,再导出一个 clientLoader 函数,并且在该函数内完成路由数据加载的逻辑。
// pages/.../some_page.tsx
import { useClientLoaderData } from 'umi';
export default function SomePage() {
const { data } = useClientLoaderData();
return {data};
}
export async function clientLoader() {
const data = await fetch('/api/data');
return data;
}如上代码,在 clientLoader 函数返回的数据,可以在组件内调用 useClientLoaderData 获取。
优化效果
考虑一个三层嵌套路由的场景:
- 我们需要先等第一层路由的组件加载完成,然后第一层路由的组件发起数据请求
- 第一层路由的数据请求完成后,开始请求第二层路由的组件,第二层路由的组件加载好以后请求第二层路由需要的数据
- 第二层路由的数据请求完成后,开始请求第三层路由的组件,第三层路由的组件加载好以后请求第三层路由需要的数据
- 第三层路由的数据请求完成后,整个页面才完成渲染
这样的瀑布流请求会严重影响用户的体验,如下图所示:
如果将组件请求数据的程序提取到 clientLoader 中,则 Umi 可以并行地请求这些数据:
Docs Docs Guides Debug.En US (`docs/docs/docs/guides/debug.en-US.md`)
order: 14 toc: content translated_at: '2024-03-17T10:34:34.282Z'
Debugging
In addition to using the browser's debugging tools for development debugging, Umi also recommends the following debugging methods to assist with project debugging.
Debugging dev Products
If you need to debug the build product of the project during the dev stage, take umi.js as an example. First, download the original umi.js to the root directory of the current project. After editing it according to debugging needs, refresh the browser, and the umi.js used by the project will be replaced with the umi.js file in the root directory. If you need to restore it after debugging, just delete the umi.js in the root directory.
Example:
# Download the umi.js of the current project
$curl http://127.0.0.1:8000/umi.js -O
# Add the content you want to debug, for example, add an "debug!!!" alert
$ echo -e '\n;alert("debug!!!");\n' >> umi.js
# Open the browser to see the alert box
# Exit debugging and return to normal state
$rm umi.jsThe same can be done to debug other JavaScript files.
XSwitch
If you need to debug or verify the current code modifications in a specific domain environment, we recommend using the Chrome plugin XSwitch.
Suppose we want to debug local code on the live project address https://www.myproject.com. The project uses https://www.myproject.com/umi.hash.js, to verify the local project, we need to replace it with the local development environment's http://127.0.0.1:000/umi.js
First, start the local environment using the environment variable SOCKET_SERVER (to prevent continuous page refreshing due to the socket server not connecting).
$SOCKET_SERVER=http://127.0.0.1:8000/ npx umi devThen, configure the resource forwarding rules in XSwitch.
{
"proxy": [
// The resource in the 0 item of the array will be replaced by the 1st item
[
"https://www.myproject.com/umi.2c8a01df.js",
"http://127.0.0.1:8000/umi.js"
],
// Using regex can easily handle the loading of js resources in the case of sub-packages
[
"https://www.myproject.com/(.*\.js)",
"http://127.0.0.1:8000/$1",
],
// If you need to verify visual presentation, don't forget to replace css resources
[
"https://www.myproject.com/umi.ae8b10e0.css",
"http://127.0.0.1:8000/umi.css"
]
]
}Refresh the page, and the content under the official domain will be replaced, making it convenient to debug in the specified environment.
To exit debugging, simply turn off the XSwitch plugin function.
:::success{title=💡}
If you use XSwitch often, you can create a new rule for saving.
:::
Docs Docs Guides Debug (`docs/docs/docs/guides/debug.md`)
order: 14 toc: content
调试
除了使用浏览器的调试工具来完成开发中的调试外,Umi 还推荐以下调试方式来协助项目的调试。
调试 dev 产物
如果你需要在 dev 阶段调试项目的构建产物,以 umi.js 举例。先将原来的 umi.js 下载到当前项目根目录下。根据调试需要进行编辑后,刷新浏览器,项目使用的 umi.js 就替换成了根目录下的 umi.js 文件。如果调试完毕需要恢复,直接删除根目录的 umi.js 即可。
举例:
# 下载当前项目的 umi.js
$curl http://127.0.0.1:8000/umi.js -O
# 增加想调试的内容,举例增加 "debug!!!" 弹窗
$ echo -e '\n;alert("debug!!!");\n' >> umi.js
# 打开浏览器就能看到 alert 弹窗
# 退出调试,恢复到正常状态
$rm umi.js以此类推即可调试其他的 JavaScript 文件。
XSwitch
如果需要在特定的域名环境调试或者验证当前的修改的代码,推荐使用 Chrome 插件 XSwitch。
假设我们想在线上项目地址 https://www.myproject.com 上调试本地代码。项目使用 https://www.myproject.com/umi.hash.js,为了验证本地的项目,需要将它替换成本地开发环境的 http://127.0.0.1:000/umi.js
首先使用环境变量 SOCKET_SERVER 启动本地环境(防止因为连接不上 socket server 导致页面不断刷新)。
$SOCKET_SERVER=http://127.0.0.1:8000/ npx umi dev然后,在 XSwitch 中配置资源转发规则。
{
"proxy": [
// 数组的第 0 项的资源会被第 1 项目替换
[
"https://www.myproject.com/umi.2c8a01df.js",
"http://127.0.0.1:8000/umi.js"
],
// 使用正则可以方便处理分包情况下 js 资源的加载
[
"https://www.myproject.com/(.*\.js)",
"http://127.0.0.1:8000/$1",
],
// 如果需要验证视觉表现,不要忘记替换 css 资源
[
"https://www.myproject.com/umi.ae8b10e0.css",
"http://127.0.0.1:8000/umi.css"
]
]
}刷新页面,正式域名下的内容就被替换了,这个时候就能方便的指定环境下调试了。
如果要退出调试,关闭 XSwitch 插件功能即可。
:::success{title=💡}
经常使用 XSwitch 的话,可新建一个规则保存。
:::
Docs Docs Guides Directory Structure.En US (`docs/docs/docs/guides/directory-structure.en-US.md`)
order: 2 toc: content translated_at: '2024-03-17T10:34:04.617Z'
Directory Structure
This documents the directory structure conventionally (or recommended) in Umi projects. Please follow this directory structure to organize code during project development.
.
├── config
│ └── config.ts
├── dist
├── mock
│ └── app.ts|tsx
├── public
├── src
│ ├── .umi
│ ├── .umi-production
│ ├── layouts
│ │ ├── BasicLayout.tsx
│ │ ├── index.less
│ ├── models
│ │ ├── global.ts
│ │ └── index.ts
│ ├── pages
│ │ ├── index.less
│ │ └── index.tsx
│ ├── utils // Recommended directory
│ │ └── index.ts
│ ├── services // Recommended directory
│ │ └── api.ts
│ ├── app.(ts|tsx)
│ ├── global.ts
│ ├── global.(css|less|sass|scss)
│ ├── overrides.(css|less|sass|scss)
│ ├── favicon.(ico|gif|png|jpg|jpeg|svg|avif|webp)
│ └── loading.(tsx|jsx)
├── node_modules
│ └── .cache
│ ├── bundler-webpack
│ ├── mfsu
│ └── mfsu-deps
├── .env
├── plugin.ts
├── .umirc.ts // Choose between this and the config/config.ts file
├── package.json
├── tsconfig.json
└── typings.d.tsRoot Directory
package.json
Unlike Umi 3, Umi 4 does not automatically register plugins or presets beginning with @umijs/preset-, @umijs/plugin-, umi-preset-, and umi-plugin- in package.json. If you need to customize additional plugins/presets, you must manually configure them in plugins.
.env
Environment variables, such as:
PORT=8888
COMPRESS=none.umirc.ts
Same function as the
config/config.tsfile, choose 1 out of 2..umirc.tsfile has a higher priority
Configuration file, includes all non-runtime configurations in Umi (Runtime configurations are generally defined in app.ts).
If you need to load different configurations in different environments, in Umi, this is achieved through UMI_ENV. An example of starting in a different environment:
// package.json
{
"scripts": {
"dev": "umi dev",
"dev:pre": "cross-env UMI_ENV=pre umi dev"
}
}config/config.ts
Same function as the
.umirc.tsfile, choose 1 out of 2..umirc.tsfile has a higher priority
Same as .umirc.ts, the difference is that you can consolidate all configurations in a separate config folder under the root directory to keep it neat.
dist Directory
The default output folder for artifacts after executing umi build. You can modify the output folder through the outputPath configuration.
mock Directory
Place for mock files. All .ts / .js files in this directory will be loaded by the mock service to provide simulated data. See Mock for usage details.
public Directory
Stores fixed static resources. For example, by placing public/image.png, it can be accessed during development via /image.png, and will be copied to the output folder after building.
Note:
- For svg resources, Umi supports svgr, which can be directly imported for use as a component:
import SmileUrl, { ReactComponent as SvgSmile } from './smile.svg';
// <SvgSmile />- For resources like images, Umi supports direct import to obtain the resource path:
import imgUrl from './image.png'
// >`src` Directory
.umi Directory
:::warning{title=🛎️}
Do not commit .umi temporary files to the git repository, already ignored by default in .gitignore.
:::
Temporary file directory during development, such as entry files, routes, etc., will be temporarily generated here.
.umi-production Directory
:::warning{title=🛎️}
Do not commit .umi-production temporary files to the git repository, already ignored by default in .gitignore.
:::
Temporary file directory during build, such as entry files, routes, etc., will be temporarily generated here.
app.[ts|tsx]
Runtime configuration file, where you can extend runtime capabilities, such as modifying routes, modifying the render method, etc.
The logic brought by runtime configuration runs in the browser. Therefore, when there are remote configurations, dynamic content, etc., which are not certain during local development and cannot be hardcoded, it is necessary to dynamically obtain them when the project is actually run in the browser.
layouts/index.tsx
Global layout, will be effective under all routes by default. For example, with the following route relationship:
[
{ path: '/', component: '@/pages/index' },
{ path: '/users', component: '@/pages/users' },
]The output will be:
<Layout>
index
users
</Layout>When you need to turn off layout, you can use layout: false. When you need more layers of layout, consider using wrappers, only available in configuration routes:
routes: [
{ path: '/', component: './index', layout: false },
{
path: '/users',
component: './users',
wrappers: ['@/wrappers/auth']
}
]pages Directory
Conventionally, the route table is generated based on the file hierarchy structure of the pages/* folder for convention-based routing.
In configuration-based routing, if component is written as a relative path, it will start looking for files from this folder:
routes: [
// `./index` === `@/pages/index`
{ path: '/', component: './index' }
]Basic Routes
Assuming the pages directory structure is as follows:
+ pages/
+ users/
- index.tsx
- index.tsxThen, the automatically generated routing configuration would be as follows:
[
{ path: '/', component: '@/pages/index.tsx' },
{ path: '/users/', component: '@/pages/users/index.tsx' },
]Dynamic Routes
Directories or files prefixed with $ are considered dynamic routes. If no parameter name is specified after $, it represents * wildcard. For example, with the following directory structure:
+ pages/
+ foo/
- $slug.tsx
+ $bar/
- $.tsx
- index.tsxThe generated routing configuration would be as follows:
[
{ path: '/', component: '@/pages/index.tsx' },
{ path: '/foo/:slug', component: '@/pages/foo/$slug.tsx' },
{ path: '/:bar/*', component: '@/pages/$bar/$.tsx' },
]pages/404.tsx
In convention-based routing, this file will automatically be registered as the global fallback page for 404. If you are using configuration-based routing, you need to manually configure the catchall route at the end of the route table:
routes: [
// other routes ...
{ path: '/*', component: '@/pages/404.tsx' }
]global.(j|t)sx?
Global pre-script file.
Unlike other front-end frameworks, Umi does not have an explicit main application entry (like src/index.ts). So when you have logic that needs to run globally or at the application front-end, consider including it in global.ts.
When you need to add a global Context or modify application runtime, please use app.tsx.
global.(css|less|sass|scss)
Global style file.
When you need to use styles globally, consider adding them to this file.
:::info{title=💡}
Note that the priority of this file is after the styles of third-party component libraries. So, when you need to override the styles of a third-party library, please use overrides.css.
:::
overrides.(css|less|sass|scss)
High-priority global style file.
This file is generally dedicated for overriding third-party library styles, where all CSS selectors will be prefixed with body to lift the priority.
loading.(tsx|jsx)
Global loading component.
Umi 4 defaults to code splitting by page, so there is a loading process during page switching. Configure loading animations through this file.
plugin.ts
Project-level Umi plugin.
When you have Umi customization needs, you often use plugin API (such as modifying product html). In this case, you can create this file for customization:
import type { IApi } from 'umi';
export default (api: IApi) => {
api.onDevCompileDone((opts) => {
opts;
// console.log('> onDevCompileDone', opts.isFirstCompile);
});
api.modifyHTML(($) => {
$;
});
api.chainWebpack((memo) => {
memo;
});
};favicon
Site favicon icon file.
When a src/favicon.(ico|gif|png|jpg|jpeg|svg|avif|webp) file exists, it will automatically add the site favicon to the output:
<link rel="shortcut icon" href="/favicon.png">If using external resources, etc., you can manually configure the site icon with favicons. Configuration value takes precedence over convention.
Docs Docs Guides Directory Structure (`docs/docs/docs/guides/directory-structure.md`)
order: 2 toc: content
目录结构
这里罗列了 Umi 项目中约定(或推荐)的目录结构,在项目开发中,请遵照这个目录结构组织代码。
.
├── config
│ └── config.ts
├── dist
├── mock
│ └── app.ts|tsx
├── public
├── src
│ ├── .umi
│ ├── .umi-production
│ ├── layouts
│ │ ├── BasicLayout.tsx
│ │ ├── index.less
│ ├── models
│ │ ├── global.ts
│ │ └── index.ts
│ ├── pages
│ │ ├── index.less
│ │ └── index.tsx
│ ├── utils // 推荐目录
│ │ └── index.ts
│ ├── services // 推荐目录
│ │ └── api.ts
│ ├── app.(ts|tsx)
│ ├── global.ts
│ ├── global.(css|less|sass|scss)
│ ├── overrides.(css|less|sass|scss)
│ ├── favicon.(ico|gif|png|jpg|jpeg|svg|avif|webp)
│ └── loading.(tsx|jsx)
├── node_modules
│ └── .cache
│ ├── bundler-webpack
│ ├── mfsu
│ └── mfsu-deps
├── .env
├── plugin.ts
├── .umirc.ts // 与 config/config 文件 2 选一
├── package.json
├── tsconfig.json
└── typings.d.ts根目录
package.json
与 Umi 3 不同,Umi 4 不会自动注册 package.json 中以 @umijs/preset-、@umijs/plugin-、umi-preset- 和 umi-plugin- 开头的插件、预设,若你需要自定义额外的插件、预设,需要手动配置到 plugins 。
.env
环境变量,比如:
PORT=8888
COMPRESS=none.umirc.ts
与
config/config.ts文件功能相同,2 选 1 。.umirc.ts文件优先级较高
配置文件,包含 Umi 所有非运行时配置(运行时配置一般定义于 app.ts)。
若你需要在不同环境中加载不同配置,这在 Umi 中是根据 UMI_ENV 来实现的,一个不同环境启动的例子:
// package.json
{
"scripts": {
"dev": "umi dev",
"dev:pre": "cross-env UMI_ENV=pre umi dev"
}
}config/config.ts
与
.umirc.ts文件功能相同,2 选 1 。.umirc.ts文件优先级较高
与 .umirc.ts 相同,区别是你可以单独在一个 config 文件夹下集中管理所有的配置,保持项目根目录整洁。
dist 目录
执行 umi build 后产物的默认输出文件夹。可通过 outputPath 配置修改产物输出文件夹。
mock 目录
存放 mock 文件,此目录下所有 .ts / .js 文件会被 mock 服务加载,从而提供模拟数据,使用方法详见 Mock 。
public 目录
存放固定的静态资源,如存放 public/image.png ,则开发时可以通过 /image.png 访问到,构建后会被拷贝到输出文件夹。
注:
- 对于 svg 资源,Umi 支持 svgr ,可以直接导入作为组件使用:
import SmileUrl, { ReactComponent as SvgSmile } from './smile.svg';
// <SvgSmile />- 对于图片等资源,Umi 支持直接导入获取资源路径:
import imgUrl from './image.png'
// >`src` 目录
.umi 目录
:::warning{title=🛎️}
不要提交 .umi 临时文件到 git 仓库,默认已在 .gitignore 被忽略。
:::
dev 时的临时文件目录,比如入口文件、路由等,都会被临时生成到这里。
.umi-production 目录
:::warning{title=🛎️}
不要提交 .umi-production 临时文件到 git 仓库,默认已在 .gitignore 被忽略。
:::
build 时的临时文件目录,比如入口文件、路由等,都会被临时生成到这里。
app.[ts|tsx]
运行时配置 文件,可以在这里扩展运行时的能力,比如修改路由、修改 render 方法等。
运行时配置带来的逻辑会在浏览器中运行,因此当有远程配置、动态内容时,这些我们在本地开发时还不确定,不能写死,所以需要在浏览器实际运行项目时动态获取他们。
layouts/index.tsx
全局布局,默认会在所有路由下生效,比如有以下路由关系:
[
{ path: '/', component: '@/pages/index' },
{ path: '/users', component: '@/pages/users' },
]输出为:
<Layout>
index
users
</Layout>当你需要关闭 layout 时可以使用 layout: false ,当你需要更多层 layout 时,可以考虑使用 wrappers ,仅在配置式路由可用:
routes: [
{ path: '/', component: './index', layout: false },
{
path: '/users',
component: './users',
wrappers: ['@/wrappers/auth']
}
]pages 目录
约定式路由默认以 pages/* 文件夹的文件层级结构来生成路由表。
在配置式路由中,component 若写为相对路径,将从该文件夹为起点开始寻找文件:
routes: [
// `./index` === `@/pages/index`
{ path: '/', component: './index' }
]基础路由
假设 pages 目录结构如下:
+ pages/
+ users/
- index.tsx
- index.tsx那么,会自动生成路由配置如下:
[
{ path: '/', component: '@/pages/index.tsx' },
{ path: '/users/', component: '@/pages/users/index.tsx' },
]动态路由
约定带 $ 前缀的目录或文件为动态路由。若 $ 后不指定参数名,则代表 * 通配,比如以下目录结构:
+ pages/
+ foo/
- $slug.tsx
+ $bar/
- $.tsx
- index.tsx会生成路由配置如下:
[
{ path: '/', component: '@/pages/index.tsx' },
{ path: '/foo/:slug', component: '@/pages/foo/$slug.tsx' },
{ path: '/:bar/*', component: '@/pages/$bar/$.tsx' },
]pages/404.tsx
在使用约定式路由时,该文件会自动被注册为全局 404 的 fallback 页面。若你使用配置式路由,需要自行配置兜底路由到路由表最后一个:
routes: [
// other routes ...
{ path: '/*', component: '@/pages/404.tsx' }
]global.(j|t)sx?
全局前置脚本文件。
Umi 区别于其他前端框架,没有显式的程序主入口(如 src/index.ts),所以当你有需要在应用前置、全局运行的逻辑时,优先考虑写入 global.ts 。
当你需要添加全局 Context 、修改应用运行时,请使用 app.tsx 。
global.(css|less|sass|scss)
全局样式文件。
当你有需要全局使用的样式时,请考虑加入此文件。
:::info{title=💡}
需要注意的是,此文件的优先级在第三方组件库的样式之后,所以当你有覆盖第三方库样式的需求时,请使用 overrides.css 。
:::
overrides.(css|less|sass|scss)
高优先级全局样式文件。
该文件一般专用于覆盖第三方库样式,其中所有 CSS 选择器都会附加 body 前缀以抬高优先级。
loading.(tsx|jsx)
全局加载组件。
Umi 4 默认 按页分包 ,从而在页面切换时存在加载过程,通过该文件来配置加载动画。
plugin.ts
项目级 Umi 插件。
当你有 Umi 定制需求时,往往会用到 插件 API (比如 修改产物 html),此时可创建该文件进行自定义:
import type { IApi } from 'umi';
export default (api: IApi) => {
api.onDevCompileDone((opts) => {
opts;
// console.log('> onDevCompileDone', opts.isFirstCompile);
});
api.modifyHTML(($) => {
$;
});
api.chainWebpack((memo) => {
memo;
});
};favicon
站点 favicon 图标文件。
当存在 src/favicon.(ico|gif|png|jpg|jpeg|svg|avif|webp) 文件时,将会自动在产物中添加站点 favicon :
<link rel="shortcut icon" href="/favicon.png">若使用外部资源等,可以使用 favicons 手动配置站点图标,配置值优先于约定。
Docs Docs Guides Env Variables.En US (`docs/docs/docs/guides/env-variables.en-US.md`)
order: 10 toc: content translated_at: '2024-03-17T10:32:30.023Z'
Environment Variables
Umi can use environment variables for some special configuration and functions.
How to Set Environment Variables
Set when Executing Commands
For example, if you need to change the port of the umi dev development server, you can use the following command.
# OS X, Linux
$ PORT=3000 umi dev
# Windows (cmd.exe)
$ set PORT=3000&&umi devIf you need to use environment variables on different operating systems, it is recommended to use the tool cross-env.
$ pnpm install cross-env -D
$ cross-env PORT=3000 umi devSet in .env File
If your environment variables need to be shared among developers, it is recommended to set them in the .env file in your project root directory, for example:
# file .env
PORT=3000
BABEL_CACHE=noneThen execute,
$ umi devumi will start the dev server on port 3000 and disable babel's cache.
If you have some environment variables that need special configuration locally, you can configure them in the .env.local file to override the configuration in .env. For example, on top of the previous .env configuration, if you want to override the port 3000 with port 4000 for local development, you can define it as follows.
# file .env.local
PORT=4000umi will start the dev server on port 4000 while keeping babel's cache disabled.
Furthermore, the umi .env file also supports the way of configuring environment variables through variables. For example:
# file .env.local
FOO=foo
BAR=bar
CONCAT=$FOO$BAR # CONCAT=foobarUse .env.development / .env.production to configure different environment variables for development and build respectively.
Note:
- It is not recommended to add
.env.localto version control.
Use Environment Variables in Browser
All the environment variables set through .env files or command line injection are by default only effective within the Umi configuration file (Node.js environment) and cannot directly use process.env.VAR_NAME in the browser. By further configuring define, they can be injected into the browser environment:
# .env
MY_TOKEN="xxxxx"// .umirc.ts
define: { 'process.env.MY_TOKEN': process.env.MY_TOKEN }Note: We agree that all environment variables starting with UMI_APP_ will be automatically injected into the browser, no need to configure define manually.
List of Environment Variables
Listed in alphabetical order.
APP_ROOT
Specify the project root directory.
Note:
APP_ROOTcannot be set in.env, it can only be added in the command line
ANALYZE
Used to analyze the bundle composition, turned off by default.
For example:
$ ANALYZE=1 umi dev
# or
$ ANALYZE=1 umi buildYou can customize the port with the ANALYZE_PORT environment variable or customize the configuration with the analyze option.
BABEL_POLYFILL
By default, it applies all the patches for the targeted browsers based on the targets configuration, setting to none disables the built-in patch scheme.
COMPRESS
CSS and JS are compressed by default, when the value is none, no compression is applied, valid during build.
DID_YOU_KNOW
Setting to none will disable the "Did You Know" tips.
ERROR_OVERLAY
Setting to none will disable the "Error Overlay", useful when debugging Error Boundary.
FS_LOGGER
By default, physical logging is enabled, setting to none disables it, and it is temporarily not saved for webcontainer scenarios (e.g., stackbliz).
HMR
HMR feature is enabled by default, setting to none turns it off.
HOST
The default is 0.0.0.0.
PORT
Specifies the port number, the default is 8000.
SOCKET_SERVER
Specifies the socket server for HMR. For example:
$ SOCKET_SERVER=http://localhost:8000/ umi devSTRICT_PORT
If set, when the port is occupied, it will prompt the user to use another port and exit the process.
$ STRICT_PORT=8000 umi devSPEED_MEASURE
Analyzes the Webpack compile time, supports CONSOLE and JSON formats, the default is CONSOLE.
$ SPEED_MEASURE=JSON umi devUMI_ENV
When UMI_ENV is specified, it will additionally load the configuration file with the specified value, with priority as:
config.tsconfig.${UMI_ENV}.tsconfig.${dev | prod | test}.tsconfig.${dev | prod | test}.${UMI_ENV}.tsconfig.local.ts
If UMI_ENV is not specified, only the configuration file corresponding to the current environment will be loaded, the more specific down the list, the higher the priority, higher priority configurations can be moved down.
Note: Depending on the current environment, dev, prod, test configuration files will be automatically loaded, do not set the value of UMI_ENV to these.
UMI_PLUGINS
Specifies the path of additional plugins to load when the umi command is executed, separated by ,.
$ UMI_PLUGINS=./path/to/plugin1,./path/to/plugin2 umi devUMI_PRESETS
Specifies the path of additional plugin sets to load when the umi command is executed, separated by ,.
$ UMI_PRESETS=./path/to/preset1,./path/to/preset2 umi devUMI_DEV_SERVER_COMPRESS
By default, the Umi development server comes with the compress compression middleware, which prevents streaming access to SSE data during development. You can close the compress compression feature by specifying UMI_DEV_SERVER_COMPRESS=none:
UMI_DEV_SERVER_COMPRESS=none umi devWEBPACK_FS_CACHE_DEBUG
Turn on webpack's physical cache debug log.
$ WEBPACK_FS_CACHE_DEBUG=1 umi devDocs Docs Guides Env Variables (`docs/docs/docs/guides/env-variables.md`)
order: 10 toc: content
环境变量
Umi 可以通过环境变量来完成一些特殊的配置和功能。
如何设置环境变量
执行命令时设置
例如需要改变 umi dev 开发服务器的端口,可以通过如下命令实现。
# 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设置在 .env 文件中
如果你的环境变量需要在开发者之间共享,推荐你设置在项目根目录的 .env 文件中,例如:
# file .env
PORT=3000
BABEL_CACHE=none然后执行,
$ umi devumi 会以 3000 端口启动 dev server,并且禁用 babel 的缓存。
如果你有部分环境变量的配置在本地要做特殊配置,可以配置在 .env.local 文件中去覆盖 .env 的配置。比如在之前的 .env 的基础上, 你想本地开发覆盖之前 3000 端口, 而使用 4000 端口,可以做如下定义。
# file .env.local
PORT=4000umi 会以 4000 端口启动 dev server,同时保持禁用 babel 的缓存。
此外 umi .env 文件中还支持变量的方式来配置环境变量。例如:
# file .env.local
FOO=foo
BAR=bar
CONCAT=$FOO$BAR # CONCAT=foobarUmi 不支持 .env.development / .env.production 的环境变量配置文件,如需在不同的环境下有不同的变量值,请使用 cross-env 在不同的启动命令上区分,或定义在各个 UMI_ENV 对应的 Umi 配置文件内。
注意:
- 不建议将
.env.local加入版本管理中。
在浏览器中使用环境变量
所有通过 .env 环境变量文件 或 命令行注入 的环境变量均默认只在 Umi 配置文件 (Node.js 环境) 内生效,在浏览器中无法直接通过 process.env.VAR_NAME 方式使用,通过进一步配置 define 来注入到浏览器环境中:
# .env
MY_TOKEN="xxxxx"// .umirc.ts
define: { 'process.env.MY_TOKEN': process.env.MY_TOKEN }注:我们约定所有以 UMI_APP_ 开头的环境变量会默认注入到浏览器中,无需配置 define 手动注入。
环境变量列表
按字母顺序排列。
APP_ROOT
指定项目根目录。
注意:
APP_ROOT不能配在.env中,只能在命令行里添加
ANALYZE
用于分析 bundle 构成,默认关闭。
比如:
$ ANALYZE=1 umi dev
# 或者
$ ANALYZE=1 umi build可以通过 ANALYZE_PORT 环境变量自定义端口或 analyze 选项自定义配置。
BABEL_POLYFILL
默认会根据 targets 配置打目标浏览器的全量补丁,设置为 none 禁用内置的补丁方案。
COMPRESS
默认压缩 CSS 和 JS,值为 none 时不压缩,build 时有效。
DID_YOU_KNOW
设置为 none 会禁用「你知道吗」提示。
ERROR_OVERLAY
设置为 none 会禁用「Error Overlay」,在调试 Error Boundary 时会有用。
FS_LOGGER
默认会开启保存物理日志,值为 none 时不保存,同时针对 webcontainer 场景(比如 stackbliz)暂不保存。
HMR
默认开启 HMR 功能,值为 none 时关闭。
HOST
默认是 0.0.0.0。
PORT
指定端口号,默认是 8000。
SOCKET_SERVER
指定用于 HMR 的 socket 服务器。比如:
$ SOCKET_SERVER=http://localhost:8000/ umi devSTRICT_PORT
如果设置,当端口被占用时,会提示用户使用其他端口,并退出进程。
$ STRICT_PORT=8000 umi devSPEED_MEASURE
分析 Webpack 编译时间,支持 CONSOLE 和 JSON 两种格式,默认是 CONSOLE。
$ SPEED_MEASURE=JSON umi devUMI_ENV
当指定 UMI_ENV 时,会额外加载指定值的配置文件,优先级为:
config.tsconfig.${UMI_ENV}.tsconfig.${dev | prod | test}.tsconfig.${dev | prod | test}.${UMI_ENV}.tsconfig.local.ts
若不指定 UMI_ENV ,则只会加载当前环境对应的配置文件,越向下的越具体,优先级更高,高优的配置可以往下移动。
注:根据当前环境的不同,dev, prod, test 配置文件会自动加载,不能将 UMI_ENV 的值设定成他们。
UMI_PLUGINS
指定 umi 命令执行时额外加载的插件的路径,使用 , 隔开。
$ UMI_PLUGINS=./path/to/plugin1,./path/to/plugin2 umi devUMI_PRESETS
指定 umi 命令执行时额外加载插件集的路径,使用 , 隔开。
$ UMI_PRESETS=./path/to/preset1,./path/to/preset2 umi devUMI_DEV_SERVER_COMPRESS
默认 Umi 开发服务器自带 compress 压缩中间件,这会使开发时 SSE 数据的传输 无法流式获取 ,通过指定 UMI_DEV_SERVER_COMPRESS=none 来关闭 compress 压缩功能:
UMI_DEV_SERVER_COMPRESS=none umi devWEBPACK_FS_CACHE_DEBUG
开启 webpack 的物理缓存 debug 日志。
$ WEBPACK_FS_CACHE_DEBUG=1 umi devDocs Docs Guides Generator.En US (`docs/docs/docs/guides/generator.en-US.md`)
order: 12 toc: content translated_at: '2024-03-17T10:31:39.880Z'
Micro Generators
Umi comes with numerous micro generators to assist you in quickly completing some tedious work during development.
How to Use
The command below will list all currently available generators, which you can select through an interactive way, with detailed prompts provided.
$ umi generate
# Or
$ umi gYou can also use a specific generator by umi g <generatorName>.
Generator List
Page Generator
Quickly generate a new page, with multiple usage methods available.
Basic Usage
Interactive input of page name and file generation method:
$umi g page
? What is the name of page? › mypage
? How dou you want page files to be created? › - Use arrow-keys. Return to submit.
❯ mypage/index.{tsx,less}
mypage.{tsx,less}Direct generation:
$umi g page foo
Write: src/pages/foo.tsx
Write: src/pages/foo.lessGenerate a page in directory mode, with the directory containing the page's component and style files:
$umi g page bar --dir
Write: src/pages/bar/index.less
Write: src/pages/bar/index.tsxNested generation of pages:
$umi g page far/far/away/kingdom
Write: src/pages/far/far/away/kingdom.tsx
Write: src/pages/far/far/away/kingdom.lessBatch generation of multiple pages:
$umi g page page1 page2 a/nested/page3
Write: src/pages/page1.tsx
Write: src/pages/page1.less
Write: src/pages/page2.tsx
Write: src/pages/page2.less
Write: src/pages/a/nested/page3.tsx
Write: src/pages/a/nested/page3.lessCustomizing Page Template Content
If the page generator's default template does not meet your needs, you can customize the template content.
Execute --eject command:
$umi g page --ejectAfter the command is executed, the page generator will write its original template into the project's /templates/page directory:
.
├── package.json
└── templates
└── page
├── index.less.tpl
└── index.tsx.tplUsing Template Variables
Both template files support template syntax, you can insert variables like this:
import React from 'react';
import './{{{name}}}.less'
const message = '{{{msg}}}'
const count = {{{count}}}Customize argument values:
$umi g page foo --msg "Hello World" --count 10After running the command, the generated page content is as follows:
import React from 'react';
import './foo.less'
const message = 'Hello World'
const count = 10If you do not need to use template variables, you can omit the .tpl suffix, shorten index.tsx.tpl to index.tsx, and index.less.tpl to index.less.
Preset Variables
In the content generated in the previous section, we did not specify name, but it was still set to a value. This is because it belongs to the template's preset variables, below are all the preset variables currently available in the page template:
| Parameter | Default Value | Description |
|---|---|---|
name |
- | The name of the current file. If you execute pnpm umi g page foo, it will generate pages/foo.tsx and pages/foo.less files, where the value of name is "foo". |
color |
- | Generates a random RGB color. |
cssExt |
less |
The file extension of the style sheet. |
To learn more about the template syntax, please refer to mustache.
`dir` Mode
Without using dir mode, if your page template folder only customizes one template file, missing files will automatically select the default template file.
If you use dir mode, its generated content will be consistent with your page custom template folder, and will only use the default template if your page custom template folder is empty. If your page custom template folder content is as follows:
.
├── a.tsx
└── index.tsx.tplThe generated directory will be:
.
├── a.tsx
└── index.tsxFallback
If you still want to continue using the default template, you can specify --fallback, and the user-defined template will no longer be used:
$umi g page foo --fallbackComponent Generator
Generate needed components in the src/components/ directory. Like the page generator, the component generator also has multiple generation methods.
Basic Usage
Interactive generation:
$umi g component
✔ Please input you component Name … foo
Write: src/components/Foo/index.ts
Write: src/components/Foo/component.tsxDirect generation:
$umi g component bar
Write: src/components/Bar/index.ts
Write: src/components/Bar/component.tsxNested generation:
$umi g component group/subgroup/baz
Write: src/components/group/subgroup/Baz/index.ts
Write: src/components/group/subgroup/Baz/component.tsxBatch generation:
$umi g component apple banana orange
Write: src/components/Apple/index.ts
Write: src/components/Apple/component.tsx
Write: src/components/Banana/index.ts
Write: src/components/Banana/component.tsx
Write: src/components/Orange/index.ts
Write: src/components/Orange/component.tsxCustomizing Component Template Content
Similar to the Page Generator, the component generator also supports customizing the template content. First, write the original template into the project's /templates/component directory:
$umi g component --ejectUsing Template Variables
$umi g component foo --msg "Hello World"Custom component templates can omit the .tpl suffix. You can shorten index.ts.tpl to index.ts, and component.tsx.tpl to component.tsx.
The component generator will generate content consistent with your custom template folder, and you can add more custom template files as needed.
Preset Variables
| Parameter | Default Value | Description |
|---|---|---|
compName |
- | The name of the current component. If you execute pnpm umi g component foo, the value of compName is Foo. |
Fallback
$umi g component foo --fallbackRouteAPI Generator
Generate template files for the routeAPI feature.
Interactive generation:
$umi g api
✔ please input your api name: … starwar/people
Write: api/starwar/people.tsDirect generation:
$umi g api films
Write: api/films.tsNested generator:
$umi g api planets/[id]
Write: api/planets/[id].tsBatch generation:
$umi g api spaceships vehicles species
Write: api/spaceships.ts
Write: api/vehicles.ts
Write: api/species.tsMock Generator
Generate template files for the Mock feature, refer to the documentation for the specific implementation of mock.
Interactive generation:
$umi g mock
✔ please input your mock file name … auth
Write: mock/auth.tsDirect generation:
$umi g mock acl
Write: mock/acl.tsNested generation:
$umi g mock users/profile
Write: mock/users/profile.tsPrettier Configuration Generator
Generate prettier configuration for the project, after executing the command, umi will generate recommended prettier configuration and install related dependencies.
$umi g prettier
info - Write package.json
info - Write .prettierrc
info - Write .prettierignoreJest Configuration Generator
Generate jest configuration for the project, after executing the command, umi will generate Jest configuration and install related dependencies. Choose whether to use @testing-library/react for UI testing as needed.
$umi g jest
✔ Will you use @testing-library/react for UI testing?! … yes
info - Write package.json
info - Write jest.config.tsTailwind CSS Configuration Generator
Enable Tailwind CSS configuration for the project, after executing the command, umi will generate Tailwind CSS and install related dependencies.
$umi g tailwindcss
info - Write package.json
set config:tailwindcss on /Users/umi/playground/.umirc.ts
set config:plugins on /Users/umi/playground/.umirc.ts
info - Update .umirc.ts
info - Write tailwind.config.js
info - Write tailwind.cssDvaJS Configuration Generator
Enable Dva configuration for the project, after executing the command, umi will generate Dva.
$umi g dva
set config:dva on /Users/umi/umi-playground/.umirc.ts
set config:plugins on /Users/umi/umi-playground/.umirc.ts
info - Update config file
info - Write example modelPrecommit Configuration Generator
Generate precommit configuration for the project, after executing the command, umi will add husky and Git commit message format validation behavior, default formatting the Git staging area code before each Git commit.
Note: If it is an initialized
@umijs/maxproject, usually this generator is not needed, as husky is already configured.
$umi g precommit
info - Update package.json for devDependencies
info - Update package.json for scripts
info - Write .lintstagedrc
info - Create .husky
info - Write commit-msg
info - Write pre-commitDocs Docs Guides Generator (`docs/docs/docs/guides/generator.md`)
order: 12 toc: content
微生成器
Umi 中内置了众多微生成器,协助你在开发中快速地完成一些繁琐的工作。
如何使用
下面的命令会列出目前所有可用的生成器,可以通过交互式方式来选择你使用的功能,都有详细的提示。
$ umi generate
# 或者
$ umi g你也可以通过 umi g <generatorName> 的形式来使用对应的生成器。
生成器列表
页面生成器
快速生成一个新页面,有以下多种使用方式。
基本使用
交互式输入页面名称和文件生成方式:
$umi g page
? What is the name of page? › mypage
? How dou you want page files to be created? › - Use arrow-keys. Return to submit.
❯ mypage/index.{tsx,less}
mypage.{tsx,less}直接生成:
$umi g page foo
Write: src/pages/foo.tsx
Write: src/pages/foo.less以目录方式生成页面,目录下为页面的组件和样式文件:
$umi g page bar --dir
Write: src/pages/bar/index.less
Write: src/pages/bar/index.tsx嵌套生成页面:
$umi g page far/far/away/kingdom
Write: src/pages/far/far/away/kingdom.tsx
Write: src/pages/far/far/away/kingdom.less批量生成多个页面:
$umi g page page1 page2 a/nested/page3
Write: src/pages/page1.tsx
Write: src/pages/page1.less
Write: src/pages/page2.tsx
Write: src/pages/page2.less
Write: src/pages/a/nested/page3.tsx
Write: src/pages/a/nested/page3.less对页面模板内容进行自定义
如果页面生成器使用的默认模板不符合你的需求,你可以对模板内容进行自定义设置。
执行 --eject 命令:
$umi g page --eject执行命令后,页面生成器会把它的原始模板写入到项目的 /templates/page 目录:
.
├── package.json
└── templates
└── page
├── index.less.tpl
└── index.tsx.tpl使用模板变量
两个模板文件都支持模板语法,你可以像下面这样插入变量:
import React from 'react';
import './{{{name}}}.less'
const message = '{{{msg}}}'
const count = {{{count}}}可以自定义参数值:
$umi g page foo --msg "Hello World" --count 10运行命令后,生成的页面内容如下:
import React from 'react';
import './foo.less'
const message = 'Hello World'
const count = 10如果你不需要使用模板变量,可以省略 .tpl 后缀名,将 index.tsx.tpl 简写为 index.tsx,index.less.tpl 简写为 index.less。
预设变量
在上一小节生成的内容中,我们并没有指定 name,但它还是被设置为了一个值。这是因为它属于模板中预设的变量,下面是目前页面模板所有的预设变量:
| 参数 | 默认值 | 说明 |
|---|---|---|
name |
- | 当前文件的名称。如果执行 pnpm umi g page foo,会生成 pages/foo.tsx 和 pages/foo.less 两个文件,其中 name 的值为 "foo"。 |
color |
- | 随机生成一个 RGB 颜色。 |
cssExt |
less |
样式文件的后缀名。 |
如果想了解更多模板语法的内容,请查看 mustache。
`dir` 模式
在不使用 dir 模式的情况下,如果你的页面模板文件夹只自定义了一个模板文件,缺失的文件会自动选取默认的模板文件。
如果使用 dir 模式,它的生成内容会和你的页面自定义模板文件夹保持一致,只有在页面自定义模板文件夹为空时才使用默认模板。如果你的页面自定义模板文件夹内容如下:
.
├── a.tsx
└── index.tsx.tpl生成的目录将是:
.
├── a.tsx
└── index.tsx回退
如果还想继续使用默认的模板,可以指定 --fallback,此时不再使用用户自定义的模板:
$umi g page foo --fallback组件生成器
在 src/components/ 目录下生成项目需要的组件。和页面生成器一样,组件生成器也有多种生成方式。
基本使用
交互式生成:
$umi g component
✔ Please input you component Name … foo
Write: src/components/Foo/index.ts
Write: src/components/Foo/component.tsx直接生成:
$umi g component bar
Write: src/components/Bar/index.ts
Write: src/components/Bar/component.tsx嵌套生成:
$umi g component group/subgroup/baz
Write: src/components/group/subgroup/Baz/index.ts
Write: src/components/group/subgroup/Baz/component.tsx批量生成:
$umi g component apple banana orange
Write: src/components/Apple/index.ts
Write: src/components/Apple/component.tsx
Write: src/components/Banana/index.ts
Write: src/components/Banana/component.tsx
Write: src/components/Orange/index.ts
Write: src/components/Orange/component.tsx对组件模板内容进行自定义
与页面生成器相同,组件生成器也支持对模板内容自定义。首先,将原始模板写入到项目的 /templates/component 目录:
$umi g component --eject使用模板变量
$umi g component foo --msg "Hello World"自定义组件模板可以省略 .tpl 后缀名。你可以将 index.ts.tpl 简写为 index.ts,component.tsx.tpl 简写为 component.tsx。
组件生成器将生成与你的自定义模板文件夹相一致的内容,你可以根据需要添加更多的自定义模板文件。
预设变量
| 参数 | 默认值 | 说明 |
|---|---|---|
compName |
- | 当前组件的名称。如果执行 pnpm umi g component foo, compName 的值为 Foo。 |
回退
$umi g component foo --fallbackRouteAPI 生成器
生成 routeAPI 功能的模板文件。
交互式生成:
$umi g api
✔ please input your api name: … starwar/people
Write: api/starwar/people.ts直接生成:
$umi g api films
Write: api/films.ts嵌套生成器:
$umi g api planets/[id]
Write: api/planets/[id].ts批量生成:
$umi g api spaceships vehicles species
Write: api/spaceships.ts
Write: api/vehicles.ts
Write: api/species.tsMock 生成器
生成 Mock 功能的模板文件,mock 的具体实现参考文档。
交互式生成:
$umi g mock
✔ please input your mock file name … auth
Write: mock/auth.ts直接生成:
$umi g mock acl
Write: mock/acl.ts嵌套生成:
$umi g mock users/profile
Write: mock/users/profile.tsPrettier 配置生成器
为项目生成 prettier 配置,命令执行后,umi 会生成推荐的 prettier 配置和安装相应的依赖。
$umi g prettier
info - Write package.json
info - Write .prettierrc
info - Write .prettierignoreJest 配置生成器
为项目生成 jest 配置,命令执行后,umi 会生成 Jest 配置和安装相应的依赖。根据需要选择是否要使用 @testing-library/react 做 UI 测试。
$umi g jest
✔ Will you use @testing-library/react for UI testing?! … yes
info - Write package.json
info - Write jest.config.tsTailwind CSS 配置生成器
为项目开启 Tailwind CSS 配置,命令执行后,umi 会生成 Tailwind CSS 和安装相应的依赖。
$umi g tailwindcss
info - Write package.json
set config:tailwindcss on /Users/umi/playground/.umirc.ts
set config:plugins on /Users/umi/playground/.umirc.ts
info - Update .umirc.ts
info - Write tailwind.config.js
info - Write tailwind.cssDvaJS 配置生成器
为项目开启 Dva 配置,命令执行后,umi 会生成 Dva 。
$umi g dva
set config:dva on /Users/umi/umi-playground/.umirc.ts
set config:plugins on /Users/umi/umi-playground/.umirc.ts
info - Update config file
info - Write example modelPrecommit 配置生成器
为项目生成 precommit 配置,命令执行后,umi 会为我们添加 husky 和 Git commit message 格式校验行为,在每次 Git commit 前会将 Git 暂存区的代码默认格式化。
注意:如果是初始化出来的
@umijs/max项目,通常不需要该生成器,因为已经配置好 husky 了
$umi g precommit
info - Update package.json for devDependencies
info - Update package.json for scripts
info - Write .lintstagedrc
info - Create .husky
info - Write commit-msg
info - Write pre-commitDocs Docs Guides Getting Started.En US (`docs/docs/docs/guides/getting-started.en-US.md`)
order: -1 toc: content translated_at: '2024-03-17T10:30:12.385Z'
Getting Started
Environment Setup
First, you need to have node installed and make sure your node version is 18 or above. (It's recommended to use nvm to manage node versions on Mac or Linux, and nvm-windows for Windows users.)
Install nvm on Mac or Linux.
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
$ nvm -v
0.39.1Install node.
$ nvm install 18
$ nvm use 18
$ node -v
v18.10.0Then you need a package management tool. While npm comes with node by default, you can also choose other options,
Install pnpm.
curl -fsSL https://get.pnpm.io/install.sh | sh -
$ pnpm -v
7.3.0Create a Project
Create a project using the official tool,
PNPM
$ pnpm dlx create-umi@latest
✔ Install the following package: create-umi? (Y/n) · true
✔ Pick Npm Client › pnpm
✔ Pick Npm Registry › taobao
Write: .gitignore
Write: .npmrc
Write: .umirc.ts
Copy: layouts/index.tsx
Write: package.json
Copy: pages/index.tsx
Copy: pages/users.tsx
Copy: pages/users/foo.tsx
> @ postinstall /private/tmp/sorrycc-vylwuW
> umi setup
info - generate filesBUN
$ bunx create-umi
✔ Pick Umi App Template › Simple App
✔ Pick Npm Client › pnpm
✔ Pick Npm Registry › npm
Write: .gitignore
Write: .npmrc
Write: .umirc.ts
Write: package.json
Copy: src/assets/yay.jpg
Copy: src/layouts/index.less
Write: src/layouts/index.tsx
Copy: src/pages/docs.tsx
Copy: src/pages/index.tsx
Write: tsconfig.json
Copy: typings.d.ts
ready - Git initialized successfullyNPM
$ npx create-umi@latest
Need to install the following packages:
create-umi@latest
Ok to proceed? (y) y
✔ Pick Umi App Template › Simple App
✔ Pick Npm Client › npm
✔ Pick Npm Registry › taobao
Write: .gitignore
Write: .npmrc
Write: .umirc.ts
Write: package.json
Copy: src/assets/yay.jpg
Copy: src/layouts/index.less
Write: src/layouts/index.tsx
Copy: src/pages/docs.tsx
Copy: src/pages/index.tsx
Write: tsconfig.json
Copy: typings.d.ts
> postinstall
> umi setupYARN
$ yarn create umi
success Installed "[email protected]" with binaries:
- create-umi
✔ Pick Umi App Template › Simple App
✔ Pick Npm Client › yarn
✔ Pick Npm Registry › taobao
Write: .gitignore
Write: .npmrc
Write: .umirc.ts
Write: package.json
Copy: src/assets/yay.jpg
Copy: src/layouts/index.less
Write: src/layouts/index.tsx
Copy: src/pages/docs.tsx
Copy: src/pages/index.tsx
Write: tsconfig.json
Copy: typings.d.ts
yarn install v1.22.18
success Saved lockfile.
$ umi setup
info - generate filesNote: Initializing a project with bun is faster, requires bun >= 0.4.0 version.
For users in China, it's advised to choose pnpm + taobao source for a significant speed increase. This step will automatically install dependencies, and after installation, umi setup will execute to perform some file preprocessing, etc.
Creating a Project From a Template
# Create an electron template project from @umijs/electron-template
pnpm create umi --template electronCommand Options
The following options are available when creating a project with create-umi:
| option | description |
|---|---|
--no-git |
Create a project without initializing Git |
--no-install |
Create a project without automatically installing dependencies |
Start the Project
Run the pnpm dev command,
$ pnpm dev
╔═════════════════════════════════════════════════════╗
║ App listening at: ║
║ > Local: https://127.0.0.1:8000 ║
ready - ║ > Network: https://192.168.1.1:8000 ║
║ ║
║ Now you can open a browser with the above addresses👆 ║
╚═════════════════════════════════════════════════════╝
event - compiled successfully in 1121 ms (388 modules)
event - MFSU compiled successfully in 1308 ms (875 modules)Open http://localhost:8000/ in your browser, and you will see the following interface,
Enable Prettier (Optional)
If you want to use prettier to automatically format your project's code, execute pnpm umi g,
$ pnpm umi g
✔ Pick generator type › Enable Prettier -- Enable Prettier
info - Write package.json
info - Write .prettierrc
info - Write .prettierignore
info - Install dependencies with pnpmDeploy and Publish
Run the pnpm build command,
> umi build
event - compiled successfully in 1179 ms (567 modules)
event - build index.htmlThe build artifacts will by default be generated in the ./dist directory,
./dist
├── index.html
├── umi.css
└── umi.jsAfter building, you can deploy the dist directory to your server.
Docs Docs Guides Lint.En US (`docs/docs/docs/guides/lint.en-US.md`)
order: 13 toc: content translated_at: '2024-03-17T10:29:06.038Z'
Coding Standards
We often use ESLint and Stylelint in projects to help us control coding quality. To achieve low-cost, high-performance, and more stable integration of the above tools, Umi provides out-of-the-box Lint capabilities, including the following features:
- Recommended Configurations: Provides ESLint and Stylelint recommended configurations that can be directly inherited and used
- Unified CLI: Provides
umi lintCLI, integrating calls to ESLint and Stylelint - Stable Rules: Always ensures the stability of rules to avoid situations where upstream configuration updates cause lint failures in existing projects
The ESLint configuration has the following characteristics:
- Quality-Related Only: We have selected dozens of quality-related rules from hundreds of rules to whitelist, returning to the essence of Lint, without conflict with Prettier's rules
- Performance Priority: Some TypeScript rules are of low practicability but incur high project-wide compilation costs, so we disable these rules to improve performance
- Built-in Common Plugins: Includes react, react-hooks, @typescript/eslint, jest, meeting daily needs
Additionally, the Stylelint configuration also includes built-in support for CSS-in-JS, allowing for the detection of stylesheet syntax errors in JS files. Sounds attractive? Let's see how to integrate it.
How to Use
Installation
To save on installation size, at present, the Lint module is only built into Umi Max. Use max lint to execute the lint process. If you are using Umi, you need to first install @umijs/lint:
$ npm i @umijs/lint -D
# or
$ pnpm add @umijs/lint -DThen install ESLint and Stylelint:
The current version of
stylelintused by@umijs/lintis v14
$ npm i -D eslint "stylelint@^14"
# or
$ pnpm add -D eslint "stylelint@^14"Enable Configuration
Inherit the configuration provided by Umi in your .eslintrc.js and .stylelintrc.js:
// .eslintrc.js
module.exports = {
// For Umi projects
extends: require.resolve('umi/eslint'),
// For Umi Max projects
extends: require.resolve('@umijs/max/eslint'),
}
// .stylelintrc.js
module.exports = {
// For Umi projects
extends: require.resolve('umi/stylelint'),
// For Umi Max projects
extends: require.resolve('@umijs/max/stylelint'),
}After the configuration files are created, we can already use the eslint and stylelint commands to execute lint, but we still recommend using the umi lint command for a more convenient experience.
CLI
The usage of the umi lint command is as follows:
$ umi lint [glob] [--fix] [--eslint-only] [--stylelint-only] [--cssinjs]Parameters explanation:
[glob]: Optional, specify the files to lint, default is `{src,test}/**/*.{js,jsx,ts,tsx,css,less}`
--quiet: Optional, disable reporting of `warn` rules, only output `error`
--fix: Optional, auto-fix lint errors
--eslint-only: Optional, execute ESLint only
--stylelint-only: Optional, execute Stylelint only
--cssinjs: Optional, enable CSS-in-JS support for StylelintGenerally, directly executing umi lint should meet most needs.
Standalone utoo-lint
Use umi lint --utlint to explicitly select utoo-lint. This entry point runs only utoo-lint. Without --utlint, umi lint continues to run ESLint and Stylelint as before. Umi Max projects can use max lint --utlint.
Install @utoo/lint in your project (Node.js 20 or later is required). This entry point does not require @umijs/lint, ESLint, or Stylelint:
$ pnpm add -D @utoo/lintCreate utlint.config.ts, for example:
import { defineConfig, globalIgnores } from '@utoo/lint/config';
export default defineConfig(
globalIgnores(['**/.umi/**', '**/.umi-production/**', '**/.umi-test/**', 'dist']),
{
files: ['{src,test}/**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}'],
rules: {
'no-debugger': 'error',
'no-constant-condition': 'warn',
},
},
);This is a minimal example; select rules for your project. Existing ESLint projects can generate a config using the migration steps below.
$ umi lint --utlint
$ umi lint --utlint --fix src
$ umi lint --utlint --config utlint.config.json srcArguments other than --utlint are forwarded to utoo-lint, including file paths, native options, and subcommands. With no file arguments, utoo-lint handles config discovery and file selection; the legacy entry point's default glob is not injected. Do not combine --utlint with --eslint-only, --stylelint-only, or --cssinjs. Run style checks separately with umi lint --stylelint-only.
Migrating from ESLint
Keep your existing ESLint config and its dependencies while previewing the conversion:
If your config extends umi/eslint or @umijs/max/eslint, update Umi / Max and @umijs/lint together to versions containing this entry point and its migration config-loading support.
$ umi lint --utlint migrate eslint --from .eslintrc.js --printThen generate a separate config:
$ umi lint --utlint migrate eslint --from .eslintrc.js --output utlint.config.jsonFor flat config projects, change --from to eslint.config.js. This uses the migration tool provided by @utoo/lint. It leaves the original ESLint config intact and does not overwrite an existing output file by default.
Review unsupported rules, rule mappings, and ignored items in the migration report. Verify file scopes, ignored directories, rule options, and lint results. If unsupported rules remain, the tool still generates a config but exits with code 1. Keep the corresponding ESLint checks or explicitly choose replacements before completing the migration. Do not assume plugin rules or rules requiring type information migrate equivalently. See the ESLint migration guide for details.
Once verified, update JavaScript / TypeScript scripts and lint-staged, for example:
{
"scripts": {
"lint:js": "umi lint --utlint",
"lint:style": "umi lint --stylelint-only"
},
"lint-staged": {
"*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}": "umi lint --utlint",
"*.{css,less}": "umi lint --stylelint-only"
}
}Keep Stylelint and Prettier configs and dependencies as needed. Remove ESLint config and dependencies only after confirming no other tools still use them.
Integrating with Git Workflow
We also recommend using lint-staged and Husky, integrating umi lint with the Git workflow to automatically lint the current changes when committing code.
lint-staged
lint-staged is used to drive the umi lint command, checking only the changed content each time.
Installation method:
$ npm i lint-staged -D
#or
$ pnpm add lint-staged -DConfigure lint-staged in package.json:
{
+ "lint-staged": {
+ "*.{js,jsx,ts,tsx,css,less}": [
+ "umi lint"
+ ]
+ }
}Now, if you execute git add sample.js and then npx lint-staged, it will only check the current changes in sample.js.
Husky
Husky is used to bind Git Hooks to execute desired commands at specified timings (e.g., pre-commit). For installation methods, please refer to the Husky documentation: https://typicode.github.io/husky/#/?id=automatic-recommended
After initialization, modify the content of .husky/pre-commit manually:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
- npm test
+ npx lint-stagedNow you're all set. Each time you execute the git commit command, umi lint will automatically check the code changes, ensuring coding quality while also ensuring execution efficiency.
Prettier
On top of enabling umi lint, we also suggest using Prettier together to ensure that the team's coding style is basically consistent.
Refer to the Prettier documentation to configure it with lint-staged: https://prettier.io/docs/en/install.html#git-hooks
Appendix
- ESLint rules built into Umi: https://github.com/umijs/umi/blob/master/packages/lint/src/config/eslint/rules/recommended.ts
- Stylelint configuration built into Umi: https://github.com/umijs/umi/blob/master/packages/lint/src/config/stylelint/index.ts
Docs Docs Guides Lint (`docs/docs/docs/guides/lint.md`)
order: 13 toc: content
编码规范
我们通常会在项目中使用 ESLint、Stylelint 来协助我们把控编码质量,为了实现低成本、高性能、更稳定地接入上述工具,Umi 提供了开箱即用的 Lint 能力,包含以下特性:
- 推荐配置:提供 ESLint 及 Stylelint 推荐配置,可以直接继承使用
- 统一的 CLI:提供
umi lintCLI,集成式调用 ESLint 和 Stylelint - 规则稳定:始终确保规则的稳定性,不会出现上游配置更新导致存量项目 lint 失败的情况
其中,ESLint 配置具备如下特点:
- 仅质量相关:我们从数百条规则中筛选出数十条与编码质量相关的规则进行白名单开启,回归 Lint 本质,且不会与 Prettier 的规则冲突
- 性能优先:部分 TypeScript 的规则实用性低但项目全量编译的成本却很高,我们对这些规则进行禁用以提升性能
- 内置常用插件:包含 react、react-hooks、@typescript/eslint、jest,满足日常所需
另外,Stylelint 配置还内置 CSS-in-JS 支持,可以检测出 JS 文件中的样式表语法错误。听起来很有吸引力?来看看如何接入吧。
使用方式
安装
为了节省安装体积,目前仅在 Umi Max 中内置了 Lint 模块,使用 max lint 来执行 lint 过程。如果你使用的是 Umi,需要先安装 @umijs/lint:
$ npm i @umijs/lint -D
# or
$ pnpm add @umijs/lint -D然后安装 ESLint 及 Stylelint:
目前
@umijs/lint使用的stylelint版本是 v14
$ npm i -D eslint "stylelint@^14"
# or
$ pnpm add -D eslint "stylelint@^14"启用配置
在 .eslintrc.js 及 .stylelintrc.js 里继承 Umi 提供的配置:
// .eslintrc.js
module.exports = {
// Umi 项目
extends: require.resolve('umi/eslint'),
// Umi Max 项目
extends: require.resolve('@umijs/max/eslint'),
}
// .stylelintrc.js
module.exports = {
// Umi 项目
extends: require.resolve('umi/stylelint'),
// Umi Max 项目
extends: require.resolve('@umijs/max/stylelint'),
}在配置文件创建完毕后,我们其实已经可以通过 eslint、stylelint 命令来执行 lint 了,但我们仍然推荐使用 umi lint 命令,以获得更便捷的体验。
CLI
umi lint 命令的用法如下:
$ umi lint [glob] [--fix] [--eslint-only] [--stylelint-only] [--cssinjs]参数说明:
[glob]: 可选,指定要 lint 的文件,默认为 `{src,test}/**/*.{js,jsx,ts,tsx,css,less}`
--quiet: 可选,禁用 `warn` 规则的报告,仅输出 `error`
--fix: 可选,自动修复 lint 错误
--eslint-only: 可选,仅执行 ESLint
--stylelint-only: 可选,仅执行 Stylelint
--cssinjs: 可选,为 Stylelint 启用 CSS-in-JS 支持通常来说,直接执行 umi lint 应该就能满足大部分情况。
独立使用 utoo-lint
通过 umi lint --utlint 显式选择 utoo-lint。这个入口仅运行 utoo-lint;不加 --utlint 时,umi lint 仍按原来的方式运行 ESLint 和 Stylelint。Umi Max 项目使用 max lint --utlint。
在项目中安装 @utoo/lint(需要 Node.js 20 或更高版本)。独立入口不需要安装 @umijs/lint、ESLint 或 Stylelint:
$ pnpm add -D @utoo/lint创建 utlint.config.ts,例如:
import { defineConfig, globalIgnores } from '@utoo/lint/config';
export default defineConfig(
globalIgnores(['**/.umi/**', '**/.umi-production/**', '**/.umi-test/**', 'dist']),
{
files: ['{src,test}/**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}'],
rules: {
'no-debugger': 'error',
'no-constant-condition': 'warn',
},
},
);这是最小配置示例,规则按项目需要选择。已有 ESLint 项目可使用下面的迁移方式生成配置。
$ umi lint --utlint
$ umi lint --utlint --fix src
$ umi lint --utlint --config utlint.config.json src除 --utlint 外的参数直接传给 utoo-lint,包括文件路径、原生选项和子命令。未指定文件时使用 utoo-lint 自身的配置发现和文件选择逻辑,不使用旧入口的默认 glob。不要与 --eslint-only、--stylelint-only 或 --cssinjs 混用;样式检查可单独运行 umi lint --stylelint-only。
从 ESLint 迁移
保留现有 ESLint 配置及其依赖,先预览转换结果:
如果配置继承了 umi/eslint 或 @umijs/max/eslint,请将 Umi / Max 及 @umijs/lint 一并升级到包含此入口的版本,以获得迁移时的配置加载适配。
$ umi lint --utlint migrate eslint --from .eslintrc.js --print再生成独立配置:
$ umi lint --utlint migrate eslint --from .eslintrc.js --output utlint.config.json使用 flat config 的项目可将 --from 改为 eslint.config.js。迁移命令复用 @utoo/lint 自带的转换工具,不会修改原 ESLint 配置,也不会默认覆盖已有的输出文件。
检查迁移报告中的不支持规则、规则映射和忽略项,并核对文件范围、忽略目录、规则选项及检查结果。存在不支持的规则时,工具仍会生成配置,但返回退出码 1;这时迁移尚未完成,应保留相应的 ESLint 检查,或明确选择替代规则。插件及需要类型信息的规则不能假定等价迁移,详见 ESLint 迁移指南。
验证完成后,将 JavaScript / TypeScript 的检查脚本和 lint-staged 切换到新入口,例如:
{
"scripts": {
"lint:js": "umi lint --utlint",
"lint:style": "umi lint --stylelint-only"
},
"lint-staged": {
"*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}": "umi lint --utlint",
"*.{css,less}": "umi lint --stylelint-only"
}
}Stylelint 和 Prettier 按需保留各自配置及依赖。确认没有其他工具使用 ESLint 后,再移除其配置和依赖。
与 Git 工作流结合
我们也推荐使用 lint-staged 和 Husky,将 umi lint 与 Git 工作流结合使用,以便在提交代码时自动 lint 本次变更的代码。
lint-staged
lint-staged 用来驱动 umi lint 命令,每次仅将变更的内容交给 umi lint 进行检查。
安装方式:
$ npm i lint-staged -D
#or
$ pnpm add lint-staged -D在 package.json 中配置 lint-staged:
{
+ "lint-staged": {
+ "*.{js,jsx,ts,tsx,css,less}": [
+ "umi lint"
+ ]
+ }
}此时如果执行 git add sample.js 后,再执行 npx lint-staged,就能实现仅检查 sample.js 本次的变更了。
Husky
Husky 用来绑定 Git Hooks、在指定时机(例如 pre-commit)执行我们想要的命令,安装方式请参考 Husky 文档:https://typicode.github.io/husky/#/?id=automatic-recommended
初始化完成后,需要手动修改 .husky/pre-commit 文件的内容:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
- npm test
+ npx lint-staged至此大功告成,每次执行 git commit 命令的时候,umi lint 就能自动对本次变更的代码进行检查,在确保编码质量的同时也能确保执行效率。
Prettier
在启用 umi lint 的基础上,我们也建议与 Prettier 一同使用,以确保团队的代码风格是基本一致的。
可参考 Prettier 文档将其配置到 lint-staged 中:https://prettier.io/docs/en/install.html#git-hooks
附录
- Umi 内置的 ESLint 规则列表:https://github.com/umijs/umi/blob/master/packages/lint/src/config/eslint/rules/recommended.ts
- Umi 内置的 Stylelint 配置:https://github.com/umijs/umi/blob/master/packages/lint/src/config/stylelint/index.ts
Docs Docs Guides Mfsu.En US (`docs/docs/docs/guides/mfsu.en-US.md`)
order: 19 toc: content translated_at: '2024-03-17T10:28:01.027Z'
MFSU
What is MFSU
MFSU is a package acceleration solution based on the new feature of webpack5, Module Federation. Its core idea is to divide and conquer by separating the compilation of application source code and application dependencies, and building less frequently changed application dependencies into a Module Federation remote application, thus avoiding the compilation of dependencies during application hot updates.
Activating MFSU significantly reduces the time needed for hot updates; therefore, we have enabled the MFSU feature by default in Umi projects. Of course, you can also disable it by configuring mfsu: false.
Two Strategies of MFSU
The key point of MFSU is how to analyze the actual dependencies used in the application code. MFSU has two working modes based on different analysis methods.
Normal Strategy (Compile-time Analysis)
Use the following configuration to enable
mfsu: {
strategy: 'normal',
}In modern front-end projects, code needs to be transpiled before it is used in production. During this process, the transpiler (like babel) will insert new dependencies into the code. These inserted dependencies are invisible at the project code level but are collected through the transpilation plugins.
The compile-time analysis working mode of MFSU first compiles the application project source code separately, collecting the project's own dependencies and the dependencies introduced during compilation. After the compilation of the project code is completed, the collected results are used to continue the construction of the project dependency part of the code.
The entire process is serial, as shown in the example of building React references in the diagram below.
Eager Strategy (Scanning Method)
mfsu: {
strategy: 'eager',
}Different from the compile-time analysis method, the scanning analysis method first reads all source code files in the project, and then obtains the project dependencies through static analysis. This process is very fast, taking only about 700ms for a project with 170,000 lines of code and more than 1400 files. The cost of such fast analysis is that the collected dependencies will miss the dependencies inserted during project code compilation; these dependencies will eventually be compiled and packaged together with the project code.
After analyzing the project dependencies, Umi takes this dependency information and parallelly compiles both the project code and dependencies.
As you can see from the diagram below, the compilation part is parallel.
Two Build Tools
MFSU supports using Webpack or esbuild to build project dependencies. The default configuration uses Webpack, and it is very compatible with the Webpack ecosystem.
Esbuild can be enabled with mfsu: { esbuild: true } to enjoy the high-speed construction of Esbuild.
How to Choose
The advantage of compile-time analysis is that the collected dependencies are complete, and the construction and packaging of project codes and dependency codes are completely separated; after modifying the project code, only the project code part needs to be constructed. The disadvantage is also obvious, the construction process is serial.
The advantage of the scanning method is that the time-consuming code build is all parallel, which greatly improves the cold start time for larger projects. The downside is that some runtime dependencies will be compiled together with the project code.
Based on the analysis of advantages and disadvantages, the following suggestions are given:
- If you do not use the Module Federation feature, and project dependencies do not change frequently, it is recommended to try esbuild build first
- If you are in a mono repo project, it is recommended to use the "normal" strategy; it is recommended to enable the configuration "monoreporedirect"
- If your project is large and has a large code base, the "eager" strategy is recommended
- If your project is just starting and dependencies will frequently change, the "eager" strategy is recommended
- For other types of projects, feel free to choose.
Common Issues
Dependency Missing
error - [MFSU][eager] build worker failed AssertionError [ERR_ASSERTION]: filePath not found of lodash.capitalizeCheck your dependencies to ensure that the corresponding dependency has been installed. (As in the example of lodash.capitalize)
Multiple Instances of React
The following error occurs in the browser
The root cause is that in some complex scenarios, React's code is packaged multiple times, resulting in multiple React instances at runtime. The solution is to avoid multiple instances using the shared configuration of Module Federation.
If other dependencies also have multiple instances issues, they can be solved in a similar way.
mfsu: {
shared: {
react: {
singleton: true,
},
},
},:::info{title=⚠️}
If the MF plugin is enabled, you need to enable shared, please refer to.
:::
Externals script Compatibility Issue
If project dependency A depends on B, and the project has configured an externals script type for B as follows.
externals: {
b: ['script https://cdn/b.js', b]
}It will cause an error when enabling MFSU.
import * as b from 'b';
console.log(b);Normally, b should be Module information, but what is obtained is Promise<Module>. I understand this is a problem with webpack, which has not handled the compatibility issue between externals script and module federation well, possibly also because externals script is rarely known and used.
The solution is not to use it mixed with MFSU, only enable it when process.env.NODE_ENV === 'production'.
externals: {
...(process.env.NODE_ENV === 'production' ? {b: ['script https://cdn/b.js', b]} : {})
}Dependency Cycle Issue
Scenario 1
In a monorepo, the project depends on package A, A depends on package B, and B is provided by a subpackage in the project monorepo. This forms a situation where the project source code depends on A, and A depends back on the project source code.
In this case, it is recommended to use the MFSU's exclude configuration.
mfsu: {
exclude: [
'B'
]
}Scenario 2
An unreasonable implementation of a certain dependency in the project, the project relies on the functionality related to the Bigfish plugin (i.e., references the content under the .umi directory); after enabling MFSU, the project may not compile normally; The solution and Scenario 1 are a similar configuration. Add this package to the mfsu.exclude field.
Worker Compatibility Issue
If the project code needs to be used in a Worker, then the dependencies required by the Worker need to be added to the MFSU's exclude configuration.
Worker related dependencies can only be bypassed in this way, because Module Federation shares modules through the window object, so modules from Module Federation cannot be used in workers.
Docs Docs Guides Mfsu (`docs/docs/docs/guides/mfsu.md`)
order: 19 toc: content
MFSU
什么是 MFSU
MFSU 是一种基于 webpack5 新特性 Module Federation 的打包提速方案。其核心的思路是通过分而治之,将应用源代码的编译和应用依赖的编译分离,将变动较小的应用依赖构建为一个 Module Federation 的 remote 应用,以免去应用热更新时对依赖的编译。
开启 MFSU 可以大幅减少热更新所需的时间了;因此我们在 Umi 的项目中默认开启了 MFSU 功能。当然你也可以通过配置 mfsu: false 来关闭它。
MFSU 的两种策略
MFSU 最关键的一点是如何将应用代码的实际使用的依赖分析出来。根据不同的分析方式 MFSU 有两种工作方式。
normal 策略 (编译时分析)
采用以下配置启用
mfsu: {
strategy: 'normal',
}现代前端项目中的代码都需要经过转译(transpile),才会在生产环境中使用。在转译的过程中转译器(比如:babel) 就会在代码中插入新的依赖。这些插入的依赖在项目代码层面是不可见, 但通过转译的插件才收集到。
MFSU 编译时分析的工作方式,先对应用项目源码单独进行编译,编译的同时收集项目的本身依赖和编译引入的依赖。待项目代码编译完成以后,使用收集到的结果继续进行项目依赖部分的代码的构建。
从下图中以 React 引用的构建举例可以看出,整个过程是串行的。
eager 策略 (扫描方式)
mfsu: {
strategy: 'eager',
}和编译时分析的方式不同,扫描分析的方式会先读取项目中的所有源代码文件,然后通过静态分析的方式获取项目的依赖。这个过程非常的快,在一个有 17 万行代码,1400 多个文件项目中,分析一次只需要 700ms 左右。如此快速的分析的代价是:收集到的依赖会缺失后面项目代码编译插入的依赖;这部分的依赖最终和项目代码一起编译打包。
分析完项目依赖之后,Umi 会拿着这份依赖信息,并行的去进行项目代码的编译和依赖的编译。
从下图可以看出,编译部分是并行。
两种构建工具
MFSU 支持使用 Webpack 或者 esbuild 构建项目的依赖。默认配置使用 Webpack,和 Webpack 生态很好的兼容。
Esbuild 通过 mfsu: { esbuild: true } 来开启,享受 Esbuild 的高效的构建速度。
如何选择
编译时分析的好处是收集的依赖是完整的,项目代码和依赖代码的构建打包完全分离;再项目代码修改以后只需要构建项目代码部分。缺点也很明显,构建的过程是串行的。
扫描的方式的优点在于耗时的代码构建都是并行的,对于较大项目的冷启动时间改善非常明显。缺点则是有一部分运行时依赖会和项目代码一起编译。
基于优缺点的分析,给出以下建议
- 如果不使用 Module Federation 的功能的话,项目依赖变动不频繁,建议先尝试 esbuild 构建
- 如果在 mono repo 项目中, 推荐使用 "normal" 策略; 推荐开启配置 "monoreporedirect"
- 如果你的项目较大,项目代码基数较大,推荐使用 "eager" 策略
- 如果项目刚刚启动,会频繁的改动依赖,推荐使用 "eager" 策略
- 其他类型的项目则随意选择。
常见问题
依赖缺失
error - [MFSU][eager] build worker failed AssertionError [ERR_ASSERTION]: filePath not found of lodash.capitalize检查你的依赖确保,对应的依赖已经安装。( 如例子中的 lodash.capitalize)
React 多实例问题
在浏览器中有如下报错
根因在某些复杂场景下,React 的代码被打包多份,在运行时产出了多个 React 实例。解法通过 Module Federation 的 shared 配置来避免多实例的出现。
如果有其他依赖出现多实例的问题,可以通过类似的方式解决。
mfsu: {
shared: {
react: {
singleton: true,
},
},
},:::info{title=⚠️}
如果开启了 MF插件, 需要开启 shared,请参考。
:::
externals script 兼容问题
如果项目依赖 a,a 依赖 b,而项目配置了 b 的 script 类型的 externals 如下。
externals: {
b: ['script https://cdn/b.js', b]
}在开启 MFSU 时会报错。
import * as b from 'b';
console.log(b);上述代码的 b 正常应该是 Module 信息,拿到的却是 Promise<Module>。我理解这是 webpack 的问题,没有处理好 externals script 和 module federation 之间的兼容问题,可能也是因为 externals script 很少有人知道和在使用。
解法是不要和 MFSU 混着用,只在 process.env.NODE_ENV === 'production' 时开启。
externals: {
...(process.env.NODE_ENV === 'production' ? {b: ['script https://cdn/b.js', b]} : {})
}依赖环问题
场景 1
在使用 monorepo 时 ,项目依赖了 A 包,A 依赖了 B 包,而 B 包是项目 monorepo 中子包提供。这行就形成项目源码依赖 A ,A 又重新依赖项目源码的情况。
这种情况建议使用 MFSU 的 exclude 配置。
mfsu: {
exclude: [
'B'
]
}场景 2
项目的某个依赖的不合理的实现,项目中依赖了 Bigfish 插件相关的功能(即:引用了 .umi 目录下的内容 );在开启 MFSU 后项目可能不能正常编译;解法和场景 1 类似配置。将这个包配置进 mfsu.exclude 字段中。
worker 兼容问题
如果项目代码需要在 Worker 中使用,那么需要将 Worker 需要的依赖添加到 MFSU 的 exclude 配置中。
Worker 相关依赖只能通过这样方式来绕过,因为 Module Federation 是通过 window 对象来共享模块的,所以在 worker 中不能使用 Module Federation 中的模块。
Docs Docs Guides Mock.En US (`docs/docs/docs/guides/mock.en-US.md`)
order: 5 toc: content translated_at: '2024-03-17T10:26:57.083Z'
Mock
Umi provides an out-of-the-box Mock feature, which enables the setup of Mock data in a convenient and simple manner.
:::info{title=💡}
What is Mock data: After the front-end and back-end have agreed on the API interface, the front end can use Mock data to simulate the data that the API should return locally. This allows front-end and back-end development to proceed simultaneously, without being blocked by the ongoing development of the backend API.
:::
Directory Conventions
Umi conventionally treats all files under the /mock directory as Mock files, like this directory structure:
.
├── mock
├── todos.ts
├── items.ts
└── users.ts
└── src
└── pages
└── index.tsxThus, todos.ts, items.ts, and users.ts in the /mock directory will be processed by Umi as Mock files.
Mock Files
Mock files export an object by default, where each key of the object corresponds to a Mock interface, and the value is the data that should be returned by this interface, like in this Mock file:
// ./mock/users.ts
export default {
// The return value can be an array
'GET /api/users': [
{ id: 1, name: 'foo' },
{ id: 2, name: 'bar' }
],
// The return value can also be an object
'GET /api/users/1': { id: 1, name: 'foo' },
}This declares two Mock interfaces. Through GET /api/users, you can get an array with two user data. Through GET /api/users/1, you can get simulated data for a user.
Request Methods
When the HTTP request method is GET, you can omit the method part and only need the path, like:
// ./mock/users.ts
export default {
'/api/users': [
{ id: 1, name: 'foo' },
{ id: 2, name: 'bar' }
],
'/api/users/1': { id: 1, name: 'foo' },
}You can also use different request methods, such as POST, PUT, DELETE:
// ./mock/users.ts
export default {
'POST /api/users': { result: 'true' },
'PUT /api/users/1': { id: 1, name: 'new-foo' },
}Custom Functions
In addition to directly declaring static return values, you can also use functions to declare how to compute the return values, like:
export default {
'POST /api/users/create': (req, res) => {
// Add CORS headers
res.setHeader('Access-Control-Allow-Origin', '*');
res.end('ok');
}
}For the APIs of req and res, refer to the Express@4 official documentation for further understanding.
defineMock
Furthermore, you can use the defineMock type helper function to provide code hints for writing mock objects, like:
import { defineMock } from "umi";
export default defineMock({
"/api/users": [
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
],
"/api/users/1": { id: 1, name: "foo" },
"GET /api/users/2": (req, res) => {
res.status(200).json({ id: 2, name: "bar" });
},
});defineMock simply provides type hints; the input and output are completely identical.
Disabling Mock
Mock functionality is enabled by default in Umi. If it's not needed, it can be disabled from the config file:
// .umirc.ts
export default {
mock: false,
};Or by using an environmental variable:
MOCK=none umi devIntegrating Mock.js
In Mock, we often use Mock.js to help us conveniently generate random simulated data. If you are using Umi's Mock functionality, it's recommended to pair it with this library to enhance the realism of the simulated data:
import mockjs from 'mockjs';
export default {
// Using third-party libraries like mockjs
'GET /api/tags': mockjs.mock({
'list|100': [{ name: '@city', 'value|1-100': 50, 'type|0-2': 1 }],
}),
};Other Configurations
For a complete list of other configuration options for Mock functionality, please see the configuration section of the documentation.
Docs Docs Guides Mock (`docs/docs/docs/guides/mock.md`)
order: 5 toc: content
Mock
Umi 提供了开箱即用的 Mock 功能,能够用方便简单的方式来完成 Mock 数据的设置。
:::info{title=💡}
什么是 Mock 数据:在前后端约定好 API 接口以后,前端可以使用 Mock 数据来在本地模拟出 API 应该要返回的数据,这样一来前后端开发就可以同时进行,不会因为后端 API
还在开发而导致前端的工作被阻塞。
:::
目录约定
Umi 约定 /mock 目录下的所有文件为 Mock 文件,例如这样的目录结构:
.
├── mock
├── todos.ts
├── items.ts
└── users.ts
└── src
└── pages
└── index.tsx则 /mock 目录中的 todos.ts, items.ts 和 users.ts 就会被 Umi 视为 Mock 文件 来处理。
Mock 文件
Mock 文件默认导出一个对象,而对象的每个 Key 对应了一个 Mock 接口,值则是这个接口所对应的返回数据,例如这样的 Mock 文件:
// ./mock/users.ts
export default {
// 返回值可以是数组形式
'GET /api/users': [
{ id: 1, name: 'foo' },
{ id: 2, name: 'bar' }
],
// 返回值也可以是对象形式
'GET /api/users/1': { id: 1, name: 'foo' },
}就声明了两个 Mock 接口,透过 GET /api/users 可以拿到一个带有两个用户数据的数组,透过 GET /api/users/1 可以拿到某个用户的模拟数据。
请求方法
当 HTTP 的请求方法是 GET 时,可以省略方法部分,只需要路径即可,例如:
// ./mock/users.ts
export default {
'/api/users': [
{ id: 1, name: 'foo' },
{ id: 2, name: 'bar' }
],
'/api/users/1': { id: 1, name: 'foo' },
}也可以用不同的请求方法,例如 POST,PUT,DELETE:
// ./mock/users.ts
export default {
'POST /api/users': { result: 'true' },
'PUT /api/users/1': { id: 1, name: 'new-foo' },
}自定义函数
除了直接静态声明返回值,也可以用函数的方式来声明如何计算返回值,例如:
export default {
'POST /api/users/create': (req, res) => {
// 添加跨域请求头
res.setHeader('Access-Control-Allow-Origin', '*');
res.end('ok');
}
}关于 req 和 res 的 API 可参考 Express@4 官方文档 来进一步了解。
defineMock
另外,也可以使用 defineMock 类型帮助函数来提供编写 mock 对象的代码提示,如:
import { defineMock } from "umi";
export default defineMock({
"/api/users": [
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
],
"/api/users/1": { id: 1, name: "foo" },
"GET /api/users/2": (req, res) => {
res.status(200).json({ id: 2, name: "bar" });
},
});defineMock 仅仅提供类型提示,入参与出参完全一致。
关闭 Mock
Umi 默认开启 Mock 功能,如果不需要的话可以从配置文件关闭:
// .umirc.ts
export default {
mock: false,
};或是用环境变量的方式关闭:
MOCK=none umi dev引入 Mock.js
在 Mock 中我们经常使用 Mock.js 来帮我们方便地生成随机的模拟数据。如果你使用了 Umi 的 Mock
功能,建议你搭配这个库来提升模拟数据的真实性:
import mockjs from 'mockjs';
export default {
// 使用 mockjs 等三方库
'GET /api/tags': mockjs.mock({
'list|100': [{ name: '@city', 'value|1-100': 50, 'type|0-2': 1 }],
}),
};其他配置
关于 Mock 功能完整的其他配置项,请在文档的 配置 章节中查看。
Docs Docs Guides Plugins.En US (`docs/docs/docs/guides/plugins.en-US.md`)
order: 16 toc: content translated_at: '2024-03-17T10:25:42.549Z'
Plugin Development
The essence of Umi lies in its plugin mechanism. Based on the plugin mechanism of Umi, you can enhance the build-time and runtime capabilities of your project. You can freely write plugins using the Plugin API we provide, thus achieving rich functionalities like modifying the code packaging configuration, modifying startup code, convention directory structure, modifying HTML, etc.
Core Concepts
The essence of a plugin is a method that receives one parameter: api. Within the plugin, you can call methods provided by api to register some hooks, which Umi will execute at specific times.
For example:
import { IApi } from 'umi';
export default (api: IApi) => {
api.describe({
key: 'changeFavicon',
config: {
schema(joi) {
return joi.string();
},
},
enableBy: api.EnableBy.config
});
api.modifyConfig((memo)=>{
memo.favicons = api.userConfig.changeFavicon;
return memo;
});
};The purpose of this plugin is to change the favicons in the configuration based on the user-configured changeFavicon value (a very simple example with no practical use XD). As you can see, a plugin is essentially a method that receives the parameter api. In this method, we called api.modifyConfig to register a hook: (memo)=>{...}. After you have configured changeFavicon in the configuration, Umi will register the plugin. During Umi's configuration collection lifecycle, the hook we registered in the plugin will be executed, changing the favicon in the configuration to the changeFavicon configured by the user.
plugin and preset
The role of a preset is to preset some plugins, which is typically used to register a batch of presets and plugins. In presets, the method mentioned above that accepts the api can have a return value, which is an object containing plugins and presets properties, for the purpose of registering the corresponding plugins or plugin sets.
For example:
import { IApi } from 'umi';
export default (api: IApi) => {
return {
plugins: ['./plugin_foo','./plugin_bar'],
presets: ['./preset_foo']
}
};Their registration order is worth noting: presets are always registered before plugins. Umi maintains two queues to register presets and plugins sequentially, the registered preset_foo in this example would be placed at the head of the presets queue, and plugin_foo and plugin_bar would be placed at the tail of the plugins queue one by one. The purpose of putting preset at the head is to ensure the order and relationship between presets are controllable.
Another noteworthy point is: in plugins, you can also return some plugins or presets, but Umi won't do anything about it.
Plugin's id and key
Each plugin corresponds to an id and key.
The id is an abbreviation for the plugin's path and serves as the unique identifier of the plugin; while the key is the key name used for plugin configuration.
For example, the plugin node_modules/@umijs/plugin-foo/index.js, typically, its id is @umijs/plugin-foo, and the key is foo. This allows developers to configure the item with the key name foo in the configuration to configure the plugin.
Enabling Plugins
There are two ways to enable plugins: enable in environment variables and enable in configuration. (Different from umi@3, we no longer support automatic enabling of plugins in the dependencies of package.json)
Note: Here we refer to third-party plugins, as Umi's built-in plugins are universally enabled in the configuration through their keys.
Environment Variables
You can also register additional plugins through the environment variables UMI_PRESETS and UMI_PLUGINS.
For example:
$ UMI_PRESETS = foo/preset.js umi devNote: It is not recommended for use in projects, usually for secondary packaging based on Umi framework.
Configuration
Plugins are configured in the configuration through presets and plugins, for example:
export default {
presets: ['./preset/foo','bar/presets'],
plugins: ['./plugin', require.resolve('plugin_foo')]
}The content of the configuration is the path of the plugin.
Order of Plugins
The registration of Umi plugins follows a certain order:
- All presets are registered before plugins.
- Built-in plugins -> plugins in environment variables -> plugins in user config.
- Plugins registered at the same time (in the same array) are registered in sequence.
- Presets registered in a preset are executed immediately, while plugins registered are executed at the end.
Disabling Plugins
There are two ways to disable plugins
Configuring key as false
For example:
export default{
mock: false
}This would disable Umi's built-in mock plugin.
Disabling Other Plugins in a Plugin
This can be done by using api.skipPlugins(pluginId[]), see Plugin API for more details.
Viewing Plugin Registration Status
Command Line
$ umi plugin listConfiguring Plugins
Configure plugins through the key of the plugin, for example:
export default{
mock: { exclude: ['./foo'] }
}Here mock is the key of Umi's built-in mock plugin.
For instance, if we install a plugin umi-plugin-bar, its default key is bar, then it can be configured:
export default{
bar: { ... }
}Default Naming Rules for Plugin Keys
If the plugin is a package, the default value of the key will be the package name without prefix. For example, the default key for @umijs/plugin-foo is foo, and the default key for @alipay/umi-plugin-bar is bar. It is worth noting that this default rule requires your package name to conform to the Umi plugin naming convention.
If the plugin is not a package, the default value of the key will be the filename of the plugin. For example, the default key for ./plugins/foo.js is foo.
To avoid unnecessary trouble, we recommend that you explicitly declare the key for your own written plugins.
The Mechanism and Lifecycle of Umi Plugins
Lifecycle
- init stage: At this stage, Umi loads various configuration information. Including loading
.envfiles; requiringpackage.json; loading user configurations; resolving all plugins (built-in plugins, environment variables, and user configs in sequence). - initPresets stage: At this stage, Umi registers presets. Presets can register additional plugins by returning
{ presets, plugins }, where presets will be added to the top of the presets queue, and plugins will be added to the end of the plugins queue. - initPlugins stage: At this stage, Umi registers plugins. These plugins include additional plugins added by presets in the previous stage. A point worth noting is that even though plugins can also
return { presets, plugins }, Umi will not do anything about it. The init of a plugin is essentially executing the plugin's code (but the essence of the plugin's code is just calling the api to register various hooks, and the execution of hooks does not occur at this stage, so this is called plugin registration). - resolveConfig stage: At this stage, Umi organizes definitions of
config schemafrom various plugins, then executes hooks likemodifyConfig,modifyDefaultConfig,modifyPathsetc., to collect configurations. - collectionAppData stage: At this stage, Umi executes the
modifyAppDatahook, to maintain the metadata of the App. (AppDatais a new api added inumi@4) - onCheck stage: At this stage, Umi executes the
onCheckhook. - onStart stage: At this stage, Umi executes the
onStarthook. - runCommand stage: At this stage, Umi runs the current cli command to be executed, (for example
umi dev, here the dev command will be executed) The various core functionalities of Umi are implemented in the command. Including most of the hooks registered by our plugins calling the api.
The above is the overall process of Umi's plugin mechanism.
`register()`, `registerMethod()`, and `applyPlugins()`
register() accepts a key and a hook, maintaining a key-hook[] map, each call to register() will register an additional hook for the key.
register() registered hooks are for use by applyPlugins. The execution sequence of these hooks follows tapable.
registerMethod() accepts a key and a fn, it registers a method on the api. If no fn is passed to registerMethod(), it will register a "register" on the api: it will register the result of register() passed in key and curried as fn to the api. This allows for registering hooks for the key quickly by calling this "register".
For more specific use of the above api, please refer to Plugin API.
The Principle of PluginAPI
Umi assigns a PluginAPI object to each plugin, which references both the plugin itself and Umi's service.
Umi has proxied the get() method of the PluginAPI object according to the following rules:
- pluginMethod: If prop is a method in Umi's maintained
pluginMethods[](methods registered throughregisterMethod()), then it returns this method. - service props: If prop is an attribute in the serviceProps array (these attributes are properties that Umi allows plugins direct access to), then it returns the corresponding property of the service.
- static props: If prop is an attribute in the staticProps array (these attributes are static variables, such as some type definitions and constants), then it returns it.
- Otherwise, it returns the property of the api.
Therefore, most of the apis provided to plugins by Umi are implemented by relying on registerMethod(), you can directly use our apis to quickly register hooks in plugins. This is also a manifestation of Umi's decoupling of framework and functionalities: Umi's service only provides plugin management functionalities, while the apis all rely on plugins to provide.
preset-umi
umi-core provides a set of plugin registration and management mechanisms. And Umi's core functionalities are all implemented by preset-umi.
preset-umi is essentially an inbuilt plugin set, which provides plugins in three categories:
- registerMethods, registering some of the "registers" mentioned above for developers to quickly register hooks, occupying most of the PluginAPI.
- features, providing Umi with some features, such as appData, lowImport, mock, etc.
- commands, registering various commands, providing various functionalities of Umi CLI. Umi's ability to operate normally in the terminal relies on the functionalities provided by commands.
Docs Docs Guides Plugins (`docs/docs/docs/guides/plugins.md`)
order: 16 toc: content
开发插件
Umi 的核心就在于它的插件机制。基于 Umi 的插件机制,你可以获得扩展项目的编译时和运行时的能力。你可以利用我们提供的 插件API 来自由地编写插件,进而实现修改代码打包配置、修改启动代码、约定目录结构、修改 HTML 等丰富的功能。
核心概念
插件的本质就是一个方法,该方法接收了一个参数:api。在插件中,你可以调用 api 提供的方法进行一些 hook 的注册,随后 Umi 会在特定的时机执行这些 hook。
比如:
import { IApi } from 'umi';
export default (api: IApi) => {
api.describe({
key: 'changeFavicon',
config: {
schema(joi) {
return joi.string();
},
},
enableBy: api.EnableBy.config
});
api.modifyConfig((memo)=>{
memo.favicons = api.userConfig.changeFavicon;
return memo;
});
};这个插件的作用是根据用户配置的 changeFavicon 值来更改配置中的 favicons,(一个很简单且没有实际用途的例子XD)。可以看到插件其实就是一个接收了参数 api 的方法。在这个方法中,我们调用了 api.modifyConfig 注册了一个 hook: (memo)=>{...}。当你在配置中配置了 changeFavicon 之后, Umi 会注册该插件。在 Umi 收集配置的生命周期里,我们在插件里注册的 hook 将被执行,此时配置中的 favicon 就会被修改为用户配置中的 changeFavicon 。
plugin 和 preset
preset 的作用是预设一些插件,它通常用来注册一批 presets 和 plugins。在 preset 中,上述提到的接受 api 的方法可以有返回值,该返回值是一个包含 plugins 和 presets 属性的对象,其作用就是注册相应的插件或者插件集。
比如:
import { IApi } from 'umi';
export default (api: IApi) => {
return {
plugins: ['./plugin_foo','./plugin_bar'],
presets: ['./preset_foo']
}
};它们的注册顺序是值得注意的:presets 始终先于 plugins 注册。Umi 维护了两个队列分别用来依次注册 presets 和 plugins,这个例子中的注册的 preset_foo 将被置于 presets 队列队首,而 plugin_foo 和 plugin_bar 将被依次置于 plugins 队列队尾。这里把 preset 放在队首的目的在于保证 presets 之间的顺序和关系是可控的。
另外一个值得注意的点是:在 plugin 中,你也可以 return 一些 plugins 或者 presets,但是 Umi 不会对它做任何事情。
插件的 id 和 key
每个插件都对应一个 id 和 key。
id 是插件所在路径的简写,作为插件的唯一标识;而 key 则是用于插件配置的键名。
比如插件 node_modules/@umijs/plugin-foo/index.js ,通常来说,它的 id 是 @umijs/plugin-foo , key 是 foo。此时就允许开发者在配置中来配置键名为 foo 的项,用来对插件进行配置。
启用插件
插件有两种启用方式: 环境变量中启用和配置中启用。(与 umi@3 不同,我们不再支持对 package.json 中依赖项的插件实现自动启用)
注意:这里的插件指的是第三方插件,Umi 的内置插件统一在配置中通过对其 key 进行配置来启用。
环境变量
还可以通过环境变量 UMI_PRESETS 和 UMI_PLUGINS 注册额外插件。
比如:
$ UMI_PRESETS = foo/preset.js umi dev注意: 项目里不建议使用,通常用于基于 Umi 的框架二次封装。
配置
在配置里通过 presets 和 plugins 配置插件,比如:
export default {
presets: ['./preset/foo','bar/presets'],
plugins: ['./plugin', require.resolve('plugin_foo')]
}配置的内容为插件的路径。
插件的顺序
Umi 插件的注册遵循一定的顺序:
- 所有的 presets 都先于 plugins 被注册。
- 内置插件 -> 环境变量中的插件 -> 用户配置中的插件
- 同时注册(同一个数组里)的插件按顺序依次注册。
- preset 中注册的 preset 立即执行, 注册的 plugin 最后执行。
禁用插件
有两种方式禁用插件
配置 key 为 false
比如:
export default{
mock: false
}会禁用 Umi 内置的 mock 插件。
在插件中禁用其他插件
可通过 api.skipPlugins(pluginId[]) 的方式禁用,详见插件 API。
查看插件注册情况
命令行
$ umi plugin list配置插件
通过配置插件的 key 来配置插件,比如:
export default{
mock: { exclude: ['./foo'] }
}这里 mock 就是 Umi 内置插件 mock 的 key。
再比如我们安装一个插件 umi-plugin-bar, 其 key 默认是 bar, 就可以配置:
export default{
bar: { ... }
}插件 key 的默认命名规则
如果插件是一个包的话,key 的默认值将是去除前缀的包名。比如 @umijs/plugin-foo 的 key 默认为 foo, @alipay/umi-plugin-bar 的 key 默认为 bar。值得注意的是,该默认规则要求你的包名符合 Umi 插件的命名规范。
如果插件不是一个包的话,key 的默认值将是插件的文件名。比如 ./plugins/foo.js 的 key 默认为 foo 。
为了避免不必要的麻烦,我们建议你为自己编写的插件显式地声明其 key。
Umi 插件的机制及其生命周期
生命周期
- init stage: 该阶段 Umi 将加载各类配置信息。包括:加载
.env文件; requirepackage.json;加载用户的配置信息; resolve 所有的插件(内置插件、环境变量、用户配置依次进行)。 - initPresets stage: 该阶段 Umi 将注册 presets。presets 在注册的时候可以通过
return { presets, plugins }来添加额外的插件。其中 presets 将添加到 presets 队列的队首,而 plugins 将被添加到 plugins 队列的队尾。 - initPlugins stage: 该阶段 Umi 将注册 plugins。这里的 plugins 包括上个阶段由 presets 添加的额外的 plugins, 一个值得注意的点在于: 尽管 plugins 也可以
return { presets, plugins },但是 Umi 不会对其进行任何操作。插件的 init 其实就是执行插件的代码(但是插件的代码本质其实只是调用 api 进行各种 hook 的注册,而 hook 的执行并非在此阶段执行,因此这里叫插件的注册)。 - resolveConfig stage: 该阶段 Umi 将整理各个插件中对于
config schema的定义,然后执行插件的modifyConfig、modifyDefaultConfig、modifyPaths等 hook,进行配置的收集。 - collectionAppData stage: 该阶段 Umi 执行
modifyAppDatahook,来维护 App 的元数据。(AppData是umi@4新增的 api ) - onCheck stage: 该阶段 Umi 执行
onCheckhook。 - onStart stage: 该阶段 Umi 执行
onStarthook。 - runCommand stage: 该阶段 Umi 运行当前 cli 要执行的 command,(例如
umi dev, 这里就会执行 dev command)Umi 的各种核心功能都在 command 中实现。包括我们的插件调用 api 注册的绝大多数 hook。
以上就是 Umi 的插件机制的整体流程。
`register()` 、 `registerMethod()` 以及 `applyPlugins()`
register() 接收一个 key 和 一个 hook,它维护了一个 key-hook[] 的 map,每当调用 register() 的时候,就会为 key 额外注册一个 hook。
register() 注册的 hooks 供 applyPlugins 使用。 这些 hook 的执行顺序参照 tapable。
registerMethod() 接收一个 key 和 一个 fn,它会在 api 上注册一个方法。如果你没有向 registerMethod() 中传入 fn,那么 registerMethod() 会在 api 上注册一个“注册器”: 它会将 register() 传入 key 并柯里化后的结果作为 fn 注册到 api 上。这样就可以通过调用这个“注册器”,快捷地为 key 注册 hook 了。
关于上述 api 的更具体的使用,请参照插件 API。
PluginAPI 的原理
Umi 会为每个插件赋予一个 PluginAPI 对象,这个对象引用了插件本身和 Umi 的 service。
Umi 为 PluginAPI 对象的 get() 方法进行了 proxy,具体规则如下:
- pluginMethod: 如果 prop 是 Umi 所维护的
pluginMethods[](通过 registerMethod()注册的方法 )中的方法,则返回这个方法。 - service props: 如果 prop 是 serviceProps 数组中的属性(这些属性是 Umi 允许插件直接访问的属性),则返回 service 对应的属性。
- static props: 如果 prop 是参数 staticProps 数组中的属性(这些属性是静态变量,诸如一些类型定义和常量),则将其返回。
- 否则返回 api 的属性。
因此,Umi 提供给插件的 api 绝大多数都是依靠 registerMethod() 来实现的,你可以直接使用我们的这些 api 快速地在插件中注册 hook。这也是 Umi 将框架和功能进行解耦的体现: Umi 的 service 只提供插件的管理功能,而 api 都依靠插件来提供。
preset-umi
umi-core 提供了一套插件的注册及管理机制。而 Umi 的核心功能都靠 preset-umi 来实现。
preset-umi 其实就是内置的一个插件集,它提供的插件分为三大类:
- registerMethods 这类插件注册了一些上述提到的“注册器”,以供开发者快速地注册 hook,这类方法也占据了 PluginAPI 中的大多数。
- features 这类插件为 Umi 提供了一些特性,例如 appData、lowImport、mock 等。
- commands 这类插件注册了各类 command, 提供了 Umi CLI 的各种功能。Umi 能够在终端中正常运行,依靠的就是 command 提供的功能。
Docs Docs Guides Prepare.En US (`docs/docs/docs/guides/prepare.en-US.md`)
order: 1 toc: content translated_at: '2024-03-17T10:23:41.561Z'
Development Environment
This article will guide you through setting up a local development environment for an Umi.js project from scratch.
Nodejs
Umi.js development requires Node.js, so please ensure that Node.js is installed on your computer and its version is above 18.
:::info{title=💡}
If you are a macOS user, it is recommended to use nvm to manage the versions of Node.js; Windows users are recommended to use nvm-windows.
:::
This article will use nvm to install Node.js on macOS or Linux environments:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
nvm -v
0.39.1After installing nvm, use the following commands to install Node.js:
nvm install 18
nvm use 18After the installation is complete, use the following commands to check if the installation was successful and the correct version was installed:
node -v
v18.14.0Dependency Management
After installing Node, the npm dependency management tool comes with it, but Umi.js recommends using pnpm for dependency management:
curl -fsSL https://get.pnpm.io/install.sh | sh -After the installation is complete, you can use the following command to check if the installation was successful:
pnpm -v
7.3.0IDE
After installing Node.js and pnpm (or another dependency management tool), you need to choose an IDE or text editor you are comfortable with to write code. If you don't have a preferred IDE yet, you can choose one from the list below:
- Visual Studio Code (Recommended)
- WebStorm (Recommended)
- IntelliJ IDEA
- Sublime Text
- Atom
Next Steps
Congratulations! Your local environment is now ready to start developing Umi.js projects. Go to boilerplate to learn how to quickly start a project with the Umi.js scaffold! 🎉
Docs Docs Guides Prepare (`docs/docs/docs/guides/prepare.md`)
order: 1 toc: content
开发环境
本文将带领你从零开始在本地搭建一个 Umi.js 项目的开发环境。
Nodejs
Umi.js 需要使用 Node.js 来进行开发,因此请先确保电脑已经安装了 Node.js 且版本在 18 以上。
:::info{title=💡}
如果你是 macOS 用户,建议使用 nvm 来管理 Node.js 的版本;
Windows 用户建议使用 nvm-windows 。
:::
本文将在 macOS 或 Linux 环境下使用 nvm 安装 Node.js :
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
nvm -v
0.39.1安装完成 nvm 之后,使用以下命令来安装 Node.js :
nvm install 18
nvm use 18安装完成后,使用以下命令来检查是否安装成功并且安装了正确的版本:
node -v
v18.14.0依赖管理
Node 安装完成后会自带 npm 依赖管理工具,但 Umi.js 推荐使用 pnpm 来管理依赖:
curl -fsSL https://get.pnpm.io/install.sh | sh -安装完成后,可以用以下命令检查是否安装成功:
pnpm -v
7.3.0IDE
安装完 Node.js 及 pnpm (或其他依赖管理工具) 后,你需要使用一个自己习惯的 IDE 或文本编辑器来编写代码。如果你还没有习惯的 IDE,可以从下方挑选一个:
- Visual Studio Code (推荐)
- WebStorm (推荐)
- IntelliJ IDEA
- Sublime Text
- Atom
下一步
恭喜你!你的本地环境已经准备好开始开发 Umi.js 项目了,马上前往 脚手架 学习如何使用 Umi.js 脚手架快速启动一个项目吧!🎉
Docs Docs Guides Proxy.En US (`docs/docs/docs/guides/proxy.en-US.md`)
order: 6 toc: content translated_at: '2024-03-17T10:23:20.172Z'
Proxy
A proxy, also known as a network proxy, is a special network service that allows one endpoint (usually the client) to connect indirectly to another endpoint (usually the server). - Wikipedia
In project development (dev), all network requests (including resource requests) are responded to and dispatched through a local server. We use the http-proxy-middleware middleware to proxy specified requests to another target server. For example, the request fetch('/api') to retrieve data from the remote http://jsonplaceholder.typicode.com/.
To achieve the above requirement, we only need to use proxy configuration in the configuration file:
export default {
proxy: {
'/api': {
'target': 'http://jsonplaceholder.typicode.com/',
'changeOrigin': true,
'pathRewrite': { '^/api' : '' },
},
},
}The above configuration indicates that requests with the /api prefix are proxied to http://jsonplaceholder.typicode.com/, replacing /api in the request address with '', and changing the request origin to the target URL. For instance, a request to /api/a is actually requesting http://jsonplaceholder.typicode.com/a.
We generally use this capability to solve cross-origin access problems in development. Due to the same-origin policy in browsers (or webview), we used to have the server support the Cross-Origin Resource Sharing (CORS) policy to circumvent cross-origin access issues. Now with local node services, we can use proxies to address this problem.
XMLHttpRequest cannot load https://api.example.com. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8000' is therefore not allowed access.
The principle is actually quite simple - browsers have cross-origin issues, but servers do not. We request the local service of the same origin, which then requests the remote service of a different origin. It's important to note that proxying requests means proxying the request service and does not directly modify the original request URL. It just passes the data returned by the target server to the front-end. Therefore, the request URL you see in the browser is still http://localhost:8000/api/a.
It's worth noting that proxy can only solve the cross-origin access problem during development (dev) and can be used in same-origin deployment when deploying. If a cross-origin problem occurs in production (build), similar configurations can be transferred to the Nginx container.
Docs Docs Guides Proxy (`docs/docs/docs/guides/proxy.md`)
order: 6 toc: content
代理
代理也称网络代理,是一种特殊的网络服务,允许一个终端(一般为客户端)通过这个服务与另一个终端(一般为服务器)进行非直接的连接。- 维基百科
在项目开发(dev)中,所有的网络请求(包括资源请求)都会通过本地的 server 做响应分发,我们通过使用 http-proxy-middleware 中间件,来代理指定的请求到另一个目标服务器上。如请求 fetch('/api') 来取到远程 http://jsonplaceholder.typicode.com/ 的数据。
要实现上述的需求我们只需要在配置文件中使用 proxy 配置:
export default {
proxy: {
'/api': {
'target': 'http://jsonplaceholder.typicode.com/',
'changeOrigin': true,
'pathRewrite': { '^/api' : '' },
},
},
}上述配置表示,将 /api 前缀的请求,代理到 http://jsonplaceholder.typicode.com/,替换请求地址中的 /api 为 '',并且将请求来源修改为目标url。如请求 /api/a,实际上是请求 http://jsonplaceholder.typicode.com/a。
一般我们使用这个能力来解决开发中的跨域访问问题。由于浏览器(或者 webview)存在同源策略,之前我们会让服务端配合使用 Cross-Origin Resource Sharing (CORS) 策略来绕过跨域访问问题。现在有了本地的 node 服务,我们就可以使用代理来解决这个问题。
XMLHttpRequest cannot load https://api.example.com. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8000' is therefore not allowed access.
原理其实很简单,就是浏览器上有跨域问题,但是服务端没有跨域问题。我们请求同源的本地服务,然后让本地服务去请求非同源的远程服务。需要注意的是,请求代理,代理的是请求的服务,不会直接修改发起的请求 url。它只是将目标服务器返回的数据传递到前端。所以你在浏览器上看到的请求地址还是 http://localhost:8000/api/a。
值得注意的是 proxy 暂时只能解开发时(dev)的跨域访问问题,可以在部署时使用同源部署。如果在生产上(build)发生跨域问题的话,可以将类似的配置转移到 Nginx 容器上。
Docs Docs Guides Routes.En US (`docs/docs/docs/guides/routes.en-US.md`)
order: 3 toc: content translated_at: '2024-03-17T10:22:52.868Z'
Routing
In Umi applications, which are single-page applications, the navigation between pages is done client-side in the browser without re-fetching the HTML from the server. The HTML is loaded only once during the app initialization. Each page comprises different components, so navigating between pages essentially means toggling between these components. You need to associate different routing paths with their respective components in the configuration.
Route Configuration Types
Refer to history configuration.
Configuring Routes
Routes are configured in the configuration file via the routes attribute, which is an array of route information.
For example:
// .umirc.ts
export default {
routes: [
{ path: '/', component: 'index' },
{ path: '/user', component: 'user' },
],
}Umi 4 defaults to page-level code splitting for faster page loads. Since the loading process is asynchronous, you often need to write a loading.tsx to add loading styles to your project, improving the user experience.
:::info{title=💡}
You can set the network to slow in Chrome Devtools > Network tab, then switch routes to check if the loading component works.
:::
path
- Type:
string
path supports only two types of placeholders: the first is dynamic parameters, in the form of :id, and the second is the * wildcard, which can only appear at the end of the route string.
✅ The following are currently supported route path configurations:
/groups
/groups/admin
/users/:id
/users/:id/messages
/files/*
/files/:id/*❌ The following route path configurations are currently not supported:
/users/:id?
/tweets/:id(\d+)
/files/*/cat.jpg
/files-*component
- Type:
string
Configures the React component path to render when the location and path match. It can be an absolute path or a relative path. If it's a relative path, it will start looking from src/pages.
If pointing to a file in the src directory, you can use @, e.g., component: '@/layouts/basic'. It's recommended to use @ to organize the routing file location.
routes
Configure sub-routes, usually used when adding a layout component for multiple paths.
For example:
export default {
routes: [
{ path: '/login', component: 'login' },
{
path: '/',
component: '@/layouts/index',
routes: [
{ path: '/list', component: 'list' },
{ path: '/admin', component: 'admin' },
],
},
],
}In the global layout src/layouts/index, use <Outlet/> to render sub-routes:
import { Outlet } from 'umi'
export default function Page() {
return (
<Outlet/>
)
}This way, accessing /list and /admin will include the src/layouts/index layout component.
redirect
- Type:
string
Configure route redirection.
For example:
export default {
routes: [
{ path: '/', redirect: '/list' },
{ path: '/list', component: 'list' },
],
}Accessing / will redirect to /list, where src/pages/list will render the content.
wrappers
- Type:
string[]
Configure wrapper components for the route component, adding more features to the current route component.
For example, it can be used for route-level permission verification:
export default {
routes: [
{ path: '/user', component: 'user',
wrappers: [
'@/wrappers/auth',
],
},
{ path: '/login', component: 'login' },
]
}Then, in src/wrappers/auth,
import { Navigate, Outlet } from 'umi'
export default (props) => {
const { isLogin } = useAuth();
if (isLogin) {
return <Outlet />;
} else{
return <Navigate to="/login" />;
}
}This way, accessing /user checks permissions with the auth component. If the check passes, src/pages/user is rendered; otherwise, it redirects to /login.
:::info{title=🚨}
Each component in wrappers adds a nested route layer to the current route component. If you want to keep the route structure unchanged, consider using a higher-order component. Implement the logic in the wrapper first, then decorate the corresponding route component with that higher-order component.
:::
Example:
// src/hocs/withAuth.tsx
import { Navigate } from 'umi'
const withAuth = (Component) => ()=>{
const { isLogin } = useAuth();
if (isLogin) {
return <Component />;
} else{
return <Navigate to="/login" />;
}
}// src/pages/user.tsx
const TheOldPage = ()=>{
// ...
}
export default withAuth(TheOldPage)layout
- Type:
boolean
Configure layout: false to individually disable the global layout for a specific route:
// .umirc.ts
export default {
routes: [
// Disable the global layout for the login page, allowing you to implement the entire page
{ path: '/login', component: '@/pages/Login', layout: false },
],
}Note:
The global layout might come from the convention
layouts/index.tsx, or added by plugins (like the layout plugin in@umijs/maxthat automatically adds menu layouts). When configuringlayout: false, all layouts are canceled, and the component content occupies the whole page, which is often used for scenarios like login pages.layout: falseonly affects the first-level routes. See more examples in Global layout.
Convention-based Routing
Apart from configuration-based routing, Umi also supports convention-based routing. Convention routing, also known as file-based routing, doesn't require manual configuration. The routing configuration is derived from the directory and file names and their naming within the file system.
If there is no routes configuration, Umi enters convention-based routing mode and analyzes the src/pages directory to obtain routing configurations.
For example, the following file structure:
.
└── pages
├── index.tsx
└── users.tsxWill produce the following routing configuration,
[
{ path: '/', component: '@/pages/index' },
{ path: '/users', component: '@/pages/users' },
]When using convention-based routing, the convention is that all
(j|t)sx?files undersrc/pagesare considered routes. If you need to modify the default rules, you can use the conventionRoutes configuration.
Dynamic Routes
By convention, directories or files prefixed with $ are dynamic routes. If a parameter name is not specified after $, it represents a wildcard *. For example, the following directory structure:
src/pages/users/$id.tsxbecomes/users/:idsrc/pages/users/$id/settings.tsxbecomes/users/:id/settings
A complete example, such as the file structure below,
+ pages/
+ foo/
- $slug.tsx
+ $bar/
- $.tsx
- index.tsxWill generate the following route configuration:
[
{ path: '/', component: '@/pages/index.tsx' },
{ path: '/foo/:slug', component: '@/pages/foo/$slug.tsx' },
{ path: '/:bar/*', component: '@/pages/$bar/$.tsx' },
];Global layout
The convention is for src/layouts/index.tsx to be the global layout. Return a React component and use <Outlet /> to render nested routes.
For example, the following directory structure:
.
└── src
├── layouts
│ └── index.tsx
└── pages
├── index.tsx
└── users.tsxWill generate the following routes:
[
{
path: '/',
component: '@/layouts/index',
routes: [
{ path: '', component: '@/pages/index' },
{ path: 'users', component: '@/pages/users' },
],
},
]You can use layout: false for fine-grained control to disable the global layout for a specific route. This option only takes effect at the first level:
routes: [
{
path: '/',
component: './index',
// 🟢
layout: false
},
{
path: '/users',
routes: [
// 🔴 Doesn't take effect, as the layout here is not the global layout but `/users`
{ layout: false }
]
}
]A custom global layout could look like this:
import { Outlet } from 'umi'
export default function Layout() {
return <Outlet />
}Different Global Layouts
You might need different global layouts for different routes. Umi does not support this configuration, but you can differentiate location.path in src/layouts/index.tsx to render different layouts.
For example, for a simple layout on /login,
import { useLocation, Outlet } from 'umi';
export default function() {
const location = useLocation();
if (location.pathname === '/login') {
return <SimpleLayout><Outlet /></SimpleLayout>
}
// Using `useAppData` / `useSelectedRoutes` for more routing information
// const { clientRoutes } = useAppData()
// const routes = useSelectedRoutes()
return (
<>
<Header />
<Outlet />
<Footer />
</>
);
}404 Route
The convention is for src/pages/404.tsx to be the 404 page. It must return a React component.
For example, the following directory structure,
.
└── pages
├── 404.tsx
├── index.tsx
└── users.tsxWill generate the routes,
[
{ path: '/', component: '@/pages/index' },
{ path: '/users', component: '@/pages/users' },
{ path: '/*', component: '@/pages/404' },
]This way, if you access /foo, neither / nor /users can match, so it will fallback to the 404 route rendered by src/pages/404.tsx.
The 404 page automatically applies in convention-based routing only. If using configuration-based routing, you'll need to manually configure the 404 wildcard route.
Page Navigation
For imperative navigation, use the history API
Within components, you can also use the useNavigate hook
Link Component
For example:
import { Link } from 'umi';
export default function Page() {
return (
<Link to="/users">Users Page</Link>
)
}Then, clicking on Users Page navigates to the /users address.
Note:
Linkis used only for internal navigations within a single-page application; use theatag for external navigations.
Route Component Parameters
Umi 4 uses react-router@6 for routing components, and routing parameters can be accessed using its hooks.
match Information
const match = useMatch('/comp/:id')
// match
{
"params": {
"id": "paramId"
},
"pathname": "/comp/paramId/",
"pathnameBase": "/comp/paramId",
"pattern": {
"path": "/comp/:id",
"caseSensitive": false,
"end": true
}
}location Information
const location = useLocation();
// location
{
"pathname": "/path/",
"search": "",
"hash": "",
"state": null,
"key": "default"
}:::warning{title=🚨}
It is recommended to use useLocation instead of directly accessing history.location. The difference is in the pathname part.history.location.pathname is the complete browser path; whereas, useLocation returns the pathname relative to the project's configured base.
Example: If the project is configured with base: '/testbase',
Current browser address is https://localhost:8000/testbase/page/apple
history.location.pathname is /testbase/page/apple
useLocation().pathname is /page/apple
:::
Dynamic Routing Parameters
// Route configuration /comp/:id
// Current location /comp/paramId
const params = useParams();
// params
{
"id": "paramId"
}query Information
// Current location /comp?a=b;
const [searchParams, setSearchParams] = useSearchParams();
searchParams.get('a') // b
searchParams.toString() // a=b
setSearchParams({a:'c',d:'e'}) // location changes to /comp?a=c&d=eRefer to searchParams API for searchParams apis.
Docs Docs Guides Routes (`docs/docs/docs/guides/routes.md`)
order: 3 toc: content
路由
在 Umi 应用是单页应用,页面地址的跳转都是在浏览器端完成的,不会重新请求服务端获取 html,html 只在应用初始化时加载一次。所有页面由不同的组件构成,页面的切换其实就是不同组件的切换,你只需要在配置中把不同的路由路径和对应的组件关联上。
路由类型配置
请参考 history 配置。
配置路由
在配置文件中通过 routes 进行配置,格式为路由信息的数组。
比如:
// .umirc.ts
export default {
routes: [
{ path: '/', component: 'index' },
{ path: '/user', component: 'user' },
],
}Umi 4 默认按页拆包,从而有更快的页面加载速度,由于加载过程是异步的,所以往往你需要编写 loading.tsx 来给项目添加加载样式,提升用户体验。
:::info{title=💡}
你可以在 Chrome Devtools > 网络 Tab 中将网络设置成低速,然后切换路由查看加载组件是否生效。
:::
path
- Type:
string
path 只支持两种占位符配置,第一种是动态参数 :id 的形式,第二种是 * 通配符,通配符只能出现路由字符串的最后。
✅ 以下是目前支持的路由路径配置形式:
/groups
/groups/admin
/users/:id
/users/:id/messages
/files/*
/files/:id/*❌ 以下是目前不支持的路由路径配置形式:
/users/:id?
/tweets/:id(\d+)
/files/*/cat.jpg
/files-*component
- Type:
string
配置 location 和 path 匹配后用于渲染的 React 组件路径。可以是绝对路径,也可以是相对路径。如果是相对路径,会从 src/pages 开始寻找。
如果指向 src 目录的文件,可以用 @,比如 component: '@/layouts/basic',推荐使用 @ 组织路由文件位置。
routes
配置子路由,通常在需要为多个路径增加 layout 组件时使用。
比如:
export default {
routes: [
{ path: '/login', component: 'login' },
{
path: '/',
component: '@/layouts/index',
routes: [
{ path: '/list', component: 'list' },
{ path: '/admin', component: 'admin' },
],
},
],
}在全局布局 src/layouts/index 中,通过 <Outlet/> 来渲染子路由:
import { Outlet } from 'umi'
export default function Page() {
return (
<Outlet/>
)
}这样,访问 /list 和 /admin 就会带上 src/layouts/index 这个 layout 组件。
redirect
- Type:
string
配置路由跳转。
比如:
export default {
routes: [
{ path: '/', redirect: '/list' },
{ path: '/list', component: 'list' },
],
}访问 / 会跳转到 /list 。
重定向时,默认不会携带原 url 的查询参数,如需保持原参数,添加 keepQuery 选项即可:
routes: [
{ path: '/', redirect: '/list', keepQuery: true },
// 注:若你需在跳转时处理参数,可以自行实现一个跳转组件
]wrappers
- Type:
string[]
配置路由组件的包装组件,通过包装组件可以为当前的路由组件组合进更多的功能。
比如,可以用于路由级别的权限校验:
export default {
routes: [
{ path: '/user', component: 'user',
wrappers: [
'@/wrappers/auth',
],
},
{ path: '/login', component: 'login' },
]
}然后在 src/wrappers/auth 中,
import { Navigate, Outlet } from 'umi'
export default (props) => {
const { isLogin } = useAuth();
if (isLogin) {
return <Outlet />;
} else{
return <Navigate to="/login" />;
}
}这样,访问 /user,就通过 auth 组件做权限校验,如果通过,渲染 src/pages/user,否则跳转到 /login。
:::info{title=🚨}wrappers 中的每个组件会给当前的路由组件增加一层嵌套路由,如果你希望路由结构不发生变化,推荐使用高阶组件。先在高阶组件中实现 wrapper 中的逻辑,然后使用该高阶组件装饰对应的路由组件。
:::
举例:
// src/hocs/withAuth.tsx
import { Navigate } from 'umi'
const withAuth = (Component) => ()=>{
const { isLogin } = useAuth();
if (isLogin) {
return <Component />;
} else{
return <Navigate to="/login" />;
}
}// src/pages/user.tsx
const TheOldPage = ()=>{
// ...
}
export default withAuth(TheOldPage)layout
- Type:
boolean
通过配置 layout: false 可以单独关闭某一个路由的全局布局:
// .umirc.ts
export default {
routes: [
// 取消 login 页面的全局布局,从而自行实现整个页面
{ path: '/login', component: '@/pages/Login', layout: false },
],
}注:
全局布局可能来自于
layouts/index.tsx约定,或插件添加的 layout(如@umijs/max自带的 layout 插件将自动添加菜单布局),当配置layout: false时,将取消所有 layout ,此时组件内容占据整个页面,多用于登录页等场景。layout: false仅对一级路由生效,更多例子详见 全局 layout 。
约定式路由
除配置式路由外,Umi 也支持约定式路由。约定式路由也叫文件路由,就是不需要手写配置,文件系统即路由,通过目录和文件及其命名分析出路由配置。
如果没有 routes 配置,Umi 会进入约定式路由模式,然后分析 src/pages 目录拿到路由配置。
比如以下文件结构:
.
└── pages
├── index.tsx
└── users.tsx会得到以下路由配置,
[
{ path: '/', component: '@/pages/index' },
{ path: '/users', component: '@/pages/users' },
]使用约定式路由时,约定
src/pages下所有的(j|t)sx?文件即路由。如果你需要修改默认规则,可以使用 conventionRoutes 配置。
动态路由
约定,带 $ 前缀的目录或文件为动态路由。若 $ 后不指定参数名,则代表 * 通配,比如以下目录结构:
比如:
src/pages/users/$id.tsx会成为/users/:idsrc/pages/users/$id/settings.tsx会成为/users/:id/settings
举个完整的例子,比如以下文件结构,
+ pages/
+ foo/
- $slug.tsx
+ $bar/
- $.tsx
- index.tsx会生成路由配置如下:
[
{ path: '/', component: '@/pages/index.tsx' },
{ path: '/foo/:slug', component: '@/pages/foo/$slug.tsx' },
{ path: '/:bar/*', component: '@/pages/$bar/$.tsx' },
];全局 layout
约定 src/layouts/index.tsx 为全局路由。返回一个 React 组件,并通过 <Outlet /> 渲染嵌套路由。
如以下目录结构:
.
└── src
├── layouts
│ └── index.tsx
└── pages
├── index.tsx
└── users.tsx会生成如下路由:
[
{
path: '/',
component: '@/layouts/index',
routes: [
{ path: '', component: '@/pages/index' },
{ path: 'users', component: '@/pages/users' },
],
},
]可以通过 layout: false 来细粒度关闭某个路由的 全局布局 显示,该选项只在一级生效:
routes: [
{
path: '/',
component: './index',
// 🟢
layout: false
},
{
path: '/users',
routes: [
// 🔴 不生效,此时该路由的 layout 并不是全局布局,而是 `/users`
{ layout: false }
]
}
]一个自定义的全局 layout 格式如下:
import { Outlet } from 'umi'
export default function Layout() {
return <Outlet />
}不同的全局 layout
你可能需要针对不同路由输出不同的全局 layout,Umi 不支持这样的配置,但你仍可以在 src/layouts/index.tsx 中对 location.path 做区分,渲染不同的 layout 。
比如想要针对 /login 输出简单布局,
import { useLocation, Outlet } from 'umi';
export default function() {
const location = useLocation();
if (location.pathname === '/login') {
return <SimpleLayout><Outlet /></SimpleLayout>
}
// 使用 `useAppData` / `useSelectedRoutes` 可以获得更多路由信息
// const { clientRoutes } = useAppData()
// const routes = useSelectedRoutes()
return (
<>
<Header />
<Outlet />
<Footer />
</>
);
}404 路由
约定 src/pages/404.tsx 为 404 页面,需返回 React 组件。
比如以下目录结构,
.
└── pages
├── 404.tsx
├── index.tsx
└── users.tsx会生成路由,
[
{ path: '/', component: '@/pages/index' },
{ path: '/users', component: '@/pages/users' },
{ path: '/*', component: '@/pages/404' },
]这样,如果访问 /foo,则 / 和 /users 都不能匹配,于是会 fallback 到 404 路由,通过 src/pages/404.tsx 进行渲染。
404 只有约定式路由会自动生效,如果使用配置式路由,需要自行配置 404 的通配路由。
页面跳转
命令式跳转请使用 history API
组件内还可以使用 useNavigate hook
Link 组件
比如:
import { Link } from 'umi';
export default function Page() {
return (
<Link to="/users">Users Page</Link>
)
}然后点击 Users Page 就会跳转到 /users 地址。
注意:
Link只用于单页应用的内部跳转,如果是外部地址跳转请使用a标签
路由组件参数
Umi 4 使用 react-router@6 作为路由组件,路由参数的获取使其 hooks。
match 信息
const match = useMatch('/comp/:id')
// match
{
"params": {
"id": "paramId"
},
"pathname": "/comp/paramId/",
"pathnameBase": "/comp/paramId",
"pattern": {
"path": "/comp/:id",
"caseSensitive": false,
"end": true
}
}location 信息
const location = useLocation();
// location
{
"pathname": "/path/",
"search": "",
"hash": "",
"state": null,
"key": "default"
}:::warning{title=🚨}
推荐使用 useLocation, 而不是直接访问 history.location. 两者的区别是 pathname 的部分。history.location.pathname 是完整的浏览器的路径名;而 useLocation 中返回的 pathname 是相对项目配置的base的路径。
举例:项目如果配置 base: '/testbase',
当前浏览器地址为 https://localhost:8000/testbase/page/apple
history.location.pathname 为 /testbase/page/apple
useLocation().pathname 为 /page/apple
:::
路由动态参数
// 路由配置 /comp/:id
// 当前 location /comp/paramId
const params = useParams();
// params
{
"id": "paramId"
}query 信息
// 当前 location /comp?a=b;
const [searchParams, setSearchParams] = useSearchParams();
searchParams.get('a') // b
searchParams.toString() // a=b
setSearchParams({a:'c',d:'e'}) // location 变成 /comp?a=c&d=esearchParams的 api 参考
Docs Docs Guides Styling.En US (`docs/docs/docs/guides/styling.en-US.md`)
order: 7 toc: content translated_at: '2024-03-17T10:21:09.036Z'
Styles
This document introduces various ways of using styles in Umi projects.
Using CSS Styles
You can declare various styles in .css files in Umi projects, and then import them in .js files to make them effective.
For example, declare the style of the .title class as red in the src/pages/index.css file according to the following code:
.title {
color: red;
}Then import it in the src/pages/index.tsx file to make it effective.
// src/pages/index.tsx
import './index.css';
export default function () {
return Hello World;
}The style imported in this way will be effective throughout the Umi project, i.e., regardless of which .js
file you import from, its declared style can be used on any page and component. If you want to avoid this situation, you can use the CSS Modules feature to limit the scope of the style.
Using CSS Modules
When importing styles in js files, if you assign it a variable name, you can import the style as a CSS Module.
// src/pages/index.tsx
import styles from './index.css';
export default function () {
return
Hello World
;
}In the example above, the styles declared in the index.css file will not affect the global styles and will only be effective for styles used from the styles variable.
Using CSS Preprocessors
Umi natively supports importing LESS (recommended), SASS, and SCSS styles. You can directly import and use these styles processed by CSS preprocessors in the same way as you import CSS files.
:::info{title=💡}
To use SASS (SCSS) in Umi, you need to install additional preprocessor dependencies, such as: npm add -D sass
:::
// src/pages/index.tsx
import './index.less';
import './index.sass';
import './index.scss';
export default function () {
return Hello World;
}CSS Module usage is also supported:
// src/pages/index.tsx
import lessStyles from './index.less';
import sassStyles from './index.sass';
import scssStyles from './index.scss';
export default function () {
return
Hello World
I am blue
I am red
;
}Umi also provides built-in support for .styl and .stylus files. stylus related preprocessor dependency must be installed first. Other usage is similar to the examples above.
# .styl and .stylus
npm add -D stylusAdvanced Settings
If you need to use other style preprocessors besides the common LESS, SASS, or SCSS, you can add your own Loader through the chainWebpack interface provided by Umi plugins.
Using Tailwindcss
Umi provides an internal Tailwindcss
plugin, and you can easily enable it using the Micro-generator.
Using UnoCSS
Similar to Tailwindcss, Umi also provides an internal UnoCSS plugin, which can be enabled in the same way.
- Install
plugin-unocss - Install
unocssand@unocss/cli
pnpm i unocss @unocss/cli- Enable the plugin in Umi settings and declare the file directory where
unocsswill be used
// .umirc.ts
export default {
plugins: [
require.resolve('@umijs/plugins/dist/unocss')
],
unocss: {
// Range of files to detect className, if the project does not include the src directory, use `pages/**/*.tsx`
watch: ['src/**/*.tsx']
},
};- Add a
unocss.config.ts
configuration file in the project directory and include the UnoCSS Presets needed by the project
// unocss.config.ts
import {defineConfig, presetAttributify, presetUno} from 'unocss';
export function createConfig({strict = true, dev = true} = {}) {
return defineConfig({
envMode: dev ? 'dev' : 'build', presets: [presetAttributify({strict}), presetUno()],
});
}
export default createConfig();- Start the project for development. The plugin will listen to the
unocss.watchfield in the settings file, dynamically generate style files, and automatically apply them.
Docs Docs Guides Styling (`docs/docs/docs/guides/styling.md`)
order: 7 toc: content
样式
本文介绍在 Umi 项目中使用样式的各种方式。
使用 CSS 样式
你可以在 Umi 项目中使用 .css 文件声明各种样式,然后在 .js 文件中引入即可生效。
例如,在 src/pages/index.css 文件按照以下代码声明 .title 类的样式为红色:
.title {
color: red;
}然后在 src/pages/index.tsx 文件中引入即可生效。
// src/pages/index.tsx
import './index.css';
export default function () {
return Hello World;
}按照此种引入方式的样式会在整个 Umi 项目中生效,即无论你从哪个 .js
文件引入,他声明的样式可以在任何页面和组件中使用。如果你想要避免这种情况,可以使用 CSS Modules 的功能来限制样式的作用域。
使用 CSS Modules
在 js 文件中引入样式时,如果赋予他一个变量名,就可以将样式以 CSS Module 的形式引入。
// src/pages/index.tsx
import styles from './index.css';
export default function () {
return
Hello World
;
}上面的示例中,index.css 文件中声明的样式不会对全局样式造成影响,只会对从 styles 变量中使用的样式生效。
使用 CSS 预处理器
Umi 默认支持 LESS (推荐),SASS 和 SCSS 样式的导入,你可以直接按照引入 CSS 文件的方式引入并使用这些由 CSS 预处理器处理的样式。
:::info{title=💡}
在 Umi 中使用 SASS (SCSS) 需要额外安装预处理依赖 如: npm add -D sass
:::
// src/pages/index.tsx
import './index.less';
import './index.sass';
import './index.scss';
export default function () {
return Hello World;
}同样也支持 CSS Module 的用法:
// src/pages/index.tsx
import lessStyles from './index.less';
import sassStyles from './index.sass';
import scssStyles from './index.scss';
export default function () {
return
Hello World
I am blue
I am red
;
}Umi 也同时提供了对 .styl 和 .stylus 文件的内置支持。使用前必须安装 stylus 相应的预处理器依赖,其他用法用上面的例子
# .styl and .stylus
npm add -D stylus进阶设置
如果你需要使用除了常见的 LESS, SASS 或 SCSS 以外的其他样式预处理器,你可以透过 Umi
插件提供的 chainWebpack 接口来加入自己需要的 Loader。
使用 Tailwindcss
Umi 提供了内置的 Tailwindcss
插件,并且可以直接方便地使用 微生成器 来启用。
使用 UnoCSS
与 Tailwindcss 相同,Umi 也提供了内置的 UnoCSS 插件,可以按照相同方式开启。
- 安装
plugin-unocss - 安装
unocss及@unocss/cli
pnpm i unocss @unocss/cli- 在 Umi 设置中启用插件,并声明会用到
unocss的文件目录
// .umirc.ts
export default {
plugins: [
require.resolve('@umijs/plugins/dist/unocss')
],
unocss: {
// 检测 className 的文件范围,若项目不包含 src 目录,可使用 `pages/**/*.tsx`
watch: ['src/**/*.tsx']
},
};- 在项目目录下加入
unocss.config.ts
配置文件,并加入项目需要的 UnoCSS Presets
// unocss.config.ts
import {defineConfig, presetAttributify, presetUno} from 'unocss';
export function createConfig({strict = true, dev = true} = {}) {
return defineConfig({
envMode: dev ? 'dev' : 'build', presets: [presetAttributify({strict}), presetUno()],
});
}
export default createConfig();- 启动项目进行开发,插件会监听设置文件中的
unocss.watch字段,动态生成样式文件并自动套用
Docs Docs Guides Test.En US (`docs/docs/docs/guides/test.en-US.md`)
order: 15 toc: content translated_at: '2024-03-17T10:00:43.176Z'
Testing
Automated testing is an effective means to ensure quality, and Umi 4 provides scaffolding for unit testing. Umi 4 recommends using Jest and @testing-library/react to complete the unit tests in your project.
Configuration
Quickly configure Jest using Umi 4's micro-generator reference, and if you need to modify the Jest related configuration, you can do so in jest.config.ts.
Umi project
import { Config, configUmiAlias, createConfig } from 'umi/test';
export default async () => {
return (await configUmiAlias({
...createConfig({
target: 'browser',
jsTransformer: 'esbuild',
jsTransformerOpts: { jsx: 'automatic' },
}),
// Override the default Jest configuration of Umi, such as
// displayName: "Umi jest",
})) as Config.InitialOptions;
};@umijs/max project
import { Config, configUmiAlias, createConfig } from '@umijs/max/test';
export default async () => {
return (await configUmiAlias({
...createConfig({
target: 'browser',
jsTransformer: 'esbuild',
jsTransformerOpts: { jsx: 'automatic' },
}),
// Override the default Jest configuration of Umi, such as
// displayName: "Umi jest",
})) as Config.InitialOptions;
};After configuration, you can start writing unit tests.
Non-UI Related Tests
Suppose we need to test a utils function reverseApiData that swaps the keys and values of the data object from an API request's result.
It's recommended to place the test file in the same directory as the module being tested for easy review and understanding of the module's functionality.
.
└── utils
├── reverseApiData.test.ts
└── reverseApiData.ts// utils/reverseApiData.ts
export async function reverseApiData(url: string, fetcher = fetch) {
const res = await fetcher(url);
const json = await res.json();
const { data = {} } = json;
const reversed: Record<string, any> = {};
for (const key of Object.keys(data)) {
const val = data[key];
reversed[val] = key;
}
return reversed;
}First, let's write our first test case to ensure fetcher uses the passed url to request API data
import { reverseApiData } from './reverseApiData';
// The test case name indicates the purpose of the test
test('reverseApiData use fetcher to request url', async () => {
// Test cases are written in the structure of 3A
// Arrange stage, prepare mock functions or data
const fetcher = jest.fn().mockResolvedValue({
json: () => Promise.resolve(),
});
// Act on the object under test
await reverseApiData('https://api.end/point', fetcher);
// Assert the test result
expect(fetcher).toBeCalledWith('https://api.end/point');
});Run the tests
$npx jest
info - generate files
PASS src/utils/reverseApiData.test.ts
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 0.894 s, estimated 1 s
Ran all test suites.:::info{title=💡}
You can use npx jest --watch to keep the Jest process alive, saving you the wait time to start Jest again.
:::
Let's write another case to test the utility function's ability to reverse key-value pairs.
test('reverseApiData reverse simple object', async () => {
const fetcher = jest.fn().mockResolvedValue({
json: () => Promise.resolve({ data: { a: 'b' } }),
});
const reversed = await reverseApiData('url', fetcher);
expect(reversed).toEqual({ b: 'a' });
});Having each test case focus on one aspect of functionality can give us more accurate feedback during refactoring, such as what functionality a change has broken. Please refer to more cases code.
UI Testing
For component and UI-related testing, the use of @testing-library/react is recommended.
Judging the Rendering Result
- Using Jest's snapshot
// examples/test-test/components/Greet/Greet.test.tsx
import { render } from '@testing-library/react';
import React from 'react';
import Greet from './Greet';
test('renders Greet without name by snapshot', () => {
const { container } = render(<Greet />);
expect(container).toMatchSnapshot();
});After running npx jest, a __snapshots__ folder and the snapshot of the test case will be generated in the directory alongside the test cases. Please add them to version control.
- Using Jest's inline snapshot
// examples/test-test/components/Greet/Greet.test.tsx
test('renders Greet without name by inline snapshot', () => {
const { container } = render(<Greet />);
expect(container).toMatchInlineSnapshot();
});After running npx jest, the snapshot string will be filled in the parameters of the toMatchInlineSnapshot function. This method is suitable for short rendering results.
- Using @testing-library/jest-dom assertions
// examples/test-test/components/Greet/Greet.test.tsx
import '@testing-library/jest-dom';
test('renders Greet without name assert by testing-library', () => {
const { container } = render(<Greet />);
const greetDom = screen.getByText('Anonymous');
expect(greetDom).toBeInTheDocument();
});For more assertion APIs
Judging Component Behavior
// examples/test-test/components/Greet/Greet.test.tsx
test('Greet click', async () => {
const onClick = jest.fn();
const { container } = render(<Greet onClick={onClick} />);
const greetDom = screen.getByText('Anonymous');
await fireEvent.click(screen.getByText(/hello/i));
expect(onClick).toBeCalledTimes(1);
});Docs Docs Guides Test (`docs/docs/docs/guides/test.md`)
order: 15 toc: content
测试
自动化测试是保障质量的有效手段,Umi 4 提供单元测试的脚手架。Umi 4 推荐使用 Jest 和 @testing-library/react 来完成项目中的单元测试。
配置
使用 Umi 4 的微生成器快速地配置好 Jest 参考,如果你需要修改 jest 相关的配置,可以在 jest.config.ts 修改。
umi 项目
import { Config, configUmiAlias, createConfig } from 'umi/test';
export default async () => {
return (await configUmiAlias({
...createConfig({
target: 'browser',
jsTransformer: 'esbuild',
jsTransformerOpts: { jsx: 'automatic' },
}),
// 覆盖 umi 的默认 jest 配置, 如
// displayName: "Umi jest",
})) as Config.InitialOptions;
};@umijs/max 项目
import { Config, configUmiAlias, createConfig } from '@umijs/max/test';
export default async () => {
return (await configUmiAlias({
...createConfig({
target: 'browser',
jsTransformer: 'esbuild',
jsTransformerOpts: { jsx: 'automatic' },
}),
// 覆盖 umi 的默认 jest 配置, 如
// displayName: "Umi jest",
})) as Config.InitialOptions;
};配置完后,就可以开始编写单元测试了。
与 UI 无关的测试
假设我们需要测试一个 utils 函数 reverseApiData, 它将 api 请求的结果 data 对象的 key 和 value 互换。
我们推荐将测试文件和被测模块放在同一级目录,这样可以方便查看测试文件以便理解模块的功能。
.
└── utils
├── reverseApiData.test.ts
└── reverseApiData.ts// utils/reverseApiData.ts
export async function reverseApiData(url: string, fetcher = fetch) {
const res = await fetcher(url);
const json = await res.json();
const { data = {} } = json;
const reversed: Record<string, any> = {};
for (const key of Object.keys(data)) {
const val = data[key];
reversed[val] = key;
}
return reversed;
}先来写我们第一个测试用例, 确保 fetcher 使用传入的 url 请求 api 的数据
import { reverseApiData } from './reverseApiData';
// 测试用例名字表明测试的目的
test('reverseApiData use fetcher to request url', async () => {
// 测试用例以 3A 的结构来写
// Arrange 准备阶段,准备 mock 函数或者数据
const fetcher = jest.fn().mockResolvedValue({
json: () => Promise.resolve(),
});
// Act 执行被测对象
await reverseApiData('https://api.end/point', fetcher);
// Assert 断言测试结果
expect(fetcher).toBeCalledWith('https://api.end/point');
});执行测试
$npx jest
info - generate files
PASS src/utils/reverseApiData.test.ts
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 0.894 s, estimated 1 s
Ran all test suites.:::info{title=💡}
可以使用npx jest --watch 让 jest 进程不退出,这样能省去启动重新 jest 的等待时间。
:::
我们再写一个用例来测试这个工具函数完成了键值的对换功能。
test('reverseApiData reverse simple object', async () => {
const fetcher = jest.fn().mockResolvedValue({
json: () => Promise.resolve({ data: { a: 'b' } }),
});
const reversed = await reverseApiData('url', fetcher);
expect(reversed).toEqual({ b: 'a' });
});让每个测试用例只关注一个功能点,可以让用例在重构的时候给我们更准确的反馈,例如改动破坏了什么功能。更多的用例请参考 代码。
UI 测试
组件和 UI 相关的测试推荐使用 @testing-library/react。
渲染结果判断
- 使用 jest 的 snapshot
// examples/test-test/components/Greet/Greet.test.tsx
import { render } from '@testing-library/react';
import React from 'react';
import Greet from './Greet';
test('renders Greet without name by snapshot', () => {
const { container } = render(<Greet />);
expect(container).toMatchSnapshot();
});执行 npx jest 后会在测试用例同级目录会生成 __snapshots__ 文件夹和用例的 snapshot,请加入到版本管理中。
- 使用 jest 的 inline snapshot
// examples/test-test/components/Greet/Greet.test.tsx
test('renders Greet without name by inline snapshot', () => {
const { container } = render(<Greet />);
expect(container).toMatchInlineSnapshot();
});执行 npx jest 后会在 toMatchInlineSnapshot 函数的参数中填入 snapshot 字符串;这种方式适合渲染结果比较短的内容。
- 使用 @testing-library/jest-dom 断言
// examples/test-test/components/Greet/Greet.test.tsx
import '@testing-library/jest-dom';
test('renders Greet without name assert by testing-library', () => {
const { container } = render(<Greet />);
const greetDom = screen.getByText('Anonymous');
expect(greetDom).toBeInTheDocument();
});更多断言 API
组件行为判断
// examples/test-test/components/Greet/Greet.test.tsx
test('Greet click', async () => {
const onClick = jest.fn();
const { container } = render(<Greet onClick={onClick} />);
const greetDom = screen.getByText('Anonymous');
await fireEvent.click(screen.getByText(/hello/i));
expect(onClick).toBeCalledTimes(1);
});Docs Docs Guides Typescript.En US (`docs/docs/docs/guides/typescript.en-US.md`)
order: 9 toc: content translated_at: '2024-03-17T09:59:29.526Z'
TypeScript
Umi enables TypeScript by default. If the project is created using the official scaffolding, the built-in files are mainly in the format of xx.(ts|tsx).
TypeScript Hints in Configuration
If you want to have TypeScript syntax hints during configuration, you can wrap the configuration with defineConfig():
// .umirc.ts
import { defineConfig } from 'umi';
export default defineConfig({
routes: [
{ path: '/', component: '@/pages/index' },
],
});Docs Docs Guides Typescript (`docs/docs/docs/guides/typescript.md`)
order: 9 toc: content
TypeScript
Umi 默认开启 TypeScript,如果是使用官方脚手架创建项目,内置的文件是以 xx.(ts|tsx) 为主的。
配置中的 TypeScript 提示
如果想要在配置时拥有 TypeScript 语法提示,可以在配置的地方包一层 defineConfig():
// .umirc.ts
import { defineConfig } from 'umi';
export default defineConfig({
routes: [
{ path: '/', component: '@/pages/index' },
],
});Docs Docs Guides Use Plugins.En US (`docs/docs/docs/guides/use-plugins.en-US.md`)
order: 4 toc: content translated_at: '2024-03-17T09:58:19.770Z'
Plugins
Using Plugins
In a standard Umi application, by default, no plugins are included. If you want to use Max's features (such as data flow, antd, etc.), you will need to manually install the plugins and enable them:
pnpm add -D @umijs/pluginsTo enable the antd plugin:
// .umirc.ts
export default {
plugins: ['@umijs/plugins/dist/antd'],
antd: {}
}The difference between Umi and Max is that Max already has most plugins built-in, such as data flow (initial-state, model), antd, etc. These plugins can be loaded and enabled from @umijs/plugins/dist/*.
For further information about the features and configuration instructions offered by Max, please refer to the Umi Max section.
:::info{title=💡}
Should I choose Max?
Using Max does not mean you have to use all of its features; you can disable plugins according to your needs. So, when you need Max's features, you can always choose to create a Max project.
:::
Project-level Plugins
If you want to quickly use the functionality of plugins in your project (such as modifying the output html), you can create a plugin.ts in the project's root directory to write a project-level plugin, which will be automatically loaded as a plugin.
For a more detailed explanation of the directory structure, please refer to the Directory Structure section.
Developing Plugins
Please refer to the Developing Plugins section.
Docs Docs Guides Use Plugins (`docs/docs/docs/guides/use-plugins.md`)
order: 4 toc: content
插件
使用插件
在普通的 Umi 应用中,默认 不附带任何插件 ,如需使用 Max 的功能(如 数据流、antd 等),需要手动安装插件并开启他们:
pnpm add -D @umijs/plugins如开启 antd 插件:
// .umirc.ts
export default {
plugins: ['@umijs/plugins/dist/antd'],
antd: {}
}Umi 与 Max 的区别是 Max 已经内置了大部分插件,如 数据流( initial-state 、 model )、antd 等,这些插件都可以在 @umijs/plugins/dist/* 加载并且开启。
如需进一步了解 Max 具备的功能和配置说明,请参阅 Umi Max 章节。
:::info{title=💡}
我是否应该选择 Max ?
使用 Max 并不代表需要使用全部 Max 的功能,可以根据需求关闭插件,所以当你需要使用 Max 的功能时,可以总是选择创建 Max 项目。
:::
项目级插件
若你想在项目中快速使用插件的功能(如 修改产物的 html ),可以在项目的根目录创建 plugin.ts 编写一个项目级插件,该文件将被自动作为插件加载。
有关更详细的目录结构说明请参阅 目录结构 章节。
开发插件
请参阅 开发插件 章节。
Docs Docs Guides Use Vue.En US (`docs/docs/docs/guides/use-vue.en-US.md`)
order: 17 toc: content translated_at: '2024-03-17T09:57:48.935Z'
Using Vue
This article introduces how to use Vue in Umi, where most of the configuration for Umi Vue is the same as React. Here we only list some configurations unique to Vue.
How to Start
Installation
pnpm add @umijs/preset-vue -DConfigure Preset
// In .umirc.ts or config/config.ts
export default {
presets: [require.resolve('@umijs/preset-vue')],
};Routing
Config-based Routing
:::info
Here, we only list the parts that are different from React router configuration.
:::
name
Named routes
In addition to path, you can provide a name for any route:
export default {
routes: [
{
path: '/user/:username',
name: 'user',
component: 'index'
}
]
}To link to a named route, you can pass an object to the to property of the router-link component:
<router-link :to="{ name: 'user', params: { username: 'erina' }}">
User
</router-link>The effect is the same as imperatively calling router.push:
router.push({ name: 'user', params: { username: 'erina' } })Both methods navigate to the path /user/erina.
redirect
You also use the routes configuration for redirections; for example, redirecting from /home to /:
export default {
routes: [
{
path: '/home',
redirect: '/'
}
]
}The target of the redirect can also be a named route:
export default {
routes: [
{
path: '/home',
redirect: {
name: 'homepage'
}
}
]
}alias
Redirect means that when a user accesses /home, the URL will be replaced with / and then matched to /. But what is an alias?
Aliasing / to /home means that when the user visits /home, the URL still is /home, but it will be matched as if the user is visiting /.
The corresponding route configuration is:
export default {
routes: [
{
path: '/',
component: 'index',
alias: '/home'
}
]
}With aliases, you are free to map your UI structure to any URL without being constrained by the nesting structure of the configuration. Make aliases start with / to turn nested paths into absolute paths. You can even combine the two by providing multiple aliases with an array:
export default {
routes: [
{
path: '/users',
component: 'users',
routes: [
// Render UserList for these 3 URLs
// - /users
// - /users/list
// - /people
{ path: '', component: '/users/UserList', alias: ['/people', 'list'] },
]
}
]
}Page Navigation
<script lang="ts" setup>
import { useRouter, useRoute } from 'umi';
const router = useRouter()
const route = useRoute()
const onHello = () => {
router.push({
name: 'search',
query: {
...route.query,
},
})
}
</script>For more details, see here
router-link
For more details, see here
router-view
For more details, see here
Runtime Configuration
You can control Vue and Vue-router related configuration through exporting settings in the conventional src/app.tsx.
router
Configure router settings
// src/app.tsx
export const router: RouterConfig = {
// @ts-ignore
scrollBehavior(to, from) {
console.log('scrollBehavior', to, from);
},
};onMounted({app, router})
Callback for successful Vue app mount, where you can get instances of app and router for global component registration, route interceptors, etc.
export function onMounted({ app, router }: any) {
console.log('onMounted', app, router);
app.provide('umi-hello', {
h: 'hello',
w: 'word',
});
}rootContainer(container)
Modify the root component rendered by vue-router.
For example, to wrap a parent component around the outside
import { h } from 'vue'
export function rootContainer(container) {
return h(ThemeProvider, null, container);
}Examples
For more examples, see demos:
Docs Docs Guides Use Vue (`docs/docs/docs/guides/use-vue.md`)
order: 17 toc: content
使用 Vue
本文介绍如何在 Umi 中使用 Vue , Umi Vue 大部分配置和 React 相同,这里只列出一些 Vue 独有的配置。
启动方式
安装
pnpm add @umijs/preset-vue -D配置预设
// .umirc.ts or config/config.ts 中
export default {
presets: [require.resolve('@umijs/preset-vue')],
};路由
配置式路由
:::info
这里仅列出和 React 路由配置差异部分。
:::
name
命名路由
除了 path 之外,你还可以为任何路由提供 name :
export default {
routes: [
{
path: '/user/:username',
name: 'user',
component: 'index'
}
]
}要链接到一个命名的路由,可以向 router-link 组件的 to 属性传递一个对象:
<router-link :to="{ name: 'user', params: { username: 'erina' }}">
User
</router-link>效果和命令式地调用 router.push 一致:
router.push({ name: 'user', params: { username: 'erina' } })在这方法都能导航到路径 /user/erina。
redirect
重定向也是通过 routes 配置来完成,下面例子是从 /home 重定向到 /:
export default {
routes: [
{
path: '/home',
redirect: '/'
}
]
}重定向的目标也可以是一个命名的路由:
export default {
routes: [
{
path: '/home',
redirect: {
name: 'homepage'
}
}
]
}alias
重定向是指当用户访问 /home 时,URL 会被 / 替换,然后匹配成 /。那么什么是别名呢?
将 / 别名为 /home,意味着当用户访问 /home 时,URL 仍然是 /home,但会被匹配为用户正在访问 /。
上面对应的路由配置为:
export default {
routes: [
{
path: '/',
component: 'index',
alias: '/home'
}
]
}通过别名,你可以自由地将 UI 结构映射到一个任意的 URL,而不受配置的嵌套结构的限制。使别名以 / 开头,以使嵌套路径中的路径成为绝对路径。你甚至可以将两者结合起来,用一个数组提供多个别名:
export default {
routes: [
{
path: '/users',
component: 'users',
routes: [
// 为这 3 个 URL 呈现 UserList
// - /users
// - /users/list
// - /people
{ path: '', component: '/users/UserList', alias: ['/people', 'list'] },
]
}
]
}页面跳转
<script lang="ts" setup>
import { useRouter, useRoute } from 'umi';
const router = useRouter()
const route = useRoute()
const onHello = () => {
router.push({
name: 'search',
query: {
...route.query,
},
})
}
</script>更多详见
router-link
router-view
运行时配置
可以通过在约定的 src/app.tsx 通过 export 配置来控制 vue vue-router 相关的配置。
router
配置路由配置
// src/app.tsx
export const router: RouterConfig = {
// @ts-ignore
scrollBehavior(to, from) {
console.log('scrollBehavior', to, from);
},
};onMounted(\{app, router\})
Vue app mount 成功回调,这里可以拿到 app 的实例及 router 的实例,可以进行全局组件注册,路由拦截器等。
export function onMounted({ app, router }: any) {
console.log('onMounted', app, router);
app.provide('umi-hello', {
h: 'hello',
w: 'word',
});
}rootContainer(container)
修改交给 vue-router 渲染时的根组件。
比如用于在外面包一个父组件
import { h } from 'vue'
export function rootContainer(container) {
return h(ThemeProvider, null, container);
}Examples
更多详见 demo :
Docs Docs Introduce Contributing.En US (`docs/docs/docs/introduce/contributing.en-US.md`)
order: 3 toc: content translated_at: '2024-03-17T09:56:34.842Z'
Contributing
❤️ Loving Umi and want to get involved? Thanks!
Environment Setup
Node.js and pnpm
Developing Umi requires Node.js 18+ and pnpm v8.
It's recommended to use Volta to manage the node and pnpm version. And you need to set the VOLTA_FEATURE_PNPM environment variable to enable pnpm support.
export VOLTA_FEATURE_PNPM=1Clone the Project
$ git clone [email protected]:umijs/umi.git
$ cd umiInstall Dependencies and Build
$ pnpm i && pnpm buildDeveloping Umi
Start the dev Command
A must-run command for local development of Umi, for compiling TypeScript files in the src directory to the dist directory, and incrementally compiles on file changes.
$ pnpm devIf you find it rather slow, you can also run the pnpm dev command for a specific package, for example.
$ cd packages/umi
$ pnpm devRun Example
The examples directory contains various examples used for testing. Running an example is a common way to ensure functions work correctly during Umi development. Each example is equipped with a dev script, so just enter the example and run pnpm dev.
$ cd examples/boilerplate
$ pnpm devTo run in vite mode, add the --vite argument,
$ pnpm dev --viteTesting
Currently running tests is fast, completing in just over 10s+. It’s recommended to run tests locally before submitting a PR to reduce Round Trips.
$ pnpm test
...
Test Suites: 1 skipped, 43 passed, 43 of 44 total
Tests: 6 skipped, 167 passed, 173 total
Snapshots: 0 total
Time: 13.658 s
Ran all test suites.To run tests for specific files, use pnpm jest because pnpm test runs with turborepo enabled.
For example,
$ pnpm jest packages/plugin-docs/src/compiler.test.tsContributing to Umi Documentation
Umi's documentation is implemented by Umi@4 and the @umijs/plugin-docs plugin, essentially an Umi project. Execute the following commands in the root directory to start development:
# Start Umi document development
# Compilation takes longer on first launch, please be patient
$ pnpm docs:devOpen the specified port number to see real-time updates of documentation and results of the @umijs/plugin-docs plugin development.
Writing Umi Documentation
Umi documentation is written in MDX format. MDX is an extension of the Markdown format allowing you to insert JSX components while writing Umi documentation.
:::success{title=🏆︎}
When writing Documentation, you can find usable components in packages/plugin-docs/client/theme-doc/components. When writing Blog posts, usable components can be found in packages/plugin-docs/client/theme-blog/components.
:::
Umi documentation code highlighting is based on Rehype Pretty Code, for full capabilities and instructions, please visit its official documentation.
Execute the following command in the root directory to format existing Umi documentation:
$ pnpm format:docsAfter formatting the documentation, it is recommended to only submit the Umi documents you have written or modified. Different document contributors have varying writing styles and formatting may not always retain the original intended style.
Participating in Umi Documentation Plugin Development
Open a new terminal and execute the following commands:
$ cd packages/plugin-docs
$ pnpm dev:cssNow, when you modify the tailwind.css file or TailwindCSS style classes during development, a tailwind.out.css stylesheet file will be automatically compiled and generated.
Umi listens for changes in the docs and packages/plugin-docs/client directories, but does not listen to changes in packages/plugin-docs/src.
:::info{title=💡}
If you need to compile files in packages/plugin-docs/src, please move to the packages/plugin-docs directory and execute the pnpm build command, then restart development.
:::
Executing the following command in the root directory to format the code of Umi documentation plugin:
$ pnpm format:plugin-docsExecute the following command in the root directory to build Umi documentation:
$ pnpm docs:buildAdding a New Package
Adding a new package has a script encapsulated, no need to manually copy package.json, etc.:
# Create package directory
$ mkdir packages/foo
# Initialize package development
$ pnpm bootstrapUpdating Dependencies
It is not recommended for non-Core Maintainers to update a large number of dependencies because it involves pre-packaging of dependencies and there are many points to note.
Run pnpm dep:update to update dependencies.
$ pnpm dep:updateSince Umi pre-packages some dependencies, after updating dependencies, it's necessary to check if the updated dependency is in devDependencies and whether it has been pre-packaged. If so, execute pnpm build:deps in the corresponding package and specify the dependency to update pre-packaged dependency files.
$ pnpm build:deps --dep webpack-manifest-pluginRelease
Only Core Maintainers can carry out releases. Umi now uses npm Trusted Publishing/OIDC, so the local release command only bumps versions, creates and pushes the release commit/tag, and no longer runs npm publish locally.
$ pnpm releaseAfter the push, the GitHub Actions Release workflow obtains npm publishing permission through OIDC, runs pnpm release:publish, and publishes packages to npm with --provenance.
Rolling Back through dist-tag
For example, to roll back to 4.0.81.
$ pnpm -rc --filter "./packages/**" exec pnpm dist-tag add \[email protected] latestJoining the Contributor Group
For those who have submitted Bugfix or Feature type PRs and are interested in participating in maintaining Umi, you can first scan the QR code below with DingTalk (mention your github id), and then I will add you to the group.

If you don't know what you can contribute, you can search TODO or FIXME in the source code.
Docs Docs Introduce Contributing (`docs/docs/docs/introduce/contributing.md`)
order: 3 toc: content
参与贡献
❤️ Loving Umi and want to get involved? Thanks!
环境准备
Node.js 和 pnpm
开发 Umi 需要 Node.js 18+ 和 pnpm v8。
推荐使用 Volta 来管理 Node.js 和 pnpm 版本。并且需要设置 VOLTA_FEATURE_PNPM 环境变量来启用 pnpm 支持。
export VOLTA_FEATURE_PNPM=1Clone 项目
$ git clone [email protected]:umijs/umi.git
$ cd umi安装依赖并构建
$ pnpm i && pnpm build开发 Umi
启动 dev 命令
本地开发 Umi 必开命令,用于编译 src 下的 TypeScript 文件到 dist 目录,同时监听文件变更,有变更时增量编译。
$ pnpm dev如果觉得比较慢,也可以只跑特定 package 的 pnpm dev 命令,比如。
$ cd packages/umi
$ pnpm dev跑 Example
examples 目录下保存了各种用于测试的例子,跑 example 是开发 Umi 时确认功能正常的常用方式。每个 example 都配了 dev 脚本,所以进入 example 然后执行 pnpm dev 即可。
$ cd examples/boilerplate
$ pnpm dev如果要用 vite 模式跑,加 --vite 参数,
$ pnpm dev --vite测试
目前跑测试很快,10s+ 就完成了。推荐本地跑一遍再提 PR,减少 Round Trip。
$ pnpm test
...
Test Suites: 1 skipped, 43 passed, 43 of 44 total
Tests: 6 skipped, 167 passed, 173 total
Snapshots: 0 total
Time: 13.658 s
Ran all test suites.如果需要只跑部分文件的用例,用 pnpm jest,因为 pnpm test 是开了 turborepo 功能的。
比如,
$ pnpm jest packages/plugin-docs/src/compiler.test.ts贡献 Umi 文档
Umi 的文档由 Umi@4 和 @umijs/plugin-docs 插件实现,本质上就是一个 Umi 项目。在根目录执行如下命令即可开始 Umi 文档的开发:
# 启用 Umi 文档开发
# 首次启动时编译耗时较长,请耐心等待
$ pnpm docs:dev打开指定的端口号,即可实时查看文档更新的内容,以及 @umijs/plugin-docs 插件开发的成果。
撰写 Umi 文档
Umi 文档的编写基于 MDX 格式。MDX 是 Markdown 格式的拓展,允许您在撰写 Umi 文档时插入 JSX 组件。
:::success{title=🏆︎}
撰写 文档(Document) 时,可用的组件可以在 packages/plugin-docs/client/theme-doc/components 目录下找到。撰写 博客(Blog) 时,可用的组件可以在 packages/plugin-docs/client/theme-blog/components 目录下找到。
:::
Umi 文档的代码高亮基于 Rehype Pretty Code,完整的能力和使用说明请移步它的官方文档。
在根目录执行如下命令可以格式化仓库中已有的 Umi 文档:
$ pnpm format:docs格式化文档后,建议仅提交您撰写或修改的 Umi 文档。不同文档贡献者的写作风格有一定的差异,格式化以后不一定能保留原来期望的样式。
参与 Umi 文档插件开发
新建一个终端,执行如下命令:
$ cd packages/plugin-docs
$ pnpm dev:css现在,当您修改了 tailwind.css 文件或在开发时修改了 TailwindCSS 样式类时,会自动编译并生成 tailwind.out.css 样式表文件。
Umi 会监听 docs 和 packages/plugin-docs/client 目录下文件的变化,而不会监听 packages/plugin-docs/src 目录。
:::info{title=💡}
如果您需要编译 packages/plugin-docs/src 中的文件,请移动到 packages/plugin-docs 目录下执行 pnpm build 命令,然后重启开发。
:::
在根目录执行如下命令可以格式化 Umi 文档插件的代码:
$ pnpm format:plugin-docs在根目录执行如下命令可以构建 Umi 文档:
$ pnpm s新增 package
新增 package 有封装脚本,无需手动复制 package.json 等文件:
# 创建 package 目录
$ mkdir packages/foo
# 初始化 package 开发
$ pnpm bootstrap更新依赖
不推荐非 Core Maintainer 做大量依赖更新。因为涉及依赖预打包,有较多需注意的点。
执行 pnpm dep:update 可更新依赖。
$ pnpm dep:update由于 Umi 有针对依赖做预打包处理,更新依赖后还需检查更新的依赖是否为 devDependencies 并且有对此做依赖预打包。如果有,需要在对应 package 下执行 pnpm build:deps 并指定依赖,用于更新预打包的依赖文件。
$ pnpm build:deps --dep webpack-manifest-plugin发布
只有 Core Maintainer 才能执行发布。Umi 已切换到 npm Trusted Publishing/OIDC,本地发布命令只负责 bump version、生成 release commit/tag 并推送,不再直接执行 npm publish。
$ pnpm release推送后,GitHub Actions 的 Release workflow 会通过 OIDC 获取 npm 发布权限,执行 pnpm release:publish,并带上 --provenance 将 package 发布到 npm。
通过 dist-tag 回滚
比如要回滚到 4.0.81 。
$ pnpm -rc --filter "./packages/**" exec pnpm dist-tag add \[email protected] latest加入 Contributor 群
提交过 Bugfix 或 Feature 类 PR 的同学,如果有兴趣一起参与维护 Umi,可先用钉钉扫下方二维码(注明 github id)加我钉钉,然后我会拉到群里。

如果你不知道可以贡献什么,可以到源码里搜 TODO 或 FIXME 找找。
Docs Docs Introduce Faq.En US (`docs/docs/docs/introduce/faq.en-US.md`)
order: 5 toc: content translated_at: '2024-03-17T09:54:04.320Z'
FAQ
Can I disable dynamicImport?
Yes, but it is not recommended to do so.
- Install dependencies
pnpm i babel-plugin-dynamic-import-node -D- Add
extraBabelPluginsto the configuration, but only enable it for production
// .umirc.ts
export default {
extraBabelPlugins: process.env.NODE_ENV === 'production'
? ['babel-plugin-dynamic-import-node']
: []
}How to configure loading for dynamicImport when it's not present?
Define src/loading.tsx:
See Directory Structure > loading.tsx
Can I use React 17?
Since Umi v4 has upgraded the default version of React to v18, be aware of compatibility between dependency libraries and React 18 when using Umi4. If you still need to use React 17, run the following commands and restart.
pnpm add react@^17 react-dom@^17Constantly restarting and refreshing the page after proxying static resources locally

Solution: Configure SOCKET_SERVER=127.0.0.1:${port} to start the project
SOCKET_SERVER=http://127.0.0.1:8000 pnpm devError evaluating function `round`: argument must be a number

Solution: In the new version of less, / is recognized as property shorthand by default. To restore the old behavior (where / was used as a calculation symbol by default), configure lessLoader: { math: 'always' }.
The layout configuration option in routes does not take effect
The layout configuration has been moved to app.ts, see Runtime Config > layout
Where did document.ejs go and how do I customize the HTML template
In addition to the injection through the configuration of external script, css, you can also use the project-level plugin to more flexibly modify HTML products, see: issuecomment-1151088426
Why are external js files configured in scripts by default inserted after umi.js
React only starts running after the page has fully loaded, so inserting after umi.js will not affect the project.
If you need to insert it before umi.js, see issuecomment-1176960539
How do I code split with Umi4?
Umi 4 splits code by page by default. If you feel the need for further optimization, you can use sub-package strategies or manual splitting, see: Code Splitting Guide
If you have the requirement to pack all js products into a single umi.js file, please disable dynamicImport.
Where did _layout.tsx go and how do I nest routes?
Umi 4 uses react-router v6, and nested route content is displayed through <Outlet />. See: issuecomment-1206194329
How to use GraphQL
For configuring graph-ql loader, see: discussions/8218
How to use WebAssembly
Configure as follows:
// .umirc.ts
export default {
chainWebpack(config) {
config.set('experiments', {
...config.get('experiments'),
asyncWebAssembly: true
})
const REG = /\.wasm$/
config.module.rule('asset').exclude.add(REG).end();
config.module
.rule('wasm')
.test(REG)
.exclude.add(/node_modules/)
.end()
.type('webassembly/async')
.end()
},
}See an actual example: discussions/8541
How to customize loaders
Depending on the scenario, you may need to exclude the file type you need to load from the static asset rules first and then add your own loader or modify it. Refer to the following examples:
How to use css modules in third-party packages
Directly publish the source code of third-party packages'
jsx/ts/tsxto npm, no need to translate tojs. Umi 4 supports direct use.If the third-party package output is
js, you need to include it for additional processing by babel to support css modules:
// .umirc.ts
export default {
extraBabelIncludes: ['your-pkg-name']
}How to solve the lack of hot updates in npm linked packages
Umi 4 has mfsu enabled by default and ignores changes in node_modules by default. Exclude the package from mfsu as follows:
// .umirc.ts
export default {
mfsu: {
exclude: ['package-name']
},
}What is the priority order of config files for different environments?
For the detailed loading priority, see UMI_ENV, and the same applies to config/config.ts or .umirc.ts.
Problems with IE compatibility
Umi 4 is not IE compatible by default in the context of modern browsers.
If you need to adjust the build for compatibility targets, for older browsers, or for IE, please refer to Legacy Browser Compatibility.
SSR Issues
SSR is still an experimental feature and not recommended for production environments. If any issues are encountered, they should be reported on issues.
Vue / Vite Issues
Umi 4 now supports Vite mode and Vue. There may be edge cases, so report any issues on issue.
Why does the pathname obtained from `history` differ from the one obtained from `useLocation`?
This situation occurs when the project is configured with base. history.location.pathname reflects the browser's pathname, which includes the base. Route-related hooks, however, return the frontend route definition's pathname, which does not include the base. Reference.
How to change the compression encoding format of the output
By default, the js/css compressor esbuild uses ascii encoding for compression, which may lead to the encoding of Chinese characters and increase the size of the output.
Adjust the encoding to utf8 to prevent character conversion:
// .umirc.ts
export default {
jsMinifierOptions: { charset: 'utf8' },
cssMinifierOptions: { charset: 'utf8' }
}Or switch the compressor to resolve this:
// .umirc.ts
export default {
jsMinifier: 'terser',
cssMinifier: 'cssnano'
}How to configure devServer options
Umi 4 no longer supports configuring devServer, but you can find alternatives as follows:
Use the
proxyoption to set up a proxy, and modify request header information throughonProxyReq, see #10760.Write a project-level plugin to insert an express middleware to modify requests, see #10060.
Docs Docs Introduce Faq (`docs/docs/docs/introduce/faq.md`)
order: 5 toc: content
FAQ
可以关闭 dynamicImport 吗?
可以,但不建议关闭。
1、安装依赖
pnpm i babel-plugin-dynamic-import-node -D2、配置里加上 extraBabelPlugins ,但只针对 production 开启
// .umirc.ts
export default {
extraBabelPlugins: process.env.NODE_ENV === 'production'
? ['babel-plugin-dynamic-import-node']
: []
}没有 dynamicImport 怎么配置它的 loading ?
定义 src/loading.tsx :
可以用 react 17 吗?
由于 umi v4 升级了默认的 react 版本到 v18,使用 umi4 时注意下依赖库和 react 18 的兼容情况,如果还需要使用 react 17,请执行以下命令并重启。
pnpm add react@^17 react-dom@^17代理静态资源到本地后,一直 restart 刷新页面

解法:配置 SOCKET_SERVER=127.0.0.1:${port} 启动项目
SOCKET_SERVER=http://127.0.0.1:8000 pnpm devError evaluating function `round`: argument must be a number

解法:新版 less 中 / 默认被识别为属性简写,通过配置 lessLoader: { math: 'always' } 恢复旧版行为(默认将 / 用作计算符号)。
routes 里的 layout 配置选项不生效
layout 配置被移动到了 app.ts ,详见 runtime-config > layout
document.ejs 去哪了,如何自定义 HTML 模板
除了可以通过 配置项 注入外部 script 、css 外,还可以使用项目级插件更灵活的修改 HTML 产物,参见:issuecomment-1151088426
scripts 里配置的外部 js 文件为什么默认插入到 umi.js 的后面
react 只有在页面加载完毕后才会开始运行,所以插到 umi.js 后面不会影响项目。
若需要插到 umi.js 前面,可参见 issuecomment-1176960539
umi4 我怎么分包
Umi 4 默认按页拆包,如果你觉得还需要优化,可以使用分包策略或手动拆包,详见:代码拆分指南
如果你有将所有 js 产物打包成单 umi.js 文件的需求,请关闭 dynamicImport 。
_layout.tsx 去哪了,我怎么嵌套路由
Umi 4 使用 react-router v6 ,通过 <Outlet /> 展示嵌套路由内容,可参见:issuecomment-1206194329
怎么用 GraphQL
配置 graph-ql loader 的方式可参见: discussions/8218
怎么用 WebAssembly
配置如下:
// .umirc.ts
export default {
chainWebpack(config) {
config.set('experiments', {
...config.get('experiments'),
asyncWebAssembly: true
})
const REG = /\.wasm$/
config.module.rule('asset').exclude.add(REG).end();
config.module
.rule('wasm')
.test(REG)
.exclude.add(/node_modules/)
.end()
.type('webassembly/async')
.end()
},
}一个实际例子可参见:discussions/8541
怎么自定义 loader
根据场景不同,你可能要先从 静态资源规则 中排除你需要加载的文件类型,再添加你自己的 loader / 或修改,可参考如下实例:
第三方包里如何使用 css modules
直接将第三方包的
jsx/ts/tsx源码发布到 npm ,无需转译为jsUmi 4 支持直接使用。若第三方包产物是
js的情况,需要将其纳入 babel 额外处理,才可以支持 css modules:
// .umirc.ts
export default {
extraBabelIncludes: ['your-pkg-name']
}npm link 的包不热更新怎么解决
Umi 4 默认开启 mfsu ,默认忽略 node_modules 的变化,配置从 mfsu 排除该包即可:
// .umirc.ts
export default {
mfsu: {
exclude: ['package-name']
},
}我的环境很多,多环境 config 文件的优先级是怎样的
加载优先级详见 UMI_ENV ,无论是 config/config.ts 还是 .umirc.ts 同理。
IE 兼容性问题
现代浏览器主流背景下,Umi 4 默认不兼容 IE 。
若你有调整构建兼容目标、兼容非现代浏览器、兼容 IE 浏览器的需求,请参考 非现代浏览器兼容 。
SSR 问题
SSR 目前还处于实验性特性,不建议在生产环境使用,若发现问题可即时在 issue 反馈。
Vue / Vite 问题
Umi 4 新增了 Vite 模式和 Vue 支持,可能存在 edge case ,若发现问题可即时在 issue 反馈。
`history` 中取的 pathname 为什么和 `useLocation` 中的不一样
这种情况是在项目配置了 base 。 history.location.pathname 取到的值是浏览器地址中的 pathname,它是包含 base 的;而路由相关 hooks 取到的值是前端路由定义中的 pathname,它是不包含 base 的。参考。
调整产物的压缩编码格式
默认 js / css 的压缩器 esbuild 会采用 ascii 格式编码压缩,这可能导致中文字符被转码,增大产物体积。
可通过配置调整到 utf8 编码,防止字符被转换:
// .umirc.ts
export default {
jsMinifierOptions: { charset: 'utf8' },
cssMinifierOptions: { charset: 'utf8' }
}或通过切换压缩器来解决:
// .umirc.ts
export default {
jsMinifier: 'terser',
cssMinifier: 'cssnano'
}devServer 选项怎么配置
Umi 4 不再支持配置 devServer 选项,但你可以通过以下方式找到替代:
Docs Docs Introduce Introduce.En US (`docs/docs/docs/introduce/introduce.en-US.md`)
order: 1 toc: content translated_at: '2024-03-17T09:52:32.853Z'
Introduction to Umi

What is Umi?
Umi, pronounced as "Wu Mi" in Chinese, is an extensible enterprise-level front-end application framework. Umi is based on routing, supporting both configuration-based routing and convention-based routing, ensuring the completeness of routing features and further extending functionalities based on this. Accompanied by a plugin system with a complete lifecycle, it covers every lifecycle from the source code to the build product, supporting various functional extensions and business needs.
Umi is the underlying front-end framework of Ant Group, serving directly or indirectly over 10,000 applications, including Java, Node, H5 Wireless, Hybrid applications, pure front-end assets applications, CMS applications, Electron applications, Serverless applications, and more. It has served our internal users well and many external users, including Taobao system, Feizhu, Alibaba Cloud, ByteDance, Tencent, Koubei, Meituan, etc. In ByteDance's research report in 2021, Umi was the choice of 25.33% of developers.
Umi has many interesting features, such as:
- Enterprise-level, considering more in terms of security, stability, best practices, and constraint ability
- Plugin-based, everything can be modified, and Umi itself is also composed of plugins
- MFSU, a Webpack packaging solution faster than Vite
- Complete routing based on React Router 6
- The fastest request by default
- SSR & SSG
- Stable white-box performance with ESLint and Jest
- Framework-level integration of React 18
- Best practices for Monorepo
...
When Not to Use Umi?
If your project,
- Needs to support IE 8 or lower browsers
- Needs to support React versions below 16.8.0
- Needs to run in environments below Node 14
- Has strong webpack customization needs and subjective willingness
- Needs to choose different routing solutions
...
Umi might not be suitable for you.
Why Not?
create-react-app
create-react-app is a scaffolding tool and not the same type as meta frameworks like Umi, next.js, remix, ice, modern.js, etc. Scaffolding can quickly start a project, which is sufficient for individual projects, but not enough for teams. Because using scaffolding is like spilt water; once it starts, it cannot iterate. Meanwhile, the encapsulation and abstraction that scaffolding can do are very limited.
next.js
If SSR is needed, next.js is a very good choice (of course, Umi also supports SSR); but if only CSR is needed, Umi would be a better choice. By comparison, Umi has better extensibility; and Umi offers many more down-to-earth features, such as configuration-based routing, patch scheme, integration of antd, micro frontends, internationalization, permissions, etc.; at the same time, Umi is more stable, because it locks all dependencies that can be locked and proactively updates regularly. A certain subversion of Umi will not fail to run due to reinstallation of dependencies.
remix
Remix is a framework I really like, and Umi 4 has copied (learned) a lot from it. But Remix is a Server framework, its built-in loaders and actions run on the server side, so it requires certain deployment environments. Umi applies loaders, actions, and the request mechanism of Remix to both the client and server side, not only making server requests fast but also achieving theoretically fastest values for pure CSR projects. At the same time, Remix is based on esbuild for packaging, which may not be suitable for projects with compatibility requirements or especially large dependencies.
Docs Docs Introduce Introduce (`docs/docs/docs/introduce/introduce.md`)
order: 1 toc: content
Umi 介绍

Umi 是什么?
Umi,中文发音为「乌米」,是可扩展的企业级前端应用框架。Umi 以路由为基础,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。然后配以生命周期完善的插件体系,覆盖从源码到构建产物的每个生命周期,支持各种功能扩展和业务需求。
Umi 是蚂蚁集团的底层前端框架,已直接或间接地服务了 10000+ 应用,包括 Java、Node、H5 无线、离线(Hybrid)应用、纯前端 assets 应用、CMS 应用、Electron 应用、Serverless 应用等。他已经很好地服务了我们的内部用户,同时也服务了不少外部用户,包括淘系、飞猪、阿里云、字节、腾讯、口碑、美团等。在 2021 年字节的调研报告中,Umi 是其中 25.33% 开发者的选择。
Umi 有很多非常有意思的特性,比如。
1、企业级,在安全性、稳定性、最佳实践、约束能力方面会考虑更多
2、插件化,啥都能改,Umi 本身也是由插件构成
3、MFSU,比 Vite 还快的 Webpack 打包方案
4、基于 React Router 6 的完备路由
5、默认最快的请求
6、SSR & SSG
7、稳定白盒性能好的 ESLint 和 Jest
8、React 18 的框架级接入
9、Monorepo 最佳实践
...
什么时候不用 Umi?
如果你的项目,
1、需要支持 IE 8 或更低版本的浏览器
2、需要支持 React 16.8.0 以下的 React
3、需要跑在 Node 14 以下的环境中
4、有很强的 webpack 自定义需求和主观意愿
5、需要选择不同的路由方案
...
Umi 可能不适合你。
为什么不是?
create-react-app
create-react-app 是脚手架,和 Umi、next.js、remix、ice、modern.js 等元框架不是同一类型。脚手架可以让我们快速启动项目,对于单一的项目够用,但对于团队而言却不够。因为使用脚手架像泼出去的水,一旦启动,无法迭代。同时脚手架所能做的封装和抽象都非常有限。
next.js
如果要做 SSR,next.js 是非常好的选择(当然,Umi 也支持 SSR);而如果只做 CSR,Umi 会是更好的选择。相比之下,Umi 的扩展性会更好;并且 Umi 做了很多更贴地气的功能,比如配置式路由、补丁方案、antd 的接入、微前端、国际化、权限等;同时 Umi 会更稳定,因为他锁了能锁的全部依赖,定期主动更新。某一个子版本的 Umi,不会因为重装依赖之后而跑不起来。
remix
Remix 是我非常喜欢的框架,Umi 4 从中抄(学)了不少东西。但 Remix 是 Server 框架,其内置的 loader 和 action 都是跑在 server 端的,所以会对部署环境会有一定要求。Umi 将 loader、action 以及 remix 的请求机制同时运用到 client 和 server 侧,不仅 server 请求快,纯 CSR 的项目请求也可达到理论的最快值。同时 Remix 基于 esbuild 做打包,可能不适用于对兼容性有要求或者依赖尺寸特别大的项目。
Docs Docs Introduce Philosophy.En US (`docs/docs/docs/introduce/philosophy.en-US.md`)
order: 2 toc: content translated_at: '2024-03-17T09:51:30.781Z'
Design Philosophy
Umi has gone from 1 to 4, experimenting with many things, both successes and failures. We have gradually accumulated these successful experiences, guiding us on how to build a robust enterprise-level framework.
The design philosophy includes:
- Technological Convergence
- Plugins and Plugin Collections
- Best Practices
- Enterprise-Level
- import all from umi
- Compile-Time Framework
- Dependency Prepacking
- Fast by Default
- Constraints and Openness
Below, we introduce the first five points, while the last four points (6-9) had a detailed share at the SEE Conf on January 8, 2022, see SEE Conf: Umi 4 Design Philosophy Manuscript.
Technological Convergence
Technological convergence is particularly important for teams, encompassing two meanings: 1) convergence of technology stacks and 2) convergence of dependencies. Convergence of technology stacks refers to the plethora of technology stacks available in the community, with numerous choices for each technical direction, such as data flows which alone could number over 100, and how developers should make their choice; After converging the technology stack, it's also necessary to converge dependencies, as the projects of developers within a team should not have many fragmented dependencies, each of which comes with an upgrading cost.
We hope that once developers depend on Umi, they no longer need to worry about dependencies like babel, webpack, postcss, react, react-router, etc., and by depending on @umijs/max, they need not be concerned with dependencies and technology stacks of developing mid-end projects.
Plugins and Plugin Collections
Umi satisfies different scenarios and business needs through the mechanism of plugins and plugin collections. A plugin is to extend a function, whereas a plugin collection is to extend a category of business. For example, to support vue, we could have @umijs/preset-vue, including build and runtime related to vue; to support the H5 application type, there could be @umijs/preset-h5, aggregating H5 related functionalities together.
As an analogy, plugin collections are similar to babel's presets and eslint's configs.
Best Practices
Best practice is what we believe to be the best way to do something at the moment. The subject is us, so it could be relatively subjective. For instance, in aspects like routing, patch solutions, data flow, requests, permissions, internationalization, micro-frontends, icons usage, editor usage, charts, forms, etc., Umi will offer our best practices. Most of these practices come from internal practices and discussions at Ant Group, with some from the community. They are subjective and time-sensitive, so there might be relatively frequent iterations.
The need for best practices arises, 1) because there are too many solutions in the community to choose from, and 2) many people lack the energy, experience, or even willingness to make choices. Especially for non-professional front-end developers, having choices is better than having none, regardless of what those choices might be.
Enterprise-Level
The npm community is "deteriorating" with political, malicious, advertisement, and job-seeking packages frequently appearing. Thus, how to ensure your project won’t “crash overnight” is an unavoidable issue for frameworks providing services to enterprises.
Umi achieves this by locking down versions, prepacking dependencies, hacking eslint to lock eslint's dependencies, and using configuration to lock down babel patch dependencies, so that Umi won't crash after you reinstall node_modules, aiming to be "Still Usable in Ten Years".
import all from umi
Many may be hearing about this for the first time. import all from umi means all imports come from umi. For instance, dva is not import { connect } from 'dva', but import { connect } from 'umi', exported from Umi. The methods exported not only come from Umi itself but also from Umi plugins.
// A large number of plugins provide additional exports for umi
import { connect, useModel, useIntl, useRequest, MicroApp, ... } from 'umi';The advantage is that Umi manages a large number of dependencies so users don’t need to manually install them; at the same time, developers will also have fewer import statements in their code.
Docs Docs Introduce Philosophy (`docs/docs/docs/introduce/philosophy.md`)
order: 2 toc: content
设计思路
Umi 从 1 做到 4,试错了很多东西,也试对了不少。这些试对的点我们逐渐沉淀下来,就成为指引我们如何做好企业级框架的思路。
设计思路包括,
1、技术收敛
2、插件和插件集
3、最佳实践
4、企业级
5、import all from umi
6、编译时框架
7、依赖预打包
8、默认快
9、约束与开放
下面仅介绍 1-5 的 5 点,剩下 6-9 的 4 点在 2022.1.8 的 SEE Conf 中有过详细分享,详见《SEE Conf: Umi 4 设计思路文字稿》。
技术收敛
技术收敛对团队而言尤其重要,他包含两层含义,1)技术栈收敛 2)依赖收敛。技术栈收敛指社区那么多技术栈,每个技术方向都有非常多选择,比如数据流应该就不下 100 种,开发者应该如何选择;收敛了技术栈之后还需要收敛依赖,团队中,开发者的项目不应该有很多细碎的依赖,每一个依赖都附带着升级成本。
我们希望开发者依赖 Umi 之后就无需关心 babel、webpack、postcss、react、react-router 等依赖,而依赖 @umijs/max 之后无需再关心开发中台项目的依赖和技术栈。
插件和插件集
Umi 通过提供插件和插件集的机制来满足不同场景和业务的需求。插件是为了扩展一个功能,而插件集是为了扩展一类业务。比如要支持 vue,我们可以有 @umijs/preset-vue,包含 vue 相关的构建和运行时;比如要支持 h5 的应用类型,可以有 @umijs/preset-h5,把 h5 相关的功能集合到一起。
如果要类比,插件集和 babel 的 preset,以及 eslint 的 config 都类似。
最佳实践
最佳实践是我们认为做某件事当下最好的方式,主语是我们,所以会相对比较主观。比如路由、补丁方案、数据流、请求、权限、国际化、微前端、icons 使用、编辑器使用、图表、表单等方面,Umi 都会给出我们的最佳实践。这些最佳实践大部分来自蚂蚁集团内部的实践和讨论,也有部分来自社区。他们是主观和时间敏感的,所以可能会有相对比较频繁的迭代。
之所以需要最佳实践,1)是社区太多方案选择,2)是很多人没有选择的精力和经验甚至意愿。尤其是针对非专业的前端,有选择比没选择好,不管这个选择如何。
企业级
npm 社区「世风日下」,涉政包、恶意包、广告求职包频出,所以如何确保不会「睡一觉醒来项目挂了」是面对企业生成提供服务的框架绕不开的一个点。
Umi 通过写死版本、依赖预打包、通过 eslint hack 锁定 eslint 依赖,通过配置锁定 babel 补丁依赖等方式,让 Umi 不会在你重装 node_modules 之后就挂掉,并以此来实现「十年后依旧可用」。
import all from umi
很多人可能都第一次听到。import all from umi 意思是所有 import 都来自 umi。比如 dva 不是 import { connect } from 'dva',而是 import { connect } from 'umi',从 Umi 中导出。导出的方法不仅来自 Umi 自身,还来自 Umi 插件。
这是两年前 Umi 3 加的功能,最近发现 Remix、prisma、vitekit 等框架和工具都有类似实现。
// 大量插件为 umi 提供额外导出内容
import { connect, useModel, useIntl, useRequest, MicroApp, ... } from 'umi';这带来的好处是。通过 Umi 将大量依赖管理起来,用户无需手动安装;同时开发者在代码中也会少很多 import 语句。
Docs Docs Introduce Upgrade To Umi 4.En US (`docs/docs/docs/introduce/upgrade-to-umi-4.en-US.md`)
order: 4 toc: content translated_at: '2024-03-17T09:50:41.558Z'
Upgrade to Umi 4
Upgrade Steps
Upgrading to Umi 4 can be completed in just a few easy steps, which can simply be described as - "Reinstall dependencies, modify configuration":
- Dependency Handling
- Start Command
- Upgrading Non-Official Plugins
- Configuration Layer Migration
- Code Layer Modifications
Dependency Handling
Your project's package.json needs to upgrade Umi and replace the corresponding Umi plugins.
If umi@3 was developed using a combination of umi + @umijs/preset-react, then you can directly upgrade using the new max.
{
"devDependencies": {
+ "@umijs/max": "^4.0.0",
- "umi": "^3.0.0",
- "@umijs/preset-react": "^1.2.2"
}
}Delete node_module, then execute npm install to reinstall dependencies.
Start Command
If using @umijs/max, you can replace umi with the max command, such as max dev, max build, etc.
umi@4 moved some pre-project operations to the setup command, such as umi g tmp commands in umi@3, which need to be replaced with umi setup
package.json
{
"scripts": {
- "build": "umi build",
+ "build": "max build",
- "postinstall": "umi g tmp",
+ "postinstall": "max setup",
- "start": "umi dev",
+ "start": "max dev",
}
}Upgrading Non-Official Plugins
For some non-Umi official Umi plugins used in the project, please contact the relevant authors to update them in a timely manner according to plugin api changes.
When migrating the project, you can first turn off references to the corresponding plugin packages, such as temporarily commenting out the plugins in the configuration, and removing all dependencies starting with umi-plugin-, @umijs/plugin-, and @umijs/preset- in package.json.
Configuration Layer Migration
Configurations provided by max are as follows in config/config.ts:
It’s worth noting that some rules that were conventionally enabled by plugins need to be explicitly configured in
umi@4, as we want less 'black box' behavior inumi@4.
import { defineConfig, utils } from 'umi';
export default defineConfig({
model: {},
antd: {},
request: {},
initialState: {},
mock: {
include: ['src/pages/**/_mock.ts'],
},
dva: {},
layout: {
// https://umijs.org/docs/max/layout-menu#configuration-at-build-time
title: 'UmiJS',
locale: true,
},
// https://umijs.org/zh-CN/plugins/plugin-locale
locale: {
// default zh-CN
default: 'zh-CN',
antd: true,
// default true, when it is true, will use `navigator.language` overwrite default
baseNavigator: true,
},
});Configurations with differences are as follows in config/config.ts:
import { defineConfig, utils } from 'umi';
export default defineConfig({
- fastRefresh: {},
+ fastRefresh: true,
dva: {
// The parameter hmr is no longer supported
- hmr: true,
},
// Default webpack5
- webpack5: {},
})Code Layer Modifications
Umi 4 upgrades react-router@5 to react-router@6, so there are some differences in the use of related api.
Props are empty objects by default, the following properties cannot be taken directly from props
children
import { Outlet } from 'umi';
<Outlet />;Mainly needs modifications in the global layout
Such as layouts/index.tsx:
import React from 'react';
+ import { Outlet } from 'umi';
export default function Layout(props) {
return (
- { props.children }
+ <Outlet />
);
}Change the way that route components rendered with React.cloneElement, for example:
import React from 'react';
+ import { Outlet } from 'umi';
export default function RouteComponent(props) {
return (
- { React.cloneElement(props.children, { someProp: 'p1' }) }
+ <Outlet context={{ someProp: 'p1' }} />
);
}Change components to get values from useOutletContext
import React from 'react';
+ import { useOutletContext } from 'umi';
- export function Comp(props){
+ export function Comp() {
+ const props = useOutletContext();
return props.someProp;
}history
+ import { history } from 'umi';
export default function Page(props) {
return (
{
- props.history.push('list');
+ history.push('list');
}}>
);
}location
It’s recommended to use useLocation in components or hooks, and window.location elsewhere.
export default function Page(props) {
+ const { location } = window;
return (
- { props.location }
+ { location }
);
}Or
+ import { useLocation } from 'umi';
export default function Page(props) {
+ let location = useLocation();
return (
- { props.location }
+ { location }
);
}match
+ import { useMatch } from 'umi';
export default function Page(props) {
+ const match = useMatch({ path: 'list/search/:type' });
return (
- { props.match }
+ { match }
);
}In the use of class component components:
import { matchPath } from 'umi';
class Page extends Component {
+ match = matchPath({ path: 'list/search/:type' }, window.location.pathname);
state = {}
render() {
return (
- {this.props.match.type}
+ {this.match.type}
)
}
}For more Umi related api
Pay attention to the difference in match data:
// match v5
isExact: true
params: {}
path: "/users/abc"
url: "/users/abc"
// match v6
params:{ }
pathname: "/list/search/articles"
pathnameBase: "/list/search/articles"
pattern: {path: 'list/search/:type'}For more changes and API updates, please refer to react-router@6
After completing the above operations, execute max dev, and visit http://localhost:8000, please verify all functions meet expectations.
If your project cannot start normally, you may need to do the following:
Configuration Changes
TODO
FAQ
Can't find query in location?
Query in location is no longer supported, later it’s recommended to use search
- const { query } = history.location;
+ import { parse } from 'query-string';
+ const query = parse(history.location.search);Can't find *.d file, or its reference
In umi@3, importing would automatically find the corresponding .d.ts file, such as:
import { ButtonType } from './button';
If there is a .button.d.ts file, it will correctly execute in umi@3, but it will cause an error in umi@4, you may need more standardized type references.
- import { ButtonType } from './button';
+ import type { ButtonType } from './button.d';Docs Docs Introduce Upgrade To Umi 4 (`docs/docs/docs/introduce/upgrade-to-umi-4.md`)
order: 4 toc: content
升级到 Umi 4
升级步骤
升级到 Umi 4 只需要简单的几步操作就能完成,简单的描述整个过程就是 - “重装依赖,修改配置”:
- 依赖处理
- 启动命令
- 非官方插件升级
- 配置层迁移
- 代码层修改
依赖处理
项目的 package.json 需要升级 Umi,并替换掉对应的 Umi 插件。
如果 umi@3 中是使用 umi + @umijs/preset-react 的组合进行开发的,那可以直接使用新版的 max 直接升级。
{
"devDependencies": {
+ "@umijs/max": "^4.0.0",
- "umi": "^3.0.0",
- "@umijs/preset-react": "^1.2.2"
}
}删除 node_module,执行下 npm install 重装依赖。
启动命令
如果使用了 @umijs/max 可以使用 max 命令来替换 umi,max dev,max build 等。
umi@4 将一些项目前置操作放到了 setup 命令中,如 umi@3 中的 umi g tmp 等命令,需要使用 umi setup 替换
package.json
{
"scripts": {
- "build": "umi build",
+ "build": "max build",
- "postinstall": "umi g tmp",
+ "postinstall": "max setup",
- "start": "umi dev",
+ "start": "max dev",
}
}非官方插件升级
在项目中用到的一些非 Umi 官方提供的 Umi 插件,请联系相关作者及时根据插件 api 变动。
项目迁移时可先关闭对相应插件包的引用,如临时注释配置中的 plugins,移除 package.json 中以 umi-plugin-,@umijs/plugin- 和 @umijs/preset- 开头的所有依赖。
配置层迁移
max 提供的的配置项如下 config/config.ts :
需要注意的是,之前的一些插件约定开启的规则,在
umi@4中几乎都要通过显式的配置开启,因为希望在umi@4中有更少的“黑盒”。
import { defineConfig, utils } from 'umi';
export default defineConfig({
model: {},
antd: {},
request: {},
initialState: {},
mock: {
include: ['src/pages/**/_mock.ts'],
},
dva: {},
layout: {
// https://umijs.org/docs/max/layout-menu#构建时配置
title: 'UmiJS',
locale: true,
},
// https://umijs.org/zh-CN/plugins/plugin-locale
locale: {
// default zh-CN
default: 'zh-CN',
antd: true,
// default true, when it is true, will use `navigator.language` overwrite default
baseNavigator: true,
},
});存在差异的配置项如下 config/config.ts :
import { defineConfig, utils } from 'umi';
export default defineConfig({
- fastRefresh: {},
+ fastRefresh: true,
dva: {
// 不再支持 hmr 这个参数
- hmr: true,
},
// 默认 webpack5
- webpack5: {},
})代码层修改
Umi 4 中将 react-router@5 升级到 react-router@6,所以路由相关的一些 api 存在着使用上的差异。
props 默认为空对象,以下属性都不能直接从 props 中取出
children
import { Outlet } from 'umi';
<Outlet />;主要在全局 layout 中需要修改
如 layouts/index.tsx:
import React from 'react';
+ import { Outlet } from 'umi';
export default function Layout(props) {
return (
- { props.children }
+ <Outlet />
);
}使用了 React.cloneElement 方式渲染的路由组件改造,示例
import React from 'react';
+ import { Outlet } from 'umi';
export default function RouteComponent(props) {
return (
- { React.cloneElement(props.children, { someProp: 'p1' }) }
+ <Outlet context={{ someProp: 'p1' }} />
);
}组件改成从 useOutletContext 取值
import React from 'react';
+ import { useOutletContext } from 'umi';
- export function Comp(props){
+ export function Comp() {
+ const props = useOutletContext();
return props.someProp;
}history
+ import { history } from 'umi';
export default function Page(props) {
return (
{
- props.history.push('list');
+ history.push('list');
}}>
);
}location
建议组件或 hooks 里用 useLocation 取,其他地方就用 window.location 获取。
export default function Page(props) {
+ const { location } = window;
return (
- { props.location }
+ { location }
);
}或者
+ import { useLocation } from 'umi';
export default function Page(props) {
+ let location = useLocation();
return (
- { props.location }
+ { location }
);
}match
+ import { useMatch } from 'umi';
export default function Page(props) {
+ const match = useMatch({ path: 'list/search/:type' });
return (
- { props.match }
+ { match }
);
}在 class component 组件中的使用方式:
import { matchPath } from 'umi';
class Page extends Component {
+ match = matchPath({ path: 'list/search/:type' }, window.location.pathname);
state = {}
render() {
return (
- {this.props.match.type}
+ {this.match.type}
)
}
}更多 Umi 相关 api
需要注意 match 数据的差异:
// match v5
isExact: true
params: {}
path: "/users/abc"
url: "/users/abc"
// match v6
params:{ }
pathname: "/list/search/articles"
pathnameBase: "/list/search/articles"
pattern: {path: 'list/search/:type'}更多改动和 api 变更,请查阅 react-router@6
完成以上操作后,执行下 max dev,访问 http://localhost:8000,请验证所有功能都符合预期。
如果你的项目无法正常启动,你可能还需要做如下操作:
配置变更
TODO
FAQ
location 中的 query 找不到?
location 中的 query 不再支持了,后续推荐用 search
- const { query } = history.location;
+ import { parse } from 'query-string';
+ const query = parse(history.location.search);\*.d 文件找不到,或者它的引用找不到
在 umi@3 中通过 import 会自动找到同名的 .d.ts 文件,如:
import { ButtonType } from './button';
如果存在 .button.d.ts 文件,在 umi@3 中会正确执行,但是在 umi@4 中会发生报错,你可能需要更加规范的引用类型。
- import { ButtonType } from './button';
+ import type { ButtonType } from './button.d';Docs Docs Max Access.En US (`docs/docs/docs/max/access.en-US.md`)
order: 7 toc: content translated_at: '2024-03-17T09:49:43.250Z'
Permissions
How to Enable
Configuring activation. Requires src/access.ts to provide permission configuration.
export default {
access: {},
// access plugin depends on the initial State so it needs to be enabled at the same time
initialState: {},
};Introduction
We have agreed that src/access.ts is our permission definition file, which needs to export a function by default. The method will be executed when the project is initialized. This method needs to return an object, where each value of the object corresponds to a defined permission. As shown below:
// src/access.ts
export default function (initialState) {
const { userId, role } = initialState;
return {
canReadFoo: true,
canUpdateFoo: role === 'admin',
canDeleteFoo: (foo) => {
return foo.ownerId === userId;
},
};
}Where initialState is the data provided by the initialization state plugin initial-state, you can use this data to initialize your user permissions.
Configuration
Extended Routing Configuration
With the layout plugin, you can easily implement permission control for some pages. As shown below, users can access the page only if they have the canReadPageA permission (defined in src/access.ts). Otherwise, it will render the default permission error page built into the Layout plugin by default.
export const routes = [
{
path: '/pageA',
component: 'PageA',
access: 'canReadPageA', // Some key of the permission definition return value
},
];Custom Permission Page Configuration
As mentioned above, to configure a custom permission page, you need to define it in src/app.tsx.
export const layout: RunTimeLayoutConfig = () => {
return {
// Customize the 403 page
unAccessible: 'unAccessible',
// Customize the 404 page
noFound: 'noFound',
};
};access
- Type:
string
The corresponding permission name.
API
useAccess
We provide a hook for obtaining permission-related information in components, as shown below:
import React from 'react';
import { useAccess } from 'umi';
const PageA = (props) => {
const { foo } = props;
the access = useAccess();
if (access.canReadFoo) {
// If can read Foo, then...
}
return <>TODO</>;
};
export default PageA;Combined with the Access component, it's easy to control the permissions of elements within pages.
Access
You can use the useAccess hook and the <Access /> component provided by the plugin to control permissions in your application. The Access component supports the following properties:
accessible
- Type:
boolean
Whether permission is granted, usually obtained through useAccess and passed in.
fallback
- Type:
React.ReactNode
The display when there is no permission, by default no content is displayed without permission.
children
- Type:
React.ReactNode
What is displayed when permission is granted.
A complete example is as follows:
import React from 'react';
import { useAccess, Access } from 'umi';
const PageA = (props) => {
const { foo } = props;
const access = useAccess(); // members of access: canReadFoo, canUpdateFoo, canDeleteFoo
if (access.canReadFoo) {
// If can read Foo, then...
}
return (
<Access
accessible={access.canReadFoo}
fallback={Can not read foo content.}
>
Foo content.
</Access>
<Access
accessible={access.canUpdateFoo}
fallback={Can not update foo.}
>
Update foo.
</Access>
<Access
accessible={access.canDeleteFoo(foo)}
fallback={Can not delete foo.}
>
Delete foo.
</Access>
);
};The return value
accessofuseAccess()is the set of permissions defined insrc/access.ts, which can be used to control the flow of code execution within components.The
<Access>component has two properties:accessibleandfallback. Whenaccessibleistrue, it will render the child component; whenaccessibleisfalse, it will render theReactNodecorresponding to thefallbackproperty.
Docs Docs Max Access (`docs/docs/docs/max/access.md`)
order: 7 toc: content
权限
启用方式
配置开启。同时需要 src/access.ts 提供权限配置。
export default {
access: {},
// access 插件依赖 initial State 所以需要同时开启
initialState: {},
};介绍
我们约定了 src/access.ts 为我们的权限定义文件,该文件需要默认导出一个方法,导出的方法会在项目初始化时被执行。该方法需要返回一个对象,对象的每一个值就对应定义了一条权限。如下所示:
// src/access.ts
export default function (initialState) {
const { userId, role } = initialState;
return {
canReadFoo: true,
canUpdateFoo: role === 'admin',
canDeleteFoo: (foo) => {
return foo.ownerId === userId;
},
};
}其中 initialState 是通过初始化状态插件 initial-state 提供的数据,你可以使用该数据来初始化你的用户权限。
配置
扩展的路由配置
配合 layout 插件你可以很简单的实现针对某些页面的权限控制。如下所示,只有拥有了 canReadPageA (在 src/access.ts 中定义)权限,用户才可以访问该页面。否则会默认渲染 Layout 插件内置的权限错误页面。
export const routes = [
{
path: '/pageA',
component: 'PageA',
access: 'canReadPageA', // 权限定义返回值的某个 key
},
];自定义权限页面配置
上面说到默认渲染 Layout 插件内置的权限错误页面,如果想配置自定义权限页面需要在 src/app.tsx 中定义。
export const layout: RunTimeLayoutConfig = () => {
return {
// 自定义 403 页面
unAccessible: 'unAccessible',
// 自定义 404 页面
noFound: 'noFound',
};
};access
- Type:
string
对应的权限名称。
API
useAccess
我们提供了一个 Hooks 用于在组件中获取权限相关信息,如下所示:
import React from 'react';
import { useAccess } from 'umi';
const PageA = (props) => {
const { foo } = props;
const access = useAccess();
if (access.canReadFoo) {
// 如果可以读取 Foo,则...
}
return <>TODO</>;
};
export default PageA;配合 Access 组件可以很简单的实现页面内的元素的权限控制。
Access
可以在业务组件中使用插件提供的 React hook useAccess 以及组件 <Access /> 对应用进行权限控制了。组件 Access 支持的属性如下:
accessible
- Type:
boolean
是否有权限,通常通过 useAccess 获取后传入进来。
fallback
- Type:
React.ReactNode
无权限时的显示,默认无权限不显示任何内容。
children
- Type:
React.ReactNode
有权限时的显示。
完整示例如下:
import React from 'react';
import { useAccess, Access } from 'umi';
const PageA = (props) => {
const { foo } = props;
const access = useAccess(); // access 的成员: canReadFoo, canUpdateFoo, canDeleteFoo
if (access.canReadFoo) {
// 如果可以读取 Foo,则...
}
return (
<Access
accessible={access.canReadFoo}
fallback={Can not read foo content.}
>
Foo content.
</Access>
<Access
accessible={access.canUpdateFoo}
fallback={Can not update foo.}
>
Update foo.
</Access>
<Access
accessible={access.canDeleteFoo(foo)}
fallback={Can not delete foo.}
>
Delete foo.
</Access>
);
};useAccess()的返回值access就是src/access.ts中定义的权限集合,可以利用它进行组件内代码执行流的控制。<Access>组件拥有accessible和fallback两个属性,当accessible为true时会渲染子组件,当accessible为false会渲染fallback属性对应的ReactNode。
Docs Docs Max Analytics.En US (`docs/docs/docs/max/analytics.en-US.md`)
order: 14 toc: content translated_at: '2024-03-17T09:48:57.403Z'
Site Statistics
@umijs/max has built-in site statistics functionality, currently supporting Google Analytics and Baidu Analytics
How to Enable
Configure it to be enabled and enter the respective analytics service key as required.
Example:
{
analytics: {
ga_v2: 'G-abcdefg', // google analytics key (GA 4)
baidu: 'baidu_tongji_key',
// If you are using the old version GA v1, please use `ga` to configure
ga: 'ga_old_key'
}
}Environment Variables
The key for Google Analytics 4 can also be configured through the environment variable GA_V2_KEY, and the old version uses GA_KEY.
Docs Docs Max Analytics (`docs/docs/docs/max/analytics.md`)
order: 14 toc: content
站点统计
@umijs/max 内置了站点统计的功能,目前支持 Google Analytics 和百度统计
启用方式
配置开启,按照需求配置进对应的统计服务的 key 即可。
举例:
{
analytics: {
ga_v2: 'G-abcdefg', // google analytics 的 key (GA 4)
baidu: 'baidu_tongji_key',
// 若你在使用 GA v1 旧版本,请使用 `ga` 来配置
ga: 'ga_old_key'
}
}环境变量
Google Analytics 4 的 key 也可以通过环境变量 GA_V2_KEY 来配置,旧版本为 GA_KEY 。
Docs Docs Max Antd.En US (`docs/docs/docs/max/antd.en-US.md`)
order: 3 toc: content translated_at: '2024-03-17T09:48:51.045Z'
antd
Integration of the antd component library.
How to Enable
Configure to enable, example:
// config/config.ts
export default {
antd: {
// configProvider
configProvider: {},
// themes
dark: true,
compact: true,
// babel-plugin-import
import: true,
// less or css, default less
style: 'less',
// shortcut of `configProvider.theme`
// used to configure theme token, antd v5 only
theme: {},
// antd <App /> valid for version 5.1.0 or higher, default: undefined
appConfig: {},
// Transform DayJS to MomentJS
momentPicker: true,
// Add StyleProvider for legacy browsers
styleProvider: {
hashPriority: 'high',
legacyTransformer: true,
},
},
};Introduction
Includes the following features:
- Built-in antd, the current built-in version is
^4.0.0 - On-demand compilation based on babel-plugin-import
- When using antd@4, you can switch to the dark theme with one click, see the image below
Configuration
Build-time Configuration
Note: Build-time configuration will go through JSON conversion, so here you can only set configurations that conform to the JSON format. For function configurations like algorithm, set them at Runtime Configuration.
dark
Enables dark theme.
- Type:
boolean - Default:
false
compact
Enables compact theme.
- Type:
boolean - Default:
false
For example:
export default {
antd: {
dark: true,
compact: true,
},
};Dark theme activation is only supported with antd version 4. Compact theme is supported with antd@>4.1.0.
import
- Type:
boolean
Configure antd's babel-plugin-import for on-demand loading.
style
- Type:
"less" | "css" - Default:
less
Configure the use of antd styles, default is less.
configProvider
- Type:
object
Configure antd's configProvider.
theme
- Type:
object
Configure theme token for antd@5, equivalent to setting configProvider.theme, and this configuration item has higher priority.
Note: This configuration is only available for antd v5
appConfig
- Type:
object
Configure the App wrapper component for antd, please note that for [email protected] ~ 5.2.3, it can only be enabled through appConfig: {}, and only antd >=5.3.0 supports more App configuration projects.
Note: This configuration is only available for antd v5.1.0 and above
momentPicker
- Type:
boolean
Configure whether antd's DatePicker, TimePicker, Calendar components use moment as the date processing library, default is false.
Note: This configuration is only available for antd v5 and above
styleProvider
- Type:
object
Configure antd's StyleProvider component, which is used to support legacy browsers, such as IE11. When your project is configured with legacy or targets that include ie, it will automatically be downgraded, no manual configuration required.
With antd 5.17.0 and above, set styleProvider: { layer: true } to place component styles in @layer antd and lower their priority. Also set antd.configProvider: {} (or keep your existing configuration) to enable an inner ConfigProvider for icon styles. Set layer: false to explicitly disable it; leaving it unset preserves the default or inherited StyleProvider behavior.
Note:
This configuration is only available for antd v5 and above.
The downgrade of CSS relies on
@ant-design/cssinjs. If you have explicitly installedantd, please also install and ensure that your@ant-design/cssinjsversion corresponds correctly withantd.
Runtime Configuration
In the runtime configuration of app.ts(x), you can modify the values of antd's ConfigProvider. Before using this feature, make sure you have enabled the antd.configProvider option, otherwise changes to ConfigProvider will not take effect:
// .umirc.ts
antd: {
configProvider: {}
}For example, to configure antd 5's theme preset algorithm and the maximum number of message notifications:
// app.ts
import { RuntimeAntdConfig } from 'umi';
import { theme } from 'antd';
export const antd: RuntimeAntdConfig = (memo) => {
memo.theme ??= {};
memo.theme.algorithm = theme.darkAlgorithm; // Configure the preset dark algorithm for antd5
memo.appConfig = {
message: {
// Configure the maximum number of messages displayed, when exceeding the limit, the earliest messages will be automatically closed
maxCount: 3,
}
}
return memo;
};Dynamically Switch Global Configuration
Note: This feature is only available for antd v5
Use useAntdConfig / useAntdConfigSetter methods to dynamically get and modify antd's ConfigProvider configuration, which can typically be used to dynamically modify themes.
Note: This feature depends on ConfigProvider, please also enable configProvider: {}.
import { Layout, Space, Button, version, theme, MappingAlgorithm } from 'antd';
import { useAntdConfig, useAntdConfigSetter } from 'umi';
const { darkAlgorithm, defaultAlgorithm } = theme;
export default function Page() {
const setAntdConfig = useAntdConfigSetter();
const antdConfig = useAntdConfig();
return (
<Layout>
<h1>with antd@{version}</h1>
<Space>
isDarkTheme
<Switch
checked={antdConfig?.theme?.algorithm.includes(darkAlgorithm)}
onChange={(data) => {
// This configuration will deep merge with the original configuration
setAntdConfig({
theme: {
algorithm: [
data ? darkAlgorithm : defaultAlgorithm,
],
},
});
// or
setAntdConfig((config) => {
const algorithm = config.theme!.algorithm as MappingAlgorithm[];
if (algorithm.includes(darkAlgorithm)) {
config.theme!.algorithm = [defaultAlgorithm]
} else {
config.theme!.algorithm = [darkAlgorithm];
}
return config;
});
}}
></Switch>
</Space>
</Layout>
);
}Using setAntdConfig, you can dynamically modify any property supported by antd@5 ConfigProvider.
FAQ
How to use other versions of antd?
Install the version of antd you need in your project.
Docs Docs Max Antd (`docs/docs/docs/max/antd.md`)
order: 3 toc: content
antd
整合 antd 组件库。
启用方式
配置开启,示例:
// config/config.ts
export default {
antd: {
// configProvider
configProvider: {},
// themes
dark: true,
compact: true,
// babel-plugin-import
import: true,
// less or css, default less
style: 'less',
// shortcut of `configProvider.theme`
// use to configure theme token, antd v5 only
theme: {},
// antd <App /> valid for version 5.1.0 or higher, default: undefined
appConfig: {},
// Transform DayJS to MomentJS
momentPicker: true,
// Add StyleProvider for legacy browsers
styleProvider: {
hashPriority: 'high',
legacyTransformer: true,
},
},
};介绍
包含以下功能:
- 内置 antd,目前内置版本是
^4.0.0 - 基于 babel-plugin-import 做按需编译
- 使用 antd@4 时,可一键切换为暗色主题,见下图
配置
构建时配置
注意:构建时配置会经过 json 转换,所以这里只能进行符合 json 格式的配置,如有例如 algorithm 等函数配置,可在运行时配置中进行设置。
dark
开启暗色主题。
- Type:
boolean - Default:
false
compact
开启紧凑主题。
- Type:
boolean - Default:
false
比如:
export default {
antd: {
dark: true,
compact: true,
},
};启用暗色主题,只有 antd 使用版本 4 时才支持。紧凑主题在 antd@>4.1.0 时支持。
import
- Type:
boolean
配置 antd 的 babel-plugin-import 按需加载。
style
- Type:
"less" | "css" - Default:
less
配置使用 antd 的样式,默认 less。
configProvider
- Type:
object
配置 antd 的 configProvider。
theme
- Type:
object
配置 antd@5 的 theme token,等同于配置 configProvider.theme,且该配置项拥有更高的优先级。
注意:该配置项仅 antd v5 可用
appConfig
- Type:
object
配置 antd 的 App 包裹组件,请注意 [email protected] ~ 5.2.3 仅能通过 appConfig: {} 启用,只有 antd >=5.3.0 才支持更多 App 配置项目。
注意:该配置项仅 antd v5.1.0 及以上可用
momentPicker
- Type:
boolean
配置 antd 的 DatePicker、TimePicker、Calendar 组件是否使用 moment 作为日期处理库,默认为 false。
注意:该配置项仅 antd v5 及以上可用
styleProvider
- Type:
object
配置 antd 的 StyleProvider 组件,该组件用于兼容低版本浏览器,如 IE11。当你的项目配置了 legacy 或者 targets 包含 ie 时,会自动进行降级处理,不需要手动配置。
使用 antd 5.17.0 及以上版本时,可配置 styleProvider: { layer: true },将组件样式放入 @layer antd,降低样式优先级。请同时配置 antd.configProvider: {}(或保留已有配置),以启用内层 ConfigProvider 处理图标样式。设置 layer: false 可显式关闭,未设置时保留 StyleProvider 的默认或继承行为。
注意:
该配置项仅 antd v5 及以上可用。
降级 CSS 需要依赖
@ant-design/cssinjs,若你显示安装了antd,请安装并确保你的@ant-design/cssinjs版本与antd正确对应。
运行时配置
在 app.ts(x) 运行时配置中可以修改 antd ConfigProvider 的值,使用此功能前,确保你已经打开了 antd.configProvider 选项,否则对 ConfigProvider 的修改不会生效:
// .umirc.ts
antd: {
configProvider: {}
}如配置 antd 5 的主题预设算法和 message 弹出框最大数:
// app.ts
import { RuntimeAntdConfig } from 'umi';
import { theme } from 'antd';
export const antd: RuntimeAntdConfig = (memo) => {
memo.theme ??= {};
memo.theme.algorithm = theme.darkAlgorithm; // 配置 antd5 的预设 dark 算法
memo.appConfig = {
message: {
// 配置 message 最大显示数,超过限制时,最早的消息会被自动关闭
maxCount: 3,
}
}
return memo;
};动态切换全局配置
注意:该功能仅 antd v5 可用
通过 useAntdConfig / useAntdConfigSetter 方法来动态获取、修改 antd 的 ConfigProvider 配置,通常可用于动态修改主题。
注:此功能需依赖 ConfigProvider ,请一并开启 configProvider: {} 。
import { Layout, Space, Button, version, theme, MappingAlgorithm } from 'antd';
import { useAntdConfig, useAntdConfigSetter } from 'umi';
const { darkAlgorithm, defaultAlgorithm } = theme;
export default function Page() {
const setAntdConfig = useAntdConfigSetter();
const antdConfig = useAntdConfig();
return (
<Layout>
<h1>with antd@{version}</h1>
<Space>
isDarkTheme
<Switch
checked={antdConfig?.theme?.algorithm.includes(darkAlgorithm)}
onChange={(data) => {
// 此配置会与原配置深合并
setAntdConfig({
theme: {
algorithm: [
data ? darkAlgorithm : defaultAlgorithm,
],
},
});
// or
setAntdConfig((config) => {
const algorithm = config.theme!.algorithm as MappingAlgorithm[];
if (algorithm.includes(darkAlgorithm)) {
config.theme!.algorithm = [defaultAlgorithm]
} else {
config.theme!.algorithm = [darkAlgorithm];
}
return config;
});
}}
></Switch>
</Space>
</Layout>
);
}使用 setAntdConfig 可以动态修改 antd@5 ConfigProvider 组件支持的所有属性。
FAQ
如何使用 antd 的其他版本?
在项目中安装你需要的 antd 版本。
Docs Docs Max Charts.En US (`docs/docs/docs/max/charts.en-US.md`)
order: 4 toc: content translated_at: '2024-03-17T09:39:12.613Z'
Charts
The Umi team recommends using Ant Design Charts or Pro Components in line with Ant Design to add visualization charts 📈 to your project.
This tutorial will provide you with some common use cases.
Ant Design Charts
Ant Design Charts is a React implementation of the AntV project, developed by the data visualization team of Ant Group.
You can install the complete Ant Design Charts package:
pnpm install @ant-design/chartsYou can also just include the sub-packages you use, for example:
# Install statistical chart package
pnpm install @ant-design/plotsIn the example usage below, we will keep imports to a minimum.
You can also directly read the complete Getting Started documentation and Chart Examples of Ant Design Charts.
Line Chart
Now, we need to create a line chart to display these data.
First, include the statistical chart package:
pnpm install @ant-design/plotsWrite code to fetch the data (omitted below):
import { useState, useEffect } from 'react';
const DemoLine = () => {
const [data, setData] = useState([]);
useEffect(() => {
asyncFetch();
}, []);
const asyncFetch = () => {
fetch('https://gw.alipayobjects.com/os/bmw-prod/1d565782-dde4-4bb6-8946-ea6a38ccf184.json')
.then((response) => response.json())
.then((json) => setData(json))
.catch((error) => {
console.log('fetch data failed', error);
});
};
};Thus, we have obtained the data and saved the JSON object's content to data. Each data object is like:
{
"Date": "2010-01",
"scales": 1998,
}Display the data on the line chart:
import React from 'react';
import { Line } from '@ant-design/plots';
const DemoLine: React.FC = () => {
// fetch data
const config = {
data,
padding: 'auto',
xField: 'Date',
yField: 'scales',
xAxis: {
// type: 'timeCat',
tickCount: 5,
},
smooth: true,
};
return <Line {...config} />;
};In this, the Date attribute in data will serve as the X-axis horizontal axis of the line chart, and the scales attribute will serve as the Y-axis vertical axis for drawing.
The complete line chart code and effect can be viewed on this page.
Column Chart
Now, we need to display the page load time through a column chart.
First, include the statistical chart package:
pnpm install @ant-design/plotsSuppose we have the following data:
const data = [
{
type: '0-1 seconds',
value: 0.55,
},
{
type: '1-3 seconds',
value: 0.21,
},
{
type: '3-5 seconds',
value: 0.13,
},
{
type: '5+ seconds',
value: 0.11,
},
];Especially, for the case of 5+ seconds, we want to mark it with a bright color. Then we can write the column chart code as follows:
import React from 'react';
import { Column } from '@ant-design/plots';
const DemoColumn: React.FC = () => {
// fetch data
const paletteSemanticRed = '#F4664A';
const brandColor = '#5B8FF9';
const config = {
data,
xField: 'type',
yField: 'value',
seriesField: '',
color: ({ type }) => {
if (type === '5+ seconds') {
return paletteSemanticRed;
}
return brandColor;
},
legend: false,
xAxis: {
label: {
autoHide: true,
autoRotate: false,
},
},
};
return <Column {...config} />;
};The complete column chart code and effect can be viewed on this page.
Word Cloud
Now, we need to display the names of some countries in the world in a word cloud manner. The more populous the country, the larger the font size of its name in the word cloud.
First, include the statistical chart package:
pnpm install @ant-design/plotsGet the data containing country population figures. It looks like:
{
"country": "China",
"value": 1383220000,
"category": "asia",
}Render the data to get the word cloud:
import React from 'react';
import { WordCloud } from '@ant-design/plots';
const DemoWordCloud: React.FC = () => {
// fetch data
const config = {
data,
wordField: 'country',
weightField: 'value',
color: '#122c6a',
interactions: [
{
type: 'element-active',
},
],
state: {
active: {
style: {
lineWidth: 2,
},
},
},
};
return <WordCloud {...config} />;
};The complete word cloud code and effect can be viewed on this page.
Dot Map
Now, we need to display the distribution of cities and districts in our country on the map in the form of dots.
First, include the map package:
pnpm install @ant-design/mapsGet the data containing all district data. It looks like:
{
"list": [
{
"lnglat": [
116.258446,
37.686622
],
"name": "Jing County",
"style": 2
},
// ...
]
}Where, style of 0 stands for municipal city, 1 for county-level city, and 2 for district.
Render the data to get the dot map:
import React from 'react';
import { DotMap } from '@ant-design/maps';
const DemoDotMap: React.FC = () => {
// fetch data
const config = {
map: {
type: 'mapbox',
style: 'dark',
zoom: 3,
center: [107.4976, 32.1697],
pitch: 0,
},
source: {
data,
parser: {
type: 'json',
coordinates: 'lnglat',
},
},
size: 4,
color: {
field: 'style',
value: ({ style }) => {
if (style == 0) {
return '#14B4C9';
} else if (style == 1) {
return '#3771D9';
} else {
return '#B8EFE2';
}
},
},
legend: {
type: 'category',
position: 'bottomleft',
items: [
{
color: '#14B4C9',
value: 'Municipal city',
},
{
color: '#3771D9',
value: 'County-level city',
},
{
color: '#B8EFE2',
value: 'District',
},
],
},
};
return <DotMap {...config} />;
};The complete dot map code and effect can be viewed on this page.
Pro Components
Pro Components are aimed at mid-to-background applications, offering a higher degree of abstraction on top of Ant Design, providing a higher-level design specification to help developers quickly build high-quality pages.
You should import the sub-packages you use as needed:
# Import advanced table
pnpm install @ant-design/pro-table
# Import advanced list
pnpm install @ant-design/pro-listYou can also directly read Pro Components' complete Getting Started documentation, Table Examples and List Examples.
The examples below will assume you have already imported the sub-packages you will use.
Pro Table Advanced Table
Now, you need to quickly build a table containing members and related information.
Member information is as follows:
const realNames = ['Ma Baba', 'Zhang Sanfeng', 'Fei Peng', 'Xu Changqing'];
const nickNames = ['Baba', 'Junbao', 'Jingtian', 'Mr. Xu'];
const emails = ['[email protected]', '[email protected]', '[email protected]', '[email protected]'];
const phones = ['18800001234', '13900002345', '17200003456', '17800004567'];Define a Member type:
export type Member = {
id: number;
realName: string;
nickName: string;
email: string;
phone: string;
};Process member information to build a Member array:
const memberList: Member[] = [];
for (let i = 0; i < realNames.length; i++) {
memberList.push({
id: `${102047 + i}`,
realName: realNames[i],
nickName: nickNames[i],
email: emails[i],
phone: phones[i],
});
}Pass the array to Pro Table to quickly build the table:
import React from 'react';
import type { ProColumns } from '@ant-design/pro-table';
import ProTable from '@ant-design/pro-table';
// resolve member info list
const MemberList: React.FC = () => {
const columns: ProColumns<Member>[] = [
{
dataIndex: 'realName',
title: 'Name',
},
{
dataIndex: 'nickName',
title: 'Nickname',
},
{
dataIndex: 'email',
title: 'Account',
},
{
dataIndex: 'phone',
title: 'Phone number',
},
{
title: 'Action',
dataIndex: 'x',
valueType: 'option',
render: (_, record) => {
return [<a key="edit">Edit</a>, <a key="remove">Remove</a>];
},
},
];
return (
columns={columns}
request={(params, sorter, filter) => {
console.log(params, sorter, filter);
return Promise.resolve({
data: memberList,
success: true,
});
}}
rowKey="id"
pagination={{
showQuickJumper: true,
}}
toolBarRender={false}
search={false}
/>
);
}The complete table code and effect can be viewed on this page.
Pro List Advanced List
Now, you need to quickly build a list containing test information.
Test information is as follows:
export type Test = {
id: number;
name: string;
image: string;
desc: string;
};
const testList: Test[] = [
{
id: 9903,
name: 'Yuque\'s Sky',
image:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
desc: 'Covered all test cases for the login module',
},
{
id: 9904,
name: 'Ant Design',
image:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
desc: 'Covered all test cases, all scenarios have been verified under the Node 17 testing environment',
},
{
id: 9905,
name: 'Ant Group Experience Technology',
image:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
desc: 'Covered all testing requirements, all scenarios have been verified under the Ubuntu 14.04 testing environment',
},
{
id: 9906,
name: 'TechUI',
image:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
desc: 'Covered all testing requirements, all scenarios have been verified under MacOS testing environment',
},
];Pass the test information to Pro List to quickly build the list:
import React from 'react';
import { Button } from 'antd';
import ProList from '@ant-design/pro-list';
// resolve test info list
const MemberList: React.FC = () => {
return (
toolBarRender={() => {
return [
New
,
];
}}
rowKey="id"
headerTitle="Test Results"
dataSource={testList}
showActions="hover"
showExtra="hover"
metas={{
title: {
dataIndex: 'name',
},
avatar: {
dataIndex: 'image',
},
dataIndex: 'desc',
},
actions: {
render: (text, row) => [
<a key="link">Link</a>,
<a key="warning">Alert</a>,
<a key="view">View</a>,
],
},
}}
/>
);
}The complete list code and effect can be viewed on this page.
Docs Docs Max Charts (`docs/docs/docs/max/charts.md`)
order: 4 toc: content
图表
Umi 团队推荐使用与 Ant Design 一脉相承的 Ant Design Charts 或 Pro Components 来为您的项目添加可视化图表 📈。
本教程将为您提供一些常见的使用案例。
Ant Design Charts
Ant Design Charts 是 AntV 项目的 React 实现,由蚂蚁集团数据可视化团队开发。
您可以安装完整的 Ant Design Charts 包:
pnpm install @ant-design/charts也可以仅引入使用到的子包,例如:
# 安装统计图表包
pnpm install @ant-design/plots在下面的使用示例中,我们将最小化引入。
您也可以直接阅读 Ant Design Charts 完整的上手文档和图表示例。
曲线图
现在,我们需要将这些数据制作为一个曲线图展示出来。
首先,引入统计图表包:
pnpm install @ant-design/plots编写代码获取数据(后略):
import { useState, useEffect } from 'react';
const DemoLine = () => {
const [data, setData] = useState([]);
useEffect(() => {
asyncFetch();
}, []);
const asyncFetch = () => {
fetch('https://gw.alipayobjects.com/os/bmw-prod/1d565782-dde4-4bb6-8946-ea6a38ccf184.json')
.then((response) => response.json())
.then((json) => setData(json))
.catch((error) => {
console.log('fetch data failed', error);
});
};
};这样,我们获取到了数据,并将数据 JSON 对象的内容保存到 data 中去。每个数据对象形如:
{
"Date": "2010-01",
"scales": 1998,
}将数据展示到曲线图上:
import React from 'react';
import { Line } from '@ant-design/plots';
const DemoLine: React.FC = () => {
// fetch data
const config = {
data,
padding: 'auto',
xField: 'Date',
yField: 'scales',
xAxis: {
// type: 'timeCat',
tickCount: 5,
},
smooth: true,
};
return <Line {...config} />;
};其中,data 中数据的 Date 属性将作为曲线图的 X 横坐标,scales 属性将作为曲线图的 Y 纵坐标绘图。
完整的曲线图代码和效果可查看此页面。
柱状图
现在,我们需要把页面加载的时间通过柱状图展示出来。
首先,引入统计图表包:
pnpm install @ant-design/plots假设我们有如下 data:
const data = [
{
type: '0-1 秒',
value: 0.55,
},
{
type: '1-3 秒',
value: 0.21,
},
{
type: '3-5 秒',
value: 0.13,
},
{
type: '5+ 秒',
value: 0.11,
},
];特别的,对于 5+ 秒 的情况,我们想要用鲜明的颜色标注出来。那么可以编写柱状图代码如下:
import React from 'react';
import { Column } from '@ant-design/plots';
const DemoColumn: React.FC = () => {
// fetch data
const paletteSemanticRed = '#F4664A';
const brandColor = '#5B8FF9';
const config = {
data,
xField: 'type',
yField: 'value',
seriesField: '',
color: ({ type }) => {
if (type === '5+ 秒') {
return paletteSemanticRed;
}
return brandColor;
},
legend: false,
xAxis: {
label: {
autoHide: true,
autoRotate: false,
},
},
};
return <Column {...config} />;
};完整的柱状图代码和效果可查看此页面。
词云
现在,我们需要把世界上部分国家的名字以词云的方式展示出来。国家的人数越多,词云上国家的名字字体越大。
首先,引入统计图表包:
pnpm install @ant-design/plots获取包含国家人口数量的 data。形如:
{
"country": "China",
"value": 1383220000,
"category": "asia",
}渲染数据,获得词云图:
import React from 'react';
import { WordCloud } from '@ant-design/plots';
const DemoWordCloud: React.FC = () => {
// fetch data
const config = {
data,
wordField: 'country',
weightField: 'value',
color: '#122c6a',
interactions: [
{
type: 'element-active',
},
],
state: {
active: {
style: {
lineWidth: 2,
},
},
},
};
return <WordCloud {...config} />;
};完整的词云图代码和效果可查看此页面。
散点地图
现在,我们需要将我国城市和区县分布在地图上以散点的形式展示出来。
首先,引入地图包:
pnpm install @ant-design/maps获取包含所有区县数据的 data。形如:
{
"list": [
{
"lnglat": [
116.258446,
37.686622
],
"name": "景县",
"style": 2
},
// ...
]
}其中,style 为 0 表示为地级市,1 表示为县城市,2 表示为区县。
渲染数据,获得散点地图:
import React from 'react';
import { DotMap } from '@ant-design/maps';
const DemoDotMap: React.FC = () => {
// fetch data
const config = {
map: {
type: 'mapbox',
style: 'dark',
zoom: 3,
center: [107.4976, 32.1697],
pitch: 0,
},
source: {
data,
parser: {
type: 'json',
coordinates: 'lnglat',
},
},
size: 4,
color: {
field: 'style',
value: ({ style }) => {
if (style == 0) {
return '#14B4C9';
} else if (style == 1) {
return '#3771D9';
} else {
return '#B8EFE2';
}
},
},
legend: {
type: 'category',
position: 'bottomleft',
items: [
{
color: '#14B4C9',
value: '地级市',
},
{
color: '#3771D9',
value: '县城市',
},
{
color: '#B8EFE2',
value: '区县',
},
],
},
};
return <DotMap {...config} />;
};完整的散点地图代码和效果可查看此页面。
Pro Components
Pro Components 面向中后台类应用,对 Ant Design 进行了更高程度的抽象,提供了更上层的设计规范,能够助开发者快速搭建出高质量的页面。
您应当按需引入使用到的子包:
# 引入高级表格
pnpm install @ant-design/pro-table
# 引入高级列表
pnpm install @ant-design/pro-list您也可以直接阅读 Pro Components 完整的上手文档,表格示例和列表示例。
下面的示例中将默认您已经引入了使用到的子包。
Pro Table 高级表格
现在,您需要快速构建一个包含有成员和相关信息的表格。
成员信息如下:
const realNames = ['马巴巴', '张三丰', '飞蓬', '徐长卿'];
const nickNames = ['巴巴', '君宝', '景天', '姓徐的'];
const emails = ['[email protected]', '[email protected]', '[email protected]', '[email protected]'];
const phones = ['18800001234', '13900002345', '17200003456', '17800004567'];定义一个 Member 类型:
export type Member = {
id: number;
realName: string;
nickName: string;
email: string;
phone: string;
};处理成员信息,构建一个 Member 数组:
const memberList: Member[] = [];
for (let i = 0; i < realNames.length; i++) {
memberList.push({
id: `${102047 + i}`,
realName: realNames[i],
nickName: nickNames[i],
email: emails[i],
phone: phones[i],
});
}将数组传递给 Pro Table,快速构建表格:
import React from 'react';
import type { ProColumns } from '@ant-design/pro-table';
import ProTable from '@ant-design/pro-table';
// resolve member info list
const MemberList: React.FC = () => {
const columns: ProColumns<Member>[] = [
{
dataIndex: 'realName',
title: '姓名',
},
{
dataIndex: 'nickName',
title: '昵称',
},
{
dataIndex: 'email',
title: '账号',
},
{
dataIndex: 'phone',
title: '手机号',
},
{
title: '操作',
dataIndex: 'x',
valueType: 'option',
render: (_, record) => {
return [<a key="edit">编辑</a>, <a key="remove">移除</a>];
},
},
];
return (
columns={columns}
request={(params, sorter, filter) => {
console.log(params, sorter, filter);
return Promise.resolve({
data: memberList,
success: true,
});
}}
rowKey="id"
pagination={{
showQuickJumper: true,
}}
toolBarRender={false}
search={false}
/>
);
}完整的表格代码和效果可查看此页面。
Pro List 高级列表
现在,您需要快速构建一个包含测试信息的列表。
测试信息如下:
export type Test = {
id: number;
name: string;
image: string;
desc: string;
};
const testList: Test[] = [
{
id: 9903,
name: '语雀的天空',
image:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
desc: '覆盖了登录模块的所有测试用例',
},
{
id: 9904,
name: 'Ant Design',
image:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
desc: '覆盖了所有测试用例,所有的案例均已在 Node 17 测试环境验证完成',
},
{
id: 9905,
name: '蚂蚁集团体验科技',
image:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
desc: '覆盖了所有测试需求,所有的案例均已在 Ubuntu 14.04 测试环境验证完成',
},
{
id: 9906,
name: 'TechUI',
image:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
desc: '覆盖了所有测试需求,所有的案例均已在 MacOS 测试环境验证完成',
},
];将测试信息传递给 Pro List,快速构建列表:
import React from 'react';
import { Button } from 'antd';
import ProList from '@ant-design/pro-list';
// resolve test info list
const MemberList: React.FC = () => {
return (
toolBarRender={() => {
return [
新建
,
];
}}
rowKey="id"
headerTitle="测试结果"
dataSource={testList}
showActions="hover"
showExtra="hover"
metas={{
title: {
dataIndex: 'name',
},
avatar: {
dataIndex: 'image',
},
dataIndex: 'desc',
},
actions: {
render: (text, row) => [
<a key="link">链路</a>,
<a key="warning">报警</a>,
<a key="view">查看</a>,
],
},
}}
/>
);
}完整的列表代码和效果可查看此页面。
Docs Docs Max Data Flow.En US (`docs/docs/docs/max/data-flow.en-US.md`)
order: 5 toc: content translated_at: '2024-03-18T00:49:20.502Z'
Data Stream
@umi/max has a built-in data flow management plugin, which is a lightweight data management solution based on the hooks paradigm. It can be used to manage global shared data in Umi projects.
Configuration
e.g.
export default {
model: {
extraModels: ['src/models/userModel.ts'],
preferSourceNamespace: false,
sort: (a, b) => a.namespace.localeCompare(b.namespace),
},
};extraModels
- Type:
string[] - Default:
[]
Configure extraModels to automatically add these Model files to the data stream management.
preferSourceNamespace
- Type:
boolean - Default:
false
Whether to resolve namespace from Model source (applies to both conventional Models and extraModels).
It is off by default so a business return field named namespace is not mistaken for the registry key. When enabled, the namespace declared in source takes precedence, which helps customize namespaces when sharing Models across a monorepo / packages.
// .umirc.ts
export default {
model: {
preferSourceNamespace: true,
// extraModels: ['../../packages/shared/src/models/tenant.ts'],
},
};
// model source
export default () => ({
namespace: 'businessTenant',
user: null,
});
// usage
useModel('businessTenant');:::warning{title=Note}
After enabling this, do not return namespace as a normal business field; it will be parsed as the Model registry name. To customize the namespace for a single file only, append #{"namespace":"xxx"} to the path instead of turning this option on.
:::
sort
- Type:
(a: Model, b: Model) => number - Default:
(a, b) => a.namespace.localeCompare(b.namespace)
Configure sort to sort the Model based on the return value of the sort function.
Getting Started
Creating a Model
The dat