Umi3升级到Umi4我踩了这些坑

9,586 阅读4分钟

最近升级Umi,踩了一些坑,总结记录了一下,发出来分享一下,大概四五千字,升级遇到问题的小伙伴可以来查阅一下。

按照官网更改依赖

项目的 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 重装依赖。

报错了

image.png

这里报错 没有umi,把postinstall删掉试一下,install成功了。

把postinstall钩子删掉了

更改启动命令

{
  "scripts": {
-    "build": "umi build",
+    "build": "max build",
-    "postinstall": "umi g tmp",
+    "postinstall": "max setup",
-    "start": "umi dev",
+    "start": "max dev",
  }
}

恢复postinstall钩子,用max setup替换umi g tmp

让后npm run dev启动一下,又报错,runtimePublicPath配置的值无效,先不启动了,按照官网改.umirc.ts

image-20230831124407683.png

fatal - AssertionError [ERR_ASSERTION]: Invalid config values: runtimePublicPath
Invalid value for runtimePublicPath:
[
  {
    "code": "invalid_type",
    "expected": "object",
    "received": "boolean",
    "path": [],
    "message": "Expected object, received boolean"
  }
]

修改配置文件

  • 更改umi@umijs/max
- import { defineConfig } from 'umi';
+ import { defineConfig } from '@umijs/max';
  • mock属性
- mock: isDev ? {} : false,
+ mock: {},
  • umi4默认开启webpack5,删除webpack配置
- webpack5: {}
  • 更改runtimePublicPath
- runtimePublicPath: true,
+ runtimePublicPath: {},
  • 删除title,默认为null,无需设置false
-  title: false,
  • 删除nodeModulesTransform
- nodeModulesTransform: {
-   type: 'none',
-   exclude: [],
- },
  • 删除locale不知道是干嘛的?
- locale: { antd: true },
  • 删除dynamicImport,Umi 4 默认 按页分包 ,从而在页面切换时存在加载过程,通过该文件来配置加载动画。在目录结构下添加loading.tsx
-  dynamicImport: {
-    loading: '@/components/Loading',
-  },
  • .umirc.ts中添加historyWithQuery字段
// .umirc.ts
{
+ historyWithQuery: {},
}

现在启动一下npm run dev,又报错了!!!!!

image.png

error - [MFSU][eager] worker got Error Error: Cannot find module 'umi/dist/service/service'
Require stack:

代码层面更改

将页面中所有umi的导入改为@umijs/max

- import {  } from 'umi';
+ import {  } from '@umijs/max';

在npm run dev 启动一下,依然保持,与上面保持相同,找不到模块umi/dist/service/service

然后升级了node到v20

删除node_modules和package-lock.json文件

重新npm install

再次npm run dev

它又报错了,啊啊啊!!!

image.png

ValidationError: Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema.
 - options has an unknown property 'localsConvention'. These properties are valid:
   object { url?, import?, modules?, sourceMap?, importLoaders?, esModule?, exportType? }

删掉css-loader配置

- cssLoader: {
-   localsConvention: 'asIs',
- },

重新npm run dev

报错

index.html没有暴露window.publicPath

image-20230831162052389.png

在index.html要暴露window.publicPath,但是升级后的index.html中没有暴露

image.png

原来的

image.png

  • 通过headScriptsindex.html注入全局变量脚本
// .umirc.ts
{
+ headScripts: [
+    'window.routerBase = "abc/"',
+    'window.publicPath = window.resourceBaseUrl || "abc/";',
+ ]
}

注入后:

image.png

再次执行npm run dev

又报错,但是有预感快成功了

因为上面window.publicPath配置的一个写死的字符串

改为bathPath变量

base动态路由(补充)

这里本意是将basepublicPath设置为动态的,但是在Umi4中这么设置只是暴露一个window属性,而不会动态设置变量。要动态设置basepublicPath就要使用插件动态设置basenameRuntime,代码如下:

// plugin.ts
import { IApi } from '@umijs/max';

export default (api: IApi) => {
  api.onGenerateFiles(() => {
    api.writeTmpFile({
      path: 'core/basenameRuntime.ts',
      noPluginDir: true,
      content: `
        export const modifyClientRenderOpts = (ctx) => {
          ctx.basename=window.routerBase;
          ctx.publicPath=window.publicPath;
          return ctx;
        }
      `,
    });
  });
  api.addRuntimePlugin(() => ['@@/core/basenameRuntime.ts']);
};

另外,在 umi 4 里使用的是 react router v6 ,react router v6 的设计不再包含 history ,切换路由的方法变成了:

// https://umijs.org/docs/api/api#usenavigate
import { useNavigate } from 'umi'

const nav = useNavigate()

nav('/path/to/route')

这个 react router v6 的导航方法必须在 hooks 里使用,是包含 base 的。

本来 react router v6 是不提供 history 的,但 umi 4 做了 hack 兼容加入了 history ,但区别在于 history 不是应用内的,所以 history 的切换路由方法都不会自动添加 base ,如果你要让 history 的方法也自带 base ,可以考虑 patch history 的方法:

export const modifyClientRenderOpts = (context) => {
  const h = context.history
  const originPush = h.push
  const originReplace = h.replace
  h.push = (...args) => {
   // args[0] = base + args[0]
   originPush.apply(h, args)
  }
  h.replace = (...args) => {
    originReplace.apply(h, args)
  }
  return context
}

只需要在正文的代码片段内添加这段代码到 modifyClientRenderOpts 即可。

再次启动

又又又报错

image-20230901103536633.png

改成了这样

image-20230901105138265.png

再次执行npm run dev

又报了其他错误

getInitialState注册失败

image.png

Unhandled Rejection (Error): register failed, invalid key getInitialState from plugin /Users/mac/Desktop/xxxxx/src/app.ts.

提示我getInitialState注册不成功,真坑

  • umi4需要,在.umirc.ts中添加配置
// .umirc.ts
{
+  initialState: {},
+  model: {},  // 使用useModel需要这个配置
}

再次npm run dev

跑起来了!!

路由好像失败了!!!

只展示出来一个layout,antd的主题样式自定义也没了

先解决只展示layout的问题

按照umi/max新规范,需要使用<Outlet />替换children

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

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

修改完后项目展示部分正常了

使用useHistory的部分又报错了!!!!

image-20230901122436499.png

因为Umi4将React-Router@5升级到了React-Router@6

React-Router@6中,使用useNavigateuseLocation 替换了useHistory

将项目中使用useHistory替换为useNavigate

再次执行npm run dev

项目启动,报错没了

_layout.tsx文件失效了,气人 !!!

经查询,把原来的_layout.tsx文件放到与所在目录同级,并改名为与目录名相同,如下:

原来的:

|--parent
|    |--index.tsx
|    |--_layout.tsx

更新后:

|--parent
|    |--index.tsx
|--parent.tsx   # 原来的_layout.tsx

重新npm run dev

正常了

动态路由出现错误

Umi4约定式路由使用$id.tsx,不在支持[id].tsx

总结

在项目技术栈升级实操过程中一定要有文档记录自己每一步的操作,当遇到问题时及时回溯操作的可行性。避免因为自己的操作,解决了一个问题却引入了其他问题,自己还不自知的情况。