AntD3与AntD4组件混用解决方案

开端

这周,产品提了个需求,其中包含一个多选的集连选择框,还给我截了个图,(信心满满)说:“我们项目都是用AntD组件,直接用这个组件就成,还不用额外开发。省事儿”。

image.png

然而,她不知道的是,我们用的AntD3,截图的是AntD4,而AntD3版本是有级联但是没多选的。

此时,我计上心来,npm使用别名同时安装两个版本,直接用不就成事儿了吗!

解决方案

第一步:使用别名同时安装 3 / 4 两个版本

AntD3正常安装:

npm install [email protected]

AntD4别名安装:

npm install antd4@npm:[email protected]

业务代码中使用:

import { Input, Select } from 'antd'
import { Cascader } from 'antd4'
复制代码

第二步:使用pnpm代替npm/yarn

同时安装两个版本后,遇到了第一个问题,Webpack打包出错了,AntD3和AntD4用了相同的依赖,但大版本不同,而npm平铺式的依赖安装,让这同名不同版本的依赖更加混乱了。那么有没有一种从天而降的掌法,可以让二级依赖互相隔离呢?

image.png

当然是有的,这个掌法就是pnpm

此处贴一个pnpm官网的pnpm与npm对比:pnpm.io/zh/pnpm-vs-…

于是,项目使用pnpm代替了npm,解决二级依赖混乱的问题。

第三步:引入组件样式

果然,开发从来没有一帆风顺。

此时,遇到了第二个问题,AntD4组件样式没有引入,而全局引antd4/dist/antd.css的话显然不合适。

很不凑巧的,我们项目本地开发用的Vite,线上打包用的Webpack。

又是一套组合拳:

  1. 针对Webpack:在babel插件里额外写一条babel-plugin-import配置
plugins: [
    // 引入AntD3样式
    [
      'import',
      {
        libraryName: 'antd',
        libraryDirectory: 'es',
        style: true,
      },
    ],
    // 引入AntD4样式
    [
      'import',
      {
        libraryName: 'antd4',
        libraryDirectory: 'es',
        style: true,
      },
      'import-antd4', // 用了两次 babel-plugin-import,加个key,防止babel报错
    ]
]
复制代码
  1. 针对Vite:在vite.config.js中使用插件vite-plugin-style-import额外引入antd4组件样式

import { createStyleImportPlugin, AntdResolve } from 'vite-plugin-style-import'

{
    plugins: [
        createStyleImportPlugin({
          // 引入AntD3样式
          resolves: [AntdResolve()],
          libs: [
            // 引入AntD4样式
            {
              libraryName: 'antd4',
              esModule: true,
              resolveStyle: (name) => {
                // vite开发模式下,此处路径需要额外加node_modules,很奇怪,暂时没有深究原因
                return `node_modules/antd4/es/${name}/style/index`
              },
            },
          ],
        })
    ]
}

复制代码

配置完成,解决了样式引入问题。

第四步:AntD3 / AntD4 样式隔离

搓了搓手,又开始磨刀霍霍了。

image.png

很不幸,第三个问题来了,点击Cascader下拉框不弹出。。。

开控制台检查一下Elements,发现是AntD3和AntD4的样式重名,3把4的样式覆盖了,而Cascader组件下拉框写法改了,导致不弹出。

从下图可以看到,同一类名的样式有两份。

image.png

接下来,开始思考怎么实现样式隔离。。。

查一下官方文档,ConfigProviderprefixCls属性正合我意。通过修改类名通用前缀实现样式隔离。

Property Description Type Default
prefixCls Set prefix className (cooperated with @ant-prefix) string ant

业务代码修改

import { ConfigProvider as AntD3ConfigProvider } from 'antd'
import { ConfigProvider as AntD4ConfigProvider } from 'antd4'

// ...

<AntD4ConfigProvider prefixCls="ant4">
  <AntD3ConfigProvider>
    <App />
  </AntD3ConfigProvider>
</AntD4ConfigProvider>

复制代码

打包修改

lessOption: {
  modifyVars: {
    'ant-prefix': 'ant4'
  },
  javascriptEnabled: true,
  sourceMap: false,
}
复制代码

此处的lessOption传入给Webpack的less-loaderoptions或Vite的css.preprocessorOptions.less

改完一跑,结果发现HTML tag上的类名成功改成了ant4-开头,这很好。但灾难发生了,AntD3 / AntD4所有的CSS内容都变成了ant4-开头,结果就是整个页面使用的AntD3组件完全没有样式。

初始状态:

版本 HTML class CSS class
AntD3 ant-xxx ant-xxx
AntD4 ant-xxx ant-xxx

我想要的:

版本 HTML class CSS class
AntD3 ant-xxx ant-xxx
AntD4 ant4-xxx ant4-xxx

实际结果:

版本 HTML class CSS class
AntD3 ant-xxx ant4-xxx
AntD4 ant4-xxx ant4-xxx

less的modifyVars配置不分文件,把所有@ant-prefix值全改了。显然,这个配置失败了。

image.png

想用个组件真是太难了。

实现less按需的modifyVars

接下来,就是实现less按需的modifyVars功能了。

先在less官网一顿找,发现less支持plugins lesscss.org/tools/#plug… ,顿时眼前一亮,内心毫无波澜,写个插件不就结了?

接着,在官网逛了一圈没发现写plugin的文档,文档真差。

不气馁,又跑GitHub搜一下less的文档。

先看看plugins.md

image.png

没有任何发现,再看看api.md会不会有宝藏。

image.png

Coming soon 这是什么个情况!!!

image.png

看来要拿出最后的大招,撸源码了,官方给出了一些已有插件的github地址,一个一个点开看,突然发现 less-plugin-sass2less代码中国呢写到的pluginManager.addPreProcessor有点像我要的。接着又到less源码中搜 addPreProcessor 的用法。

less 源码部分:

/**
 * Adds a pre processor object
 * @param {object} preProcessor
 * @param {number} priority - guidelines 1 = before import, 1000 = import, 2000 = after import
 */
PluginManager.prototype.addPreProcessor = function(preProcessor, priority) {
    var indexToInsertAt;
    for (indexToInsertAt = 0; indexToInsertAt < this.preProcessors.length; indexToInsertAt++) {
        if (this.preProcessors[indexToInsertAt].priority >= priority) {
            break;
        }
    }
    this.preProcessors.splice(indexToInsertAt, 0, {preProcessor: preProcessor, priority: priority});
};
复制代码

addPreProcessor支持传入 preProcessor预处理器 和 priority 优先级两个参数。

找到addPreProcessor后又去找preProcessors在哪里调用。 全局搜preProcessors关键字

// ....

if (context.pluginManager) {
    var preProcessors = context.pluginManager.getPreProcessors();
    for (var i = 0; i < preProcessors.length; i++) {
        str = preProcessors[i].process(str, { context: context, imports: imports, fileInfo: fileInfo });
    }
}
// ...
复制代码

每一个preProcessor接受less源码字符串str,和对应的文件信息{ context: context, imports: imports, fileInfo: fileInfo },返回修改后的str

一阵感叹,苦心人天不负啊!!!

image.png

接下来可以撸一个less插件了。过程不表,直接上源码。此插件支持通过不同pathReg来限定对应modifyVars的生效范围。

npm地址:www.npmjs.com/package/les…

GitHub地址:less-plugin-scope-modifyvar

使用less-plugin-scope-modifyvar

  1. 安装less插件

pnpm install -D less-plugin-scope-modifyvar

  1. less配置

去掉之前的全局modifyVars配置,改在less-plugin-scope-modifyvar插件内配置。

const LessPluginScopeModifyVar = require('less-plugin-scope-modifyvar')

lessOption: {
  javascriptEnabled: true,
  sourceMap: false,
  plugins: [
    LessPluginScopeModifyVar([
      {
        // pnpm安装后的AntD4对应的全局变量文件路径,AntD的变量都在default|variable这俩文件里
        // 嫌麻烦直接 pathReg: /antd@4.*/ 也行,就是处理耗时会增加
        pathReg: /antd@4.*/antd/es/style/themes/(default|variable)/,
        modifyVars: {
          'ant-prefix': 'ant4'
        }
      },
    ]),
  ]
}
复制代码

至此,项目就完成了AntD3与AntD4组件混用的升级。

总结一下

整体思路

  1. 用npm别名实现一个项目引两个AntD版本
  2. pnpm实现二级依赖隔离
  3. babel-plugin-import(Webpack) 或vite-plugin-style-import(Vite)实现组件样式按需引入
  4. 用AntDConfigProvider组件的prefixCls属性实现HTML tag 类名隔离
  5. less-plugin-scope-modifyvar less插件实现CSS 类名隔离

猜你喜欢

转载自juejin.im/post/7088140437004746782