Web Components:原生组件化开发新趋势
上周五晚上 11 点半,我还在公司盯着 CI/CD 流水线跑完最后一轮部署。北京的冬天冷得离谱,通勤一小时回家估计都快凌晨一点了,但想到第二天能睡到自然醒(毕竟周六),心里还是有点小期待。
就在这时,前端同事小王突然在 Slack 上 @ 我:“哥,你不是搞 DevOps 吗?能不能帮我看下这个 Web Components 的构建流程怎么集成进咱们的 pipeline?”
我愣了一下——Web Components?这玩意儿不是前端圈近几年炒得挺热的那个“原生组件化”方案吗?说实话,之前我一直以为它就是个玩具,直到那天晚上,我才意识到,它可能真的要“翻身”了。
被面试题逼出来的学习动力
其实我最早接触 Web Components,是在准备跳槽面试的时候。
某大厂二面,面试官笑眯眯地问:“你们项目有没有考虑过用 Web Components 做微前端拆分?”
我当时一脸懵,支支吾吾说了句“我们主要用 React + Module Federation……”,结果面试官点点头,没再说什么。
回去一查,好家伙,Web Components 已经悄悄成了不少一线厂的“加分项”,甚至有些团队直接拿它替代了传统框架做轻量级 UI 模块。
再加上最近我们组接了个新需求:要把一个通用的“通知弹窗”组件嵌入到多个老系统里(有些还是 jQuery 写的!),产品经理还强调“不能影响原有逻辑、不能引入额外依赖”。
React/Vue 组件肯定不行——总不能为了一个弹窗让老系统装个 React Runtime 吧?
这时候,Web Components 的“原生、无框架依赖”特性,简直像天降神兵。
Web Components 到底是啥?别被名字吓到
简单说,Web Components 是一套浏览器原生支持的组件化标准,由三块技术组成:
- Custom Elements:让你自定义 HTML 标签,比如
<my-button> - Shadow DOM:提供样式和 DOM 的封装,避免全局污染
- HTML Templates:用
<template>定义可复用的 DOM 结构
听起来是不是有点像“自己造了个 Vue”?但关键在于——它不需要任何打包工具、不需要虚拟 DOM、不需要框架运行时,现代浏览器开箱即用。
💡 小知识:Chrome、Edge、Firefox、Safari 都已全面支持(IE 除外,但谁还在乎 IE 啊?)
动手写个组件:从 Hello World 到生产可用
我决定先写个最简单的 <notification-banner> 组件试试水。
class NotificationBanner extends HTMLElement {
constructor() {
super();
// 创建 Shadow DOM
const shadow = this.attachShadow({ mode: 'open' });
// 定义模板
const template = document.createElement('template');
template.innerHTML = `
<style>
.banner {
padding: 12px 16px;
background: #e6f7ff;
border-left: 4px solid #1890ff;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
.close-btn {
float: right;
cursor: pointer;
font-weight: bold;
}
</style>
<div class="banner">
<span class="close-btn" id="close">×</span>
<slot></slot> <!-- 插槽,支持传入内容 -->
</div>
`;
// 克隆模板并挂载
shadow.appendChild(template.content.cloneNode(true));
// 绑定关闭事件
shadow.getElementById('close').addEventListener('click', () => {
this.style.display = 'none';
});
}
}
// 注册自定义元素
customElements.define('notification-banner', NotificationBanner);
然后在 HTML 里直接用:
<notification-banner>
系统将在 5 分钟后维护,请保存工作!
</notification-banner>
效果立竿见影!而且样式完全隔离,就算老系统里有 .banner { background: red !important; } 也完全不影响。
踩坑实录:上线前差点翻车
别看上面代码简单,实际落地时我可是踩了不少雷。
坑 1:属性传递不直观
一开始我想通过 message 属性传文本:
<notification-banner message="Hello"></notification-banner>
结果发现 this.getAttribute('message') 只能在 connectedCallback 里拿到,构造函数里拿不到!
后来才知道,Custom Elements 的属性监听要用 attributeChangedCallback:
static get observedAttributes() {
return ['message'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'message') {
this.shadowRoot.querySelector('.content').textContent = newValue;
}
}
坑 2:Shadow DOM 调试困难
Chrome DevTools 虽然能展开 Shadow Root,但没法像普通 DOM 一样直接编辑样式。
后来我学会了一个技巧:在 attachShadow 时设为 { mode: 'open' },然后在控制台手动执行:
document.querySelector('notification-banner').shadowRoot.querySelector('.banner').style.background = 'pink'
虽然麻烦点,但至少能调。
坑 3:旧浏览器兼容性
虽然我们内部系统基本都是 Chrome,但客户环境千奇百怪。
最后用了 @webcomponents/webcomponentsjs 这个 polyfill,体积不大(gzip 后 ~15KB),完美兜底。
工具链:如何融入现有 DevOps 流程?
作为 DevOps 工程师,我最关心的是:怎么把它塞进我们的 CI/CD 和监控体系?
构建 & 发布
我们用 GitHub Actions 做自动化发布。我把组件打包成一个独立的 JS 文件,通过 npm 发包:
# .github/workflows/release.yml
name: Publish to npm
on:
push:
tags:
- 'v*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm run build # 输出 dist/notification-banner.js
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
这样其他项目只要 <script src="https://cdn.jsdelivr.net/npm/@our-team/notification-banner"></script> 就能用,零配置、零依赖。
性能监控
我在组件里加了埋点:
connectedCallback() {
// 上报组件加载时间
performance.mark('notification-banner-start');
// ...渲染逻辑...
performance.measure('notification-banner-load', 'notification-banner-start');
// 发送性能数据到监控平台
if (window.performanceObserver) {
window.performanceObserver.observe({ entryTypes: ['measure'] });
}
}
配合我们的 APM 系统,可以实时看到组件的 FCP(First Contentful Paint)和 TTI(Time to Interactive)。
和主流框架对比:到底值不值得用?
我拉了个表格,对比了 Web Components 和 React/Vue 组件的关键指标:
| 维度 | Web Components | React Component | Vue SFC |
|---|---|---|---|
| Bundle Size | 0 KB(原生) | ~40KB+(需 React) | ~30KB+(需 Vue) |
| 跨框架复用 | ✅ 完美支持 | ❌ 仅 React | ❌ 仅 Vue |
| 学习成本 | 中(需理解 Shadow DOM) | 低(团队熟悉) | 低 |
| 开发体验 | 较弱(无 HMR、TS 支持弱) | 强(生态完善) | 强 |
| SEO 友好 | ✅ 原生 HTML | ⚠️ 需 SSR | ⚠️ 需 SSR |
| 适合场景 | 微前端、通用 UI 库、老系统嵌入 | 主应用开发 | 主应用开发 |
结论很清晰:如果你要做一个“通用、轻量、跨技术栈”的 UI 模块,Web Components 是目前最优解。
但如果是大型 SPA,还是老老实实用 React/Vue 吧,别跟自己过不去。
真实效果:双11大促稳如老狗
去年双11,我们把“库存预警弹窗”、“优惠券领取浮层”全改成了 Web Components。
结果如何?0 故障、0 兼容问题,连测试同学都说“这次前端真省心”(感动哭了)。
更爽的是,运维那边再也不用抱怨“前端又塞了个 2MB 的 bundle 导致首屏慢了”。
因为我们的 Web Components 平均只有 8~12KB(gzip 后),而且可以按需加载。
给想尝试的同学几点建议
- 别硬刚复杂交互:Web Components 适合做“展示型”或“简单交互”组件,复杂状态管理还是交给上层框架。
- 善用 Lit 或 FAST:纯手写 Custom Elements 太痛苦,推荐用 Lit(Google 出品)或 FAST(微软出品)这类轻量库,它们基于 Web Components 但提供了响应式、TS 支持等糖。
- GitHub 上找灵感:搜
web-components,能看到很多高质量项目,比如 shoelace.style(一个超美的 WC UI 库)。 - 性能优化别忘:用
requestIdleCallback延迟非关键渲染,用IntersectionObserver实现懒加载。
最后:它真的是未来吗?
说实话,我不觉得 Web Components 会取代 React/Vue。
但它绝对会在特定场景(微前端、设计系统、跨团队协作)中扮演越来越重要的角色。
就像 Docker 没有取代虚拟机,但改变了运维的思维方式一样,Web Components 正在悄悄改变前端的“组件交付”模式——从“框架内复用”走向“跨技术栈交付”。
作为一个每天和部署、监控、性能打交道的 DevOps 工程师,我真心希望前端能多产出一些这种“小而美、无副作用、开箱即用”的模块。
这样我就不用半夜被 PagerDuty 叫醒,去查“为什么首页加载慢了 2 秒”了(谢谢产品经理的“小需求”)。
P.S. 如果你正在准备前端面试,建议真刀真枪写一个 Web Components 组件放到 GitHub 上。
面试官看到你不仅会背概念,还能落地、能优化、能集成 CI/CD,大概率会眼前一亮。
毕竟,在这个“人人都会说微前端”的时代,能做出东西的人,永远稀缺。
作者:一个喜欢深夜写代码的 DevOps 工程师,坐标北京,通勤 1 小时,梦想是早日实现“部署自由”。
GitHub: @devops-night-coder(示例项目已开源)

评论 0