用 Web Components 做组件化,我做了一个可复用的 UI 库
引言:为什么是 Web Components?

作为一名在互联网公司干了四年多的前端开发,我经历过 jQuery 时代、Vue2 到 Vue3 的演变,也尝试过 React 和 Angular。在不同项目之间跳来跳去时,我总有一个痛点挥之不去——组件的复用性差,跨技术栈迁移成本高。
去年我们部门接到一个任务:为多个业务线开发一套统一风格的 UI 组件库。这个组件库需要同时支持旧项目的 jQuery 页面,以及新上的 Vue3 + Vite 工程,并且未来可能还会接入 React。
起初我们打算基于 Vue 来封装一套公共组件,但这很快就被打脸了。因为老系统压根没有引入 Vue,连打包工具都没有,更别说通过 npm 安装依赖了。那怎么办?有没有一种“写一次,随处运行”的技术方案呢?
这时候我想起了之前听说过但没真正上手的 Web Components。它号称可以脱离框架存在,原生支持浏览器,是不是正好可以解决我们的需求?于是抱着试试看的心态,开启了这次实战之旅。
问题描述:组件库开发中的三大难题

难题一:兼容性差、迁移难
我们要对接的页面从静态 HTML 文件(纯jQuery),到 Vue 项目,再到 React 甚至小程序都有。传统组件库必须绑定某一种框架,一旦换框架就得重写一遍。
难题二:样式污染严重
由于历史原因,很多老页面使用的是全局 CSS 变量和类名命名规则混乱。我们在封装组件的时候发现,只要类名稍有冲突,界面就面目全非,尤其是按钮样式和模态框位置错乱等问题频发。
难题三:性能敏感
部分用户场景下加载速度要求极高,像支付页这种对延迟非常敏感的地方,不允许引入太多额外 JS 或进行复杂的初始化过程。
这些现实问题让我们不得不思考:有没有一种既轻量又能独立运行的解决方案?
解决方案:使用 Web Components 构建跨技术栈组件库

初识 Web Components
Web Components 是一组浏览器原生特性,包括:
customElements:自定义 HTML 元素shadow DOM:创建隔离的 DOM 和 CSS 作用域HTML templates:定义元素结构模板
它的优势在于不依赖任何框架,直接在浏览器中工作。这意味着我们可以把组件封装成类似 <my-button> 这样的标签,在任意页面上直接调用而无需关心底层实现。
技术选型:Lit + Vite + TypeScript
为了提高开发效率,我们最终选择使用 Lit,这是一个由谷歌开发的小而强大的 Web Components 框架。它基于标准 API,提供了模板语法、响应式更新机制等能力,但又不像 Polymer 那么重。
开发工具链方面,我们使用了 Vite + TypeScript + TailwindCSS:
- Vite 提供极速构建体验
- TypeScript 确保类型安全
- TailwindCSS 提供统一设计语言
举个例子:封装一个 <ui-modal>
import { LitElement, html, css } from 'lit'
import { customElement, property } from 'lit/decorators.js'
@customElement('ui-modal')
export class UiModal extends LitElement {
@property({ type: boolean }) open = false
static styles = css`
:host {
display: block;
}
.modal {
position: fixed;
top: 0; left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: none;
}
.modal.active {
display: flex;
}
.content {
margin: auto;
background: white;
padding: 16px;
border-radius: 8px;
}
`
render() {
return html`
<div class="modal ${this.open ? 'active' : ''}">
<div class="content">
<slot></slot>
<button @click=${() => this.closeModal()}>关闭</button>
</div>
</div>
`
}
closeModal() {
this.open = false
this.dispatchEvent(new CustomEvent('close', { bubbles: true }))
}
}
上面是一个最简版的模态框组件,通过 Shadow DOM 实现了样式隔离,通过属性控制状态,还能对外派发事件。
如何集成到不同项目中?
这正是 Web Components 的妙处所在:
在 Vue 中使用:
<template>
<div>
<button @click="showModal">打开弹窗</button>
<ui-modal :open.sync="isOpen" @close="isOpen = false">
这是弹窗内容
</ui-modal>
</div>
</template>
<script>
import { defineCustomElement } from 'vue'
import UiModal from '../components/UiModal.ce.vue'
defineCustomElement(UiModal)
export default {
data () {
return {
isOpen: false
}
},
methods: {
showModal () {
this.isOpen = true
}
}
}
</script>
在 React 中使用:
import React, { useState } from 'react'
// 在 React 项目中注册组件
const UiModal = require('../components/UiModal.ce').default
customElements.define('ui-modal', UiModal)
function App() {
const [open, setOpen] = useState(false)
return (
<div>
<button onClick={() => setOpen(true)}>打开弹窗</button>
<ui-modal
open={open}
onClose={() => setOpen(false)}
>
这是 React 中的弹窗内容
</ui-modal>
</div>
)
}
在 jQuery 静态页面中使用:
<!-- HTML 页面 -->
<script src="/dist/bundle.js"></script> <!-- 注册 Web Components -->
<button onclick="document.querySelector('#myModal').open = true">打开弹窗</button>
<ui-modal id="myModal">
这是一个静态页面中的弹窗
</ui-modal>
只需要一段 JS 注册组件即可,完全不需要打包工具或模块加载器。
效果总结:性能优化+维护成本降低+开发者体验提升

在实际落地后,我们获得了以下几方面的显著提升:
✅ 性能优化明显
- Web Components 自身体积小,平均每个组件不到 3KB
- 不依赖框架启动逻辑,首次渲染更快
- 使用 Shadow DOM 后避免样式冲突,减少了重复样式注入
✅ 维护成本降低
- 所有组件统一开发、测试、发布流程,减少冗余代码
- 修改某个基础组件只需更新一次,各项目同步受益
- 文档体系统一建设,团队成员更容易上手
✅ 开发者体验改善
- 支持热更新(借助 Vite)
- 支持 Storybook 编写组件文档和示例
- 支持 TypeScript 类型提示
- 可以结合 Tailwind 快速开发视觉组件
经验分享:实战建议与避坑指南
如果你也有类似的跨技术栈组件复用需求,不妨考虑采用 Web Components,下面是我踩过的坑,供你参考:
🔍 需要关注浏览器兼容性
虽然主流浏览器都支持 Web Components v1 标准,但像 IE11 就完全不支持。如果你的项目需要兼容老旧浏览器,需要加上 polyfill(如 webcomponentsjs)。
不过在我们这边,IE 已经被淘汰,所以没有这个问题。
📦 注意打包方式的选择
推荐使用 ESM + Rollup/Vite 打包,保持模块化输出。如果需要兼容不支持动态导入的环境,可以使用打包配置生成 UMD 版本。
另外注意不要把所有组件打包成一个大 bundle,最好是按需引入或分块加载。
⚠️ Shadow DOM 并非万能
虽然 Shadow DOM 很适合封装内部结构和样式,但也有一些限制需要注意:
- 外部无法直接访问 shadow root 内部的节点
- 伪元素(如 ::placeholder)在 Shadow DOM 内部行为会有所不同
- SEO 友好度略低于普通 DOM
如果是用于交互组件,这些都不是大问题;但如果用于 SEO 相关内容,还是建议使用普通 DOM。
💡 调试技巧:善用 DevTools
Chrome DevTools 对 Web Components 支持很好:
- Elements 面板可以看到 shadow root 结构
- Styles 面板可以看到 scoped 的样式信息
- Network 查看组件 JS 加载情况
此外还可以使用 window.customElements.get('your-element-name') 来检查是否成功注册组件。
未来的方向
随着现代浏览器对 Web Standards 的进一步普及,以及像 Lit、StencilJS 等生态的发展,我相信 Web Components 会在未来几年成为越来越多企业的选择。
目前我们还在探索以下几个方向:
- 通过 CDN 动态加载组件脚本,做到真正的按需加载
- 结合微前端架构实现组件级别的远程加载和通信
- 继续完善 Design System,让 UI 设计与代码真正对齐
结语:做工程师而不是搬砖工
其实最开始我也怀疑 Web Components 是不是噱头,但在实际做完几个核心组件之后,我真香了。
与其不断学习新框架、迁徙代码、重构项目,不如回到浏览器原生能力本身,写出稳定、可复用、易维护的组件。毕竟,浏览器才是我们真正的操作系统。
如果你现在正在为组件跨项目复用头疼,或者想搭建一个长期维护的设计系统,强烈建议你尝试一下 Web Components。它或许不是银弹,但在合适场景下,它确实是一种优雅而持久的解法。
最后想说一句话送给每一位前端开发者:框架是会变的,但标准不会变。
祝你写码愉快,bug 少少。
参考资料

评论 0