umi3到umi4升级之路

1,977 阅读4分钟

上一篇文章是从umi2升级到umi3,这一篇记录一下umi3升级到umi4的过程以及碰见的问题。

官方链接查看

upgrade-to-umi-4

升级步骤

  1. 依赖处理
  2. 启动命令修改
  3. 非官方插件升级
  4. 配置文件修改
  5. 代码层修改

依赖处理

项目的 package.json 需要升级 Umi,并替换掉对应的 Umi 插件。

如果 umi@3 中是使用 umi + @umijs/preset-react 的组合进行开发的,那可以直接使用新版的 max 直接升级。

{
  "devDependencies": {
+   "@umijs/max": "^4.0.0",
-   "umi": "^3.0.0",
-   "@umijs/preset-react": "^1.2.2"
  }
}

删除 node_module,执行下 npm install 重装依赖。

启动命令

如果使用了 @umijs/max 可以使用 max 命令来替换 umimax devmax build 等。

umi@4 将一些项目前置操作放到了 setup 命令中,如 umi@3 中的 umi g tmp 等命令,需要使用 umi setup 替换

package.json

{
  "scripts": {
-    "build": "umi build",
+    "build": "max build",
-    "start": "umi dev",
+    "start": "max dev",
  }
}

非官方插件升级

项目迁移时可先关闭对相应插件包的引用,如临时注释配置中的 plugins,移除 package.json 中以 umi-plugin-@umijs/plugin-@umijs/preset- 开头的所有依赖。

配置层迁移

max 提供的的配置项如下 config/config.ts

需要注意的是,之前的一些插件约定开启的规则,在 umi@4 中几乎都要通过显式的配置开启,因为希望在 umi@4 中有更少的“黑盒”。

import { defineConfig, utils } from 'umi';

export default defineConfig({
  model: {},
  antd: {},
  request: {},
  initialState: {},
  mock: {
    include: ['src/pages/**/_mock.ts'],
  },
  dva: {},
  layout: {
    // https://umijs.org/docs/max/layout-menu#构建时配置
    title: 'UmiJS',
    locale: true,
  },
  // https://umijs.org/zh-CN/plugins/plugin-locale
  locale: {
    // default zh-CN
    default: 'zh-CN',
    antd: true,
    // default true, when it is true, will use `navigator.language` overwrite default
    baseNavigator: true,
  },
});

存在差异的配置项如下 config/config.ts

import { defineConfig, utils } from 'umi';

export default defineConfig({
-  fastRefresh: {},
+  fastRefresh: true,
  dva: {
   // 不再支持 hmr 这个参数
-    hmr: true,
   },
// 默认 webpack5
-   webpack5: {},
})

其他涉及到的修改

  • 移除了dynamicImport,umi4 默认按页拆包,该行为近似等同于以前的 dynamicImport ,通过 src/loading.tsx 定义加载动画。
  • ignoreMomentLocale内置,默认值为true
  • 分包配置,按照一定的优化策略进行自动分包这里也可以自己配置分包规则。
codeSplitting: {
    jsStrategy: "granularChunks",
  },
  • moment2dayjs,只要注册了moment2dayjs, moment就会被dayjs代替。默认情况下注册可以适应and的预设。当用户同时配置预设和插件时,会合并处理。
  • analyze,配置项同webpack-bundle-analyzer

代码层修改

Umi 4 中将 react-router@5 升级到 react-router@6,所以路由相关的一些 api 存在着使用上的差异。

props 默认为空对象,以下属性都不能直接从 props 中取出

image.png

children

import { Outlet } from 'umi';
<Outlet />;

主要在全局 layout 中需要修改

layouts/index.tsx

import React from 'react';
+ import { Outlet } from 'umi';

export default function Layout(props) {
  return (
    <div>
-      { props.children }
+      <Outlet />
    </div>
  );
}

使用了 React.cloneElement 方式渲染的路由组件改造,示例

import React from 'react';
+ import { Outlet } from 'umi';

export default function RouteComponent(props) {
  return (
    <div>
-      { React.cloneElement(props.children, { someProp: 'p1' }) }
+      <Outlet context={{ someProp: 'p1' }} />
    </div>
  );
}

组件改成从 useOutletContext 取值

import React from 'react';
+ import { useOutletContext } from 'umi';

- export function Comp(props){
+ export function Comp() {
+   const props = useOutletContext();

  return props.someProp;
}

history

+ import { history } from 'umi';
export default function Page(props) {
  return (
    <div onClick={()=>{
-          props.history.push('list');
+          history.push('list');
    }}>
    </div>
  );
}

location

建议组件或 hooks 里用 useLocation 取,其他地方就用 window.location 获取。

export default function Page(props) {
+  const { location } = window;
  return (
    <div>
-     { props.location }
+     { location }
    </div>
  );
}

或者

+ import { useLocation } from 'umi';
export default function Page(props) {
+    let location = useLocation();
  return (
    <div>
-     { props.location }
+     { location }
    </div>
  );
}

更多 Umi 相关 api

完成以上操作后,执行下 max dev,访问 http://localhost:8000,请验证所有功能都符合预期。

如果你的项目无法正常启动,你可能还需要做如下操作:

遇到的问题

location 中的 query 找不到?

location 中的 query 不再支持了,后续推荐用 search

- const { query } = history.location;
+ import { parse } from 'query-string';
+ const query = parse(history.location.search);

或者如下 config/config.ts ,这里要注意个别情况需要单独处理。

import { defineConfig, utils } from 'umi';

export default defineConfig({
...
 // 启用 react-router 5 兼容模式
  reactRouter5Compat: {},
  // 让 history 带上 query
  historyWithQuery: {},
...
})

开启layout,报错React is not defined

可通过升级 react 到 18 规避这个问题。(我这里只是调试发现的报错顺带升级到了react18)

自定义Layout获取路由信息

import { useAppData } from "umi";

...
 const { clientRoutes } = useAppData()
...

clientRoutes是你已注册的路由表数据,转为菜单的时候需要自己过滤出需要显示的路由,此处可以参考layout

document.ejs 去哪了,如何自定义 HTML 模板

可通过插件Api中的modifyHTML或者config/config.js中配置externalsheadScripts实现,后者适用于设置哪些模块不打包以及简单的脚本注入。

image.png 通过插件Api官网的api写的很详细了照着配置就行。 插件注册:config/config.js中配置

{
    // 默认注册${root}/plugin.ts
    plugins: [require.resolve('你的自定义插件path')]
}

自定义插件

import { IApi } from 'umi';

export default (api: IApi) => {
  api.modifyHTML(($) => {
    $('head').append([
      `<script src='https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js'></script>`,
      `<script src='others...'></script>`
    ])
    return $;
  });
};