在项目中渐进式的升级组件库

前言

众所周知,在前端项目不断的更新迭代的过程中,总会遇到所依赖的库升级的问题,对于一些已经有了一定规模的项目来说,对所依赖的库进行升级,往往是一个令人头痛的事情,特别是一些库大版本升级时,进行了很多破坏性更新的情况下。随着项目的越来越大,升级的成本也就越来越高。

为什么要升级

对项目中依赖的库进行升级,主要是因为旧的版本官方基本都不会再新增功能,只是修复一些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,这样权重就被提升了,而规则也不会失效

配置的代码如下:

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规则都增加一样的权重,就等于没增加权重

最后

在完成上述改造后,大家就可以在项目中新的路由里愉快的使用新版本的库了,同时可以对旧有的库慢慢进行升级,可以为自己设置一些任务,每个月升级多少路由,逐步进行全部替换,最后替换完成,就可以删除这些兼容代码了

参考文档

多版本共存------巨型项目组件库升级的必经之路

相关推荐
Мартин.6 分钟前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
昨天;明天。今天。2 小时前
案例-表白墙简单实现
前端·javascript·css
数云界2 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd2 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
安冬的码畜日常2 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine
ChinaDragonDreamer2 小时前
Vite:为什么选 Vite
前端
小御姐@stella2 小时前
Vue 之组件插槽Slot用法(组件间通信一种方式)
前端·javascript·vue.js
GISer_Jing2 小时前
【React】增量传输与渲染
前端·javascript·面试
eHackyd2 小时前
前端知识汇总(持续更新)
前端
万叶学编程5 小时前
Day02-JavaScript-Vue
前端·javascript·vue.js