首先得有 node,并确保 node 版本是 10.13 或以上。
使用 yarn 管理 npm 依赖
# 国内源
$ npm i yarn tyarn -g
# 后面文档里的 yarn 换成 tyarn
$ tyarn -v
安装umi.js(建议使用cnpm安装,使用yarn安装配置会很麻烦)
cnpm install -g umi
cnpm install -g create-umi
安装启动项目
create-umi
.
├── package.json
├── .umirc.ts
├── .env
├── dist
├── mock
├── public
└── src
├── .umi
├── layouts/index.tsx
├── pages
├── index.less
└── index.tsx
└── app.ts
包含插件和插件集,以 @umijs/preset-、@umijs/plugin-、umi-preset- 和 umi-plugin- 开头的依赖会被自动注册为插件或插件集。
配置文件,包含 umi 内置功能和插件的配置。
环境变量。
PORT=8888
COMPRESS=none
执行 umi build 后,产物默认会存放在这里。
存储 mock 文件,此目录下所有 js 和 ts 文件会被解析为 mock 文件。
此目录下所有文件会被 copy 到输出路径。
临时文件目录,比如入口文件、路由等,都会被临时生成到这里。不要提交 .umi 目录到 git 仓库,他们会在 umi dev 和 umi build 时被删除并重新生成。
约定式路由时的全局布局文件。
所有路由组件存放在这里。
运行时配置文件,可以在这里扩展运行时的能力,比如修改路由、修改 render 方法等。
Umi 在 .umirc.ts
或 config/config.ts
中配置项目和插件,支持 es6。一份常见的配置如下,
export default {
base: '/docs/',
publicPath: '/static/',
hash: true,
history: {
type: 'hash',
},
}
如果项目的配置不复杂,推荐在 .umirc.ts
中写配置; 如果项目的配置比较复杂,可以将配置写在 config/config.ts
中,并把配置的一部分拆分出去,比如路由配置可以拆分成单独的 routes.ts
:
// config/routes.ts
export default [
{
exact: true, path: '/', component: 'index' },
];
// config/config.ts
import {
defineConfig } from 'umi';
import routes from './routes';
export default defineConfig({
routes: routes,
});
推荐两种配置方式二选一,.umirc.ts
优先级更高。
如果你想在写配置时也有提示,可以通过 umi 的 defineConfig
方法定义配置,
import {
defineConfig } from 'umi';
export default defineConfig({
routes: [
{
path: '/', component: '@/pages/index' },
],
});
可以新建 .umirc.local.ts
,这份配置会和 .umirc.ts
做 deep merge 后形成最终配置。
注:`.umirc.local.ts` 仅在 umi dev 时有效。umi build 时不会被加载
比如,
// .umirc.ts 或者 config/config.ts
export default {
a: 1, b: 2 };
// .umirc.local.ts 或者 config/config.local.ts
export default {
c: 'local' };
拿到的配置是:
{
a: 1,
b: 2,
c: 'local',
}
注意:
可以通过环境变量 UMI_ENV
区分不同环境来指定配置。
举个例子,
// .umirc.js 或者 config/config.js
export default {
a: 1, b: 2 };
// .umirc.cloud.js 或者 config/config.cloud.js
export default {
b: 'cloud', c: 'cloud' };
// .umirc.local.js 或者 config/config.local.js
export default {
c: 'local' };
不指定 UMI_ENV 时,拿到的配置是:
{
a: 1,
b: 2,
c: 'local',
}
指定 UMI_ENV=cloud 时,拿到的配置是:
{
a: 1,
b: 'cloud',
c: 'cloud',
}
运行时配置和配置的区别是他跑在浏览器端,基于此,我们可以在这里写函数、jsx、import 浏览器端依赖等等,注意不要引入 node 依赖。
约定 src/app.tsx
为运行时配置。
修改 clientRender 参数。
比如在微前端里动态修改渲染根节点:
let isSubApp = false;
export function modifyClientRenderOpts(memo) {
return {
...memo,
rootElement: isSubApp ? 'sub-root' : memo.rootElement,
};
}
修改路由。
比如在最前面添加一个 /foo 路由,
export function patchRoutes({
routes }) {
routes.unshift({
path: '/foo',
exact: true,
component: require('@/extraRoutes/foo').default,
});
}
比如和 render 配置配合使用,请求服务端根据响应动态更新路由,
let extraRoutes;
export function patchRoutes({
routes }) {
merge(routes, extraRoutes);
}
export function render(oldRender) {
fetch('/api/routes').then(res=>res.json()).then((res) => {
extraRoutes = res.routes;
oldRender();
})
}
注意:
覆写 render。
比如用于渲染之前做权限校验,
import {
history } from 'umi';
export function render(oldRender) {
fetch('/api/auth').then(auth => {
if (auth.isLogin) {
oldRender() }
else {
history.push('/login');
oldRender()
}
});
}
在初始加载和路由切换时做一些事情。
比如用于做埋点统计,
export function onRouteChange({
location, routes, action }) {
bacon(location.pathname);
}
比如用于设置标题,
export function onRouteChange({
matchedRoutes }) {
if (matchedRoutes.length) {
document.title = matchedRoutes[matchedRoutes.length - 1].route.title || '';
}
}
修改交给 react-dom 渲染时的根组件。
比如用于在外面包一个 Provider,
export function rootContainer(container) {
return React.createElement(ThemeProvider, null, container);
}
args 包含:
Umi 允许插件注册运行时配置,如果你使用插件,肯定会在插件里找到更多运行时的配置项。
在 Umi 中,应用都是单页应用,页面地址的跳转都是在浏览器端完成的,不会重新请求服务端获取 html,html 只在应用初始化时加载一次。所有页面由不同的组件构成,页面的切换其实就是不同组件的切换,你只需要在配置中把不同的路由路径和对应的组件关联上。
在配置文件中通过 routes 进行配置,格式为路由信息的数组。
export default {
routes: [
{
exact: true, path: '/', component: 'index' },
{
exact: true, path: '/user', component: 'user' },
],
}
配置可以被 path-to-regexp@^1.7.0 理解的路径通配符。
配置 location 和 path 匹配后用于渲染的 React 组件路径。可以是绝对路径,也可以是相对路径,如果是相对路径,会从 src/pages 开始找起。
如果指向 src 目录的文件,可以用 @,也可以用 …/。比如 component: '@/layouts/basic'
,或者 component: '../layouts/basic'
,推荐用前者。
表示是否严格匹配,即 location 是否和 path 完全对应上。
比如:
export default {
routes: [
// url 为 /one/two 时匹配失败
{
path: '/one', exact: true },
// url 为 /one/two 时匹配成功
{
path: '/one' },
{
path: '/one', exact: false },
],
}
配置子路由,通常在需要为多个路径增加 layout 组件时使用。
比如:
export default {
routes: [
{
path: '/login', component: 'login' },
{
path: '/',
component: '@/layouts/index',
routes: [
{
path: '/list', component: 'list' },
{
path: '/admin', component: 'admin' },
],
},
],
}
然后在 src/layouts/index 中通过 props.children 渲染子路由,
export default (props) => {
return <div style={
{
padding: 20 }}>{
props.children }</div>;
}
这样,访问 /list 和 /admin 就会带上 src/layouts/index 这个 layout 组件。
配置路由跳转。
比如:
export default {
routes: [
{
exact: true, path: '/', redirect: '/list' },
{
exact: true, path: '/list', component: 'list' },
],
}
访问 / 会跳转到 /list,并由 src/pages/list 文件进行渲染。
配置路由的高阶组件封装。
比如,可以用于路由级别的权限校验:
export default {
routes: [
{
path: '/user', component: 'user',
wrappers: [
'@/wrappers/auth',
],
},
{
path: '/login', component: 'login' },
]
}
然后在 src/wrappers/auth 中,
import {
Redirect } from 'umi'
export default (props) => {
const {
isLogin } = useAuth();
if (isLogin) {
return <div>{
props.children }</div>;
} else {
return <Redirect to="/login" />;
}
}
这样,访问 /user
,就通过 useAuth 做权限校验,如果通过,渲染 src/pages/user
,否则跳转到 /login
,由 src/pages/login
进行渲染。
配置路由的标题。
import {
history } from 'umi';
// 跳转到指定路由
history.push('/list');
// 带参数跳转到指定路由
history.push('/list?a=b');
history.push({
pathname: '/list',
query: {
a: 'b',
},
});
// 跳转到上一个路由
history.goBack();
详见 配置#history。
import {
Link } from 'umi';
export default () => (
<div>
<Link to="/users">Users Page</Link>
</div>
);
然后点击 Users Page 就会跳转到 /users 地址。
注意:
路由组件可通过 props 获取到以下属性,
比如:
export default function(props) {
console.log(props.route);
return <div>Home Page</div>;
}
通过 cloneElement,一次就好(Umi 2 时需要两次)。
import React from 'react';
export default function Layout(props) {
return React.Children.map(props.children, child => {
return React.cloneElement(child, {
foo: 'bar' });
});
}
除配置式路由外,Umi 也支持约定式路由。约定式路由也叫文件路由,就是不需要手写配置,文件系统即路由,通过目录和文件及其命名分析出路由配置。
如果没有 routes 配置,Umi 会进入约定式路由模式,然后分析 src/pages
目录拿到路由配置。
比如以下文件结构:
.
└── pages
├── index.tsx
└── users.tsx
会得到以下路由配置,
[
{
exact: true, path: '/', component: '@/pages/index' },
{
exact: true, path: '/users', component: '@/pages/users' },
]
需要注意的是,满足以下任意规则的文件不会被注册为路由,
约定 [] 包裹的文件或文件夹为动态路由。
比如:
举个完整的例子,比如以下文件结构,
.
└── pages
└── [post]
├── index.tsx
└── comments.tsx
└── users
└── [id].tsx
└── index.tsx
会生成路由配置,
[
{
exact: true, path: '/', component: '@/pages/index' },
{
exact: true, path: '/users/:id', component: '@/pages/users/[id]' },
{
exact: true, path: '/:post/', component: '@/pages/[post]/index' },
{
exact: true,
path: '/:post/comments',
component: '@/pages/[post]/comments',
},
];
约定 [ $] 包裹的文件或文件夹为动态可选路由。
比如:
举个完整的例子,比如以下文件结构,
.
└── pages
└── [post$]
└── comments.tsx
└── users
└── [id$].tsx
└── index.tsx
会生成路由配置,
[
{
exact: true, path: '/', component: '@/pages/index' },
{
exact: true, path: '/users/:id?', component: '@/pages/users/[id$]' },
{
exact: true,
path: '/:post?/comments',
component: '@/pages/[post$]/comments',
},
];
Umi 里约定目录下有 _layout.tsx
时会生成嵌套路由,以 _layout.tsx
为该目录的 layout。layout 文件需要返回一个 React 组件,并通过 props.children 渲染子组件。
比如以下目录结构,
.
└── pages
└── users
├── _layout.tsx
├── index.tsx
└── list.tsx
会生成路由,
[
{
exact: false, path: '/users', component: '@/pages/users/_layout',
routes: [
{
exact: true, path: '/users', component: '@/pages/users/index' },
{
exact: true, path: '/users/list', component: '@/pages/users/list' },
]
}
]
约定 src/layouts/index.tsx
为全局路由。返回一个 React 组件,并通过 props.children
渲染子组件。
比如以下目录结构,
.
└── src
├── layouts
│ └── index.tsx
└── pages
├── index.tsx
└── users.tsx
会生成路由,
[
{
exact: false, path: '/', component: '@/layouts/index',
routes: [
{
exact: true, path: '/', component: '@/pages/index' },
{
exact: true, path: '/users', component: '@/pages/users' },
],
},
]
一个自定义的全局 layout 如下:
import {
IRouteComponentProps } from 'umi'
export default function Layout({
children, location, route, history, match }: IRouteComponentProps) {
return children
}
你可能需要针对不同路由输出不同的全局 layout,Umi 不支持这样的配置,但你仍可以在 src/layouts/index.tsx
中对 location.path
做区分,渲染不同的 layout 。
比如想要针对 /login 输出简单布局,
export default function(props) {
if (props.location.pathname === '/login') {
return <SimpleLayout>{
props.children }</SimpleLayout>
}
return (
<>
<Header />
{
props.children }
<Footer />
</>
);
}
约定 src/pages/404.tsx
为 404 页面,需返回 React 组件。
比如以下目录结构,
.
└── pages
├── 404.tsx
├── index.tsx
└── users.tsx
会生成路由,
[
{
exact: true, path: '/', component: '@/pages/index' },
{
exact: true, path: '/users', component: '@/pages/users' },
{
component: '@/pages/404' },
]
这样,如果访问 /foo
,/ 和 /users
都不能匹配,会 fallback 到 404 路由,通过 src/pages/404.tsx 进行渲染。
通过指定高阶组件 wrappers
达成效果。
如下,src/pages/user
:
import React from 'react'
function User() {
return <>user profile</>
}
User.wrappers = ['@/wrappers/auth']
export default User
然后在 src/wrappers/auth 中,
import {
Redirect } from 'umi'
export default (props) => {
const {
isLogin } = useAuth();
if (isLogin) {
return <div>{
props.children }</div>;
} else {
return <Redirect to="/login" />;
}
}
这样,访问 /user
,就通过 useAuth 做权限校验,如果通过,渲染 src/pages/user
,否则跳转到 /login,由 src/pages/login
进行渲染。
支持在代码层通过导出静态属性的方式扩展路由。
比如:
function HomePage() {
return <h1>Home Page</h1>;
}
HomePage.title = 'Home Page';
export default HomePage;
其中的 title 会附加到路由配置中。
插件的 id 和 key
每个插件都会对应一个 id 和一个 key,id 是路径的简写,key 是进一步简化后用于配置的唯一值。
比如插件 /node_modules/@umijs/plugin-foo/index.js
,通常来说,其 id 为 @umijs/plugin-foo
,key 为 foo。
在 umi 里,页面之间跳转有两种方式:声明式和命令式。
通过 Link 使用,通常作为 React 组件使用。
import {
Link } from 'umi';
export default () => (
<Link to="/list">Go to list page</Link>
);
通过 history 使用,通常在事件处理中被调用。
import {
history } from 'umi';
function goToListPage() {
history.push('/list');
}
也可以直接从组件的属性中取得 history
export default (props) => (
<Button onClick={
()=>props.history.push('/list');}>Go to list page</Button>
);
新建 src/pages/document.ejs
,umi 约定如果这个文件存在,会作为默认模板,比如:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Your App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
模板里可通过 context 来获取到 umi 提供的变量,context 包含:
比如:
<link rel="icon" type="image/x-icon" href="<%= context.config.publicPath %>favicon.png" />
Mock 数据是前端开发过程中必不可少的一环,是分离前后端开发的关键链路。通过预先跟服务器端约定好的接口,模拟请求数据甚至逻辑,能够让前端开发独立自主,不会被服务端的开发所阻塞。
Umi 约定 /mock
文件夹下所有文件为 mock 文件。
比如:
.
├── mock
├── api.ts
└── users.ts
└── src
└── pages
└── index.tsx
/mock
下的 api.ts
和 users.ts
会被解析为 mock 文件。
export default {
// 支持值为 Object 和 Array
'GET /api/users': {
users: [1, 2] },
// GET 可忽略
'/api/users/1': {
id: 1 },
// 支持自定义函数,API 参考 express@4
'POST /api/users/create': (req, res) => {
// 添加跨域请求头
res.setHeader('Access-Control-Allow-Origin', '*');
res.end('ok');
},
}
然后访问 /api/users 就能得到 { users: [1,2] } 的响应,其他以此类推。
详见 配置#mock。
可以通过配置关闭,
export default {
mock: false,
};
也可以通过环境变量临时关闭,
$ MOCK=none umi dev
Mock.js 是常用的辅助生成模拟数据的三方库,借助他可以提升我们的 mock 数据能力。
import mockjs from 'mockjs';
export default {
// 使用 mockjs 等三方库
'GET /api/tags': mockjs.mock({
'list|100': [{
name: '@city', 'value|1-100': 50, 'type|0-2': 1 }],
}),
};
比如,
# OS X, Linux
$ PORT=3000 umi dev
# Windows (cmd.exe)
$ set PORT=3000&&umi dev
如果要同时考虑 OS X 和 Windows,可借助三方工具 cross-env,
$ yarn add cross-env --dev
$ cross-env PORT=3000 umi dev
Umi 中约定根目录下的 .env 为环境变量配置文件。
比如:
PORT=3000
BABEL_CACHE=none
然后执行,
$ umi dev
会以 3000 端口启动 dev server,并且禁用 babel 的缓存。
按字母排序。
指定项目根目录。
注意:APP_ROOT 不能配在 .env 中,只能在命令行里添加
用于分析 bundle 构成,默认关闭。
$ ANALYZE=1 umi dev
# 或者
$ ANALYZE=1 umi build
对服务端包大小的分析,默认关闭
默认开启 Babel 编译缓存,值为 none 时禁用缓存。
默认会根据 targets 配置打目标浏览器的全量补丁,设置为 none 禁用内置的补丁方案。
默认压缩 CSS 和 JS,值为 none 时不压缩,build 时有效。
默认不开启 TypeScript 类型检查,值为 1 时启用。比如:
$ FORK_TS_CHECKER=1 umi dev
设为 none 时禁用,有些场景下 friendly-errors-webpack-plugin 会把错误给吞了
$ FRIENDLY_ERROR=none umi dev
localhost 开启 https
$ HTTPS=1 umi dev
同时也可以使用配置 https: { key: '/path/key.pem', cert: '/path/cert.pem' }
自定义证书。
设为 none 时禁用代码热更新功能。
设为 none 时不输出 HTML,umi build 时有效。
默认是 0.0.0.0
指定端口号,默认是 8000。
设为 none 时禁用进度条。比如:
$ PROGRESS=none umi dev
指定用于 HMR 的 socket 服务器。比如:
$ SOCKET_SERVER=https://localhost:7001/ umi dev
分析 Webpack 编译时间,支持 CONSOLE 和 JSON 两种格式,默认是 JSON。
$ SPEED_MEASURE=CONSOLE umi dev
默认开启 Terser 压缩缓存,值为 none 时禁用缓存。
指定不同环境各自的配置文件,详见配置#多环境多份配置。
设为 none 时不监听文件变更。比如:
$ WATCH=none umi dev
默认不监听 node_modules 下的文件修改,如果需要,可通过此环境变量进行设置。比如:
# 整个 node_modules 都监听,会慢点
WATCH_IGNORED=none umi dev
# node_modules 下除 lodash 和 umi 监听,其他忽略
WATCH_IGNORED=node_modules/(?!(lodash|umi)) umi dev
生成 umi 构建性能分析文件 dist/stats.json
,结合 Webpack Xray 或 Webapck Analyse ,WEBPACK_PROFILE 值有 verbose、normal、minimal。比如:
$ WEBPACK_PROFILE=verbose umi build
预渲染下,默认会删除服务端渲染文件 umi.server.js
,如果希望保留,使用 RM_SERVER_FILE=none
。
本文档以 css 为示例,把后缀换成 .less 同样适用。
Umi 中约定 src/global.css
为全局样式,如果存在此文件,会被自动引入到入口文件最前面。
比如用于覆盖样式,
html, body, #root {
height: 100%;
}
body {
margin: 0;
}
Umi 会自动识别 CSS Modules 的使用,你把他当做 CSS Modules 用时才是 CSS Modules。
比如:
// CSS Modules
import styles from './foo.css';
// 非 CSS Modules
import './foo.css';
Umi 内置支持 less,不支持 sass 和 stylus,但如果有需求,可以通过 chainWebpack 配置或者 umi 插件的形式支持。
TODO:别名的使用。
通过 require 引用相对路径的图片。
export default () => <img src={
require('./foo.png')} />
支持别名,比如通过 @ 指向 src 目录:
export default () => <img src={
require('@/foo.png')} />
import {
ReactComponent as Logo } from './logo.svg'
function Analysis() {
return <Logo width={
90} height={
120} />
}
import logoSrc from './logo.svg'
function Analysis() {
return <img src={
logoSrc} alt="logo" />
}
通过相对路径引用。
.logo {
background: url(./foo.png);
}
CSS 里也支持别名,但需要在前面加 ~ 前缀,
.logo {
background: url(~@/foo.png);
}
注意:
项目中使用图片有两种方式,
前者不会有任何问题;后者,如果在 JS 中引用相对路径的图片时,在发布时会根据 publicPath 引入绝对路径,所以就算没有开启 dynamicImport 时,也需要注意 publicPath 的正确性。
通过相对路径引入图片的时候,如果图片小于 10K,会被编译为 Base64,否则会被构建为独立的图片文件。
10K 这个阈值可以通过 inlineLimit 配置修改。
常见使用场景:组件体积太大,不适合直接计入 bundle 中,以免影响首屏加载速度。例如:某组件 HugeA 包含巨大的实现 / 依赖了巨大的三方库,且该组件 HugeA 的使用不在首屏显示范围内,可被单独拆出。这时候,dynamic
就该上场了。
为了简化部署成本,按需加载功能默认是关闭的,你需要在使用之前先通过配置开启,
export default {
dynamicImport: {
},
}
按需加载组件 dynamic
为什么使用 dynamic
:封装了使用一个异步组件需要做的状态维护工作,开发者可以更专注于自己的业务组件开发,而不必关心 code spliting
、async module loading
等等技术细节。
通常搭配 动态 import 语法 使用。
封装一个异步组件
import {
dynamic } from 'umi';
export default dynamic({
loader: async function() {
// 这里的注释 webpackChunkName 可以指导 webpack 将该组件 HugeA 以这个名字单独拆出去
const {
default: HugeA } = await import(/* webpackChunkName: "external_A" */ './HugeA');
return HugeA;
},
});
使用异步组件
import React from 'react';
import AsyncHugeA from './AsyncHugeA';
// 像使用普通组件一样即可
// dynamic 为你做:
// 1. 异步加载该模块的 bundle
// 2. 加载期间 显示 loading(可定制)
// 3. 异步组件加载完毕后,显示异步组件
export default () => {
return <AsyncHugeA />;
}
快速刷新(Fast Refresh)是 React 官方为 React Native 开发的模块热替换(HMR)方案,由于其核心实现与平台无关,同时也适用于 Web。
Fast Refresh
功能最大的特性是:开发环境下,可以保持组件状态,同时编辑提供即时反馈。
在配置文件加上 fastRefresh: {}
即可开启
这张 gif 动图展示的是使用 Fast Refresh 特性的开发体验,可以看出,修改组件代码后,用户名和密码状态保持,这将提升应用本地研发体验。
开发方式上与平时没有区别,正常地修改、保存、预览,只是在效果反馈上,体验更加好。
有些情况下,维持状态并不是预期,所以为了可靠起见,Fast Refresh 遇到以下情况一概不保留状态(remount):
export default () => ;
会导致状态丢失@refresh reset
指令(在源码文件中任意位置加上这行注释)强制重刷(remount),最大限度地保证可用性推荐写函数命名组件,例如:
const Foo = () => {
};
export default Foo;
Umi 默认对新手友好,所以默认不做按需加载处理,umi build
后输出 index.html
、umi.js
和 umi.css
三个文件。
某些场景 html 文件交给后端输出,前端构建并不需要输出 html 文件,可配置环境变量 HTML=none
实现。
$ HTML=none umi build
经常有同学问这个问题:为什么我本地开发是好的,部署后就没反应了,而且没有报错?
没有报错! 这是应用部署在非根路径的典型现象。为啥会有这个问题?因为路由没有匹配上,比如你把应用部署在 /xxx/ 下,然后访问 /xxx/hello,而代码里匹配的是 /hello,那就匹配不上了,而又没有定义 fallback 的路由,比如 404,那就会显示空白页。
可通过配置 base 解决。
export default {
base: '/path/to/your/app/root',
};
可通过配置 history 为 hash 为解决。
export default {
history: {
type: 'hash' },
};
要实现按需加载,需配置 dynamicImport。
export default {
dynamicImport: {
},
};
这时,就需要配置 publicPath
。至于 publicPath 是啥?具体看 webpack 文档,把他指向静态资源(js、css、图片、字体等)所在的路径。
export default {
publicPath: "http://yourcdn/path/to/static/"
}
对于需要在 html 里管理 publicPath 的场景,比如在 html 里判断环境做不同的输出,可通过配置 runtimePublicPath
为解决。
export default {
runtimePublicPath: true,
};
然后在 html 里输出:
<script>
window.publicPath = <%= YOUR PUBLIC_PATH %>
</script>
在一些场景中,无法做服务端的 html fallback,即让每个路由都输出 index.html 的内容,那么就要做静态化。
比如上面的例子,我们在 .umirc.js 里配置:
export default {
exportStatic: {
},
}
然后执行 umi build,会为每个路由输出一个 html 文件。
./dist
├── index.html
├── list
│ └── index.html
└── static
├── pages__index.5c0f5f51.async.js
├── pages__list.f940b099.async.js
├── umi.2eaebd79.js
└── umi.f4cb51da.css
注意:静态化暂不支持有变量路由的场景。
有些静态化的场景里,是不会自动读索引文件的,比如支付宝的容器环境,那么就不能生成这种 html 文件,
├── index.html
├── list
│ └── index.html
而是生成,
├── index.html
└── list.html
配置方式是在 .umirc.js 里,
export default {
exportStatic: {
htmlSuffix: true,
},
}
umi build 会生成,
./dist
├── index.html
├── list.html
└── static
├── pages__index.5c0f5f51.async.js
├── pages__list.f940b099.async.js
├── umi.2924fdb7.js
└── umi.cfe3ffab.css
export default {
exportStatic: {
htmlSuffix: true,
dynamicRoot: true,
},
}
由于 Umi 3 使用微内核架构,将之前 Umi UI 拆分到独立的仓库,通过加载 @umijs/preset-ui 使用 Umi UI。
$ yarn add @umijs/preset-ui -D
$ UMI_UI=1 umi dev
服务端渲染(Server-Side Rendering),是指由服务侧完成页面的 HTML 结构拼接的页面处理技术,发送到浏览器,然后为其绑定状态与事件,成为完全可交互页面的过程。
这么讲可能比较学术,那通过两张图来更容易地说清楚。
第一张,单页应用(SPA)和服务端渲染过的(SSR)站点在社交分享时的区别:
第二张,白屏时间上 SSR 较少,因为当 HTML 文档返回时,已经有对应的内容。(
综上两图可知,SSR 常用于以下两个场景:
也就是说,如果你是中后台应用(如 antd pro、管理后台等),请谨慎考虑是否使用 SSR。
服务端渲染,首先得有后端服务器(一般是 Node.js)才可以使用,如果我没有后端服务器,也想用在上面提到的两个场景,那么推荐使用预渲染。
预渲染与服务端渲染唯一的不同点在于渲染时机,服务端渲染的时机是在用户访问时执行渲染(即实时渲染,数据一般是最新的),预渲染的时机是在项目构建时,当用户访问时,数据不一定是最新的(如果数据没有实时性,则可以直接考虑预渲染)。
预渲染(Pre Render)在构建时执行渲染,将渲染后的 HTML 片段生成静态 HTML 文件。无需使用 web 服务器实时动态编译 HTML,适用于静态站点生成。
Umi 3 结合自身业务场景,在 SSR 上做了大量优化及开发体验的提升,具有以下特性:
默认情况下,服务端渲染功能是关闭的,你需要在使用之前通过配置开启:
export default {
ssr: {
},
}