这一篇文章主要读 umi 里的@umijs/codemod的代码。这个包作用是通过umi-codemod命令对将 umi3 升级到 umi4 的一个工具。
前言
这一篇文章主要读 umi 里的@umijs/codemod的代码。这个包作用是通过umi-codemod命令对将 umi3 升级到 umi4 的一个工具。
源码阅读
执行umi-codemod命令会先运行 codemod/bin/umi-codemod.js 这个文件代码,这个文件会判断 node 版本,因为 umi4 的 node 版本必须 >= 14。
然后是执行 codemod/src/cli.ts 这个文件,这个文件是整个 codemod 流程的主文件,可以很清晰看到 codemod 干了什么事情。准备阶段处理(codemod/src/prepare.ts)、检查项目进行验证(codemod/src/checker.ts)、修改配置(codemod/src/runner/config.ts)、修改 eslint 文件(codemod/src/runner/eslintrc.ts)、修改 js 文件(codemod/src/runner/javascript.ts)、修改 css 文件(codemod/src/runner/css.ts)、修改 package.json 文件(codemod/src/runner/packageJSON.ts)
准备阶段处理(codemod/src/prepare.ts)
这个阶段主要是提供 context 让其他阶段使用。context 如下:
1 | return { |
config
在’.umirc.ts’、’.umirc.js’、’config/config.ts’、’config/config.js’中拿到优先级最高的作为用户配置,通过 esbuild 处理成 node 可读取的缓存文件(主要用来处理 ts 文件,很棒),用 require 的方式拿到用户的 json 配置,json 配置存到 config 里。
问题:如果有多个配置会丢失配置。我的建议是每个 config 都应该被处理。
file、fileCache
使用 fast-glob 快速获取 src 目录下的文件(js,jsx,ts,tsx)路径,存到 file 里。
循环 file,通过 fs 读取文件,路径为 key,文件字符串为内容,存到 fileCache 的 map 结构里。
pkg、pkgPath
cwd+package.json 作为 package.json 路径,存到 pkgPath 里。
通过 require 方式拿到 package.json 内容,存到 pkg 里。
unexpectedLayoutConfig、absSrcPath、absAppJSPath
拿到想不到(config.layout 中不是’name’, ‘title’, ‘locale’)的布局配置,存到 unexpectedLayoutConfig 里。
cwd+src 或者 cwd 作为 src 绝对路径,存到 absSrcPath 里。
absSrcPath+’app.tsx’、 ‘app.ts’、 ‘app.jsx’、 ‘app.js’其中一个路径存在作为 app 路径,存到 absAppJSPath 里。
检查项目进行验证(codemod/src/checker.ts)
这个阶段主要做一些常规检查,给出警告和错误。如果有错误就退出命令。通过创建错误数组,然后有错误添加进数组,最后看数组是否有错误,有就退出,这种错误后置的思路值得借鉴。
检查
- git 检查
- [错误]目录是不是 git 仓库(保证是根目录)
- [错误]git 状态是不是清理干净了
- config 检查
- [错误]mpa、ssr 项目不支持升级
- [警告]singular 属性给警告提示
- pkg 检查
- [错误]alita 只支持 2 到 3 升级
- [错误]umi 只支持 3 升级
处理错误
打印错误
有错误结束程序
修改配置(codemod/src/runner/config.ts)
这个阶段会删除一些 umi4 不支持的配置项和属性、对老配置写法进行升级。
1 | // 删除key |
- 删除空的 proxy
- favicon 存在就换成 favicons,并改成数组结构
- config.layout?.name 换成 config.layout?.title
- unexpectedLayoutConfig 是 config 的布局配置,通过 babel 迁移到 app 里
- 有 layout 但没有 layout?.locale,设置 false
最后对收集要删除的属性和要设置的属性进行处理,重新写入。
我的补充
config.inspectorConfig 需要删除
config.dva?.hmr 需要删除
修改 eslint 文件(codemod/src/runner/eslintrc.ts)
这个文件主要处理 eslint 文件。
通过 fs+JSON.parse 获取 json 文件配置,require 获取 js 文件配置。
用户的 plugins、globals、rules 配置不变,extends 替换成umi/eslint。
修改 js 文件(codemod/src/runner/javascript.ts)
这个文件主要处理用户的 js 文件。主要通过 Babel 对文件相应的节点进行转换和一些检测提醒。
转换
import { dynamic } from 'umi';const AsyncComponent = dynamic({ loader: import('./AsyncComponent') });替换成const AsyncComponent = loadable(() => import('./AsyncComponent'));import loadable from "@loadable/component";存在 matchPath 调用,提示看文档改
history.push({ pathname: '/foo', query: { a: 1 } });替换成history.push({ pathname: '/foo', search: qs.stringify({ a: 1 }) });import * as qs from "query-string";检测到 props.children 或 children,提示如果是用于嵌套子路由,请改用
<Outlet>history.goBack();替换成history.back();```js
function foo(props) {
props.history, props.location, props.match, props.routes, props.route, props.location;
}
// 替换
import { history, useLocation, useMatch, useRouteData, useAppData } from ‘umi’;
function foo(props) {
const { route } = useAppData();
const { routes } = useRouteData();
const match = useMatch();
const location = useLocation();
history, location, match, routes, route, location;
}
// 其他写法
function foo(props) {
const { history, match, location, routes, route } = props;
}
// 替换
function foo(props) {
const match = useMatch();
const location = useLocation();
const { routes } = useRouteData();
const { route } = useAppData();
}
import { useAppData, useRouteData, useLocation, useMatch, history } from ‘umi’;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- `const { query } = location;`替换成`const query = qs_l_q.parse(location.search)`,增加`import qs_l_q from 'query-string';`
- `import * as styles from './index.module.less';`替换成`import styles from './index.module.less';`
### 修改 css 文件(codemod/src/runner/css.ts)
主要对 css 文件进行修改。没有写内容。
### 修改 package.json 文件(codemod/src/runner/packageJSON.ts)
这个文件用来修改 package.json 文件,主要改脚本和依赖。
#### 脚本修改
强制修改成下面的内容
```json
{
"lint": "umi lint",
"lint:fix": "umi lint -fix",
"postinstall": "umi setup",
"setup": "umi setup"
}
依赖
强制修改成下面的内容
1 | { |
总结
整体来说这个包还是个试用版,能够处理一部分升级。fs 模块做文件读写,错误后置处理思路、esbuild 处理 ts 导出的 json 格式文件、babel 转换 js 文件内容。codmode 转换不难,花时间的是整理这些规则。