用 Web Components 构建跨项目组件库的实战之路
引言:为什么要从框架转向原生?

作为一名前端开发工程师,这些年我一直在使用 React、Vue 这样的主流框架进行项目开发。但去年我们团队遇到了一个很典型的问题:多个项目的 UI 组件需要高度复用,但技术栈不统一,React 和 Vue 的组件难以共享,导致大量重复劳动。
一开始,我们尝试用微前端方案把不同技术栈整合到一起,结果却发现通信成本太高,页面加载速度慢,维护也异常复杂。后来,我们尝试了一些“通用”组件库,比如 Angular Elements 或者 Webpack Module Federation,虽然解决了一部分问题,但也带来了额外的构建和部署负担。
直到有一天在一次技术分享会上,我听到了关于 Web Components 的介绍,它那种“原生、轻量、与框架无关”的特性瞬间打动了我。回来后我们团队决定开始尝试这条路,没想到一试就彻底改变了我们的组件开发方式。
今天就来聊聊我们是如何一步步将 Web Components 应用到生产环境中的,以及过程中遇到的一些坑。
问题描述:跨项目组件复用的痛点

场景背景
公司内部同时运行着三个中大型项目:
- 项目A:使用 React + TypeScript
- 项目B:使用 Vue3 + Vite
- 项目C:是一个老的 jQuery 项目,还在用 IE11
这三个项目之间有相当一部分组件是共通的,比如按钮、表单控件、弹窗、导航栏等。我们之前的做法是每个项目都有一套自己的组件库,不仅样式差异大,而且每次功能更新都要重复修改三遍,效率低且容易出错。
更尴尬的是,当产品希望做一个统一风格的后台系统时,前端团队只能手动复制粘贴组件代码,再根据框架做适配,开发周期成倍增长。
核心挑战
- 技术栈混杂:React、Vue、jQuery 无法共享组件
- 维护成本高:相同逻辑的组件重复开发
- 风格不统一:UI 编码规范各自为政,视觉一致性差
- 依赖复杂:使用传统组件库时引入不必要的包体积
- 升级困难:老项目难以跟进新框架版本
这种状态持续了几个月之后,我们终于下定决心要寻找一种能真正“一劳永逸”的解决方案。
解决方案:用 Web Components 打造独立的组件库

初步探索阶段
我们最初的目标很简单:用一套代码写组件,所有项目都能用。
于是我们在 GitLab 上新建了一个 components-core 的仓库,准备用 Web Components 实现基础组件。当时团队里对这项技术都不算熟悉,只是知道它基于浏览器原生 API,不需要依赖特定框架,可以用 <my-button> 这样的自定义标签。
小插曲:第一次失败的尝试
第一次尝试我们选择了最原始的方式:纯 HTML/CSS/JS 写了一个按钮组件。
class MyButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<button>我是按钮</button>
<style>
:host {
display: inline-block;
}
button {
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
cursor: pointer;
}
</style>
`;
}
}
customElements.define('my-button', MyButton);
这个例子看起来简单,但实际接入项目时问题来了:
- 没有样式隔离机制,IE11 不支持 shadow DOM,样式冲突严重。
- 无法传递动态属性,比如 disabled、type 等。
- 没有事件绑定机制,按钮点击无法向外传递信息。
这说明我们需要更成熟的实践方式。
转型 Lit:让开发更高效
经过调研,我们最终选用了 Lit —— Google 推出的一个轻量级框架,专门用于构建高性能的 Web Components,支持响应式数据绑定、模板语法、良好的类型提示。
我们重写了之前的按钮组件:
import { LitElement, html, css } from 'lit';
import { property } from 'lit/decorators.js';
export class MyButton extends LitElement {
@property({ type: String }) variant: string = 'primary';
@property({ type: Boolean }) disabled = false;
static styles = css`
:host {
display: inline-block;
}
button {
padding: 8px 16px;
font-size: 14px;
border: none;
cursor: pointer;
background-color: var(--btn-bg, #007bff);
color: var(--btn-color, white);
}
`;
render() {
return html`
<button ?disabled=${this.disabled}>
${this.variant === 'primary' ? '主按钮' : '次按钮'}
</button>
`;
}
}
customElements.define('my-button', MyButton);
这样一来,组件具备了完整的可配置能力,还能通过 CSS 变量控制主题色,接入任何项目只需插入标签即可。
组件库搭建与接入流程
我们逐步完成了以下内容:
- 基础组件:按钮、输入框、标签页、对话框等
- 主题配置:通过 CSS 变量实现不同项目的视觉定制
- 构建打包:使用 Vite + rollup 构建 ESModule 输出
- 版本管理:通过 npm 私有包发布组件库
- 文档站点:搭建 Storybook 展示组件用法和示例
示例:如何在 Vue 项目中使用
只需要安装并注册:
npm install @company/components-core
<template>
<div>
<my-button variant="primary" @click="handleClick">提交</my-button>
</div>
</template>
<script setup>
import '@company/components-core/dist/my-button';
</script>
因为组件本身就是一个 DOM 元素,它完全兼容各类框架甚至 jQuery,只需确保加载顺序即可。
效果总结:性能与协作双赢

技术收益
| 项目 | 改进点 | 结果 |
|---|---|---|
| 代码维护 | 多个项目使用同一组件库 | 减少约 30% 的 UI 开发时间 |
| 包体积 | 无需引入整个框架 | 组件大小平均减少 60KB |
| 性能表现 | 原生组件无虚拟 DOM | 首屏渲染更快,特别是移动设备上 |
| 兼容性 | Polyfill 支持 IE11 | 老项目可以平滑接入 |
| 升级成本 | 独立于框架 | 前端技术演进不影响组件库 |
团队协作提升
- 所有前端成员统一使用同一个设计语言库
- 新人入职培训简化,组件使用方式一致
- 设计师反馈更好,不再出现视觉偏差
经验分享:Web Components 在实战中的注意事项
如果你也在考虑采用 Web Components,我总结了以下几个关键经验,希望能帮助你少踩坑。
✅ 使用 Lit 提升开发体验
虽然原生 Web Components 并不难,但写起来太繁琐,而且缺少响应式编程机制。推荐直接使用 Lit,它提供了模板语法、响应式属性、CSS 集成等非常实用的功能,学习曲线也很平缓。
✅ 用 Shadow DOM 做样式隔离(注意 IE11)
Shadow DOM 是 Web Components 的核心之一,它可以避免样式污染。但在支持 IE11 的项目中需要注意使用 fallback 方案,或者干脆放弃对 IE 的深度兼容(这也是我们最后做的取舍)。
✅ CSS 变量是主题定制的利器
通过暴露 CSS 变量(如 --btn-bg),可以在不同项目中轻松定制样式,而无需重新编译组件。
✅ 事件处理要标准化
Web Components 的事件本质上是原生 CustomEvent,传参要用 detail 字段:
this.dispatchEvent(new CustomEvent('change', {
detail: { value },
bubbles: true,
composed: true
}));
在外部监听事件时也要注意兼容性写法。
✅ 构建工具的选择很重要
我们早期用 Webpack,后来换成 Vite+rollup,打包速度和输出质量提升了不止一个等级。建议选择现代构建工具,优先输出 ESM。
✅ 调试技巧:别忘了 DevTools 支持
Chrome DevTools 已经完整支持 Web Components 的调试,包括 shadow DOM 查看、样式检查、事件监听等,熟练掌握这些技巧会大大提高效率。
尾声:为什么我看好 Web Components
过去一年,我们团队已经用 Web Components 替代了 70% 的跨项目 UI 组件。它的价值不仅仅在于“跨框架”,更重要的是它让我们回到了 Web 开发的本源——使用浏览器自身的能力,而不是被框架绑架。
现在,我们正在尝试在 Web Components 中集成一些 AI 助手相关的交互组件(比如语音识别按钮、自动翻译模块等),这让我更加确信:Web Components 正在成为未来 Web 应用的基础构件。
当然,它不是银弹。如果你的项目全是 React,那继续使用 React Component 也没问题;但如果你面临多技术栈、长生命周期、跨团队协作的场景,Web Components 绝对值得认真考虑。
也许正如我在一次团队分享会上说的那句话:
“真正的组件化,是脱离框架,回归原生。”
欢迎留言交流你的 Web Components 实践经验,或者你正在面临的组件库问题,我们可以一起探讨解决方案。

评论 0