前言
众所周知,在前端项目不断的更新迭代的过程中,总会遇到所依赖的库升级的问题,对于一些已经有了一定规模的项目来说,对所依赖的库进行升级,往往是一个令人头痛的事情,特别是一些库大版本升级时,进行了很多破坏性更新的情况下。随着项目的越来越大,升级的成本也就越来越高。
为什么要升级
对项目中依赖的库进行升级,主要是因为旧的版本官方基本都不会再新增功能,只是修复一些bug,当你遇到问题时,也很难获得官方的支持,特别是对于公司内部的库来说,一方面公司层面可能会要求各个项目进行升级,另一方面则是当你需要公司库的某个功能,但它又只在新版本提供了后,你就会面临究竟是自己写一套,还是升级版本的局面,而基础库的部门,大概率不会为了你的功能,而在旧版本新增功能。
如何升级
最近在为自己负责的项目进行组件库的升级,组件库时公司内部的组件库,项目依赖的版本时2.x版本,而最新版本已经到了4.x。这里就只分享一下升级方案
基本思路
在项目中共存两个版本的库,然后根据路由,一个路由一个路由的进行升级,但这里涉及到两个问题,一个是如何让一个项目存在两个版本的库,另一个则是样式问题,众所周知,css样式是会互相覆盖的,这就导致即使两个版本,如果这两个版本采用的css类名基本一致,就会样式冲突,很不幸,我的项目就是这样的。
如何让两个版本共存
我这里采用的方式是使用别名的方式,即这样子:
- npm:
perl
npm install package-compatible@npm:package@^2.2.7
- yarn:
perl
yarn add package-compatible@yarn:package@^2.2.7
- pnpm:
perl
pnpm add package-compatible@npm:package@^2.2.7
这样子在项目里就可以这样引用了
javascript
import { Table } from "package-compatible"
当然,你也可以fork一份,发一个新的包到npm上,但是我这里一没有库的源码,二是公司内申请组件库还挺麻烦的,就没有这么干
样式隔离
- 如果你所有使用的组件库支持设置prefixCls的话,直接对新版本设置prefixCls即可,后面的不用看了,这里解释下为什么不对旧版本设置prefixCls,因为项目里很多修改组件库的样式地方,如果你改了旧版本的prefixCls,这些地方大概率都不会生效了,如果不支持设置prefixCls,那么可以继续看下去
- 现在版本已经共存了,那样式怎么做隔离呢,这里我采取的方案是为项目里所有的css规则增加一个前置css类,这个前置css类被设置在body上,根据路由来动态切换body上的css类
如何添加前置css类呢
我这里采取的方案是使用PostCSS 的插件 postcss-prefix-selector 来实现,思路是先给所有选择器加上前缀 .ui-isolate,然后根据 CSS 的文件路径将其替换成 .ui-4 或 .ui-3
- 关键点
-
- 对于body xxx的规则,不能直接加前缀,而是要改成
body.ui-isolate
,因为我们采用的方案是在body上切换css类,如果你不处理,就会变成.ui-isolate body
,那这条css规则就没有办法被应用了 - 对于html xxx,或者:root的规则,不能增加前缀,因为前缀class是设置在body上的,无法应用在html的规则上,:root同理
- 对于项目中使用了css-module的地方,要进行特殊处理,原因是因为我们采用的是postCSS的插件,postCSS的插件是在css-loader之前处理的,会导致css-module对你的前缀也进行hash,导致规则失效,举例来说就是原本的规则是 .container,我们处理后变成了 .ui-isolate .container ,然后被css-loader处理后就变成了 .ui-isolate-xxxxx .container-xxxxxx,这里我们要进行这样的一个处理,将它处理成 .container.container,这样权重就被提升了,而规则也不会失效
- 对于body xxx的规则,不能直接加前缀,而是要改成
配置的代码如下:
javascript
module.exports = {
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
['postcss-prefix-selector', {
// use this string to identify prefixed selectors, which will be
// replaced to .ui-4/3 according to the file path.
prefix: '.ui-isolate',
transform: (prefix, selector, prefixedSelector, filePath, rule) => {
// html/:root
if(selector.startWith("html")||selector.startWith(":root")){
return selector;
}
// 自定义的css,涉及css-module的情况
if (filePath.includes('module') && !filePath.includes('node_modules')) {
const [firstCls,...restCls] = selector.split(" ");
return [`${firstCls}${firstCls}`,...restCls].join(" ");
}
// body的样式处理
if(selector.startWith("body"){
prefixedSelector = selector.replace('body', 'body.ui-isolate')
}
// v4 prefix
if (filePath.includes('基础组件库@4') || filePath.includes('业务组件库@4')) {
// a possible result might be ".ui-4 .ui-button"
return prefixedSelector.replace('.ui-isolate', '.ui-4');
}
// v3 prefix
if (filePath.includes('基础组件库@3') || filePath.includes('业务组件库@3')) {
// a possible result might be ".ui-3 .ui-button"
return prefixedSelector.replace('.ui-isolate', '.ui-3');
}
// default not prefix
return prefixedSelector;
},
}],
],
},
},
}
路由切换代码:
javascript
document.body.classList.add("ui-isolate","ui-3")
// 如果是已经完成改造的路由
if(isV4Complete){
document.body.classList.replace("ui-3","ui-3")
}
为什么要把前缀放到body上
因为最高只能放到body上,如果你知道如何放到html,最高可以覆盖所有场景,因为有些组件会直接将直接渲染到body上,而不是项目的根root元素上
为什么要为除了组件库外的其他css规则也增加前缀
因为组件库的css规则的权重都提升了,如果其他的css规则不提升,就会出现一些原有的样式失效问题,而为所有的css规则都增加一样的权重,就等于没增加权重
最后
在完成上述改造后,大家就可以在项目中新的路由里愉快的使用新版本的库了,同时可以对旧有的库慢慢进行升级,可以为自己设置一些任务,每个月升级多少路由,逐步进行全部替换,最后替换完成,就可以删除这些兼容代码了