前言
通过这篇文章你将了解到:
- 使用Vue3开发Web Components
- 使用本地git仓库进行离线组件库管理
- 在Vue项目中使用Web Components
什么是Web Components?
Web Components 是一组 Web 原生 API 的总称,允许开发人员创建可重用的自定义组件。
谷歌公司一直在推动浏览器的原生组件,即 Web Components API。相比第三方框架,原生组件简单直接,符合直觉,不用加载任何外部模块,代码量小。目前,它还在不断发展,但已经可用于生产环境。
项目背景
项目A是使用Vue2开发的已有项目,项目B和其他几个项目是使用Vue3开发的新项目,各项目中有一部分业务重叠,需要将重叠部分作为业务组件单独抽离出来。C为通用组件库项目。本文围绕A、B、C三个项目进行讲解。
关键构成如下:
- 项目A:
Vue2 + Vue-cli - 项目B:
Vue3 + Vite + Element Plus - 项目C:
Vue3 + Vite + Element Plus
项目C,开发组件库
先从组件库项目C入手,项目结构如下:
几个关键的文件:
src/components/Example/Example.ce.vue示例组件src/components/Example/Example.scss示例组件样式src/components/index.ts组件库入口 先上代码
Example.ce.vue
<script setup lang="ts">
import { reactive, ref } from 'vue';
import { ElButton, ElTable, ElTableColumn } from 'element-plus';
withDefaults(defineProps<{ title: string }>(), { title: 'title' });
const emits = defineEmits<{
(eventName: 'testEvent', val: number): void;
}>();
const num = ref(0);
const tableData = reactive([
{
date: '2016-05-03',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-02',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-04',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-01',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
}
]);
const onClick = () => {
num.value++;
tableData.push({
date: '2016-05-0' + num.value,
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
});
emits('testEvent', num.value);
};
</script>
<template>
<div class="vx-example">
<div>{{ title }}</div>
<ElButton @click="onClick">{{ num }}</ElButton>
<ElTable :data="tableData" border style="width: 100%">
<ElTableColumn prop="date" label="Date" width="180" />
<ElTableColumn prop="name" label="Name" width="180" />
<ElTableColumn prop="address" label="Address" />
</ElTable>
</div>
</template>
<style lang="scss">
@use 'element-plus/theme-chalk/src/button.scss';
@use 'element-plus/theme-chalk/src/table.scss';
@use './Example.scss';
</style>
Example.scss
.vx-example {
width: 800px;
background: lightblue;
padding: 10px;
}
index.ts
import { defineCustomElement } from 'vue';
import Example from './Example/Example.ce.vue';
// 转换为自定义元素构造器
const ExampleElement = defineCustomElement(Example);
// 注册
function registerAll() {
customElements.define('vx-example', ExampleElement);
}
export { Example, registerAll };
作为Vue3组件引入
首先让我们在项目C中新建一个测试页面,先引入组件,并且添加测试响应事件。
import Example from '@/components/Example/Example.ce.vue';
const onVueEvent = (val: number) => {
console.log('vue', val);
};
在template中添加标签
<Example title="Vue Component" @test-event="onVueEvent" />
在style中添加如下引入
@use '@/components/Example/Example.scss';
不出意外测试页面会显示如下组件,点击按钮数字会增加,并且控制台会打印对应的数字。
这里相信大家会有几个疑问:
- 为什么组件要以.ce.vue结尾?
- 为什么样式不写在组件的style部分,而是单独引入?
- 为什么组件要引入element-plus的组件再使用?
- 为什么组件要引入element-plus的组件样式? 这里引用官网教程里的一段话
官方 SFC 工具支持以“自定义元素模式”(需要
@vitejs/plugin-vue@^1.4.0或vue-loader@^16.5.0)导入 SFC。以自定义元素模式加载的 SFC 将其<style>标签作为 CSS 字符串内联,并在组件的styles选项中暴露出来,然后会被defineCustomElement获取并在实例化时注入隐式根。要选用此模式,只需使用.ce.vue作为文件拓展名即可
因为使用.ce.vue结尾,做为vue组件引入时不会加载组件内的样式,所以使用了一个单独的样式文件引入来解决这个问题。
问题3是因为只有明确引入的内容才会打包进Web Components组件,如果不引入的话,element的组件标签会被当作原生标签加载,那么页面就会错误。
问题4是因为Web Components组件的样式使用的是shadow dom内的style,所以需要将用到的样式单独引入一份。
更多的介绍可以在官网教程的Vue 与 Web Components部分进行了解。
我们继续进行,看看如何以Web Components加载组件。
作为Web Components引入
首先我们在项目入口添加如下代码:
import { registerAll } from '@/components';
registerAll();
然后在刚才的测试页面增加一些测试代码,最终完整的测试页面代码如下:
<script setup lang="ts">
import Example from '@/components/Example/Example.ce.vue';
import { onMounted } from 'vue';
onMounted(() => {
const example = document.getElementById('example');
if (example) {
example.addEventListener('testEvent', (e) => {
console.log('web', e);
});
}
});
const onVueEvent = (val: number) => {
console.log('vue', val);
};
</script>
<template>
<div>
<Example title="Vue Component" @test-event="onVueEvent" />
<vx-example id="example" style="margin-top: 20px" title="Web Component" />
</div>
</template>
<style lang="scss" scoped>
@use '@/components/Example/Example.scss';
</style>
此时页面会显示如下:
Web Components组件的事件会被调度为原生的CustomEvents,附加的事件参数 (payload) 会作为数组暴露在CustomEvent对象的details property上。
点击组件内按钮控制台会打印如下内容,这个detail就是我们自定义事件所传递的参数。
细心的朋友可能会发现虽然此时页面可以正确加载,但是加载Web Components组件时控制台会产生下面这样的警告:
只要在vite.config.ts内增加如下配置即可。
plugins: [
vue({
template: {
compilerOptions: {
// 将所有以 vx- 开头的标签作为自定义元素处理
isCustomElement: tag => tag.startsWith('vx-')
}
}
})
]
完整的vite.config.ts文件如下:
import { defineConfig, loadEnv } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';
import eslintPlugin from 'vite-plugin-eslint';
import { visualizer } from 'rollup-plugin-visualizer';
import copy from 'rollup-plugin-copy';
export default ({ mode }) =>
defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
// 将所有以 vx- 开头的标签作为自定义元素处理
isCustomElement: (tag) => tag.startsWith('vx-')
}
}
}),
visualizer({ filename: './lib/stats.html' }),
copy({
hook: 'generateBundle',
//执行拷贝
targets: [
{
src: './node_modules/element-plus/theme-chalk/base.css',
dest: './lib'
}
]
})
],
build: {
lib: {
entry: resolve(__dirname, 'src/components/index.ts'), // 设置入口文件
name: 'data-jump-components', // 起个名字,安装、引入用
fileName: () => `data-jump-components.js`, // 打包后的文件名
formats: ['es']
},
outDir: 'lib'
}
});
重点有两部分:
- copy插件,用来拷贝element的css
- build配置,打包为lib
build后的lib文件夹如下,这就是我们的Web Components组件库。
这里将一下为什么要拷贝element的base.css,是因为element-plus使用了css变量,加载在shadow dom内的样式里无法读取这些变量,需要在页面顶层声明这些变量,也就是引入组件库时需要在项目引入base.css。
到这里我们的组件库就剩最后一步了,提交代码到git仓库。
项目B,Vue3项目引入组件库
首先我们在项目B中运行如下命令:
npm i git+http://192.168.20.51/data-jump/data-jump-components.git
这个地址就是我们项目C的git地址,这里要注意需要有项目C拉取代码的权限。
然后在项目中这样按照正常的Vue组件一样使用就可以了:
<script setup lang="ts">
import Example from 'data-jump-components/src/components/Example/Example.ce.vue';
</script>
<template>
<div>
<Example title="Vue Component" />
</div>
</template>
<style lang="scss" scoped>
@use 'data-jump-components/src/components/Example/Example.scss';
</style>
项目A,Vue2项目引入组件库
运行如下命令:
npm i git+http://192.168.20.51/data-jump/data-jump-components.git
在vue.config.js增加如下配置:
// vue.config.js
module.exports = {
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.tap(options => ({
...options,
compilerOptions: {
// 将所有以 vx- 开头的标签作为自定义元素处理
isCustomElement: tag => tag.startsWith('vx-')
}
}))
}
}
在项目入口增加如下代码:
import 'data-jump-components/lib/base.css';
import { registerAll } from 'data-jump-components/lib/data-jump-components';
registerAll();
在需要使用组件的位置加入下面的标签即可:
<vx-example id="example" title="Web Component" />
到这里我们就完成了整个组件库的创建和本地安装。
扩展,在任何地方使用组件库
只需要新建一个html文件,与打包好的lib文件夹放在一起,输入以下代码就可以使用我们的组件了。
<!DOCTYPE html>
<html>
<head>
<link type="text/css" href="./lib/base.css" rel="stylesheet" />
<script type="module">
import { registerAll } from './lib/data-jump-components.js';
registerAll();
</script>
</head>
<body>
<div id="app">
<vx-example id="example" title="Web Component" />
</div>
</body>
</html>
回顾
容易出现问题的几个关键点:
- 组件需要以
.ce.vue结尾 - 组件内的使用的组件和样式需要明确引入
- 使用时需要全局引入用到的css变量
- 使用时需要在构建工具的配置文件内配置自定义元素规则,来跳过组件的解析