项目需求多种多样,会出现开源组件库中的组件无法满足需求或者有 bug 的情况,可以区分以下情况,进行相应的处理。
以下示例以在 Vue2 中使用 Ant Design Vue 进行讲解。
样式修改
局部修改
这里我们以组件 Button 按钮为例,该组件文档上只枚举了固定的几种按钮类型,并确定了相关的颜色。自定义修改按钮的背景色及相关色彩是个常见的需求。
现进行自定义样式处理。
<template>
<div>
<a-button class="custom-btn">Default</a-button>
</div>
</template>
<style lang="less" scoped>
.custom-btn {
background: blue;
color: white;
}
</style>
将代码运行,可以看到按钮色彩发生了改变。这里我们并没有使用深度选择器。
我们在浏览器中查看具体的组件dom解析
<div id="app">
<div data-v-93538cd2="">
<button data-v-93538cd2="" type="button" class="custom-btn ant-btn">
<span>Default</span>
</button>
</div>
</div>
可以看到,我们在组件上添加的类选择器名称 custom-btn 直接渲染在原生 button 上。
那我们可以简单理解,当我们自定义的类,直接渲染在我们需要自定义样式的 dom 元素上,则不需要进行特殊的处理,直接可以自定义样式。
我们找一个稍微复杂一点的 Tabs 标签页 组件,进一步说明。
<template>
<div>
<a-tabs class="custom-tabs">
<a-tab-pane key="1" tab="Tab 1">
Content of Tab Pane 1
</a-tab-pane>
<a-tab-pane key="2" tab="Tab 2" force-render>
Content of Tab Pane 2
</a-tab-pane>
</a-tabs>
</div>
</template>
先进行无任何处理的样式修改:
<style lang="less" scoped>
.custom-tabs {
.ant-tabs-tab {
border: 1px solid blue;
}
}
样式没变化。。。我们看下该段样式编译后的结束:
<style type="text/css">
.custom-tabs .ant-tabs-tab[data-v-93538cd2] {
border: 1px solid blue;
}
</style>
看到 .ant-tabs-tab[data-v-93538cd2] 大概知道啥原因了。在我们自己的代码中,每一个dom节点,都会加上 scope 属性如 data-v-93538cd2,规避单页项目不同业务组件之间的样式相互影响。而现在使用的是组件库的组件,组件渲染后的dom上并没有 scope 属性。所以这里的样式是由于匹配不到,所以没生效。
这里的类选择器名称 ant-tabs-tab 是在浏览器查找需要自定义样式的 dom 节点上找到,那我们试试深度选择器 ::v-deep
。
<style lang="less" scoped>
.custom-tabs {
::v-deep .ant-tabs-tab {
border: 1px solid blue;
}
}
到这样式发生了变化。这里相对上面的例子,明显的变化是自定义类不在我们直接想自定义样式的 dom 节点上,我们需要自己在浏览器中查找想自定义样式的 dom 节点上已有的类,而这个 dom 类是在子组件中,不与父组件在同一dom层级。
在我们实际开发使用中,组件库组件未必挂载在当前组件内容中,比如挂载在页面body下。这里我们以 Modal 对话框组件为例。
<template>
<div>
<a-modal class="custom-modal" :visible="true" title="Basic Modal">
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</a-modal>
</div>
</template>
<style lang="less" scoped>
.custom-modal {
.ant-modal-title {
font-weight: bold;
color: red!important;
}
}
</style>
嗯,没生效。。。,那分析下,编译后的css代码在浏览中咋样。
<style type="text/css">
.custom-modal .ant-modal-title[data-v-93538cd2] {
font-weight: bold;
color: red!important;
}
</style>
看到 .ant-modal-title[data-v-93538cd2] 同样大概知道啥原因了。原因与上文一样,所以这里的样式是由于匹配不到,所以没生效。
那修改下样式,去除 scope 属性。
<style lang="less">
.custom-modal {
.ant-modal-title {
font-weight: bold;
color: red!important;
}
}
</style>
嗯,生效了。不过这里 modal 组件加上 custom-modal。那后续其他业务组件,也需要对 modal 组件进行样式自定义,名称极有可能也叫 custom-modal。
而由于上面我们并没有添加 scope 属性,这个样式是会造成全局影响。所以,更合理的做法,是对当前业务组件添加个容器类名。如这里是登录组件。那加个 login-wrap 类。
<style lang="less">
.login-wrap {
.custom-modal {
.ant-modal-title {
font-weight: bold;
color: red!important;
}
}
}
</style>
至此,对组件库组件的样式处理,目前这三类基本涉及到绝大部分场景。
下面对 vue 中使用深度选择器进一步讲解。
当项目中使用的 css 原生样式,则可以使用 >>>
深度选择器来修改第三方组件的样式。
当项目中使用的 css 预处理器,如 less 或 scss, 则可以使用 /deep/
或者 ::v-deep
深度选择器来修改外用第三方组件的样式,其中深度选择器 ::v-deep
比较通用。
在vue3 中可以使用 :deep(<inner-selector>)
来深度选择到你要修改的样式,注意:这里没有空格。>>>
,/deep/
,::v-deep
在 vue3 中均以被弃用,虽然样式仍然生效,但是 Vue 已不推荐。
具体弃用的原因可以翻看这篇文章 scoped-styles-changes
全局修改
如果你要修改全局的样式,你可以在全局样式文件中写样式覆盖,引入到 main.js 中即可全局生效。
import "./src/styles/global.less";
内容修改
这里我们以 upload 组件为例。
拷贝源码进行修改
特定需求:在原有组件展示的信息基础上,还要显示上传文件的大小。
首先找到对应的 upload 组件:node_modules => ant-design-vue => es => upload。
对 upload 文件夹进行整体拷贝,将之粘贴到项目代码中,并对其进行修改。这样既可满足特定业务需求,又不会影响原组件在其他常规业务中的使用。
首先修改复制过来的 upload 组件对文件的路径引用,对依然还需要引用 node_modules 下的 ant-design-vue 组件库的代码文件,则批量修改路径为。
import xxx from 'ant-design-vue/es/xxx'
然后在 UploadList.js 中修改对应的代码
var preview = file.url ? [h(
'a',
...
[file.name]
), downloadOrDelete] : [h(
'span',
...
[file.name]
), downloadOrDelete];
修改后的代码
var preview = file.url ? [h(
'a',
...
[file.name, h('span', { style: { marginLeft: '20px' } }, [(file.size / 1024).toFixed(2) + 'kb'])]
), downloadOrDelete] : [h(
'span',
...
[file.name, h('span', { style: { marginLeft: '20px' } }, [(file.size / 1024).toFixed(2) + 'kb'])]
), downloadOrDelete];
效果对比:
局部使用
在需要使用的文件中引入、注册、使用
<template>
<custom-upload
name="file"
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
>
<a-button> <a-icon type="upload" /> Click to Upload </a-button>
</custom-upload>
</template>
<script>
import customUpload from '@/components/pc/upload'
export default {
components: { customUpload },
};
</script>
全局使用
在main.js中进行全局引入。
import customUpload from '@/components/upload'
Vue.use(customUpload)
然而业务代码中并未找到 customUpload 组件。
我们翻看下 upload 组件 index.js 的实现。
import Upload from './Upload';
...
Upload.install = function (Vue) {
...
Vue.component(Upload.name, Upload);
...
};
export default Upload;
可以看到,Vue.component
进行全局注册的组件名引用的是 Upload 文件的 name。
我们找到 Upload.js 文件,可以看到
...
export default {
name: 'AUpload',
...
}
这里依然用的是以前的name,所以这里需要修改为我们自定义的组件名称 CustomUpload。
到这,刷新下界面,可以看到效果已经显现。
此种方式,需要注意组件库版本的锁死,否则当组件库进行版本升级后,复制过来的代码与 node_modules 中组件库的代码可能已不再匹配。
修改源码并打包替换
上面方式是对编译后的代码进行更改。那我们也可以对组件库源码进行修改,并进行编译,将编译后的文件进行替换。
- 我们进入 ant-design-vue git 地址[https://github.com/vueComponent/ant-design-vue]
- 由于现在的项目还是基于 vue2 项目,所以这里选择组件库的 tag 为 1.7.8,由于我们不需要考虑项目的其他分支或者tag代码,所以可以直接对当前code进行zip下载。
- 下载解压后,对当前目录执行
npm i
命令,包下载完成后,找到 package.json 文件,看到可以执行npm run start
命令,进行项目启动。
到这里项目已经成功启动了,那我们也可以展开对 upload 组件代码的更改。但目前我们还无从下手,不知道从哪里进行更改。
那我们回到最开始项目执行的命令,npm run start
,那实际执行的语句是 cross-env NODE_ENV=development webpack-dev-server --config webpack.config.js
从上一步我们知道项目启动的配置文件是 webpack.config.js,我们找到这个文件,可以看到一段代码
...
entry: {
app: './examples/index.js',
},
...
我们知道了应用的入口文件是 /examples/index.js,那我们找到这个文件,可以看到
import 'babel-polyfill';
import Vue from 'vue';
import App from './App.vue';
import Antd from 'ant-design-vue';
import 'ant-design-vue/style.js';
Vue.use(Antd);
new Vue({
el: '#app',
render: h => h(App),
});
到这,已经看到页面渲染的代码在 App.vue 文件中。我们点开这个文件,可以看到里面的代码与当前项目展示一致。那我们对 App.vue 文件进行代码处理,页面内容也跟着进行变化。
此时回到最初的问题,我们是希望修改 upload 组件的源码,但该如何进行引入以及修改呢。
我们再回到上面一段代码,可以看到我们引入了 ant-design-vue 组件库,以及 ant-design-vue/style.js 样式代码。可是翻看下 package.json 的依赖,我们并没有看到 ant-design-vue,再说,我们现在修改的就是这个框架,框架都没进行发布,怎么可能会有这个依赖包呢。这岂不是鸡生蛋蛋生鸡的问题。所以,这里引入的不是 node_modules 里面的依赖。
那我们再翻看下 webpack.config.js 文件,是否是编译工具进行了处理。
...
alias: {
'ant-design-vue': path.join(__dirname, './components'),
vue$: 'vue/dist/vue.esm.js',
},
...
到这里就明白了,原来 ant-design-vue 是别名,对应的路径是现有项目 components 里面的组件代码。
找到 ./components/index.js 文件,结合 ./examples/index.js 可以看到是组件进行了全局注入。
./components/index.js
components = [...]
const install = function(Vue) {
components.map(component => {
Vue.use(component);
});
};
./examples/index.js
...
import Vue from 'vue';
import Antd from 'ant-design-vue';
import 'ant-design-vue/style.js';
Vue.use(Antd);
...
那到这里,我们明白,在 ./examples/App.vue 文件中直接使用 upload 组件,并对 ./components/upload下的组件代码进行修改,即可看到变化。
我们在 ./examples/App.vue 中添加代码
<template>
<a-upload
name="file"
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
>
<a-button> <a-icon type="upload" /> Click to Upload </a-button>
</a-upload>
</template>
接着,我们找到 ./components/upload/UploadList.jsx 文件,对其进行修改。
const preview = file.url
? [
<a
...
>
{file.name}
<span style="margin-left: 20px;">{ (file.size / 1024).toFixed(2) + 'kb' }</span>
</a>,
...
]
: [
<span
...
>
{file.name}
<span style="margin-left: 20px;">{ (file.size / 1024).toFixed(2) + 'kb' }</span>
</span>,
...
];
可以看到,相比对编译后的代码进行修改,对组件库源码进行修改会明显简单一些。
到这可以执行打包命令 npm run dist
。可以看到项目中生成了三个编译后的文件目录,分别为es,lib,dist。
我们将这三个文件进行复制,替换我们日常开发项目下的 node_modules 中的 ant-design-vue中的同名目录。
然后对项目进行重启,即可看到效果。到这里如果想将当前改动发布到私有库中,可以参考这篇文章 公共资源包发布流程详解。
但这种弊端明显:
- 这种改动会造成所有引用该组件库的项目均受影响,如果是bug修复,那是良性的影响,但是在日常开发中,遇到更多的是组件本身不满足特定业务的需求,进而需要修改原有组件的功能,这种改变,对A项目是适当的更改,但是对B项目可能就是不恰当的变动。
- clone开源组件库进行定制开发后,将无法随开源库的迭代逐步消除本身的bug。
这种方式除非严重的bug修正,如果是定制改动,明显不如上一种方式。而bug修正,更合理的方式是在原仓库下提 PR,让作者进行合并。
patch-package 打补丁
当在项目中修改了 ant-design-vue 的源码,并且需要将改动同步到团队成员,那也可以使用 patch-package
对组件库打补丁,将修改同步到项目代码上。
我们先进行安装 npm i patch-package -S
然后进行源码的修改:找到 node_modules 目录中 ant-design-vue 下 upload 组件源码
在 UploadList.js 中进行修改
var preview = file.url ? [h(
'a',
...
[file.name]
), downloadOrDelete] : [h(
'span',
...
[file.name]
), downloadOrDelete];
修改后的代码
var preview = file.url ? [h(
'a',
...
[file.name, h('span', { style: { marginLeft: '20px' } }, [(file.size / 1024).toFixed(2) + 'kb'])]
), downloadOrDelete] : [h(
'span',
...
[file.name, h('span', { style: { marginLeft: '20px' } }, [(file.size / 1024).toFixed(2) + 'kb'])]
), downloadOrDelete];
执行命令创建组件库补丁文件:npx patch-package ant-design-vue
,这里 ant-design-vue 指的是被修改的依赖包的名字,不是被修改的文件的名字。
执行该命令后会在项目根目录中自动创建一个 patches 文件夹,该文件夹中会出现一个 修改依赖包名称+version.patch 的补丁文件。本次操作这里出现的文件名称为 ant-design-vue+1.7.8.patch。
那 .patch 文件为什么能作为补丁文件,我们打开 ant-design-vue+1.7.8.patch 文件看一下里面内容。
diff --git a/node_modules/ant-design-vue/es/upload/UploadList.js b/node_modules/ant-design-vue/es/upload/UploadList.js
index e0dfa8a..573ac90 100644
--- a/node_modules/ant-design-vue/es/upload/UploadList.js
+++ b/node_modules/ant-design-vue/es/upload/UploadList.js
@@ -234,7 +234,7 @@ export default {
}
}
}]),
- [file.name]
+ [file.name, h('span', { style: { marginLeft: '20px' } }, [(file.size / 1024).toFixed(2) + 'kb'])]
), downloadOrDelete] : [h(
'span',
{
@@ -249,7 +249,7 @@ export default {
title: file.name
}
},
- [file.name]
+ [file.name, h('span', { style: { marginLeft: '20px' } }, [(file.size / 1024).toFixed(2) + 'kb'])]
), downloadOrDelete];
var style = file.url || file.thumbUrl ? undefined : {
pointerEvents: 'none',
其实就是一些 git diff 记录描述,补丁原理呼之欲出 —— patch-package 会将当前 node_modules 下的源码与原始源码进行git diff,并在项目根目录下生成一个patch补丁文件。
那测试是否生效,首先删除 node_modules 文件夹。然后修改根目录下的 package.json 文件,在 scripts 中添加 “postinstall”: “patch-package”。
"scripts": {
"postinstall":"patch-package"
},
执行 npm i
,重新安装依赖。
D:\project\personal\frontEndTestProject\modern_dev\my_project>npm i
...
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
...
> my_project@0.1.0 postinstall
> patch-package
patch-package 8.0.0
Applying patches...
ant-design-vue@1.7.8 ✔
added 1468 packages in 2m
可以看到源码中被修改的代码还在。这里是因为 postinstall
是 Node.js 应用程序中的 NPM Scripts 钩子,它表示在 npm i
执行完毕后自动执行的脚本命令。
至此补丁文件已经生成完毕,我们需要将它提交到 git 中,直接执行常规 git 操作即可。
当其他同事拉下代码后如何应用补丁呢?已经下载过依赖包的项目,直接执行 npx patch-package
,即可将补丁打上。如果是新拉下来的项目,直接执行 npm i
,则在下载依赖包结束后,自动执行打上补丁的过程。
如果你安装的包版本和你之前生成的补丁中记录时的包版本不一样,npx patch-package
会报错,通过提示你可以更方便的定位问题
my_project>npx patch-package
patch-package 8.0.0
Applying patches...
ant-design-vue@1.7.8 ✔
Warning: patch-package detected a patch file version mismatch
Don't worry! This is probably fine. The patch was still applied
successfully. Here's the deets:
Patch file created for
ant-design-vue@1.7.8
applied to
ant-design-vue@1.7.0
At path
node_modules/ant-design-vue
This warning is just to give you a heads-up. There is a small chance of
breakage even though the patch was applied successfully. Make sure the package
still behaves like you expect (you wrote tests, right?) and then run
patch-package ant-design-vue
to update the version in the patch file name and make this warning go away.
---
patch-package finished with 1 warning(s).
如果组件库有问题需要及时处理,并需要同步到其他团队成员,这是一种低成本又规范化的方式。