= {};
+ for (const key of Object.keys(data)) {
+ const val = data[key];
+ reversed[val] = key;
+ }
+ return reversed;
+}
+```
+
+先来写我们第一个测试用例, 确保 `fetcher` 使用传入的 `url` 请求 api 的数据
+
+```ts
+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');
+});
+```
+
+执行测试
+
+```bash
+$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 的等待时间。
+:::
+
+我们再写一个用例来测试这个工具函数完成了键值的对换功能。
+
+```ts
+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' });
+});
+```
+
+让每个测试用例只关注一个功能点,可以让用例在重构的时候给我们更准确的反馈,例如改动破坏了什么功能。更多的用例请参考 [代码](https://github.com/umijs/umi/tree/master/examples/test-test/utils/reverseApiData.test.ts)。
+
+## UI 测试
+
+组件和 UI 相关的测试推荐使用 `@testing-library/react`。
+
+### 渲染结果判断
+
+- 使用 jest 的 snapshot
+
+```tsx
+// 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();
+ expect(container).toMatchSnapshot();
+});
+```
+
+执行 `npx jest` 后会在测试用例同级目录会生成 `__snapshots__` 文件夹和用例的 snapshot,请加入到版本管理中。
+
+- 使用 jest 的 inline snapshot
+
+```tsx
+// examples/test-test/components/Greet/Greet.test.tsx
+test('renders Greet without name by inline snapshot', () => {
+ const { container } = render();
+
+ expect(container).toMatchInlineSnapshot();
+});
+```
+
+执行 `npx jest` 后会在 `toMatchInlineSnapshot` 函数的参数中填入 snapshot 字符串;这种方式适合渲染结果比较短的内容。
+
+- 使用 @testing-library/jest-dom 断言
+
+```tsx
+// 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();
+
+ const greetDom = screen.getByText('Anonymous');
+ expect(greetDom).toBeInTheDocument();
+});
+```
+
+更多[断言 API](https://github.com/testing-library/jest-dom)
+
+### 组件行为判断
+
+```tsx
+// examples/test-test/components/Greet/Greet.test.tsx
+
+test('Greet click', async () => {
+ const onClick = jest.fn();
+ const { container } = render();
+
+ const greetDom = screen.getByText('Anonymous');
+ await fireEvent.click(screen.getByText(/hello/i));
+
+ expect(onClick).toBeCalledTimes(1);
+});
+```
diff --git a/docs/docs/docs/guides/typescript.en-US.md b/docs/docs/docs/guides/typescript.en-US.md
new file mode 100644
index 000000000000..b4d0057a9cf1
--- /dev/null
+++ b/docs/docs/docs/guides/typescript.en-US.md
@@ -0,0 +1,25 @@
+---
+order: 9
+toc: content
+---
+# TypeScript
+
+Umi 默认开启 TypeScript,如果是使用官方脚手架创建项目,内置的文件是以 `xx.(ts|tsx)` 为主的。
+
+## 配置中的 TypeScript 提示
+
+如果想要在配置时拥有 TypeScript 语法提示,可以在配置的地方包一层 `defineConfig()`:
+
+```ts
+// .umirc.ts
+
+import { defineConfig } from 'umi';
+
+export default defineConfig({
+ routes: [
+ { path: '/', component: '@/pages/index' },
+ ],
+});
+```
+
+![defineConfig](https://img.alicdn.com/imgextra/i4/O1CN01WqZ2Ma1ZqiNbTefi6_!!6000000003246-2-tps-1240-1000.png)
diff --git a/docs/docs/docs/guides/use-plugins.en-US.md b/docs/docs/docs/guides/use-plugins.en-US.md
new file mode 100644
index 000000000000..a7d88011c4f9
--- /dev/null
+++ b/docs/docs/docs/guides/use-plugins.en-US.md
@@ -0,0 +1,48 @@
+---
+order: 4
+toc: content
+---
+
+# 插件
+
+## 使用插件
+
+在普通的 Umi 应用中,默认 **不附带任何插件** ,如需使用 Max 的功能(如 数据流、antd 等),需要手动安装插件并开启他们:
+
+```bash
+ pnpm add -D @umijs/plugins
+```
+
+如开启 antd 插件:
+
+```ts
+// .umirc.ts
+export default {
+ plugins: ['@umijs/plugins/dist/antd'],
+ antd: {}
+}
+```
+
+Umi 与 Max 的区别是 Max 已经内置了大部分插件,如 数据流( `initial-state` 、 `model` )、`antd` 等,这些插件都可以在 `@umijs/plugins/dist/*` 加载并且开启。
+
+如需进一步了解 Max 具备的功能和配置说明,请参阅 [Umi Max](../max/introduce) 章节。
+
+
+:::info{title=💡}
+**我是否应该选择 Max ?**
+使用 Max 并不代表需要使用全部 Max 的功能,可以根据需求关闭插件,所以当你需要使用 Max 的功能时,可以总是选择创建 Max 项目。
+:::
+
+## 项目级插件
+
+若你想在项目中快速使用插件的功能(如 [修改产物的 html](../introduce/faq#documentejs-去哪了如何自定义-html-模板) ),可以在项目的根目录创建 `plugin.ts` 编写一个项目级插件,该文件将被自动作为插件加载。
+
+有关更详细的目录结构说明请参阅 [目录结构](./directory-structure) 章节。
+
+## 开发插件
+
+请参阅 [开发插件](./plugins) 章节。
+
+
+
+
diff --git a/docs/docs/docs/guides/use-vue.en-US.md b/docs/docs/docs/guides/use-vue.en-US.md
new file mode 100644
index 000000000000..2a08f363bb0f
--- /dev/null
+++ b/docs/docs/docs/guides/use-vue.en-US.md
@@ -0,0 +1,221 @@
+---
+order: 17
+toc: content
+---
+
+# 使用 Vue
+
+本文介绍如何在 Umi 中使用 Vue , Umi Vue 大部分配置和 React 相同,这里只列出一些 Vue 独有的配置。
+
+## 启动方式
+
+### 安装
+
+```bash
+pnpm add @umijs/preset-vue -D
+```
+
+### 配置预设
+
+```ts
+// .umirc.ts or config/config.ts 中
+export default {
+ presets: [require.resolve('@umijs/preset-vue')],
+};
+
+```
+
+## 路由
+
+### 配置式路由
+
+:::info
+这里仅列出和 React 路由配置差异部分。
+:::
+
+#### name
+
+命名路由
+
+除了 `path` 之外,你还可以为任何路由提供 `name` :
+
+```ts
+export default {
+ routes: [
+ {
+ path: '/user/:username',
+ name: 'user',
+ component: 'index'
+ }
+ ]
+}
+```
+
+要链接到一个命名的路由,可以向 `router-link` 组件的 `to` 属性传递一个对象:
+
+```html
+
+ User
+
+```
+
+效果和命令式地调用 `router.push` 一致:
+
+```ts
+router.push({ name: 'user', params: { username: 'erina' } })
+```
+
+在这方法都能导航到路径 `/user/erina`。
+
+#### redirect
+
+重定向也是通过 `routes` 配置来完成,下面例子是从 `/home` 重定向到 `/`:
+
+```ts
+export default {
+ routes: [
+ {
+ path: '/home',
+ redirect: '/'
+ }
+ ]
+}
+```
+
+重定向的目标也可以是一个命名的路由:
+
+```ts
+export default {
+ routes: [
+ {
+ path: '/home',
+ redirect: {
+ name: 'homepage'
+ }
+ }
+ ]
+}
+```
+
+#### alias
+
+重定向是指当用户访问 `/home` 时,URL 会被 `/` 替换,然后匹配成 `/`。那么什么是别名呢?
+
+将 `/` 别名为 `/home`,意味着当用户访问 `/home` 时,URL 仍然是 `/home`,但会被匹配为用户正在访问 `/`。
+
+上面对应的路由配置为:
+```ts
+export default {
+ routes: [
+ {
+ path: '/',
+ component: 'index',
+ alias: '/home'
+ }
+ ]
+}
+```
+
+通过别名,你可以自由地将 UI 结构映射到一个任意的 URL,而不受配置的嵌套结构的限制。使别名以 `/` 开头,以使嵌套路径中的路径成为绝对路径。你甚至可以将两者结合起来,用一个数组提供多个别名:
+
+```ts
+export default {
+ routes: [
+ {
+ path: '/users',
+ component: 'users',
+ routes: [
+ // 为这 3 个 URL 呈现 UserList
+ // - /users
+ // - /users/list
+ // - /people
+ { path: '', component: '/users/UserList', alias: ['/people', 'list'] },
+ ]
+ }
+ ]
+}
+```
+
+### 页面跳转
+
+```html
+
+```
+
+更多[详见](https://router.vuejs.org/guide/advanced/composition-api.html#accessing-the-router-and-current-route-inside-setup)
+
+### router-link
+
+[详见](https://router.vuejs.org/guide/#router-link)
+
+### router-view
+
+[详见](https://router.vuejs.org/guide/#router-view)
+
+## 运行时配置
+
+可以通过在约定的 `src/app.tsx` 通过 export 配置来控制 vue vue-router 相关的配置。
+
+### router
+
+配置路由配置
+
+```ts
+// 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 的实例,可以进行全局组件注册,路由拦截器等。
+
+```ts
+export function onMounted({ app, router }: any) {
+ console.log('onMounted', app, router);
+ app.provide('umi-hello', {
+ h: 'hello',
+ w: 'word',
+ });
+}
+```
+
+### rootContainer(container)
+
+修改交给 vue-router 渲染时的根组件。
+
+比如用于在外面包一个父组件
+
+```ts
+import { h } from 'vue'
+
+export function rootContainer(container) {
+ return h(ThemeProvider, null, container);
+}
+```
+
+## Examples
+
+更多详见 demo :
+
+* [boilerplate-vue](https://github.com/umijs/umi/tree/master/examples/boilerplate-vue)
+* [with-vue-pinia](https://github.com/umijs/umi/tree/master/examples/with-vue-pinia)
+* [with-vue-element-plus](https://github.com/umijs/umi/tree/master/examples/with-vue-element-plus)
diff --git a/docs/docs/docs/introduce/contributing.en-US.md b/docs/docs/docs/introduce/contributing.en-US.md
new file mode 100644
index 000000000000..57bf115b4620
--- /dev/null
+++ b/docs/docs/docs/introduce/contributing.en-US.md
@@ -0,0 +1,197 @@
+---
+order: 3
+toc: content
+---
+
+# 参与贡献
+
+❤️ Loving Umi and want to get involved? Thanks!
+
+## 环境准备
+
+### Node.js 和 pnpm
+
+开发 Umi 需要 Node.js 16+ 和 `pnpm` v8。
+
+推荐使用 [`nvm`](https://github.com/nvm-sh/nvm) 管理 Node.js,避免权限问题的同时,还能够随时切换当前使用的 Node.js 的版本。在 Windows 系统下的开发者可以使用 [`nvm-windows`](https://github.com/coreybutler/nvm-windows)。
+
+在 `pnpm` 的[官网](https://pnpm.io/installation)选择一种方式安装即可。
+
+### Clone 项目
+
+```bash
+$ git clone git@github.com:umijs/umi.git
+$ cd umi
+```
+
+### 安装依赖并构建
+
+```bash
+$ pnpm i && pnpm build
+```
+
+## 开发 Umi
+
+### 启动 dev 命令
+
+本地开发 Umi 必开命令,用于编译 `src` 下的 TypeScript 文件到 `dist` 目录,同时监听文件变更,有变更时增量编译。
+
+```bash
+$ pnpm dev
+```
+
+如果觉得比较慢,也可以只跑特定 package 的 `pnpm dev` 命令,比如。
+
+```bash
+$ cd packages/umi
+$ pnpm dev
+```
+
+### 跑 Example
+
+`examples` 目录下保存了各种用于测试的例子,跑 example 是开发 Umi 时确认功能正常的常用方式。每个 example 都配了 dev 脚本,所以进入 example 然后执行 `pnpm dev` 即可。
+
+```bash
+$ cd examples/boilerplate
+$ pnpm dev
+```
+
+如果要用 vite 模式跑,加 `--vite` 参数,
+
+```bash
+$ pnpm dev --vite
+```
+
+### 测试
+
+目前跑测试很快,10s+ 就完成了。推荐本地跑一遍再提 PR,减少 Round Trip。
+
+```bash
+$ 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 功能的。
+
+比如,
+
+```bash
+$ pnpm jest packages/plugin-docs/src/compiler.test.ts
+```
+
+## 贡献 Umi 文档
+
+Umi 的文档由 Umi@4 和 `@umijs/plugin-docs` 插件实现,本质上就是一个 Umi 项目。在根目录执行如下命令即可开始 Umi 文档的开发:
+
+```bash
+# 安装 Umi 文档依赖
+$ pnpm doc:deps
+# 启用 Umi 文档开发
+# 首次启动时编译耗时较长,请耐心等待
+$ pnpm doc: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`](https://github.com/atomiks/rehype-pretty-code),完整的能力和使用说明请移步它的[官方文档](https://rehype-pretty-code.netlify.app)。
+
+在根目录执行如下命令可以格式化仓库中已有的 Umi 文档:
+
+```bash
+$ pnpm format:docs
+```
+
+格式化文档后,建议**仅提交您撰写或修改的 Umi 文档**。不同文档贡献者的写作风格有一定的差异,格式化以后不一定能保留原来期望的样式。
+
+### 参与 Umi 文档插件开发
+
+新建一个终端,执行如下命令:
+
+```bash
+$ 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 文档插件的代码:
+
+```bash
+$ pnpm format:plugin-docs
+```
+
+在根目录执行如下命令可以构建 Umi 文档:
+
+```bash
+$ pnpm doc:build
+```
+
+## 新增 package
+
+新增 package 有封装脚本,无需手动复制 `package.json` 等文件:
+
+```bash
+# 创建 package 目录
+$ mkdir packages/foo
+# 初始化 package 开发
+$ pnpm bootstrap
+```
+
+## 更新依赖
+
+> 不推荐非 Core Maintainer 做大量依赖更新。因为涉及依赖预打包,有较多需注意的点。
+
+执行 `pnpm dep:update` 可更新依赖。
+
+```bash
+$ pnpm dep:update
+```
+
+由于 Umi 有针对依赖做预打包处理,更新依赖后还需检查更新的依赖是否为 devDependencies 并且有对此做依赖预打包。如果有,需要在对应 package 下执行 `pnpm build:deps` 并指定依赖,用于更新预打包的依赖文件。
+
+```bash
+$ pnpm build:deps --dep webpack-manifest-plugin
+```
+
+## 发布
+
+只有 Core Maintainer 才能执行发布。
+
+```bash
+$ pnpm release
+```
+
+## 通过 dist-tag 回滚
+
+比如要回滚到 4.0.81 。
+
+```bash
+$ pnpm -rc --filter "./packages/**" exec pnpm dist-tag add \$PNPM_PACKAGE_NAME@4.0.81 latest
+```
+
+## 加入 Contributor 群
+
+提交过 Bugfix 或 Feature 类 PR 的同学,如果有兴趣一起参与维护 Umi,可先用钉钉扫下方二维码(注明 github id)加我钉钉,然后我会拉到群里。
+
+
+
+如果你不知道可以贡献什么,可以到源码里搜 TODO 或 FIXME 找找。
diff --git a/docs/docs/docs/introduce/faq.en-US.md b/docs/docs/docs/introduce/faq.en-US.md
new file mode 100644
index 000000000000..3e417a31e3e1
--- /dev/null
+++ b/docs/docs/docs/introduce/faq.en-US.md
@@ -0,0 +1,206 @@
+---
+order: 5
+toc: content
+---
+# FAQ
+
+## 可以关闭 dynamicImport 吗?
+
+可以,但不建议关闭。
+
+1、安装依赖
+
+```bash
+ pnpm i babel-plugin-dynamic-import-node -D
+```
+
+2、配置里加上 `extraBabelPlugins` ,但只针对 production 开启
+
+```ts
+// .umirc.ts
+export default {
+ extraBabelPlugins: process.env.NODE_ENV === 'production'
+ ? ['babel-plugin-dynamic-import-node']
+ : []
+}
+```
+
+## 没有 dynamicImport 怎么配置它的 loading ?
+
+定义 `src/loading.tsx` :
+
+参考 [目录结构 > loading.tsx](../guides/directory-structure#loadingtsxjsx)
+
+## 可以用 react 17 吗?
+
+由于 umi v4 升级了默认的 react 版本到 v18,使用 umi4 时注意下依赖库和 react 18 的兼容情况,如果还需要使用 react 17,请执行以下命令并重启。
+
+```bash
+ pnpm add react@^17 react-dom@^17
+```
+
+## 代理静态资源到本地后,一直 restart 刷新页面
+
+
+
+解法:配置 `SOCKET_SERVER=127.0.0.1:${port}` 启动项目
+
+```bash
+ SOCKET_SERVER=http://127.0.0.1:8000 pnpm dev
+```
+
+## Error evaluating function `round`: argument must be a number
+
+
+
+解法:新版 less 中 `/` 默认被识别为属性简写,通过配置 `lessLoader: { math: 'always' }` 恢复旧版行为(默认将 `/` 用作计算符号)。
+
+## routes 里的 layout 配置选项不生效
+
+layout 配置被移动到了 `app.ts` ,详见 [runtime-config > layout](https://umijs.org/docs/api/runtime-config#layout)
+
+
+## document.ejs 去哪了,如何自定义 HTML 模板
+
+除了可以通过 配置项 注入外部 [script](https://umijs.org/docs/api/config#scripts) 、[css](https://umijs.org/docs/api/config#styles) 外,还可以使用项目级插件更灵活的修改 HTML 产物,参见:[issuecomment-1151088426](https://github.com/umijs/umi-next/issues/868#issuecomment-1151088426)
+
+## scripts 里配置的外部 js 文件为什么默认插入到 umi.js 的后面
+
+react 只有在页面加载完毕后才会开始运行,所以插到 `umi.js` 后面不会影响项目。
+
+若需要插到 `umi.js` 前面,可参见 [issuecomment-1176960539](https://github.com/umijs/umi/issues/8442#issuecomment-1176960539)
+
+## umi4 我怎么分包
+
+Umi 4 默认按页拆包,如果你觉得还需要优化,可以使用分包策略或手动拆包,详见:[代码拆分指南](../../blog/code-splitting)
+
+如果你有将所有 js 产物打包成单 `umi.js` 文件的需求,请关闭 [dynamicImport](#可以关闭-dynamicimport-吗) 。
+
+## _layout.tsx 去哪了,我怎么嵌套路由
+
+Umi 4 使用 react-router v6 ,通过 `` 展示嵌套路由内容,可参见:[issuecomment-1206194329](https://github.com/umijs/umi/issues/8850#issuecomment-1206194329)
+
+## 怎么用 GraphQL
+
+配置 `graph-ql` loader 的方式可参见: [discussions/8218](https://github.com/umijs/umi/discussions/8218)
+
+## 怎么用 WebAssembly
+
+配置如下:
+
+```ts
+// .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](https://github.com/umijs/umi/discussions/8541)
+
+## 怎么自定义 loader
+
+根据场景不同,你可能要先从 静态资源规则 中排除你需要加载的文件类型,再添加你自己的 loader / 或修改,可参考如下实例:
+
+ - [discussions/8218](https://github.com/umijs/umi/discussions/8218)
+
+ - [discussions/8452](https://github.com/umijs/umi/discussions/8452)
+
+## 第三方包里如何使用 css modules
+
+1. 直接将第三方包的 `jsx` / `ts` / `tsx` 源码发布到 npm ,无需转译为 `js` Umi 4 支持直接使用。
+
+2. 若第三方包产物是 `js` 的情况,需要将其纳入 babel 额外处理,才可以支持 css modules:
+
+```ts
+// .umirc.ts
+export default {
+ extraBabelIncludes: ['your-pkg-name']
+}
+```
+
+## npm link 的包不热更新怎么解决
+
+Umi 4 默认开启 `mfsu` ,默认忽略 `node_modules` 的变化,配置从 `mfsu` 排除该包即可:
+
+```ts
+// .umirc.ts
+
+export default {
+ mfsu: {
+ exclude: ['package-name']
+ },
+}
+```
+
+## 我的环境很多,多环境 config 文件的优先级是怎样的
+
+加载优先级详见 [UMI_ENV](../../docs/guides/env-variables#umi_env) ,无论是 `config/config.ts` 还是 `.umirc.ts` 同理。
+
+## IE 兼容性问题
+
+现代浏览器主流背景下,Umi 4 默认不兼容 IE 。
+
+若你有调整构建兼容目标、兼容非现代浏览器、兼容 IE 浏览器的需求,请参考 [非现代浏览器兼容](../../blog/legacy-browser) 。
+
+## SSR 问题
+
+SSR 目前还处于实验性特性,不建议在生产环境使用,若发现问题可即时在 [issue](https://github.com/umijs/umi/issues) 反馈。
+
+## Vue / Vite 问题
+
+Umi 4 新增了 Vite 模式和 Vue 支持,可能存在 edge case ,若发现问题可即时在 [issue](https://github.com/umijs/umi/issues) 反馈。
+
+## `history` 中取的 pathname 为什么和 `useLocation` 中的不一样
+
+这种情况是在项目配置了 `base` 。 `history.location.pathname` 取到的值是浏览器地址中的 `pathname`,它是包含 `base` 的;而路由相关 hooks 取到的值是**前端路由**定义中的 `pathname`,它是不包含 `base` 的。[参考](../guides/routes#location-信息)。
+
+## 调整产物的压缩编码格式
+
+默认 js / css 的压缩器 `esbuild` 会采用 `ascii` 格式编码压缩,这可能导致中文字符被转码,增大产物体积。
+
+可通过配置调整到 `utf8` 编码,防止字符被转换:
+
+```ts
+// .umirc.ts
+export default {
+ jsMinifierOptions: { charset: 'utf8' },
+ cssMinifierOptions: { charset: 'utf8' }
+}
+```
+
+或通过切换压缩器来解决:
+
+```ts
+// .umirc.ts
+export default {
+ jsMinifier: 'terser',
+ cssMinifier: 'cssnano'
+}
+```
+
+## devServer 选项怎么配置
+
+Umi 4 不再支持配置 `devServer` 选项,但你可以通过以下方式找到替代:
+
+1. [`proxy`](../api/config#proxy) 选项配置代理,可通过 `onProxyReq` 修改请求头信息,可参考 [#10760](https://github.com/umijs/umi/issues/10760#issuecomment-1471158059) 。
+
+2. 编写 [项目级插件](../guides/use-plugins#项目级插件) ,插入 express 中间件以实现对请求的修改,可参考 [#10060](https://github.com/umijs/umi/issues/10060#issuecomment-1471519707) 。
+
diff --git a/docs/docs/docs/introduce/introduce.en-US.md b/docs/docs/docs/introduce/introduce.en-US.md
new file mode 100644
index 000000000000..916a111a8b02
--- /dev/null
+++ b/docs/docs/docs/introduce/introduce.en-US.md
@@ -0,0 +1,56 @@
+---
+order: 1
+toc: content
+---
+# Umi 介绍
+
+
+
+
+## Umi 是什么?
+
+Umi,中文发音为「乌米」,是可扩展的企业级前端应用框架。Umi 以路由为基础,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。然后配以生命周期完善的插件体系,覆盖从源码到构建产物的每个生命周期,支持各种功能扩展和业务需求。
+
+Umi 是蚂蚁集团的底层前端框架,已直接或间接地服务了 10000+ 应用,包括 Java、Node、H5 无线、离线(Hybrid)应用、纯前端 assets 应用、CMS 应用、Electron 应用、Serverless 应用等。他已经很好地服务了我们的内部用户,同时也服务了不少外部用户,包括淘系、飞猪、阿里云、字节、腾讯、口碑、美团等。在 2021 年字节的[调研报告](https://zhuanlan.zhihu.com/p/403206195)中,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 做打包,可能不适用于对兼容性有要求或者依赖尺寸特别大的项目。
diff --git a/docs/docs/docs/introduce/philosophy.en-US.md b/docs/docs/docs/introduce/philosophy.en-US.md
new file mode 100644
index 000000000000..0fc950eed242
--- /dev/null
+++ b/docs/docs/docs/introduce/philosophy.en-US.md
@@ -0,0 +1,66 @@
+---
+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 设计思路文字稿》](https://mp.weixin.qq.com/s?__biz=MjM5NDgyODI4MQ%3D%3D&mid=2247484533&idx=1&sn=9b15a67b88ebc95476fce1798eb49146)。
+
+## 技术收敛
+
+
+
+
+
+技术收敛对团队而言尤其重要,他包含两层含义,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 等框架和工具都有类似实现。
+
+```ts
+// 大量插件为 umi 提供额外导出内容
+import { connect, useModel, useIntl, useRequest, MicroApp, ... } from 'umi';
+```
+
+这带来的好处是。通过 Umi 将大量依赖管理起来,用户无需手动安装;同时开发者在代码中也会少很多 import 语句。
diff --git a/docs/docs/docs/introduce/upgrade-to-umi-4.en-US.md b/docs/docs/docs/introduce/upgrade-to-umi-4.en-US.md
new file mode 100644
index 000000000000..77112efa8aed
--- /dev/null
+++ b/docs/docs/docs/introduce/upgrade-to-umi-4.en-US.md
@@ -0,0 +1,303 @@
+---
+order: 4
+toc: content
+---
+# 升级到 Umi 4
+
+## 升级步骤
+
+升级到 Umi 4 只需要简单的几步操作就能完成,简单的描述整个过程就是 - “重装依赖,修改配置”:
+
+1. **依赖处理**
+2. **启动命令**
+3. **非官方插件升级**
+4. **配置层迁移**
+5. **代码层修改**
+
+### 依赖处理
+
+项目的 `package.json` 需要升级 Umi,并替换掉对应的 Umi 插件。
+
+如果 `umi@3` 中是使用 `umi` + `@umijs/preset-react` 的组合进行开发的,那可以直接使用新版的 `max` 直接升级。
+
+```diff
+{
+ "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`
+
+```diff
+{
+ "scripts": {
+- "build": "umi build",
++ "build": "max build",
+- "postinstall": "umi g tmp",
++ "postinstall": "max setup",
+- "start": "umi dev",
++ "start": "max dev",
+ }
+}
+```
+
+### 非官方插件升级
+
+在项目中用到的一些非 Umi 官方提供的 Umi 插件,请联系相关作者及时根据[插件 api 变动](../api/plugin-api)。
+
+项目迁移时可先关闭对相应插件包的引用,如临时注释配置中的 `plugins`,移除 package.json 中以 `umi-plugin-`,`@umijs/plugin-` 和 `@umijs/preset-` 开头的所有依赖。
+
+### 配置层迁移
+
+**max 提供的的配置项**如下 `config/config.ts` :
+
+> 需要注意的是,之前的一些插件约定开启的规则,在 `umi@4` 中几乎都要通过显式的配置开启,因为希望在 `umi@4` 中有更少的“黑盒”。
+
+```typescript
+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` :
+
+```typescript
+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 中取出 ![image](https://img.alicdn.com/imgextra/i4/O1CN01H9ScQv21ymaLkwZ8p_!!6000000007054-2-tps-1210-374.png)
+
+#### children
+
+```typescript
+import { Outlet } from 'umi';
+;
+```
+
+主要在全局 layout 中需要修改
+
+如 `layouts/index.tsx`:
+
+```diff
+import React from 'react';
++ import { Outlet } from 'umi';
+
+export default function Layout(props) {
+ return (
+
+- { props.children }
++
+
+ );
+}
+```
+
+使用了 `React.cloneElement` 方式渲染的路由组件改造,示例
+
+```diff
+import React from 'react';
++ import { Outlet } from 'umi';
+
+export default function RouteComponent(props) {
+ return (
+
+- { React.cloneElement(props.children, { someProp: 'p1' }) }
++
+
+ );
+}
+```
+
+组件改成从 `useOutletContext` 取值
+
+```diff
+import React from 'react';
++ import { useOutletContext } from 'umi';
+
+- export function Comp(props){
++ export function Comp() {
++ const props = useOutletContext();
+
+ return props.someProp;
+}
+```
+
+#### history
+
+```diff
++ import { history } from 'umi';
+export default function Page(props) {
+ return (
+ {
+- props.history.push('list');
++ history.push('list');
+ }}>
+
+ );
+}
+```
+
+#### location
+
+> 建议组件或 hooks 里用 useLocation 取,其他地方就用 window.location 获取。
+
+```diff
+export default function Page(props) {
++ const { location } = window;
+ return (
+
+- { props.location }
++ { location }
+
+ );
+}
+```
+
+或者
+
+```diff
++ import { useLocation } from 'umi';
+export default function Page(props) {
++ let location = useLocation();
+ return (
+
+- { props.location }
++ { location }
+
+ );
+}
+```
+
+#### match
+
+```diff
++ import { useMatch } from 'umi';
+export default function Page(props) {
++ const match = useMatch({ path: 'list/search/:type' });
+ return (
+
+- { props.match }
++ { match }
+
+ );
+}
+```
+
+在 class component 组件中的使用方式:
+
+```diff
+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](https://umijs.org/docs/api/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](https://reactrouter.com/docs/en/v6/api#uselocation)
+
+完成以上操作后,执行下 `max dev`,访问 [http://localhost:8000](http://localhost:8000),请验证所有功能都符合预期。
+
+如果你的项目无法正常启动,你可能还需要做如下操作:
+
+## 配置变更
+
+TODO
+
+## FAQ
+
+### location 中的 query 找不到?
+
+location 中的 query 不再支持了,后续推荐用 [search](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)
+
+```diff
+- 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 中会发生报错,你可能需要更加规范的引用类型。
+
+```diff
+- import { ButtonType } from './button';
++ import type { ButtonType } from './button.d';
+```
diff --git a/docs/docs/docs/max/access.en-US.md b/docs/docs/docs/max/access.en-US.md
new file mode 100644
index 000000000000..b43602684ab1
--- /dev/null
+++ b/docs/docs/docs/max/access.en-US.md
@@ -0,0 +1,166 @@
+---
+order: 7
+toc: content
+---
+# 权限
+
+## 启用方式
+
+配置开启。同时需要 `src/access.ts` 提供权限配置。
+
+```ts
+export default {
+ access: {},
+ // access 插件依赖 initial State 所以需要同时开启
+ initialState: {},
+};
+```
+
+## 介绍
+
+我们约定了 `src/access.ts` 为我们的权限定义文件,该文件需要默认导出一个方法,导出的方法会在项目初始化时被执行。该方法需要返回一个对象,对象的每一个值就对应定义了一条权限。如下所示:
+
+```js
+// 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](./layout-menu) 插件你可以很简单的实现针对某些页面的权限控制。如下所示,只有拥有了 canReadPageA (在 `src/access.ts` 中定义)权限,用户才可以访问该页面。否则会默认渲染 Layout 插件内置的权限错误页面。
+
+```ts
+export const routes = [
+ {
+ path: '/pageA',
+ component: 'PageA',
+ access: 'canReadPageA', // 权限定义返回值的某个 key
+ },
+];
+```
+
+### 自定义权限页面配置
+
+上面说到默认渲染 Layout 插件内置的权限错误页面,如果想配置自定义权限页面需要在 `src/app.tsx` 中定义。
+
+```tsx
+export const layout: RunTimeLayoutConfig = () => {
+ return {
+ // 自定义 403 页面
+ unAccessible: 'unAccessible'
,
+ // 自定义 404 页面
+ noFound: 'noFound'
,
+ };
+};
+```
+
+#### access
+
+- Type: `string`
+
+对应的权限名称。
+
+## API
+
+### useAccess
+
+我们提供了一个 Hooks 用于在组件中获取权限相关信息,如下所示:
+
+```js
+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` 支持的属性如下:
+
+#### accessible
+
+- Type: `boolean`
+
+是否有权限,通常通过 `useAccess` 获取后传入进来。
+
+#### fallback
+
+- Type: `React.ReactNode`
+
+无权限时的显示,默认无权限不显示任何内容。
+
+### children
+
+- Type: `React.ReactNode`
+
+有权限时的显示。
+
+完整示例如下:
+
+```js
+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 (
+
+
Can not read foo content. }
+ >
+ Foo content.
+
+ Can not update foo.}
+ >
+ Update foo.
+
+ Can not delete foo.}
+ >
+ Delete foo.
+
+
+ );
+};
+```
+
+- `useAccess()` 的返回值 `access` 就是 `src/access.ts` 中定义的权限集合,可以利用它进行组件内代码执行流的控制。
+
+- `` 组件拥有 `accessible` 和 `fallback` 两个属性,当 `accessible` 为 `true` 时会渲染子组件,当 `accessible` 为 `false` 会渲染 `fallback` 属性对应的 `ReactNode`。
diff --git a/docs/docs/docs/max/analytics.en-US.md b/docs/docs/docs/max/analytics.en-US.md
new file mode 100644
index 000000000000..cffe862476c3
--- /dev/null
+++ b/docs/docs/docs/max/analytics.en-US.md
@@ -0,0 +1,29 @@
+---
+order: 14
+toc: content
+---
+# 站点统计
+
+`@umijs/max` 内置了站点统计的功能,目前支持 [Google Analytics](https://analytics.google.com/analytics/web/) 和[百度统计](https://tongji.baidu.com/web/welcome/login)
+
+## 启用方式
+
+配置开启,按照需求配置进对应的统计服务的 key 即可。
+
+举例:
+
+```ts
+{
+ analytics: {
+ ga_v2: 'G-abcdefg', // google analytics 的 key (GA 4)
+ baidu: 'baidu_tongji_key',
+
+ // 若你在使用 GA v1 旧版本,请使用 `ga` 来配置
+ ga: 'ga_old_key'
+ }
+}
+```
+
+### 环境变量
+
+[Google Analytics 4](https://support.google.com/analytics/answer/10089681) 的 key 也可以通过环境变量 `GA_V2_KEY` 来配置,旧版本为 `GA_KEY` 。
diff --git a/docs/docs/docs/max/antd.en-US.md b/docs/docs/docs/max/antd.en-US.md
new file mode 100644
index 000000000000..c5dc72f9f827
--- /dev/null
+++ b/docs/docs/docs/max/antd.en-US.md
@@ -0,0 +1,220 @@
+---
+order: 3
+toc: content
+---
+# antd
+
+整合 antd 组件库。
+
+## 启用方式
+
+配置开启,示例:
+
+```ts
+// 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 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,
+ },
+ },
+};
+```
+
+## 介绍
+
+包含以下功能:
+
+1. 内置 [antd](https://ant.design/),目前内置版本是 `^4.0.0`
+2. 基于 [babel-plugin-import](https://github.com/ant-design/babel-plugin-import) 做按需编译
+3. 使用 antd@4 时,可一键切换为暗色主题,见下图
+
+![](https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*mYU9R4YFxscAAAAAAAAAAABkARQnAQ)
+
+## 配置
+
+### 构建时配置
+
+注意:构建时配置会经过 json 转换,所以这里只能进行符合 json 格式的配置,如有例如 algorithm 等函数配置,可在[运行时配置](#运行时配置)中进行设置。
+
+#### dark
+
+开启暗色主题。
+
+- Type: `boolean`
+- Default: `false`
+
+#### compact
+
+开启紧凑主题。
+
+- Type: `boolean`
+- Default: `false`
+
+比如:
+
+```ts
+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 包裹组件,请注意 `antd@5.1.0 ~ 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` 时,会自动进行降级处理,不需要手动配置。
+
+**注意:**
+
+1. 该配置项仅 antd v5 及以上可用。
+
+2. 降级 CSS 需要依赖 [`@ant-design/cssinjs`](https://ant.design/docs/react/compatible-style-cn) ,若你显示安装了 `antd` ,请安装并确保你的 `@ant-design/cssinjs` 版本与 `antd` 正确对应。
+
+### 运行时配置
+
+在 app.ts(x) 文件中可以对 antd 进行更丰富的配置,比如配置 antd5 的预设算法和 message 最大显示数:
+
+```ts
+// 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: {}` 。
+
+```tsx
+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 (
+
+ with antd@{version}
+
+ isDarkTheme
+ {
+ // 此配置会与原配置深合并
+ 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;
+ });
+ }}
+ >
+
+
+ );
+}
+```
+
+使用 `setAntdConfig` 可以动态修改 [antd@5 ConfigProvider](https://ant.design/components/config-provider-cn) 组件支持的所有属性。
+
+## FAQ
+
+### 如何使用 antd 的其他版本?
+
+在项目中安装你需要的 antd 版本。
diff --git a/docs/docs/docs/max/charts.en-US.md b/docs/docs/docs/max/charts.en-US.md
new file mode 100644
index 000000000000..492b0c31765e
--- /dev/null
+++ b/docs/docs/docs/max/charts.en-US.md
@@ -0,0 +1,533 @@
+---
+order: 4
+toc: content
+---
+# 图表
+
+Umi 团队推荐使用与 Ant Design 一脉相承的 [Ant Design Charts](https://charts.ant.design/) 或 [Pro Components](https://procomponents.ant.design/) 来为您的项目添加可视化图表 📈。
+
+本教程将为您提供一些常见的使用案例。
+
+## Ant Design Charts
+
+Ant Design Charts 是 [AntV](https://antv.vision/zh) 项目的 React 实现,由蚂蚁集团数据可视化团队开发。
+
+您可以安装完整的 Ant Design Charts 包:
+
+```bash
+pnpm install @ant-design/charts
+```
+
+也可以仅引入使用到的子包,例如:
+
+```bash
+# 安装统计图表包
+pnpm install @ant-design/plots
+```
+
+在下面的使用示例中,我们将最小化引入。
+
+您也可以直接阅读 Ant Design Charts 完整的[上手文档](https://charts.ant.design/zh/docs/manual/getting-started)和[图表示例](https://charts.ant.design/zh/examples/gallery)。
+
+### 曲线图
+
+现在,我们需要将[这些数据](https://gw.alipayobjects.com/os/bmw-prod/1d565782-dde4-4bb6-8946-ea6a38ccf184.json)制作为一个曲线图展示出来。
+
+首先,引入统计图表包:
+
+```bash
+pnpm install @ant-design/plots
+```
+
+编写代码获取数据(后略):
+
+```tsx
+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` 中去。每个数据对象形如:
+
+```json
+{
+ "Date": "2010-01",
+ "scales": 1998,
+}
+```
+
+将数据展示到曲线图上:
+
+```tsx
+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 ;
+};
+```
+
+其中,`data` 中数据的 `Date` 属性将作为曲线图的 X 横坐标,`scales` 属性将作为曲线图的 Y 纵坐标绘图。
+
+完整的曲线图代码和效果可查看[此页面](https://charts.ant.design/zh/examples/line/basic#spline)。
+
+### 柱状图
+
+现在,我们需要把页面加载的时间通过柱状图展示出来。
+
+首先,引入统计图表包:
+
+```bash
+pnpm install @ant-design/plots
+```
+
+假设我们有如下 `data`:
+
+```ts
+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+ 秒` 的情况,我们想要用鲜明的颜色标注出来。那么可以编写柱状图代码如下:
+
+```tsx
+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 ;
+};
+```
+
+完整的柱状图代码和效果可查看[此页面](https://charts.ant.design/zh/examples/column/basic#color)。
+
+### 词云
+
+现在,我们需要把世界上部分国家的名字以词云的方式展示出来。国家的人数越多,词云上国家的名字字体越大。
+
+首先,引入统计图表包:
+
+```bash
+pnpm install @ant-design/plots
+```
+
+获取包含国家人口数量的 [`data`](https://gw.alipayobjects.com/os/antfincdn/jPKbal7r9r/mock.json)。形如:
+
+```json
+{
+ "country": "China",
+ "value": 1383220000,
+ "category": "asia",
+}
+```
+
+渲染数据,获得词云图:
+
+```tsx
+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 ;
+};
+```
+
+完整的词云图代码和效果可查看[此页面](https://charts.ant.design/zh/examples/more-plots/word-cloud#basic)。
+
+### 散点地图
+
+现在,我们需要将我国城市和区县分布在地图上以散点的形式展示出来。
+
+首先,引入地图包:
+
+```bash
+pnpm install @ant-design/maps
+```
+
+获取包含所有区县数据的 [`data`](https://gw.alipayobjects.com/os/antfincdn/g5hIthhKlr/quanguoshixianweizhi.json)。形如:
+
+```json
+{
+ "list": [
+ {
+ "lnglat": [
+ 116.258446,
+ 37.686622
+ ],
+ "name": "景县",
+ "style": 2
+ },
+ // ...
+ ]
+}
+```
+
+其中,`style` 为 `0` 表示为地级市,`1` 表示为县城市,`2` 表示为区县。
+
+渲染数据,获得散点地图:
+
+```tsx
+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 ;
+};
+```
+
+完整的散点地图代码和效果可查看[此页面](https://charts.ant.design/zh/examples/map-dot/map-scatter#distribution-cities)。
+
+## Pro Components
+
+Pro Components 面向中后台类应用,对 Ant Design 进行了更高程度的抽象,提供了更上层的设计规范,能够助开发者快速搭建出高质量的页面。
+
+您应当按需引入使用到的子包:
+
+```bash
+# 引入高级表格
+pnpm install @ant-design/pro-table
+
+# 引入高级列表
+pnpm install @ant-design/pro-list
+```
+
+您也可以直接阅读 Pro Components 完整的[上手文档](https://procomponents.ant.design/docs/getting-started),[表格示例](https://procomponents.ant.design/components/table)和[列表示例](https://procomponents.ant.design/components/list)。
+
+下面的示例中将默认您已经引入了使用到的子包。
+
+### Pro Table 高级表格
+
+现在,您需要快速构建一个包含有成员和相关信息的表格。
+
+成员信息如下:
+
+```ts
+const realNames = ['马巴巴', '张三丰', '飞蓬', '徐长卿'];
+const nickNames = ['巴巴', '君宝', '景天', '姓徐的'];
+const emails = ['baba@antfin.com', 'junbao@antfin.com', 'jingtian@antfin.com', 'xvzhangmen@antfin.com'];
+const phones = ['18800001234', '13900002345', '17200003456', '17800004567'];
+```
+
+定义一个 `Member` 类型:
+
+```ts
+export type Member = {
+ id: number;
+ realName: string;
+ nickName: string;
+ email: string;
+ phone: string;
+};
+```
+
+处理成员信息,构建一个 `Member` 数组:
+
+```ts
+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,快速构建表格:
+
+```tsx
+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[] = [
+ {
+ dataIndex: 'realName',
+ title: '姓名',
+ },
+ {
+ dataIndex: 'nickName',
+ title: '昵称',
+ },
+ {
+ dataIndex: 'email',
+ title: '账号',
+ },
+ {
+ dataIndex: 'phone',
+ title: '手机号',
+ },
+ {
+ title: '操作',
+ dataIndex: 'x',
+ valueType: 'option',
+ render: (_, record) => {
+ return [编辑, 移除];
+ },
+ },
+ ];
+
+ 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}
+ />
+ );
+}
+```
+
+完整的表格代码和效果可查看[此页面](https://procomponents.ant.design/components/table)。
+
+### Pro List 高级列表
+
+现在,您需要快速构建一个包含测试信息的列表。
+
+测试信息如下:
+
+```ts
+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,快速构建列表:
+
+```tsx
+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',
+ },
+ description: {
+ dataIndex: 'desc',
+ },
+ actions: {
+ render: (text, row) => [
+ 链路,
+ 报警,
+ 查看,
+ ],
+ },
+ }}
+ />
+ );
+}
+```
+
+完整的列表代码和效果可查看[此页面](https://procomponents.ant.design/components/list)。
diff --git a/docs/docs/docs/max/data-flow.en-US.md b/docs/docs/docs/max/data-flow.en-US.md
new file mode 100644
index 000000000000..1ce7aa4144d7
--- /dev/null
+++ b/docs/docs/docs/max/data-flow.en-US.md
@@ -0,0 +1,230 @@
+---
+order: 5
+toc: content
+---
+
+# 数据流
+
+`@umi/max` 内置了**数据流管理**[插件](https://github.com/umijs/umi/blob/master/packages/plugins/src/model.ts),它是一种基于 `hooks` 范式的轻量级数据管理方案,可以在 Umi 项目中管理全局的共享数据。
+
+## 开始使用
+
+### 创建 Model
+
+数据流管理插件采用约定式目录结构,我们约定可以在 `src/models`, `src/pages/xxxx/models/`目录中,和 `src/pages/xxxx/model.{js,jsx,ts,tsx}` 文件引入 Model 文件。
+Model 文件允许使用 `.(tsx|ts|jsx|js)` 四种后缀格式,**命名空间(namespace)** 生成规则如下。
+
+| 路径 | 命名空间 | 说明 |
+| :--- |:--- | :--- |
+| `src/models/count.ts` | `count` | `src/models` 目录下不支持目录嵌套定义 model |
+| `src/pages/pageA/model.ts` | `pageA.model` | |
+| `src/pages/pageB/models/product.ts` | `pageB.product` | |
+| `src/pages/pageB/models/fruit/apple.ts` | `pageB.fruit.apple` | `pages/xxx/models` 下 model 定义支持嵌套定义 |
+
+所谓的 Model,就是一个自定义的 `hooks`,没有任何使用者需要关注的“黑魔法”。
+
+当我们需要获取 Model 中的全局数据时,调用该命名空间即可。例如,对于 Model 文件 `userModel.ts`,它的命名空间为 `userModel`。
+
+编写一个默认导出的函数:
+
+```ts
+// src/models/userModel.ts
+export default function Page() {
+ const user = {
+ username: 'umi',
+ };
+
+ return { user };
+};
+```
+
+这就是一个 Model。插件所做的工作就是将其中的状态或数据变成了**全局数据**,不同的组件在使用该 Model 时,拿到的是同一份状态或数据。
+
+:::info{title=💡}
+Model 文件需要默认导出一个函数,此函数定义了一个 `hook`。对于不符合此规范的文件,将会被过滤掉,并无法通过命名空间调用。
+:::
+
+Model 中允许使用其它 `hooks`,以计数器为例:
+
+```ts
+// src/models/counterModel.ts
+import { useState, useCallback } from 'react';
+
+export default function Page() {
+ const [counter, setCounter] = useState(0);
+
+ const increment = useCallback(() => setCounter((c) => c + 1), []);
+ const decrement = useCallback(() => setCounter((c) => c - 1), []);
+
+ return { counter, increment, decrement };
+};
+```
+
+在项目实践中,我们通常需要请求后端接口,来获取所需的数据。现在让我们来扩展前面获取用户信息的例子:
+
+```ts
+// src/models/userModel.ts
+import { useState } from 'react';
+import { getUser } from '@/services/user';
+
+export default function Page() {
+ const [user, setUser] = useState({});
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ getUser().then((res) => {
+ setUser(res);
+ setLoading(false);
+ });
+ }, []);
+
+ return {
+ user,
+ loading,
+ };
+};
+```
+
+如果您在项目中使用了 [ahooks](https://ahooks.js.org),可以像这样组织您的代码:
+
+```ts
+// src/models/userModel.ts
+import { useRequest } from 'ahooks';
+import { getUser } from '@/services/user';
+
+export default function Page() {
+ const { data: user, loading: loading } = useRequest(async () => {
+ const res = await getUser();
+ if (res) {
+ return res;
+ }
+ return {};
+ });
+
+ return {
+ user,
+ loading,
+ };
+};
+```
+
+### 使用 Model
+
+现在,您想要在某个组件中使用全局的 Model。以用户信息为例,只需要调用 `useModel` 这一钩子函数:
+
+```tsx
+// src/components/Username/index.tsx
+import { useModel } from 'umi';
+
+export default function Page() {
+ const { user, loading } = useModel('userModel');
+
+ return (
+ {loading ? <>>: {user.username}
}
+ );
+}
+```
+
+其中,`useModel()` 方法传入的参数为 Model 的**命名空间**。
+
+:::info{title=💡}
+如果您使用 VSCode 作为 Umi 项目开发的 IDE,推荐搭配 [@umijs/plugin-model](https://marketplace.visualstudio.com/items?itemName=litiany4.umijs-plugin-model)插件使用。它允许您快速跳转到定义 Model 的文件:
+
+![vscode - @umijs/plugin-model 插件演示](https://gw.alipayobjects.com/zos/antfincdn/WcVbbF6KG2/1577073518336-afe6f03d-f817-491a-848a-5feeb4ecd72b.gif)
+:::
+
+## 性能优化
+
+`useModel()` 方法可以接受可选的第二个参数,当组件只需要使用 Model 中的部分参数,而对其它参数的变化不感兴趣时,可以传入一个函数进行过滤。以实现计数器的操作按钮为例:
+
+```tsx
+// src/components/CounterActions/index.tsx
+import { useModel } from 'umi';
+
+export default function Page() {
+ const { add, minus } = useModel('counterModel', (model) => ({
+ add: model.increment,
+ minus: model.decrement,
+ }));
+
+ return (
+
+
+
+
+ );
+};
+```
+
+上面的组件并不关心计数器 Model 中的 `counter` 值,只需要使用 Model 提供的 `increment()` 和 `decrement()` 方法。于是我们传入了一个函数作为 `useModel()` 方法的第二个参数,该函数的返回值将作为 `useModel()` 方法的返回值。
+
+这样,我们过滤掉了 `counter` 这一频繁变化的值,避免了组件重复渲染带来的性能损失。
+
+## 全局初始状态
+
+`@umi/max` 内置了**全局初始状态管理**[插件](https://github.com/umijs/umi/blob/master/packages/plugins/src/initial-state.ts),允许您快速构建并在组件内获取 Umi 项目全局的初始状态。
+
+全局初始状态是一种特殊的 Model。
+
+全局初始状态在整个 Umi 项目的最开始创建。编写 `src/app.ts` 的导出方法 `getInitialState()`,其返回值将成为全局初始状态。例如:
+
+```ts
+// src/app.ts
+import { fetchInitialData } from '@/services/initial';
+
+export async function getInitialState() {
+ const initialData = await fetchInitialData();
+ return initialData;
+}
+```
+
+现在,各种插件和您定义的组件都可以通过 `useModel('@@initialState')` 直接获取到这份全局的初始状态,如下所示:
+
+```tsx
+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` |
+
+## Qiankun 父子应用间通信
+
+`@umi/max` 内置了 **Qiankun 微前端**[插件](https://github.com/umijs/umi/blob/master/packages/plugins/src/qiankun.ts),当使用数据流插件时,它允许微应用通过 `useModel('@@qiankunStateFromMaster')` 方法获取父应用传递给子应用的数据 Model,进而实现父子应用间的通信。
+
+具体的使用方法请查阅[微前端的父子应用通信章节](./micro-frontend#父子应用通信)。
+
+## API
+
+### `useModel`
+
+`useModel()` 是一个钩子函数,提供了使用 Model 的能力。它接受两个参数:
+
+| 参数 | 类型 | 介绍 |
+| --- | --- | --- |
+| `namespace` | `String` | Model 文件的命名空间 |
+| `updater` | `(model: any) => any` | 可选参数。传入一个函数,函数的返回值为当前组件中需要使用到的 Model 状态或数据,并作为 `useModel()` 方法的返回值。对优化组件性能具有重要意义。 |
+
+```tsx
+// src/components/AdminInfo/index.tsx
+import { useModel } from 'umi';
+
+export default function Page() {
+ const { user, fetchUser } = useModel('adminModel', (model) => ({
+ user: model.admin,
+ fetchUser: model.fetchAdmin,
+ }));
+
+ return <>hello>;
+};
+```
diff --git a/docs/docs/docs/max/dva.en-US.md b/docs/docs/docs/max/dva.en-US.md
new file mode 100644
index 000000000000..a0ce5007371d
--- /dev/null
+++ b/docs/docs/docs/max/dva.en-US.md
@@ -0,0 +1,341 @@
+---
+order: 13
+toc: content
+---
+# dva
+
+## 为什么需要状态管理
+
+React 的组件只是通过 jsx 以及样式按照 state 构建最终的 UI,真正将页面动态化的实际上是 state 的变化实现的。对于简单的前端应用,在组件中通过组件自身的 state 加上父组件通过 props 状态的传递就能够满足应用数据管理的需求。但是当应用膨胀到一定程度后就会导致组件内维护的状态非常的复杂,加上组件之间状态的传递,很容易导致数据管理混乱。很小的修改都可能导致难以预料的副作用。
+
+所以我们需要纯净的 UI 组件,除了渲染逻辑,不再杂糅其他(比如网络请求)。这样我们就要想办法把与渲染无关的业务逻辑抽离出来,形成独立的层(在 Umi 中就是 `src/models` 文件夹中所管理的 model )去管理。让所有组件降级为`无状态组件`,仅仅依赖 props 渲染。这样 UI 层面就不需关心渲染无关的逻辑,专注做 UI 渲染。(注:这里说的组件主要是指 page 下面的页面组件,对于 component 下的组件本身就应该是比较通用的组件,更应该仅仅依赖 props 渲染,它们也不应该有 model,数据应该通过在页面组件中通过 props 传递过去)。
+
+## 简单的数据共享
+
+对于简单的应用,不需要复杂的数据流,只需要一些简单的数据共享。我们推荐使用 [中台最佳实践简易数据流](./data-flow) 。
+
+## Umi 如何管理状态
+
+如下图所示,Umi 内置了 [Dva](https://dvajs.com) 提供了一套状态管理方案:
+
+![undefined](https://gw.alipayobjects.com/zos/skylark/48f9ff5f-ab11-4896-9fb6-65cdd83340de/2018/png/dcb7073b-fc0c-4e2c-aa39-93ac249d715c.png)
+
+数据统一在 `src/models` 中的 model 管理,组件内尽可能的不去维护数据,而是通过 connect 去关联 model 中的数据。页面有操作的时候则触发一个 action 去请求后端接口以及修改 model 中的数据,将业务逻辑分离到一个环形的闭环中,使得数据在其中单向流动。让应用更好维护。这样的思想最初来源于 Facebook 的 [flux](http://facebook.github.io/flux/)。接下来我们来具体看看如何在 Umi 中实现这样的逻辑。
+
+### 配置 dva
+
+首先你需要配置 `dva: {}` 打开 Umi 内置的 dva 插件。
+
+### 添加 model
+
+Umi 会默认将 `src/models` 下的 model 定义自动挂载,你只需要在 model 文件夹中新建文件即可新增一个 model 用来管理组件状态。
+
+在 2.0 后,为了更好的支持移动端的 H5 项目的按需加载和大型项目的 model 组织,对于某个 page 文件夹下面的 model 我们也会默认挂载,具体结构可以参考[目录结构说明](../guides/directory-structure)。但是需要注意的是 model 的 namespace 是全局的,你仍然需要保证你的 namesapce 唯一(默认是文件名)。对于大部分的项目,我们推荐统一放到 model 中进行管理即可,不需要使用该功能。
+
+model 的写法参考如下示例:
+
+```js
+import { queryUsers, queryUser } from '../../services/user';
+
+export default {
+ state: {
+ user: {},
+ },
+
+ effects: {
+ *queryUser({ payload }, { call, put }) {
+ const { data } = yield call(queryUser, payload);
+ yield put({ type: 'queryUserSuccess', payload: data });
+ },
+ },
+
+ reducers: {
+ queryUserSuccess(state, { payload }) {
+ return {
+ ...state,
+ user: payload,
+ };
+ },
+ },
+
+ test(state) {
+ console.log('test');
+ return state;
+ },
+};
+```
+
+### 把组件和 model connect 在一起
+
+新建完成 model 之后你就可以在组件中通过 ES6 的 [Decorator](http://es6.ruanyifeng.com/#docs/decorator) 方便的把 model 和组件 connect 到一起。然后你就可以在组件中通过`this.props.[modelName]`的方式来访问 model 中的数据了。(在对应的 model 中,默认 namespace 即为文件名)
+
+组件如下示例:
+
+```javascript
+import React, { Component } from 'react';
+import { connect } from 'umi';
+
+@connect(({ user }) => ({
+ user,
+}))
+class UserInfo extends Component {
+ constructor(props) {
+ super(props);
+ }
+ render() {
+ return {this.props.user.name}
;
+ }
+}
+
+export default UserInfo;
+```
+
+### 在组件中 dispatch 事件
+
+connect 方法同时也会添加 `dispatch` 到 `this.props` 上,你可以在用户触发某个事件的时候调用它来触发 model 中的 effects 或者 reducer 来修改 model 中的数据。如下所示:
+
+```javascript
+import React, { Component } from 'react';
+import { connect } from 'umi';
+
+@connect(({ user }) => ({
+ user,
+}))
+class UserInfo extends Component {
+ constructor(props) {
+ super(props);
+ }
+ render() {
+ return (
+ {
+ this.props.dispatch({
+ type: 'user/test',
+ });
+ }}
+ >
+ {this.props.user.name}
+
+ );
+ }
+}
+
+export default UserInfo;
+```
+
+### 修改数据
+
+dispatch 一个 action 之后会按照 action 中的 type 找到定义在 model 中的一个 effect 或者 reducer。
+
+如果是 effect,那么可以去请求后端数据,然后再触发一个 reducer 来修改数据。通过 reducer 修改数据之后组件便会按照最新的数据更新,至此,一次数据的流动就结束了。
+
+## 文档
+
+### model 定义
+
+一个 model 中可以定义如下几个部分:
+
+- namespace # model 的命名空间,唯一标识一个 model,如果与文件名相同可以省略不写
+- state # model 中的数据
+- effects # 异步 action,用来发送异步请求
+- reducers # 同步 action,用来修改 state
+
+### connect
+
+`connect` 的是用来将 model 和组件关联在一起的,它会将相关数据和 `dispatch` 添加到组件的 `props` 中。如下所示:
+
+```jsx
+import React, { Component } from 'react';
+import { connect } from 'umi';
+
+const mapModelToProps = allModels => {
+ return {
+ test: 'hello world',
+ // props you want connect to Component
+ };
+};
+
+@connect(mapModelToProps)
+class UserInfo extends Component {
+ render() {
+ return {this.props.test}
;
+ }
+}
+
+export default UserInfo;
+```
+
+推荐通过注解的方式调用 connect,它等同于 `export default connect(mapModelToProps)(UserInfo);`。connect 接收一个参数,是一个方法,在该方法中你接收到所有的 model 信息,需要返回要添加到 props 上的对象。在上面的例子中你就可以通过 `this.props.test` 得到 `hello world` 的字符串了。
+
+### dispatch
+
+在使用 `connect` 将组件和 model 关联在一起的同时框架也会添加一个 `this.props.dispatch` 的方法,通过该方法你可以触发一个 action 到 model 中。如下所示:
+
+```jsx
+render () {
+ return {
+ this.props.dispacth({
+ type: 'modelnamespace/actionname',
+ sometestdata: 'xxx',
+ othertestata: {},
+ }).then(() => {
+ // it will return a promise
+ // action success
+ });
+ }}>test
+}
+```
+
+通过 `this.props.dispatch` 触发的 action 分为 effect 和 reducer 两类,下面是对他们的更多细节说明。
+
+### Reducer
+
+reducer 是一个函数,用来处理修改数据的逻辑(同步,不能请求后端)。接受 state 和 action,返回老的或新的 state 。即:`(state, action) => state`。
+
+#### 增删改
+
+以 todos 为例。
+
+```javascript
+exports default {
+ namespace: 'todos',
+ state: [],
+ reducers: {
+ add(state, { payload: todo }) {
+ return state.concat(todo);
+ },
+ remove(state, { payload: id }) {
+ return state.filter(todo => todo.id !== id);
+ },
+ update(state, { payload: updatedTodo }) {
+ return state.map(todo => {
+ if (todo.id === updatedTodo.id) {
+ return { ...todo, ...updatedTodo };
+ } else {
+ return todo;
+ }
+ });
+ },
+ },
+};
+```
+
+#### 嵌套数据的增删改
+
+建议最多一层嵌套,以保持 state 的扁平化,深层嵌套会让 reducer 很难写和难以维护。
+
+```javascript
+app.model({
+ namespace: 'app',
+ state: {
+ todos: [],
+ loading: false,
+ },
+ reducers: {
+ add(state, { payload: todo }) {
+ const todos = state.todos.concat(todo);
+ return { ...state, todos };
+ },
+ },
+});
+```
+
+下面是深层嵌套的例子,应尽量避免。
+
+```javascript
+app.model({
+ namespace: 'app',
+ state: {
+ a: {
+ b: {
+ todos: [],
+ loading: false,
+ },
+ },
+ },
+ reducers: {
+ add(state, { payload: todo }) {
+ const todos = state.a.b.todos.concat(todo);
+ const b = { ...state.a.b, todos };
+ const a = { ...state.a, b };
+ return { ...state, a };
+ },
+ },
+});
+```
+
+### Effect
+
+effects 是定义在 model 中的。它也是一种类型的 action,主要用于和后端的异步通讯。通过 effects 请求后端发送和接收必要的数据之后可以通过 put 方法再次发送一个 reducer 来修改数据。
+
+effect 通过 ES6 中 [Generator 函数](http://es6.ruanyifeng.com/#docs/generator) 来支持通过顺序的代码实现异步的请求,示例如下:
+
+```javascript
+export default {
+ namespace: 'todos',
+ effects: {
+ *addRemote({ payload: todo }, { put, call }) {
+ yield call(addTodo, todo);
+ yield put({ type: 'add', payload: todo });
+ },
+ },
+};
+```
+
+effects 中定义的 action 都必须是通过 `*` 定义的 Generator 函数,然后在函数中通过关键字 `yield` 来触发异步逻辑。
+
+#### Effects
+
+##### put
+
+用于触发 action 。
+
+```javascript
+yield put({ type: 'todos/add', payload: 'Learn Dva' });
+```
+
+##### call
+
+用于调用异步逻辑,支持 promise 。
+
+```javascript
+const result = yield call(fetch, '/todos');
+```
+
+##### select
+
+用于从 state 里获取数据。
+
+```javascript
+const todos = yield select(state => state.todos);
+```
+
+### loading
+
+框架会默认添加一个命名空间为 loading 的 model,该 model 包含 effects 异步加载 loading 的相关信息,它的 state 格式如下:
+
+```js
+{
+ global: Boolean, // 是否真正有异步请求发送中
+ models: {
+ [modelnamespace]: Boolean, // 具体每个 model 的加载情况
+ },
+ effects: {
+ [modelnamespace/effectname]: Boolean, // 具体每个 effect 的加载情况
+ },
+}
+```
+
+你可以使用该 model 实现在组件中添加 loading 动画。
+
+## 调试
+
+### redux
+
+dva 的底层是基于 redux,所以你可以安装 redux 的[开发者工具](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=zh-CN)用来查看 model 中的数据和变化的记录。
+
+![reduxdevtool](https://lh3.googleusercontent.com/wfhSnnYEQc3TCXbRTpTloa-XZesgDt0xAogzGoLF1BUCU04aYhdwAjueJYTtDxfRiqjUfC539g=w640-h400-e365)
+
+## 参考文章
+
+- [dva 官网](https://dvajs.com/)
diff --git a/docs/docs/docs/max/i18n.en-US.md b/docs/docs/docs/max/i18n.en-US.md
new file mode 100644
index 000000000000..59ce33439601
--- /dev/null
+++ b/docs/docs/docs/max/i18n.en-US.md
@@ -0,0 +1,528 @@
+---
+order: 8
+toc: content
+---
+
+# 国际化
+
+`@umi/max` 内置了[国际化插件](https://github.com/umijs/umi/blob/master/packages/plugins/src/locale.ts),它可以轻松地将国际化功能集成到你的 Umi 应用程序之中。
+
+## 开始使用
+
+国际化插件采用约定式目录结构,我们约定在 `src/locales` 目录下引入多语言文件。
+
+多语言文件的命名需遵循此规范:`.(js|json|ts)`。其中,`` 为分隔符,默认为 `-`,可以通过 `baseSeparator` 项配置。
+
+例如,如果您需要在项目中引入简体中文和英文的多语言支持,可以在 `src/locales` 目录下创建 `zh-CN.ts` 和 `en-US.ts` 两个文件:
+
+```diff
+src
+ + locales
+ + zh-CN.ts
+ + en-US.ts
+ pages
+```
+
+在 `.umirc.ts` 中配置国际化插件:
+
+```ts
+export default {
+ locale: {
+ // 默认使用 src/locales/zh-CN.ts 作为多语言文件
+ default: 'zh-CN',
+ baseSeparator: '-',
+ },
+};
+```
+
+关于配置的更多介绍可参见[配置插件](#配置插件)章节。
+
+现在,添加您的第一条多语言内容:
+
+```ts
+// src/locales/zh-CN.ts
+export default {
+ welcome: '欢迎光临 Umi 的世界!',
+};
+```
+
+```ts
+// src/locales/en-US.ts
+export default {
+ welcome: "Welcome to Umi's world!",
+};
+```
+
+您也可以使用 `.json` 文件来存放多语言的内容:
+
+```json
+// src/locales/zh-CN.json
+{
+ "welcome": "欢迎光临 Umi 的世界!",
+}
+
+// src/locales/en-US.json
+{
+ "welcome": "Welcome to Umi's world!",
+}
+```
+
+一切就绪,现在您可以在 Umi 中使用多语言内容。交给我们的 `` 组件吧,只需要将前面的 `welcome` 作为参数 `id` 的值传入即可:
+
+```tsx
+import { FormattedMessage } from 'umi';
+
+export default function Page() {
+ return (
+
+
+
+ );
+};
+```
+
+渲染的结果如下:
+
+```html
+
+欢迎光临 Umi 的世界!
+
+
+Welcome to Umi's world!
+```
+
+## 在组件的参数中使用
+
+在某些情况下,您需要将多语言内容作为参数传递给某个组件。可以通过 `intl` 对象来实现:
+
+```tsx
+import { Alert } from 'antd';
+import { useIntl } from 'umi';
+
+export default function Page() {
+ const intl = useIntl();
+ const msg = intl.formatMessage({
+ id: 'welcome',
+ });
+
+ return ;
+};
+```
+
+在底层,国际化插件基于 [`react-intl`](https://github.com/formatjs/formatjs/tree/main/packages/react-intl) 封装,并支持它的所有接口,详情可见[此文档](https://github.com/formatjs/formatjs/blob/main/website/docs/react-intl/api.md)。
+
+在上面的代码中,我们就运用到了 `react-intl` 提供的 `useIntl()` 接口来初始化 `intl` 对象,并调用此对象的 [`formatMessage()`](https://github.com/formatjs/formatjs/blob/main/website/docs/react-intl/api.md#formatmessage) 方法来格式化字符串。
+
+## 格式化字符串
+
+您可能想要在多语言翻译中动态插值,那么可以像这样编写多语言内容:
+
+```ts
+// src/locales/zh-CN.ts
+export default {
+ user: {
+ welcome: '{name},今天也是美好的一天!',
+ },
+};
+```
+
+```ts
+// src/locales/en-US.ts
+export default {
+ user: {
+ welcome: '{name}, what a nice day!',
+ },
+};
+```
+
+在上面,我们编写了特殊的语法 `{name}`,这允许我们在运行时动态赋值:
+
+```tsx
+import { FormattedMessage } from 'umi';
+
+export default function Page() {
+ return (
+
+
+
+ );
+};
+```
+
+如果您希望通过 `intl` 对象来实现,那么可以这样对它赋值:
+
+```tsx
+import { useIntl } from 'umi';
+
+export default function Page() {
+ const intl = useIntl();
+ const msg = intl.formatMessage(
+ {
+ id: 'user.welcome',
+ },
+ {
+ name: '张三',
+ },
+ );
+
+ return {msg}
;
+};
+```
+
+注意,用于赋值的键值对对象应当作为 `formatMessage()` 方法的第二个参数传递。
+
+渲染的结果如下:
+
+```html
+
+张三,今天也是美好的一天!
+
+
+张三, what a nice day!
+```
+
+## 切换语言
+
+通过预设的 `` 组件可以帮助您快速地向项目中添加切换语言的功能,只需要像这样编写:
+
+```tsx
+import { SelectLang } from 'umi';
+
+export default function Page() {
+ return ;
+};
+```
+
+更多情况下,您可能需要自己编写切换语言的组件。这时就轮到 `setLocale()` 接口大显身手了:
+
+```ts
+import { setLocale } from 'umi';
+
+// 切换时刷新页面
+setLocale('en-US');
+```
+
+使用该方法切换语言时,默认情况下会刷新当前的页面。您可以设置它的第二个参数为 `false` 来实现无刷新切换语言:
+
+```ts
+// 切换时不刷新页面
+setLocale('en-US', false);
+```
+
+如果需要切换为默认的语言,只需要调用此方法而不用传递任何参数:
+
+```ts
+// 如果您的默认语言为 zh-CN
+// 那么以下调用具有与 setLocale('zh-CN') 同样的效果
+setLocale();
+```
+
+## 多语言默认值
+
+为了页面的一致性,当 Umi 没有在当前的多语言文件中找到 `id` 对应的内容时,它会直接将 `id` 渲染为页面上的内容。
+
+例如,编写了如下多语言文件:
+
+```ts
+// src/locales/zh-CN.ts
+export default {
+ table: {
+ submit: '提交表单',
+ },
+};
+```
+
+```ts
+// src/locales/en-US.ts
+export default {
+ // table: {
+ // submit: 'SUBMIT TABLE',
+ // },
+};
+```
+
+有如下组件:
+
+```tsx
+import { Button } from 'antd';
+import { FormattedMessage } from 'umi';
+
+export default function Page() {
+ return (
+
+ );
+};
+```
+
+渲染的结果为:
+
+```html
+
+
+
+
+
+```
+
+特别的,如果您需要在没有完成国际化适配的情况下,给出一个默认的值,可以使用 `defaultMessage` 参数:
+
+```tsx
+import { Button } from 'antd';
+import { FormattedMessage } from 'umi';
+
+export default function Page() {
+ return (
+
+ );
+};
+```
+
+使用 `formatMessage()` 方法时,也可以这么做:
+
+```tsx
+import { Button } from 'antd';
+import { useIntl } from 'umi';
+
+export default function Page() {
+ const intl = useIntl();
+ const msg = intl.formatMessage({
+ id: 'table.submit',
+ defaultMessage: 'SUBMIT TABLE',
+ });
+
+ return ;
+};
+```
+
+不推荐使用 `defaultMessage` 配置默认值,因为这会编写大量重复的国际化内容。最好的情况是,在您进行国际化适配时,确保了每个多语言文件中都包含所有用到的键。
+
+## 常用接口介绍
+
+### `addLocale` 动态添加多语言支持
+
+无需创建并编写单独的多语言文件,使用 `addLocale()` 接口可以在运行时动态添加语言支持。它接受三个参数:
+
+| 参数 | 类型 | 介绍 |
+| --------- | -------- | ----------------------------- |
+| `name` | `String` | 多语言的 Key |
+| `message` | `Object` | 多语言的内容对象 |
+| `options` | `Object` | `momentLocale` 和 `antd` 配置 |
+
+例如,您想要动态引入繁体中文的多语言支持,可以编写代码如下:
+
+```ts
+import { addLocale } from 'umi';
+import zhTW from 'antd/es/locale/zh_TW';
+
+addLocale(
+ 'zh-TW',
+ {
+ welcome: '歡迎光臨 Umi 的世界!',
+ },
+ {
+ momentLocale: 'zh-tw',
+ antd: zhTW,
+ },
+);
+```
+
+### `getAllLocales` 获取多语言列表
+
+通过 `getAllLocales()` 接口可以获取当前所有多语言选项的数组,包括通过 `addLocale()` 方法添加的多语言选项。该接口默认会在 `src/locales` 目录下寻找形如 `zh-CN.(js|json|ts)` 的文件,并返回多语言的 Key。
+
+```ts
+import { getAllLocales } from 'umi';
+
+getAllLocales();
+// [en-US, zh-CN, ...]
+```
+
+### `getLocale` 获取当前选择的语言
+
+通过 `getLocale()` 接口可以获取当前选择的语言:
+
+```ts
+import { getLocale } from 'umi';
+
+getLocale();
+// zh-CN
+```
+
+### `useIntl` 获取 `intl` 对象
+
+`useIntl()` 很有可能会是您开发中最常用的接口,通过它可以获取 `intl` 对象,并进一步执行 `formatMessage()` 等方法来实现您多元的需求:
+
+```json
+// src/locales/en-US.json
+{
+ "welcome": "Hi, {name}."
+}
+```
+
+```ts
+import { useIntl } from 'umi';
+
+const intl = useIntl();
+const msg = intl.formatMessage(
+ {
+ id: 'welcome',
+ },
+ {
+ name: 'Jackson',
+ },
+);
+console.log(msg);
+// Hi, Jackson.
+```
+
+关于 `intl` 对象的更多用法,请参阅 `react-intl` 的[接口文档](https://github.com/formatjs/formatjs/blob/main/website/docs/react-intl/api.md)。
+
+### `setLocale` 设置语言
+
+通过 `setLocale()` 接口可以使用编程的方法动态设置当前的语言。它有两个参数:
+
+| 参数 | 类型 | 介绍 |
+| ------------ | --------- | ------------------------------------------ |
+| `lang` | `String` | 切换到的语言 |
+| `realReload` | `Boolean` | 切换时是否刷新页面,默认为 `true` 刷新页面 |
+
+```ts
+import { setLocale } from 'umi';
+
+// 切换时刷新页面
+setLocale('en-US');
+
+// 切换时不刷新页面
+setLocale('en-US', false);
+```
+
+## 配置插件
+
+您可以在 `.umirc.ts` 中配置国际化插件。默认值如下:
+
+```ts
+export default {
+ locale: {
+ antd: false, // 如果项目依赖中包含 `antd`,则默认为 true
+ baseNavigator: true,
+ baseSeparator: '-',
+ default: 'zh-CN',
+ title: false,
+ useLocalStorage: true,
+ },
+};
+```
+
+配置的详细介绍如下:
+
+| 配置项 | 类型 | 默认值 | 介绍 |
+| --- | --- | --- | --- |
+| `antd` | `Boolean` | `false`;如果项目包含 `antd` 依赖,则为 `true` | `antd` 的国际化支持。更多介绍可参见[此文档](https://ant.design/docs/react/i18n-cn)。 |
+| `baseNavigator` | `Boolean` | `true` | 开启**浏览器语言检测**。默认情况下,当前语言环境的识别按照:`localStorage` 中 `umi_locale` 值 > 浏览器检测 > `default` 设置的默认语言 > `zh-CN` |
+| `baseSeparator` | `String` | `-` | 语言(Language)与国家(Country) 之间的**分割符**。默认情况下为 `-`,返回的语言及目录文件为 `zh-CN`、`en-US` 和 `sk` 等。若指定为 `_`,则 `default` 默认为 `zh_CN`。 |
+| `default` | `String` | `zh-CN` | 项目**默认语言**。当检测不到具体语言时,使用 `default` 设置的默认语言。 |
+| `title` | `Boolean` | `false` | 开启[**标题国际化**](#标题国际化)。 |
+| `useLocalStorage` | `Boolean` | `true` | 自动使用 `localStorage` 保存当前使用的语言。 |
+
+### 标题国际化
+
+在路由配置中添加 `title` 项即可启用国际化支持,自动将页面的标题转为对应的多语言内容。
+
+例如,编写多语言文件如下:
+
+```ts
+// src/locales/zh-CN.ts
+export default {
+ 'site.title': 'Umi - 企业级 React 应用开发框架',
+ 'about.title': 'Umi - 关于我',
+};
+```
+
+```ts
+// src/locales/en-US.ts
+export default {
+ 'site.title': 'Umi - Enterprise-level React Application Framework',
+ 'about.title': 'Umi - About me',
+};
+```
+
+配置路由内容如下:
+
+```ts
+// .umirc.ts
+export default {
+ title: 'site.title',
+ routes: [
+ {
+ path: '/',
+ component: 'Index',
+ },
+ {
+ path: '/about',
+ component: 'About',
+ title: 'about.title',
+ },
+ ],
+};
+```
+
+访问页面时:
+
+- `/` 路由。多语言选项为 `zh-CN` 时,页面标题为 `Umi - 企业级 React 应用开发框架`;为 `en-US` 时,页面标题为 `Umi - Enterprise-level React Application Framework`。
+- `/about` 路由。多语言选项为 `zh-CN` 时,页面标题为 `Umi - 关于我`;为 `en-US` 时,页面标题为 `Umi - About me`。
+
+## 运行时拓展
+
+国际化插件允许您在运行时对它进行一些拓展与定制。
+
+### 自定义 `getLocale`
+
+您可以自定义获取页面语言 `getLocale()` 方法的逻辑,例如通过识别链接 `?locale=en-US`,将 `en-US` 作为当前页面的语言:
+
+```ts
+// src/app.ts
+import qs from 'qs';
+
+export const locale = {
+ getLocale() {
+ const { search } = window.location;
+ const { locale = 'zh-CN' } = qs.parse(search, { ignoreQueryPrefix: true });
+ return locale;
+ },
+};
+```
+
+### 自定义选项配置
+Umi 的 i18n 是基于 `react-intl` 实现的,
+当您需要配置更多 `react-intl` 初始化选项的时候,可以在 `app.ts` 中配置,具体配置选项可以参考[react-intl 文档](https://formatjs.io/docs/react-intl/components)
+```js
+// src/app.ts
+import { RuntimeConfig } from '@umijs/max'
+
+export const locale: RuntimeConfig['locale'] = {
+ textComponent: 'span',
+ onError: () => {
+ console.log('error handler...');
+ }
+ // locale: string
+ // formats: CustomFormats
+ // messages: Record | Record
+ // defaultLocale: string
+ // defaultFormats: CustomFormats
+ // timeZone?: string
+ // textComponent?: React.ComponentType | keyof React.ReactHTML
+ // wrapRichTextChunksInFragment?: boolean
+ // defaultRichTextElements?: Record>
+ // onError(err: string): void
+}
+
+```
+## FAQ
+
+### 为什么不直接使用 `formatMessage` 这个语法糖?
+
+虽然 `formatMessage` 直接使用起来会非常方便,但是它脱离了 React 的生命周期,最严重的问题就是切换语言时无法触发 DOM 的重新渲染。为了解决这个问题,我们切换语言时就需要刷新一下浏览器,用户体验很差。所以推荐大家使用 `useIntl` 或者 `injectIntl`,可以实现同样的功能。
diff --git a/docs/docs/docs/max/introduce.en-US.md b/docs/docs/docs/max/introduce.en-US.md
new file mode 100644
index 000000000000..c6a67c99858b
--- /dev/null
+++ b/docs/docs/docs/max/introduce.en-US.md
@@ -0,0 +1,47 @@
+---
+order: 1
+toc: content
+---
+# Umi Max 简介
+
+Umi 作为一个可扩展的企业级前端应用框架,在蚂蚁集团内部已经已直接或间接地服务了 10000+ 应用。在工程实践的过程中,解决大量前端开发中开发中遇到的常见问题,这些经验累积成 Umi 各个插件。为了方便开发者更加方便的使用这些插件,在我们这些插件开源的基础上,直接将他们集成到一起,打造了 `@umijs/max`。 让开发者直接可以通过脚手架马上获得和蚂蚁集团开发 Umi 应用一样的开发体检。
+
+## 如何使用
+
+在使用 `create-umi` 选择 `Ant Design Pro` 模板,就能使用 `@umijs/max` 来创建项目了。
+
+```bash {4}
+$ npx create-umi@latest
+? Pick Umi App Template › - Use arrow-keys. Return to submit.
+ Simple App
+❯ Ant Design Pro
+ Vue Simple App
+```
+
+:::info{title=💡}
+在 Umi Max 项目中命令行请使用 `max{:bash}`,而不是原来的 `umi{:bash}`,示例如下
+:::
+
+```bash /max/
+$ npx max g jest
+```
+
+新建的项目默认安装以下插件, 可以按需开启:
+
+- [权限](./access)
+- [站点统计](./analytics)
+- [Antd](./antd)
+- [图表](./charts)
+- [dva](./dva)
+- [initial-state](./data-flow#全局初始状态)
+- [数据流](./data-flow)
+- [布局和菜单](./layout-menu)
+- [国际化(多语言)](./i18n)
+- [model](./data-flow)
+- [乾坤微前端](./micro-frontend)
+- [请求库](./request)
+- [Tailwind CSS](./tailwindcss)
+- [CSS-IN-JS](./styled-components)
+- [请求方案](./react-query)
+- [全局数据存储方案](./valtio)
+- [Module Federation](./mf)
diff --git a/docs/docs/docs/max/layout-menu.en-US.md b/docs/docs/docs/max/layout-menu.en-US.md
new file mode 100644
index 000000000000..4eeb39e25f97
--- /dev/null
+++ b/docs/docs/docs/max/layout-menu.en-US.md
@@ -0,0 +1,241 @@
+---
+order: 2
+toc: content
+---
+# 布局与菜单
+
+## 启用方式
+
+配置开启。
+
+```ts
+// config/config.ts
+export default {
+ layout: {
+ title: 'your app title',
+ },
+};
+```
+
+## 介绍
+
+为了进一步降低研发成本,我们将布局通过 Umi 插件的方式内置,只需通过简单的配置即可拥有 Ant Design 的 Layout([ProLayout](https://procomponents.ant.design/components/layout)),包括导航以及侧边栏。从而做到用户无需关心布局。
+
+- 默认为 Ant Design 的 Layout [@ant-design/pro-layout](https://www.npmjs.com/package/@ant-design/pro-layout),支持它全部配置项。
+- 顶部导航/侧边栏菜单根据路由中的配置自动生成。
+- 默认支持对路由的 403/404 处理和 Error Boundary。
+- 搭配 `access` [插件](./access)一起使用,可以完成对路由权限的控制。
+- 搭配 `initial-state` [插件](https://github.com/umijs/umi/blob/master/packages/plugins/src/initial-state.ts) 和 [数据流](./data-flow) 插件一起使用,可以拥有默认用户登陆信息的展示。
+
+> 想要动态菜单?查看这里 [菜单的高级用法](https://beta-pro.ant.design/docs/advanced-menu-cn)
+
+## 配置
+
+### 构建时配置
+
+可以通过配置文件 `config/config.ts` 中的 `layout` 属性开启插件。
+
+```ts
+import { defineConfig } from 'umi';
+
+export default defineConfig({
+ layout: {
+ title: 'Ant Design',
+ locale: false, // 默认开启,如无需菜单国际化可关闭
+ },
+});
+```
+
+#### title
+
+- Type: `string`
+- Default: `name` in package.json
+
+显示在布局左上角的产品名,默认值为包名。
+
+#### locale
+
+- Type: `boolean`
+- Default: `false`
+
+是否开始国际化配置。开启后路由里配置的菜单名会被当作菜单名国际化的 key,插件会去 locales 文件中查找 `menu.[key]`对应的文案,默认值为该 key,路由配置的 name 字段的值就是对应的 key 值。如果菜单是多级路由假设是二级路由菜单,那么插件就会去 locales 文件中查找 `menu.[key].[key]`对应的文案,该功能需要配置 [`i18n`](./i18n) 使用。如无需菜单国际化可配置 `false` 关闭。
+
+### 运行时配置
+
+运行时配置写在 `src/app.tsx` 中,key 为 `layout`。
+
+```tsx
+import { RunTimeLayoutConfig } from '@umijs/max';
+
+export const layout: RunTimeLayoutConfig = (initialState) => {
+ return {
+ // 常用属性
+ title: 'Ant Design',
+ logo: 'https://img.alicdn.com/tfs/TB1YHEpwUT1gK0jSZFhXXaAtVXa-28-27.svg',
+
+ // 默认布局调整
+ rightContentRender: () => ,
+ footerRender: () => ,
+ menuHeaderRender: undefined,
+
+ // 其他属性见:https://procomponents.ant.design/components/layout#prolayout
+ };
+};
+```
+
+除了下面的插件支持的特有配置外,运行时配置支持所有的构建时配置并透传给 [`@ant-design/pro-layout`](https://procomponents.ant.design/components/layout#prolayout)。
+
+#### title
+
+- Type: `string`
+- Default: `name` in package.json
+
+显示在布局左上角的产品名,默认值为包名。
+
+#### logo
+
+- Type: `string`
+- default: Ant Design Logo
+
+显示在布局左上角产品名前的产品 Logo。
+
+#### logout
+
+- Type: `(initialState: any) => void`
+- Default: `null`
+
+用于运行时配置默认 Layout 的 UI 中,点击退出登录的处理逻辑,默认不做处理。
+
+> 注:默认在顶部右侧并不会显示退出按钮,需要在运行配置中配合运行时配置 `app.ts(x)` 中的 `getInitialState` 返回一个对象,才可以显示
+
+#### rightRender
+
+- Type: `(initialState: any) => React.ReactNode`
+- Default: 展示用户名、头像、退出登录相关组件
+
+`initialState` 是运行时配置 `app.ts(x)` 中的 `getInitialState` 返回的对象。
+
+#### ErrorBoundary
+
+- Type: `ReactNode`
+- Default: Ant Design Pro 的错误页。
+
+发生错误后展示的组件。
+
+### 扩展的路由配置
+
+Layout 插件会基于 Umi 的路由,封装了更多的配置项,支持更多配置式的能力。新增:
+
+- 侧边栏菜单配置。
+- 布局路由级别展示/隐藏相关配置。
+- 与权限插件结合,配置式实现权限路由的功能。
+
+示例如下:
+
+```typescript
+// config/route.ts
+export const routes: IBestAFSRoute[] = [
+ {
+ path: '/welcome',
+ component: 'IndexPage',
+ name: '欢迎', // 兼容此写法
+ icon: 'testicon',
+ // 更多功能查看
+ // https://beta-pro.ant.design/docs/advanced-menu
+ // ---
+ // 新页面打开
+ target: '_blank',
+ // 不展示顶栏
+ headerRender: false,
+ // 不展示页脚
+ footerRender: false,
+ // 不展示菜单
+ menuRender: false,
+ // 不展示菜单顶栏
+ menuHeaderRender: false,
+ // 权限配置,需要与 plugin-access 插件配合使用
+ access: 'canRead',
+ // 隐藏子菜单
+ hideChildrenInMenu: true,
+ // 隐藏自己和子菜单
+ hideInMenu: true,
+ // 在面包屑中隐藏
+ hideInBreadcrumb: true,
+ // 子项往上提,仍旧展示,
+ flatMenu: true,
+ },
+];
+```
+
+#### name
+
+- Type: `string`
+
+菜单上显示的名称,没有则不展示该菜单。
+
+#### icon
+
+- Type: `string`
+
+菜单上显示的 antd 的 icon,为了按需加载 layout 插件会帮你自动转化为 Antd icon 的 dom。支持类型可以在 [antd icon](https://ant.design/components/icon-cn/) 中找到。示例:
+
+```ts
+// 线框风格
+icon: 'home'; // outlined 线框风格可简写
+icon: 'HomeOutlined';
+
+// 实底风格
+icon: 'HomeFilled';
+
+// 双色风格
+icon: 'HomeTwoTone';
+```
+
+#### access
+
+- Type: `string`
+
+当 Layout 插件配合 `plugin-access` 插件使用时生效。
+
+权限插件会将用户在这里配置的 access 字符串与当前用户所有权限做匹配,如果找到相同的项,并当该权限的值为 false,则当用户访问该路由时,默认展示 403 页面。
+
+#### locale
+
+- Type: `string`
+
+菜单的国际化配置,国际化的 key 是 `menu.${submenu-name}.${name}`。
+
+#### flatMenu
+
+- Type: `boolean`
+
+默认为 false,为 true 时在菜单中只隐藏此项,子项往上提,仍旧展示。
+
+打平菜单,如果只想要子级的 menu 不展示自己的,可以配置为 true。
+
+```tsx
+const before = [{ name: '111' }, { name: '222', children: [{ name: '333' }] }];
+// flatMenu = true
+const after = [{ name: '111' }, { name: '222' }, { name: '333' }];
+```
+
+#### xxxRender
+
+- Type: `boolean`
+
+xxxRender 设置为 false,即可不展示部分 layout 模块
+
+- `headerRender=false` 不显示顶栏
+- `footerRender=false` 不显示页脚
+- `menuRender=false` 不显示菜单
+- `menuHeaderRender=false` 不显示菜单的 title 和 logo
+
+#### hideInXXX
+
+- Type: `boolean`
+
+hideInXXX 可以管理 menu 的渲染。
+
+- `hideChildrenInMenu=true` 隐藏子菜单
+- `hideInMenu=true` 隐藏自己和子菜单
+- `hideInBreadcrumb=true` 在面包屑中隐藏
diff --git a/docs/docs/docs/max/mf.en-US.md b/docs/docs/docs/max/mf.en-US.md
new file mode 100644
index 000000000000..ac8a7305a511
--- /dev/null
+++ b/docs/docs/docs/max/mf.en-US.md
@@ -0,0 +1,431 @@
+---
+order: 17
+toc: content
+---
+
+# Module Federation 插件
+
+在 Umi 项目使用 Module Federation 功能。
+
+:::warning{title=🚨}
+Module Federation 功能需要浏览器支持 `Top Level Await` 特性。在生产环境中使用请注意浏览器是否支持([浏览器支持情况](https://caniuse.com/?search=top%20level%20await))。
+:::
+
+## 配置
+
+### 使用远端模块配置
+
+@umijs/max 项目
+
+```ts
+// .umirc.ts
+import { defineConfig } from '@umijs/max';
+
+const shared = {
+ react: {
+ singleton: true,
+ eager: true,
+ },
+ 'react-dom': {
+ singleton: true,
+ eager: true,
+ },
+};
+
+export default defineConfig({
+ // 已经内置 Module Federation 插件, 直接开启配置即可
+ mf: {
+ remotes: [
+ {
+ // 可选,未配置则使用当前 remotes[].name 字段
+ aliasName: 'mfNameAlias',
+ name: 'theMfName',
+ entry: 'https://to.the.remote.com/remote.js',
+ },
+ ],
+
+ // 配置 MF 共享的模块
+ shared,
+ },
+});
+```
+
+普通 Umi 项目
+
+```ts
+// .umirc.ts
+import { defineConfig } from 'umi';
+
+const shared = {
+ react: {
+ singleton: true,
+ eager: true,
+ },
+ 'react-dom': {
+ singleton: true,
+ eager: true,
+ },
+};
+
+export default defineConfig({
+ plugins: ['@umijs/plugins/dist/mf'], // 引入插件
+ mf: {
+ remotes: [
+ {
+ // 可选,未配置则使用当前 remotes[].name 字段
+ aliasName: 'mfNameAlias',
+ name: 'theMfName',
+ entry: 'https://to.the.remote.com/remote.js',
+ },
+ ],
+
+ // 配置 MF 共享的模块
+ shared,
+ },
+});
+```
+
+在项目中就可以使用 `import XXX from 'mfNameAlias/XXXX'` 来使用远端模块的内容了。
+
+#### 运行时远端模块加载
+
+如果需要在运行时(根据运行的环境)决定加载远端模块的地址,可以采用如下方式配置:
+
+```ts
+// .umirc.ts
+defineConfig({
+ mf: {
+ remotes: [
+ {
+ name: 'theMfName',
+ keyResolver: `(function(){
+ try {
+ return window.injectInfo.env || 'PROD'
+ } catch(e) {
+ return 'PROD'}
+ })()`,
+ entries: {
+ PRE: 'http://pre.mf.com/remote.js',
+ PROD: 'http://produ.mf.com/remote.js',
+ TEST: 'http://test.dev.mf.com/remote.js',
+ DEV: 'http://127.0.0.1:8000/remote.js',
+ },
+ },
+ ],
+ shared,
+ },
+});
+```
+
+- 使用运行时远端模块加载逻辑时,不要配置 `remotes[]#entry` , 插件会优先使用该字段。
+- `keyResolver` 用于在运行时决定使用 `entries` 哪个 key; 推荐使用 _立即调用函数表达式_ 的形式,可以在函数中实现较复杂的功能。不支持异步的函数。
+- `keyResolver` 也可以使用静态的值,配置形式 `keyResolver: '"PROD"'`
+
+### 导出远端模块配置
+
+当前项目对外提供远端模块,模块名使用如下配置字段
+
+```ts
+// .umirc.ts
+// 提取变量是为了和 MFSU 配合使用保持配置一致
+const remoteMFName = 'remoteMFName';
+
+defineConfig({
+ mf: {
+ name: remoteMFName,
+
+ // 可选,远端模块库类型, 如果模块需要在乾坤子应用中使用建议配置示例的值,
+ // 注意这里的 name 必须和最终 MF 模块的 name 一致
+ // library: { type: "window", name: "exportMFName" },
+ },
+});
+```
+
+:::info{title=🚨}
+配置的模块名必须为一个合法的 Javascript 变量名!
+:::
+
+导出的模块按照约定,将源代码目录下的 `exposes` 一级子目录名作为导出项,导出文件为该目录下的 index 文件,举例
+
+```txt
+src/exposes/
+├── Button
+│ └── index.jsx
+├── Head
+│ └── index.ts
+└── Form
+ └── index.tsx
+```
+
+对应的 Module Federation 的 exposes 为
+
+```js
+{
+ './Button': 'src/exposes/Button/index.jsx',
+ './Button': 'src/exposes/Head/index.ts',
+ './Form' : 'src/exposes/Form/index.tsx',
+}
+```
+
+### 关闭 MF 产物 hash
+
+默认情况下,当用户开启 `hash: true` 时, MF 产物中入口文件将自动携带 hash ,如 `remote.123abc.js` ,可通过设定 `remoteHash: false` 关闭(将得到 `remote.js` ),此时你可能需要修改 nginx / CDN / 网关 的响应头配置来去除该 `remote.js` 文件的缓存,否则新构建将无法生效。
+
+注:没有 hash 的更多危害与推荐做法详见 [issue #11711](https://github.com/umijs/umi/issues/11711)
+
+
+```ts
+mf: {
+ remoteHash: false
+}
+```
+
+## 运行时 API
+
+### 何时需要使用运行时 API ?
+
+采用配置的方式结合`import()`已经可以方便的使用 Module Federation 功能。如果你有以下需求就应该考虑使用运行时 API。
+
+- 远端模块的加载失败时,页面需要使用兜底组件
+- 远端模块的加载的地址无法通过同步函数来确定(需要异步调用)
+- 远端模块的加载的地址和模块名需要在运行时才能确定
+
+### safeMfImport
+
+有兜底的远端模块加载函数,接口定义如下:
+
+```ts
+safeMfImport(moduleSpecifier: string, fallback: any): Promise
+```
+
+结合 `React.lazy` 可以实现远端模块的懒加载
+
+```ts
+import { safeMfImport } from '@umijs/max';
+import React, { Suspense } from 'react';
+
+const RemoteCounter = React.lazy(() => {
+ return safeMfImport('remoteCounter/Counter', { default: () => 'Fallback' });
+});
+
+export default function Page() {
+ return (
+
+
+
+ );
+};
+```
+
+:::info{title=🚨}
+- 注意这里需要将兜底的***组件***包装到对象的`default`字段上来模拟一个模块。
+- `remoteCounter/Counter` 需要和配置对应。
+:::
+
+[实例代码](https://github.com/umijs/umi/blob/master/examples/mf-host/src/pages/safe-import.tsx)
+
+### safeRemoteComponent
+
+该 API 为封装了 `safeMfImport` 的高阶组件, 接口定义如下:
+
+```ts
+safeRemoteComponent>
+ (opts: {
+ moduleSpecifier:string;
+ fallbackComponent: React.ComponentType; // 远端组件加载失败的兜底组件
+ loadingElement: React.ReactNode ; // 组件加载中的 loading 展示
+ } ): T
+```
+
+示例:
+
+```ts
+const RemoteCounter = safeRemoteComponent>({
+ moduleSpecifier: 'remoteCounter/Counter',
+ fallbackComponent: () => 'fallbacked',
+ loadingElement: 'Loading',
+});
+
+export default function Page() {
+ return (
+
+
+
+ );
+};
+```
+
+[示例代码](https://github.com/umijs/umi/blob/master/examples/mf-host/src/pages/safe-remote-component.tsx)
+
+### rawMfImport
+
+加载远端模块,接口如下。
+
+```ts
+rawMfImport(opts: {
+ entry: string;
+ remoteName: string;
+ moduleName: string;
+}): Promise
+```
+
+示例
+
+```ts
+const RemoteCounter = React.lazy(() => {
+ return rawMfImport({
+ entry: 'http://localhost:8001/remote.js',
+ moduleName: 'Counter',
+ remoteName: 'remoteCounter',
+ });
+});
+```
+
+[示例代码](https://github.com/umijs/umi/blob/master/examples/mf-host/src/pages/raw-mf-import.tsx)
+
+### safeRemoteComponentWithMfConfig
+
+封装了`rawMfImport`的 高阶组件:
+
+```ts
+type RawRemoteComponentOpts ={
+ mfConfig:{
+ entry:string;
+ remoteName: string;
+ moduleName: string;
+ };
+ fallbackComponent: ComponentType;
+ loadingElement: ReactNode;
+}
+safeRemoteComponentWithMfConfig>(opts: RawRemoteComponentOpts): T
+```
+
+示例
+
+```ts
+const RemoteCounter = safeRemoteComponentWithMfConfig<
+ React.FC<{ init?: number }>
+>({
+ mfConfig: {
+ entry: 'http://localhost:8001/remote.js',
+ moduleName: 'Counter',
+ remoteName: 'remoteCounter',
+ },
+ fallbackComponent: () => 'raw Fallback',
+ loadingElement: 'raw Loading',
+});
+
+export default function Page() {
+ return ;
+};
+```
+
+[示例代码](https://github.com/umijs/umi/blob/master/examples/mf-host/src/pages/raw-mf-component.tsx)
+
+### registerMfRemote
+
+动态的注册 Module Federation 模块远端配置。
+
+```ts
+type MFModuleRegisterRequest = { entry: string; remoteName: string; aliasName?:string; }
+registerMfRemote (opts: MFModuleRegisterRequest): void
+```
+
+使用 `safeMfImport` 或者 `safeRemoteComponent` 时,`moduleSpecifier` 须是已经配置的远端模块。而 `rawMfImport` 的调用略啰嗦,可以使用 `registerMfRemote` 先注册,然后通过简洁的 `safeMfImport` 和 `safeRemoteComponent`。
+
+```ts
+registerMfRemote({
+ aliasName: 'registered',
+ remoteName: 'remoteCounter',
+ entry: 'http://127.0.0.1:8001/remote.js',
+});
+
+const RemoteCounter = React.lazy(() => {
+ return safeMfImport('registered/Counter', { default: null });
+});
+```
+
+[示例代码](https://github.com/umijs/umi/blob/master/examples/mf-host/src/pages/register-then-import.tsx)
+
+## 和 MFSU 一起使用
+
+Module Federation 插件会根据插件配置自动修改 MFSU 的**默认**配置以使两个功能在开发阶段正常使用,原理介绍如下:
+
+假设我们采用了如下 mf 插件的配置
+
+```ts
+// .umirc.ts
+export default defineConfig({
+ mf: {
+ name: 'remoteMFName',
+ remotes: [
+ {
+ name: 'remote1',
+ entry: 'https://to.the.remote.com/remote.js',
+ },
+ {
+ aliasName: 'aliasRemote',
+ name: 'remote2',
+ entry: 'https://to.the.remote.com/remote2.js',
+ },
+ ],
+ shared: {
+ react: {
+ singleton: true,
+ eager: true,
+ },
+ 'react-dom': {
+ singleton: true,
+ eager: true,
+ },
+ }
+ },
+});
+```
+
+那么对应最后生效的配置如下
+
+```ts
+{
+ mfsu: {
+ // mf 插件自动填充以下和 MFSU 兼容的默认配置
+ // 开启了 MFSU 也能在 DEV 阶段调试 MF 的模块
+ remoteName: 'remoteMFName',
+ remoteAliases: ['remote1', 'aliasRemote'],
+ shared: {
+ react: {
+ singleton: true,
+ eager: true,
+ },
+ 'react-dom': {
+ singleton: true,
+ eager: true,
+ },
+ }
+ },
+ mf: {
+ name: 'remoteMFName',
+ remotes: [
+ {
+ name: 'remote1',
+ entry: 'https://to.the.remote.com/remote.js',
+ },
+ {
+ aliasName: 'aliasRemote',
+ name: 'remote2',
+ entry: 'https://to.the.remote.com/remote2.js',
+ },
+ ],
+ shared: {
+ react: {
+ singleton: true,
+ eager: true,
+ },
+ 'react-dom': {
+ singleton: true,
+ eager: true,
+ },
+ },
+ },
+}
+```
diff --git a/docs/docs/docs/max/micro-frontend.en-US.md b/docs/docs/docs/max/micro-frontend.en-US.md
new file mode 100644
index 000000000000..1ebcada4136e
--- /dev/null
+++ b/docs/docs/docs/max/micro-frontend.en-US.md
@@ -0,0 +1,812 @@
+---
+order: 9
+toc: content
+---
+
+# 微前端
+
+`@umi/max` 内置了 **Qiankun 微前端**[插件](https://github.com/umijs/umi/blob/master/packages/plugins/src/qiankun.ts),它可以一键启用 Qiankun 微前端开发模式,帮助您轻松地在 Umi 项目中集成 Qiankun 微应用,构建出一个生产可用的微前端架构系统。
+
+关于 Qiankun 微前端的更多介绍请参阅[此页面](https://qiankun.umijs.org/zh/guide)。
+
+## 微前端示例
+
+![微前端示例](https://gw.alipayobjects.com/mdn/rms_655822/afts/img/A*TroZSp_cH0MAAAAAAAAAAAAAARQnAQ)
+
+如上图所示:在父应用里,我们通过导航栏切换路由后,下方显示的内容来自于不同的子应用。子应用支持单独打开;子应用之间也支持任意的嵌套。
+
+换一种更直观的理解方式:父应用和子应用其实都是**独立的前端项目**,父应用可以在内部引入子应用,子应用也可以在自己内部继续引入孙子应用,以此类推。
+
+当应用能够作为子应用被其它应用引入的时候,它就成为了我们所说的微应用。
+
+## 开始使用
+
+:::success{title=🏆︎}
+
+本教程假设您对什么是微前端,什么是 Qiankun 微应用,以及如何使用 Qiankun 微应用已经有了基本的了解。
+
+:::
+
+### 配置父应用
+
+首先需要配置父应用,注册子应用的相关信息,这样父应用才能识别子应用并在内部引入。
+
+注册子应用的方式主要有两种:
+
+- 插件注册子应用。
+- 运行时注册子应用。
+
+#### 插件注册子应用
+
+修改父应用的 Umi 配置文件,添加如下内容:
+
+```ts
+// .umirc.ts
+export default {
+ qiankun: {
+ master: {
+ apps: [
+ {
+ name: 'app1',
+ entry: '//localhost:7001',
+ },
+ {
+ name: 'app2',
+ entry: '//localhost:7002',
+ },
+ ],
+ },
+ },
+};
+```
+
+其中,`name` 为子应用的名称,在引入子应用时需要使用到它;`entry` 为子应用运行的 HTTP 地址;`master` 对象的完整 API 可[见此](#masteroptions)。
+
+#### 运行时注册子应用
+
+修改父应用的 Umi 配置文件,添加如下内容:
+
+```ts
+// .umirc.ts
+export default {
+ qiankun: {
+ master: {},
+ },
+};
+```
+
+修改父应用的 `src/app.ts` 文件,导出 `qiankun` 对象:
+
+```ts
+// src/app.ts
+export const qiankun = {
+ apps: [
+ {
+ name: 'app1',
+ entry: '//localhost:7001',
+ },
+ {
+ name: 'app2',
+ entry: '//localhost:7002',
+ },
+ ],
+};
+```
+
+### 配置子应用
+
+子应用需要导出必要的生命周期钩子,供父应用在适当的时机调用。
+
+假设您的子应用项目**基于 Umi 开发**且**引入了 `qiankun` [插件](https://github.com/umijs/umi/blob/master/packages/plugins/src/qiankun.ts)**。如果没有,可以按照[此教程](https://qiankun.umijs.org/zh/guide/getting-started#%E5%BE%AE%E5%BA%94%E7%94%A8)进行配置。
+
+修改子应用的 Umi 的配置文件,添加如下内容:
+
+```ts
+// .umirc.ts
+export default {
+ qiankun: {
+ slave: {},
+ },
+};
+```
+
+这样,微前端插件会自动在项目中创建好 Qiankun 子应用所需的生命周期钩子和方法,Easy as a cake!
+
+### 引入子应用
+
+在父应用中引入子应用,插件提供了三种不同实现的方式:
+
+- 路由绑定引入子应用。
+- `` 组件引入子应用。
+- `` 组件引入子应用。
+
+#### 路由绑定引入子应用
+
+手动配置 `.umirc.ts` 文件中的 `routes` 项,通过路由的方式绑定子应用。何时使用:
+
+- 子应用包含完整的路由切换逻辑时。
+- 父子应用路由相互关联时。
+
+现在,我们想要在 `/app1/project` 和 `/app2` 路由分别加载子应用 `app1` 和 `app2`,可以配置父应用的路由如下:
+
+```ts
+// .umirc.ts
+export default {
+ routes: [
+ {
+ path: '/',
+ component: '@/layouts/index.tsx',
+ routes: [
+ {
+ path: '/app1',
+ component: '@/layouts/app-layout.tsx',
+ routes: [
+ // 配置微应用 app1 关联的路由
+ {
+ // 带上 * 通配符意味着将 /app1/project 下所有子路由都关联给微应用 app1
+ path: '/project/*',
+ microApp: 'app1',
+ },
+ ],
+ },
+ // 配置 app2 关联的路由
+ {
+ path: '/app2/*',
+ microApp: 'app2',
+ },
+ ],
+ },
+ ],
+};
+```
+
+配置好后,子应用的路由 base 会在运行时被设置为主应用中配置的 `path`。
+
+例如,在上面的配置中,我们指定了 app1 关联的 path 为 `/app1/project`,假如 app1 里有一个路由配置为 `/user`,当我们想在父应用中访问 `/user` 对应的页面时,浏览器的 url 需要是 `base + /user`,即 `/app1/project/user` 路径,否则子应用会因为无法匹配到正确的路由而渲染空白或 404 页面。
+
+`qiankun` 插件拓展了 Umi 原有的路由对象,新增了 `microApp` 字段,它的值为注册子应用的 `name`。切换到对应路由后,Umi 将会使用 `` 组件渲染此子应用,并替换原来路由的 `component`。
+
+拓展后的 Umi 路由对象 API [可见此](#route)。
+
+#### `` 组件引入子应用
+
+通过 `` 组件加载(或卸载)子应用。何时使用:
+
+- 子应用包含完整的路由切换逻辑时。
+- 父子应用路由相互关联时。
+
+现在,我们想在父应用的某个页面中引入子应用 `app1`,可以编写代码如下:
+
+```tsx
+import { MicroApp } from 'umi';
+
+export default function Page() {
+ return ;
+}
+```
+
+使用该方式引入子应用时,父子应用的路由将一一对应。例如,当父应用路由为 `/some/page` 时,子应用路由同样为 `/some/page`。切换子应用路由时,父应用将同步切换。
+
+如果父应用的路由包含前缀,可以通过配置 `base` 属性保证父子应用的路由正确对应。例如,父应用路由为 `/prefix/router-path/some/page` 时,我们希望子应用的路由为 `/some/page`,可以修改代码如下:
+
+```tsx
+import { MicroApp } from 'umi';
+
+export default function Page() {
+ return ;
+}
+```
+
+#### `` 组件引入子应用
+
+通过 `` 组件加载(或卸载)子应用。何时使用:
+
+- 仅使用子应用的指定路由时。
+- 父子应用路由相互独立时。
+
+`` 组件是 `` 组件的变体,您需要显式提供 `url` 属性作为子应用的路由。当父应用的路由发生变化时,子应用的路由**不会改变**。
+
+现在,我们想在父应用的某个组件内部引入 `app2` 子应用,子应用的路由为 `/some/page`,可以编写代码如下:
+
+```tsx
+import { MicroAppWithMemoHistory } from 'umi';
+
+export default function Page() {
+ return ;
+}
+```
+
+### 子应用之间跳转
+
+如果子应用通过**路由绑定的方式**引入,在其它子应用的内部,可以使用 `` 跳转到对应的路由。以子应用 `app1` 和 `app2` 为例:
+
+```tsx
+// 在 app1 中
+import { MicroAppLink } from 'umi';
+
+export default function Page() {
+ return (
+ <>
+ {/* 跳转链接为 /app2/home */}
+
+
+
+ >
+ );
+}
+```
+
+在上面的例子中,点击按钮后,父应用的路由变为 `/app2/home`,渲染子应用 `app2` 内部路由为 `/home` 的页面。同理,如果想要从子应用 app2 回到子应用 app1,可以编写代码如下:
+
+```tsx
+// 在 app2 中
+import { MicroAppLink } from 'umi';
+
+export default function Page() {
+ return (
+ <>
+ {/* 跳转链接为 /app1/project/home */}
+
+
+
+ >
+ );
+}
+```
+
+您也可以从子应用跳转到父应用的指定路由:
+
+```tsx
+// 在子应用中
+import { MicroAppLink } from 'umi';
+
+export default function Page() {
+ return (
+ <>
+ {/* 跳转链接为 /table */}
+
+
+
+ >
+ );
+}
+```
+
+## 子应用生命周期
+
+Qiankun 在 single-spa 的基础上实现了一些额外的生命钩子。按照微应用的生命周期顺序,Qiankun 支持的完整的生命钩子列表如下:
+
+- `beforeLoad`,微应用**开始获取前**调用。最初,微应用为 `NOT_LOADED` 状态。
+- [`load`](https://single-spa.js.org/docs/building-applications/#load),微应用**获取完成时**调用。开始获取微应用时,微应用变成 `LOADING_SOURCE_CODE` 状态。若获取成功,微应用变成 `NOT_BOOTSTRAPPED` 状态;若获取失败,微应用变成 `LOAD_ERROR` 状态。
+- [`bootstrap`](https://single-spa.js.org/docs/building-applications/#bootstrap),微应用**初始化完成时**调用。开始初始化微应用时,微应用变成 `BOOTSTRAPPING` 状态。初始化完成时,微应用变成 `NOT_MOUNTED` 状态。
+- `beforeMount`,微应用每次**开始挂载前**调用。
+- [`mount`](https://single-spa.js.org/docs/building-applications/#mount),微应用每次**开始挂载时**调用。微应用变成 `MOUNTING` 状态。
+- `afterMount`,微应用每次**挂载完成时**调用。微应用变成 `MOUNTED` 状态。
+- `beforeUnmount`,微应用每次**开始卸载前**调用。
+- [`unmount`](https://single-spa.js.org/docs/building-applications/#unmount),微应用每次**开始卸载时**调用。微应用变成 `UNMOUNTING` 状态。
+- `afterUnmount`,微应用每次**卸载完成时**调用。微应用变成 `NOT_MOUNTED` 状态。
+- [`unload`](https://single-spa.js.org/docs/building-applications/#unload),微应用**卸载完成时**调用。微应用变成 `NOT_LOADED` 状态。
+
+此外,还存在一个特殊的生命钩子 `update`,仅在使用 `` 或 `` 组件引入微应用时生效:状态为 `MOUNTED` 的微应用**手动刷新时**调用。开始更新时,微应用变成 `UPDATING` 状态;更新完成时,微应用变成 `MOUNTED` 状态。
+
+您可以像这样手动刷新子应用:
+
+```tsx
+import { useRef } from 'react';
+import { MicroApp } from 'umi';
+
+export default function Page() {
+ const microAppRef = useRef();
+
+ // 执行此方法时,更新子应用
+ const updateMicroApp = () => {
+ microAppRef.current?.update();
+ };
+
+ return ;
+}
+```
+
+当您需要在子应用的生命周期里添加一些自定义的逻辑时,既可以在父应用中进行全局配置,也可以在子应用中进行单独配置。
+
+### 父应用配置生命周期钩子
+
+在父应用的 `src/app.ts` 中导出 `qiankun` 对象进行全局配置,所有的子应用都将实现这些生命周期钩子:
+
+```ts
+// src/app.ts
+export const qiankun = {
+ lifeCycles: {
+ // 所有子应用在挂载完成时,打印 props 信息
+ async afterMount(props) {
+ console.log(props);
+ },
+ },
+};
+```
+
+### 子应用配置生命周期钩子
+
+在子应用的 `src/app.ts` 中导出 `qiankun` 对象,实现生命周期钩子。子应用运行时仅支持配置 `bootstrap`、`mount` 和 `unmount` 钩子:
+
+```ts
+// src/app.ts
+export const qiankun = {
+ // 应用加载之前
+ async bootstrap(props) {
+ console.log('app1 bootstrap', props);
+ },
+ // 应用 render 之前触发
+ async mount(props) {
+ console.log('app1 mount', props);
+ },
+ // 应用卸载之后触发
+ async unmount(props) {
+ console.log('app1 unmount', props);
+ },
+};
+```
+
+## 父子应用通信
+
+父子应用间的通信有两种实现的方法:
+
+- 基于 `useModel()` 的通信。这是 Umi **推荐**的解决方案。
+- 基于配置的通信。
+
+### 基于 `useModel()` 的通信
+
+该通信方式基于 [数据流](https://github.com/umijs/umi/blob/master/packages/plugins/src/model.ts) 插件,此插件已经内置于 `@umi/max` 解决方案当中。
+
+该通信方式需要子应用**基于 Umi 开发**且**引入了该数据流插件**。
+
+关于此插件的详细介绍可见[数据流指南](./data-flow)。
+
+#### 主应用透传数据
+
+如果通过路由的模式引入子应用,则需要在父应用的 `src/app.ts` 里导出一个名为 `useQiankunStateForSlave()` 的函数,该函数的返回值将传递给子应用:
+
+```ts
+// src/app.ts
+export function useQiankunStateForSlave() {
+ const [globalState, setGlobalState] = useState({
+ slogan: 'Hello MicroFrontend',
+ });
+
+ return {
+ globalState,
+ setGlobalState,
+ };
+}
+```
+
+如果通过组件的模式引入子应用,直接将数据以组件参数的形式传递给子应用即可:
+
+```tsx
+import { useState } from 'react';
+import { MicroApp } from 'umi';
+
+export default function Page() {
+ const [globalState, setGlobalState] = useState({
+ slogan: 'Hello MicroFrontend',
+ });
+
+ return (
+
+ );
+}
+```
+
+#### 子应用消费数据
+
+子应用会自动生成一个全局的 Model,其命名空间为 `@@qiankunStateFromMaster`。通过 `useModel()` 方法,允许子应用在任意组件中获取并消费父应用透传的数据,如下所示:
+
+```tsx
+import { useModel } from 'umi';
+
+export default function Page() {
+ const masterProps = useModel('@@qiankunStateFromMaster');
+ return {JSON.stringify(masterProps)}
;
+}
+```
+
+或者可以通过高阶方法 `connectMaster()` 来获取并消费父应用透传的数据,如下所示:
+
+```tsx
+import { connectMaster } from 'umi';
+
+function MyPage(props) {
+ return {JSON.stringify(props)}
;
+}
+
+export default connectMaster(MyPage);
+```
+
+子应用也可以在生命周期钩子中能够获取并消费得到的 `props` 属性,根据需求[实现对应的生命周期钩子](#子应用配置生命周期钩子)即可。
+
+特别的,当父应用使用 `` 或 `` 组件的方式引入子应用时,会额外向子应用传递一个 `setLoading()` 方法,允许子应用在合适的时机执行,标记子应用加载为完成状态:
+
+```tsx
+const masterProps = useModel('@@qiankunStateFromMaster');
+masterProps.setLoading(false);
+
+// 或者
+function MyPage(props) {
+ props.setLoading(false);
+}
+connectMaster(MyPage);
+```
+
+当子应用挂载完成变成 `MOUNTED` 状态时,会自动标记为完成状态。
+
+### 基于配置的通信
+
+在配置父应用[注册子应用](#配置父应用)时,可以传入 `props` 属性,将数据传递给子应用。
+
+例如,修改父应用 `src/app.ts` 的 `qiankun` 导出方法如下:
+
+```ts
+// src/app.ts
+export const qiankun = {
+ apps: [
+ {
+ name: 'app1',
+ entry: '//localhost:7001',
+ props: {
+ accountOnClick: (event) => console.log(event),
+ accountName: 'Alex',
+ accountAge: 21,
+ },
+ },
+ ],
+};
+```
+
+子应用在生命周期钩子中能够获取并消费得到的 `props` 属性,根据需求[实现对应的生命周期钩子](#子应用配置生命周期钩子)即可。
+
+## 自定义子应用
+
+当启用子应用加载动画或错误捕获能力时,子应用接受一个额外的样式类 `wrapperClassName`,渲染的结果如下所示:
+
+```tsx
+
+
+
+
+
+```
+
+### 子应用加载动画
+
+启用此能力后,当子应用正在加载时,会自动显示加载动画。当子应用挂载完成变成 `MOUNTED` 状态时,加载状态结束,显示子应用内容。
+
+#### 基于 antd 的加载动画
+
+当您使用 antd 作为项目组件库时,可以向子应用传入 `autoSetLoading` 属性以开启子应用加载动画,插件将会自动调用 antd 的 [`` 组件](https://ant.design/components/spin-cn/)作为加载组件。
+
+如果通过路由的模式引入子应用,可以配置如下:
+
+```ts
+// .umirc.ts
+export default {
+ routes: [
+ {
+ path: '/app1',
+ microApp: 'app1',
+ microAppProps: {
+ autoSetLoading: true,
+ },
+ },
+ ],
+};
+```
+
+如果通过组件的模式引入子应用,直接将 `autoSetLoading` 作为参数传入即可:
+
+```tsx
+import { MicroApp } from 'umi';
+
+export default function Page() {
+ return ;
+}
+```
+
+#### 自定义加载动画
+
+如果您没有使用 antd 作为项目组件库,或希望覆盖默认的加载动画样式时,可以设置一个自定义的加载组件 `loader` 作为子应用的加载动画。
+
+通过路由的模式引入的子应用,只支持在运行时配置,代码如下:
+
+```tsx
+// .app.tsx
+import CustomLoader from 'src/components/CustomLoader';
+
+export const qiankun = () => ({
+ routes: [
+ {
+ path: '/app1',
+ microApp: 'app1',
+ microAppProps: {
+ loader: (loading) => ,
+ },
+ },
+ ],
+});
+```
+
+通过组件的模式引入子应用,直接将 `loader` 作为参数传入即可:
+
+```tsx
+import CustomLoader from '@/components/CustomLoader';
+import { MicroApp } from 'umi';
+
+export default function Page() {
+ return (
+ }
+ />
+ );
+}
+```
+
+其中,`loading` 为 `boolean` 类型参数,为 `true` 时表示仍在加载状态,为 `false` 时表示加载状态已结束。
+
+如果项目中希望多个子应用使用统一的自定义加载动画,可以通过在主应用配置 `defaultLoader` 来完成
+
+```ts
+// .umirc.ts
+qiankun: {
+ master: {
+ defaultLoader: '@/defaultLoader',
+ },
+},
+```
+
+其中,`defaultLoader` 为文件路径,统一约定放在 [src 目录](../guides/directory-structure.md#src-目录) 下,在 umi 中 `@` 即代表 `src` 目录。
+
+`defaultLoader` 跟上述 `loader` 的实现一致,接收一个 `loading` 为 `boolean` 类型的参数。
+
+```tsx
+// defaultLoader.tsx
+import { Spin } from 'antd';
+
+export default function (loading: boolean) {
+ return ;
+}
+```
+
+注意:`loader` 的优先级高于 `defaultLoader`。
+
+### 子应用错误捕获
+
+启用此能力后,当子应用加载出现异常时,会自动显示错误信息。
+
+#### 基于 antd 的错误捕获组件
+
+当您使用 antd 作为项目组件库时,可以向子应用传入 `autoCaptureError` 属性以开启子应用错误捕获能力,插件将会自动调用 antd 的 [`` 组件](https://ant.design/components/result-cn/)作为错误捕获组件。
+
+如(文案语言会自动读取 umi locale 配置切换):
+
+如果通过路由的模式引入子应用,可以配置如下:
+
+```ts
+// .umirc.ts
+export default {
+ routes: [
+ {
+ path: '/app1',
+ microApp: 'app1',
+ microAppProps: {
+ autoCaptureError: true,
+ },
+ },
+ ],
+};
+```
+
+如果通过组件的模式引入子应用,直接将 `autoCaptureError` 作为参数传入即可:
+
+```tsx
+import { MicroApp } from 'umi';
+
+export default function Page() {
+ return ;
+}
+```
+
+#### 自定义错误捕获组件
+
+如果您没有使用 antd 作为项目组件库,或希望覆盖默认的错误捕获组件样式时,可以设置一个自定义的组件 `errorBoundary` 作为子应用的错误捕获组件。
+
+通过路由的模式引入的子应用,只支持在运行时配置,代码如下:
+
+```tsx
+// .app.tsx
+import CustomErrorBoundary from '@/components/CustomErrorBoundary';
+
+export const qiankun = () => ({
+ routes: [
+ {
+ path: '/app1',
+ microApp: 'app1',
+ microAppProps: {
+ errorBoundary: (error) => ,
+ },
+ },
+ ],
+});
+```
+
+通过组件的模式引入子应用,将 `errorBoundary` 作为参数传入即可:
+
+```tsx
+import CustomErrorBoundary from '@/components/CustomErrorBoundary';
+import { MicroApp } from 'umi';
+
+export default function Page() {
+ return (
+ }
+ />
+ );
+}
+```
+
+其中,`error` 为 `Error` 类型参数。
+
+如果项目中希望多个子应用使用统一的自定义错误捕获组件,可以通过在主应用配置 `defaultErrorBoundary` 来完成
+
+```ts
+// .umirc.ts
+qiankun: {
+ master: {
+ defaultErrorBoundary: '@/defaultErrorBoundary',
+ },
+},
+```
+
+其中,`defaultErrorBoundary` 为文件路径,统一约定放在 [src 目录](../guides/directory-structure.md#src-目录) 下,在 umi 中 `@` 即代表 `src` 目录。
+
+`defaultErrorBoundary` 跟上述 `errorBoundary` 的实现一致,接收一个 `error` 为 `Error` 类型的参数。
+
+```tsx
+// defaultErrorBoundary.tsx
+export default function (error: Error) {
+ return {error?.message}
;
+}
+```
+
+注意:`errorBoundary` 的优先级高于 `defaultErrorBoundary`。
+
+## 环境变量
+
+如果您有一些不能显式编写在 `.umirc.ts` 或 `src/app.ts` 中的配置信息,可以将它们存放在环境变量文件中。例如编写父应用的环境变量文件 `.env` 如下:
+
+```plaintext
+INITIAL_QIANKUN_MASTER_OPTIONS="{\"apps\":[{\"name\":\"app1\",\"entry\":\"//localhost:7001\"},{\"name\":\"app2\",\"entry\":\"//localhost:7002\"}]}"
+```
+
+在内部,微前端插件会执行 `JSON.parse(process.env.INITIAL_QIANKUN_MASTER_OPTIONS)` 方法,然后将得到的结果与已有的配置信息合并。上面编写的环境变量,合并后相当于编写了如下配置信息:
+
+```ts
+export default {
+ qiankun: {
+ master: {
+ apps: [
+ {
+ name: 'app1',
+ entry: '//localhost:7001',
+ },
+ {
+ name: 'app2',
+ entry: '//localhost:7002',
+ },
+ ],
+ // ... .umirc.ts 中其它的配置信息
+ },
+ },
+};
+```
+
+需注意的是,当存在相同的配置项时,例如 `apps` 项,写在 `.umirc.ts` 中的配置项将**覆盖**环境变量中的配置项。
+
+同理,对于子应用,可以编写环境变量 `.env` 文件如下:
+
+```plaintext
+INITIAL_QIANKUN_SLAVE_OPTIONS="{\"enable\":false}"
+```
+
+相当于编写了如下配置信息:
+
+```ts
+export default {
+ qiankun: {
+ slave: {
+ enable: false,
+ // ... .umirc.ts 中其它的配置信息
+ },
+ },
+};
+```
+
+## API
+
+### MasterOptions
+
+| 属性 | 必填 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- | --- |
+| `enable` | 否 | 启用 Qiankun 微应用插件,设置为 `false` 时为不启用 | `boolean` | `undefined` |
+| `apps` | 是 | 微应用配置 | [`App[]`](#app) | `undefined` |
+| `routes` | 否 | 微应用运行时的路由 | [`Route[]`](#route) | `undefined` |
+| `defaultErrorBoundary` | 否 | 子应用默认的错误捕获组件,值为文件路径 | `string` | - |
+| `defaultLoader` | 否 | 子应用默认的加载动画,值为文件路径 | `string` | - |
+| `sandbox` | 否 | 是否开启沙箱模式 | `boolean \| { strictStyleIsolation: boolean, experimentalStyleIsolation: boolean }` | `true` |
+| `prefetch` | 否 | 是否启用微应用预加载 | `boolean \| 'all' \| string[] \| (( apps: RegistrableApp[] ) => { criticalAppNames: string[]; minorAppsName: string[] })` | `true` |
+
+关于沙箱和预加载的介绍可见[此页面](https://qiankun.umijs.org/zh/api/#startopts)。
+
+### SlaveOptions
+
+| 属性 | 必填 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- | --- |
+| `enable` | 否 | 启用 Qiankun 微应用插件,设置为 `false` 时为不启用 | `boolean` | `undefined` |
+
+### App
+
+| 属性 | 必填 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- | --- |
+| `name` | 是 | 微应用的名称 | `string` |
+| `entry` | 是 | 微应用的 HTML 地址 | `string` | `{ script: string[], styles: [] }` |
+| `credentials` | 否 | 拉取微应用时同时拉取 Cookies,详见[此介绍](https://qiankun.umijs.org/zh/faq#%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3%E6%8B%89%E5%8F%96%E5%BE%AE%E5%BA%94%E7%94%A8-entry-%E6%97%B6-cookie-%E6%9C%AA%E6%90%BA%E5%B8%A6%E7%9A%84%E9%97%AE%E9%A2%98) | `boolean` | `false` |
+| `props` | 否 | 父应用传递给微应用的数据,详见[父子应用通信章节](#父子应用通信) | `object` | `{}` |
+
+### Route
+
+| 属性 | 必填 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- | --- |
+| `path` | 是 | 路由 PATH | `string` |
+| `microApp` | 是 | 关联的微应用名称 | `string` |
+| `microAppProps` | 否 | 微应用的配置 | [`MicroAppProps`](#microappprops) | `{}` |
+
+### MicroAppProps
+
+| 属性 | 必填 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- | --- |
+| `autoSetLoading` | 否 | 自动设置微应用的加载状态 | `boolean` | `false` |
+| `loader` | 否 | 自定义的微应用加载状态组件 | `(loading) => React.ReactNode` | `undefined` |
+| `autoCaptureError` | 否 | 自动设置微应用的错误捕获 | `boolean` | `false` |
+| `errorBoundary` | 否 | 自定义的微应用错误捕获组件 | `(error: any) => React.ReactNode` | `undefined` |
+| `className` | 否 | 微应用的样式类 | `string` | `undefined` |
+| `wrapperClassName` | 否 | 包裹微应用加载组件、错误捕获组件和微应用的样式类,仅在启用加载组件或错误捕获组件时有效 | `string` | `undefined` |
+
+## FAQ
+
+### 子应用的生命周期钩子加载了,但是页面没有渲染
+
+如果页面没有报错,且通过查看 DOM 发现子应用的根节点已经有了,只是内容是空,这种基本可以确定是因为当前 url 没有匹配到子应用的任何路由导致的。
+
+比如我们在主应用中配置了:
+
+```js
+{
+ path: '/app1',
+ microApp: 'app1',
+}
+```
+
+子应用的路由配置是:
+
+```js
+{
+ path: '/user',
+ component: './User',
+}
+```
+
+那么我们必须通过 `/app1/user` 路径才能正常的访问到子应用的 user 页面。
diff --git a/docs/docs/docs/max/react-query.en-US.md b/docs/docs/docs/max/react-query.en-US.md
new file mode 100644
index 000000000000..180ec5932bd1
--- /dev/null
+++ b/docs/docs/docs/max/react-query.en-US.md
@@ -0,0 +1,92 @@
+---
+order: 11
+toc: content
+---
+# react-query
+
+@umijs/max 内置了 [react-query](https://tanstack.com/query/)(和 @tanstack/react-query 是同一个)请求方案。
+
+## 启用方式
+
+如果是 @umijs/max,配置开启。
+
+```ts
+export default {
+ reactQuery: {},
+}
+```
+
+如果是 umi,先安装 `@umijs/plugins` 依赖,再通过配置开启。
+
+```bash
+$ pnpm i @umijs/plugins -D
+```
+
+```ts
+export default {
+ plugins: ['@umijs/plugins/dist/react-query'],
+ reactQuery: {},
+}
+```
+
+## 特性
+
+插件帮你做了几件事,
+
+1、dev 模式下开启 react query 的 devtool,可通过 `reactQuery: { devtool: false }` 关闭,选项在 app.ts 里通过 `export const reactQuery = { devtool }` 配置。
+
+2、注册全局的 QueryClient,可通过 `reactQuery: { queryClient: false }` 关闭,选项在 app.ts 里通过 `export const reactQuery = { queryClient }` 配置。
+
+3、大部分 react-query 的导出可以从 `umi` 或 `@umijs/max` 里 import 使用。
+
+## 配置项
+
+可以在 `reactQuery` 中做以下配置。
+
+- `devtool`: boolean,是否开启 react query 官方 devtool 工具,默认 `true`
+- `queryClient`: boolean, 是否注册全局的 QueryClient 和 QueryClientProvier,默认 `true`
+
+比如:
+
+```ts
+export default {
+ reactQuery: {
+ devtool: false,
+ queryClient: false,
+ },
+}
+```
+
+注:以上两个配置的运行时配置需在 `app.ts` 里进行配置。
+
+## 运行时配置项
+
+包含以下配置。
+
+- `devtool`:object
+- `queryClient`: object
+
+比如:
+
+```ts
+const API_SERVER = '/path/to/api/server';
+export const reactQuery = {
+ devtool: {
+ initialIsOpen: true,
+ },
+ queryClient: {
+ defaultOptions: {
+ queries: {
+ queryFn: async ({ queryKey }) => {
+ const res = await fetch(`${API_SERVER}/${queryKey.join('/')}`);
+ if (res.status !== 200) {
+ throw new Error(res.statusText);
+ }
+ return res.json();
+ }
+ }
+ }
+ },
+};
+```
+
diff --git a/docs/docs/docs/max/request.en-US.md b/docs/docs/docs/max/request.en-US.md
new file mode 100644
index 000000000000..06a9b62039de
--- /dev/null
+++ b/docs/docs/docs/max/request.en-US.md
@@ -0,0 +1,405 @@
+---
+order: 6
+toc: content
+---
+
+# 请求
+
+`@umijs/max` 内置了插件方案。它基于 [axios](https://axios-http.com/) 和 [ahooks](https://ahooks-v2.surge.sh) 的 `useRequest` 提供了一套统一的网络请求和错误处理方案。
+
+```js
+import { request, useRequest } from 'umi';
+
+request;
+useRequest;
+```
+
+## 配置
+### 构建时配置
+```js
+export default {
+ request: {
+ dataField: 'data'
+ },
+};
+```
+
+构建时配置可以为 useRequest 配置 `dataField` ,该配置的默认值是 `data`。该配置的主要目的是方便 useRequest 直接消费数据。如果你想要在消费数据时拿到后端的原始数据,需要在这里配置 `dataField` 为 `''` 。
+
+比如你的后端返回的数据格式如下。
+
+```js
+{
+ success: true,
+ data: 123,
+ code: 1,
+}
+```
+
+那么 useRequest 就可以直接消费 `data`。其值为 123,而不是 `{ success, data, code }` 。
+
+### 运行时配置
+
+在 `src/app.ts` 中你可以通过配置 request 项,来为你的项目进行统一的个性化的请求设定。
+
+```ts
+import type { RequestConfig } from 'umi';
+
+export const request: RequestConfig = {
+ timeout: 1000,
+ // other axios options you want
+ errorConfig: {
+ errorHandler(){
+ },
+ errorThrower(){
+ }
+ },
+ requestInterceptors: [],
+ responseInterceptors: []
+};
+```
+
+除了 `errorConfig`, `requestInterceptors`, `responseInterceptors` 以外其它配置都直接透传 [axios](https://axios-http.com/docs/req_config) 的 request 配置。**在这里配置的规则将应用于所有的** `request` 和 `useRequest` **方法**。
+
+下面分别介绍 `plugin-request` 的运行时配置项。本节的末尾,我们会给出一个完整的运行时配置示例,并且对它的功能进行一个详细的描述。
+
+#### errorConfig
+如果你想要为自己的请求设定统一的错误处理方案,可以在这里进行配置。
+
+其中 `errorThrower` 接收你后端返回的数据并且需要抛出一个你自己设定的 error, 你可以在这里根据后端的数据进行一定的处理。
+
+我们的 `request` 会 catch `errorThrower` 抛出的错误,并且执行你的 `errorHandler` 方法,该方法接收两个参数,第一个参数是 catch 到的 error,第二个参数则是 request 的 opts。
+
+这里面的 `errorHandler` 和 `errorThrower` 需要配套使用。文档的末尾有一个完整的例子。
+
+如果你觉得这种方式进行错误处理过于繁琐,可以直接在拦截器中实现自己的错误处理。
+
+:::info{title=🚨}
+`errorThrower` 是利用 `responseInterceptors` 实现的,它的触发条件是: 当 `data.success` 为 `false` 时。
+:::
+
+#### requestInterceptors
+为 request 方法添加请求阶段的拦截器。
+
+传入一个数组,每个元素都是一个拦截器,它们会被按顺序依次注册到 axios 实例上。拦截器的写法同 axios request interceptor 一致,它需要接收 request config 作为参数,并且将它返回。
+
+我们建议你使用 `RequestConfig`,它能帮助你规范地书写你的拦截器。
+
+e.g.
+```ts
+const request: RequestConfig = {
+ requestInterceptors: [
+ // 直接写一个 function,作为拦截器
+ (url, options) =>
+ {
+ // do something
+ return { url, options }
+ },
+ // 一个二元组,第一个元素是 request 拦截器,第二个元素是错误处理
+ [(url, options) => {return { url, options }}, (error) => {return Promise.reject(error)}],
+ // 数组,省略错误处理
+ [(url, options) => {return { url, options }}]
+ ]
+
+}
+```
+
+另外,为了更好的兼容 umi-request,我们允许 umi-request 的拦截器写法,尽管它不能够通过 typescript 的语法检查。
+
+#### responseInterceptors
+为 request 方法添加响应阶段的拦截器。
+
+传入一个数组,每个元素都是一个拦截器,它们会被按顺序依次注册到 axios 实例上。拦截器的写法同 axios response interceptor一致。接收 axios 的 response 作为参数,并且将它返回。
+
+我们建议你使用 `RequestConfig`,它能帮助你规范地书写你的拦截器。
+
+e.g.
+```ts
+const request: RequestConfig = {
+ responseInterceptors: [
+ // 直接写一个 function,作为拦截器
+ (response) =>
+ {
+ // 不再需要异步处理读取返回体内容,可直接在data中读出,部分字段可在 config 中找到
+ const { data = {} as any, config } = response;
+ // do something
+ return response
+ },
+ // 一个二元组,第一个元素是 request 拦截器,第二个元素是错误处理
+ [(response) => {return response}, (error) => {return Promise.reject(error)}],
+ // 数组,省略错误处理
+ [(response) => {return response}]
+ ]
+
+}
+```
+
+**注意: 我们会按照你的数组顺序依次注册拦截器,但是其执行顺序参考 axios,request 是后添加的在前,response 是后添加的在后**
+
+## API
+### useRequest
+插件内置了 [@ahooksjs/useRequest](https://ahooks-v2.js.org/hooks/async) ,你可以在组件内通过该 Hook 简单便捷的消费数据。示例如下:
+```typescript
+import { useRequest } from 'umi';
+
+export default function Page() {
+ const { data, error, loading } = useRequest(() => {
+ return services.getUserList('/api/test');
+ });
+ if (loading) {
+ return loading...
;
+ }
+ if (error) {
+ return {error.message}
;
+ }
+ return {data.name}
;
+};
+```
+上面代码中 data 并不是你后端返回的数据,而是其内部的 data,(因为构建时配置默认是 'data')
+
+需要注意的是,ahooks 已经更新到3.0,而我们为了让 `umi@3` 的项目升级起来不那么困难,继续沿用了 ahooks2.0
+
+
+### request
+通过 `import { request } from '@@/plugin-request'` 你可以使用内置的请求方法。
+
+`request` 接收的 `options`除了透传 [axios](https://axios-http.com/docs/req_config) 的所有 config 之外,我们还额外添加了几个属性 `skipErrorHandler`,`getResponse`,`requestInterceptors` 和 `responseInterceptors` 。
+
+示例如下:
+```typescript
+request('/api/user', {
+ params: { name : 1 },
+ timeout: 2000,
+ // other axios options
+ skipErrorHandler: true,
+ getResponse: false,
+ requestInterceptors: [],
+ responseInterceptors: [],
+}
+```
+
+当你的某个请求想要跳过错误处理时,可以通过将`skipErrorHandler`设为 `true` 来实现
+
+request 默认返回的是你后端的数据,如果你想要拿到 axios 完整的 response 结构,可以通过传入 `{ getResponse: true }` 来实现。
+
+`requestInterceptors` 和 `responseInterceptors` 的写法同运行时配置中的拦截器写法相同,它们为 request 注册拦截器。区别在于这里注册的拦截器是 "一次性" 的。另外,这里写的拦截器会在运行时配置中的拦截器之后被注册。
+
+**注意: 当你使用了 errorHandler 时,在这里注册的 response 拦截器会失效,因为在 errorHandler 就会 throw error**
+
+### RequestConfig
+这是一个接口的定义,可以帮助你更好地配置运行时配置。
+```typescript
+import type { RequestConfig } from 'umi';
+
+export const request:RequestConfig = {};
+```
+注意,在导入时要加 type
+
+## umi@3 到 umi@4
+在 `umi@3` 到 `umi@4` 的升级中,我们弃用了 umi-request ,选用了 axios 作为默认的请求方案。在这个更换中,我们的功能也发生了一些变化。
+
+### 运行时配置的变动
+相比于 `umi@3`, `umi@4` 的运行时配置发生了较大的变化。
+```diff
+ export const request: RequestConfig = {
+ errorConfig: {
+++ errorHandler: () => {},
+++ errorThrower: () => {}
+-- errorPage: '',
+-- adaptor: ()=>{},
+ };
+-- middlewares: [],
+++ requestInterceptors: [],
+++ responseInterceptors: [],
+ ... // umi-request 和 axios 的区别。
+ };
+```
+
+- umi-request 的配置项变成了 axios 的配置项
+- 去除了 middlewares 中间件。你可以使用 axios 的 [拦截器](https://axios-http.com/docs/interceptors) 来实现相同的功能。
+- errorConfig 删除了原来的所有配置,新增了 errorHandler 和 errorThrower 来进行统一错误处理的设定。
+
+中间件的替换方式。对于一个 `umi@3` 的中间件,`next()` 方法之前的需要放在 `requestInterceptors` 中,`next()` 方法之后的内容则需要放在 `responseInterceptors` 中。
+
+```ts
+
+// 中间件
+async function middleware(ctx, next) {
+ const { url, options } = req;
+ if (url.indexOf('/api') !== 0) {
+ ctx.req.url = `/api/v1/${url}`;
+ }
+ await next();
+ if (!ctx.res.success) {
+ // do something
+ }
+}
+
+// 拦截器
+{
+ requestInterceptors:[
+ (config) => {
+ if (config.url.indexOf('/api') !== 0) {
+ config.url = `/api/v1/${url}`;
+ }
+ return config;
+ }
+ ],
+ responseInterceptors: [
+ (response) => {
+ if(!response.data.success){
+ // do something
+ }
+ }
+ ]
+}
+```
+
+### request 方法的参数变动
+[umi-request](https://github.com/umijs/umi-request#request-options) 和 [axios](https://axios-http.com/docs/req_config) 的配置项有着一定的区别。具体可以查看其各自的文档进行比较。
+
+### GET 请求参数序列化
+
+[Umi@3](https://github.com/umijs/umi-request/blob/master/src/middleware/simpleGet.js) 默认会用相同的 Key 来序列化数组。Umi@4 请求基于 axios,默认是带括号 `[]` 的形式序列化。
+
+```tsx
+// Umi@3
+import { useRequest } from 'umi';
+// a: [1,2,3] => a=1&a=2&a=3
+
+// Umi@4
+import { useRequest } from '@umijs/max';
+// a: [1,2,3] => a[]=1&a[]=2&a[]=3
+```
+
+如果希望保持 Umi@3 这种形式,可以这样做:
+
+```ts
+// src/app.[ts|tsx]
+
+/** @doc https://github.com/sindresorhus/query-string#arrayformat-1 */
++ import queryString from 'query-string';
+
+export const request: RequestConfig = {
++ paramsSerializer(params) {
++ return queryString.stringify(params);
++ },
+ ...
+}
+```
+
+## 运行时配置示例
+这里给出一个完整的运行时配置示例,以帮助你能够更好的去为自己的项目设定个性化的请求方案。
+
+```ts
+import { RequestConfig } from './request';
+
+// 错误处理方案: 错误类型
+enum ErrorShowType {
+ SILENT = 0,
+ WARN_MESSAGE = 1,
+ ERROR_MESSAGE = 2,
+ NOTIFICATION = 3,
+ REDIRECT = 9,
+}
+// 与后端约定的响应数据格式
+interface ResponseStructure {
+ success: boolean;
+ data: any;
+ errorCode?: number;
+ errorMessage?: string;
+ showType?: ErrorShowType;
+}
+
+// 运行时配置
+export const request: RequestConfig = {
+ // 统一的请求设定
+ timeout: 1000,
+ headers: {'X-Requested-With': 'XMLHttpRequest'},
+
+ // 错误处理: umi@3 的错误处理方案。
+ errorConfig: {
+ // 错误抛出
+ errorThrower: (res: ResponseStructure) => {
+ const { success, data, errorCode, errorMessage, showType } = res;
+ if (!success) {
+ const error: any = new Error(errorMessage);
+ error.name = 'BizError';
+ error.info = { errorCode, errorMessage, showType, data };
+ throw error; // 抛出自制的错误
+ }
+ },
+ // 错误接收及处理
+ errorHandler: (error: any, opts: any) => {
+ if (opts?.skipErrorHandler) throw error;
+ // 我们的 errorThrower 抛出的错误。
+ if (error.name === 'BizError') {
+ const errorInfo: ResponseStructure | undefined = error.info;
+ if (errorInfo) {
+ const { errorMessage, errorCode } = errorInfo;
+ switch (errorInfo.showType) {
+ case ErrorShowType.SILENT:
+ // do nothing
+ break;
+ case ErrorShowType.WARN_MESSAGE:
+ message.warn(errorMessage);
+ break;
+ case ErrorShowType.ERROR_MESSAGE:
+ message.error(errorMessage);
+ break;
+ case ErrorShowType.NOTIFICATION:
+ notification.open({
+ description: errorMessage,
+ message: errorCode,
+ });
+ break;
+ case ErrorShowType.REDIRECT:
+ // TODO: redirect
+ break;
+ default:
+ message.error(errorMessage);
+ }
+ }
+ } else if (error.response) {
+ // Axios 的错误
+ // 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
+ message.error(`Response status:${error.response.status}`);
+ } else if (error.request) {
+ // 请求已经成功发起,但没有收到响应
+ // \`error.request\` 在浏览器中是 XMLHttpRequest 的实例,
+ // 而在node.js中是 http.ClientRequest 的实例
+ message.error('None response! Please retry.');
+ } else {
+ // 发送请求时出了点问题
+ message.error('Request error, please retry.');
+ }
+ },
+
+ },
+
+ // 请求拦截器
+ requestInterceptors: [
+ (config) => {
+ // 拦截请求配置,进行个性化处理。
+ const url = config.url.concat('?token = 123');
+ return { ...config, url};
+ }
+ ],
+
+ // 响应拦截器
+ responseInterceptors: [
+ (response) => {
+ // 拦截响应数据,进行个性化处理
+ const { data } = response;
+ if(!data.success){
+ message.error('请求失败!');
+ }
+ return response;
+ }
+ ]
+};
+```
+
+上面的例子中的错误处理方案来自于 `umi@3` 的内置错误处理。在这个版本中,我们把它删除了,以方便用户更加自由地定制错误处理方案。如果你仍然想要使用它,可以将这段运行时配置粘贴到你的项目中。
+
+你也可以通过写响应拦截器的方式来进行自己的错误处理,**不一定局限于 errorConfig**。
diff --git a/docs/docs/docs/max/styled-components.en-US.md b/docs/docs/docs/max/styled-components.en-US.md
new file mode 100644
index 000000000000..bc8d461752c2
--- /dev/null
+++ b/docs/docs/docs/max/styled-components.en-US.md
@@ -0,0 +1,93 @@
+---
+order: 10
+toc: content
+---
+# styled-components
+
+@umijs/max 内置了 [styled-components](https://styled-components.com/) 样式方案。
+
+## 启用方式
+
+如果是 @umijs/max,配置开启。
+
+```ts {2}
+export default {
+ styledComponents: {},
+}
+```
+
+如果是 umi,先安装 `@umijs/plugins` 依赖,再通过配置开启。
+
+```bash
+$ pnpm i @umijs/plugins -D
+```
+
+```ts
+export default {
+ plugins: ['@umijs/plugins/dist/styled-components'],
+ styledComponents: {},
+}
+```
+
+## 特性
+
+插件帮你做了几件事,
+
+1、大部分 styled-components 的导出可以从 `umi` 或 `@umijs/max` 里 import 使用。
+
+2、支持通过配置的方式开启 styled-components 的 babel 插件,仅 dev 模式有效。
+
+3、支持通过运行时配置的方式声明全局样式。
+
+## 配置项
+
+可以在 `styledComponents` 中做以下配置。
+
+- `babelPlugin`: Object,开启 styled-components 的 babel 插件,仅 dev 模式有效
+
+比如:
+
+```ts
+export default {
+ styledComponents: {
+ babelPlugin: {},
+ },
+}
+```
+
+当你的导入来源不是 `umi` / `@umijs/max` 时,需将导入来源配置到 `topLevelImportPaths` 才可以使该 babel 插件生效,如:
+
+```ts
+import { styled } from 'alita'
+```
+
+```ts
+export default {
+ styledComponents: {
+ babelPlugin: {
+ topLevelImportPaths: ['alita']
+ },
+ },
+}
+```
+
+## 运行时配置项
+
+包含以下配置。
+
+- `GlobalStyle`:ReactComponent
+
+比如:
+
+```ts
+import { createGlobalStyle } from "umi";
+
+export const styledComponents = {
+ GlobalStyle: createGlobalStyle`
+ h1 {
+ background: #ccc;
+ }
+ `
+}
+```
+
diff --git a/docs/docs/docs/max/valtio.en-US.md b/docs/docs/docs/max/valtio.en-US.md
new file mode 100644
index 000000000000..fdca95ffb71e
--- /dev/null
+++ b/docs/docs/docs/max/valtio.en-US.md
@@ -0,0 +1,197 @@
+---
+order: 12
+toc: content
+---
+# valtio
+
+@umijs/max 内置了 valtio 数据流方案。
+
+## 启用 valtio
+
+配置开启。
+
+```ts
+export default {
+ valtio: {},
+}
+```
+
+## 开始使用
+
+### 基本用法
+
+极其简单。
+
+```ts
+import { proxy, useSnapshot } from 'umi';
+
+// 1、定义数据
+const state = proxy({ count: 0 });
+// 2、使用数据
+const snap = useSnapshot(state);
+snap.count;
+// 3、更新数据
+state.count += 1;
+```
+
+### React 外访问
+
+天然支持。
+
+```ts
+import { proxy } from 'umi';
+
+const state = proxy({ count: 0 });
+state.count;
+state.count += 1;
+```
+
+### 数据推导
+
+```ts
+import { proxyWithComputed } from 'umi';
+
+const state = proxyWithComputed({
+ count: 0,
+}, {
+ double: snap => snap.count * 2,
+});
+```
+
+### Action 和异步 Action
+
+两种用法,可以和 state 放一起,也可以分开。
+
+```ts
+import { proxy } from 'umi';
+
+// 方法一:放一起
+const state = proxy({
+ count: 0,
+ actions: {
+ add() {
+ // 注意这里别用 this.count,基于 snap 调用时会报错
+ state.count += 1;
+ },
+ }
+});
+// 方法二:分开放
+const state = proxy({ count: 0 });
+const actions = {
+ add() {
+ state.count += 1;
+ },
+ // 异步 action
+ async addAsync() {
+ state.count += await fetch('/api/add');
+ },
+};
+```
+
+### 数据结构的拆分与组合
+
+```ts
+import { proxy } from 'umi';
+
+// 比如如下定义
+// state.foo 和 state.bar 都是 proxy,可拆分使用
+const state = proxy({
+ foo: { a: 1 },
+ bar: { b: 1 },
+});
+
+// 组合
+const foo = proxy({ a: 1 });
+const bar = proxy({ b: 1 });
+const state = proxy({ foo, bar });
+```
+
+### 组件封装
+
+如果 props 内容和 state 无关,可以不处理;如果有关,按以下方式用 context 包一下,同时做 props 到 state 的数据同步即可。
+
+```ts
+import { proxy } from 'umi';
+
+// 1、createContext
+const MyContext = createContext();
+// 2、Provider
+const value = useRef(proxy({ count: 0 })).current;
+
+// 3、useContext
+useContext(MyContext);
+```
+
+### Redux DevTools 支持
+
+```ts
+import { proxy, proxyWithDevtools } from 'umi';
+
+const state = proxy({ count: 0 });
+proxyWithDevtools(state, { name: 'count', enabled: true });
+```
+
+### Redo & Undo 支持
+
+```ts
+import { proxyWithHistory } from 'umi';
+
+const state = proxyWithHistory({
+ count: 0,
+});
+state.value.count;
+state.value.count += 1;
+state.undo();
+state.redo();
+state.history;
+```
+
+### 持久化缓存
+
+待实现。
+
+```ts
+import { proxyWithPersistant } from 'umi';
+
+const state = proxyWithPersistant({
+ count: 0,
+}, {
+ type: 'localStorage',
+ key: 'count',
+});
+```
+
+### 扩展
+
+valtio 是基于组装式的扩展方式,相比 middleware 的方式在类型提示上会更好一些。比如我要实现前面的 proxyWithPersistant,简单点的方案只要这样,
+
+```ts
+export function proxyWithPersist(val: V, opts: {
+ key: string;
+}) {
+ const local = localStorage.getItem(opts.key);
+ const state = proxy(local ? JSON.parse(local) : val);
+ subscribe(state, () => {
+ localStorage.setItem(opts.key, JSON.stringify(snapshot(state)));
+ });
+ return state;
+}
+```
+
+### 兼容性
+
+1)需要 React 16.8 或以上,2)不支持 IE 11,3)map 和 set 不能直接用,需改用 valtio 提供的 proxyMap 和 proxySet。
+
+```ts
+import { proxy, proxyMap } from 'umi';
+
+const state = proxy({
+ todos: proxyMap([[1, {id:1,text:'Learn Umi'}]]),
+ filter: 'all',
+});
+```
+
+### 测试
+
+可以直接测 store,也可以测基于 store 的 React 组件。正常写用例即可,后者推荐用 @testing-library/react。
+