Web Components:原生组件化开发新趋势

Git冲突患者
2025-12-18 18:17
阅读 1224

上周五晚上 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">&times;</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 后),而且可以按需加载。


给想尝试的同学几点建议

  1. 别硬刚复杂交互:Web Components 适合做“展示型”或“简单交互”组件,复杂状态管理还是交给上层框架。
  2. 善用 Lit 或 FAST:纯手写 Custom Elements 太痛苦,推荐用 Lit(Google 出品)或 FAST(微软出品)这类轻量库,它们基于 Web Components 但提供了响应式、TS 支持等糖。
  3. GitHub 上找灵感:搜 web-components,能看到很多高质量项目,比如 shoelace.style(一个超美的 WC UI 库)。
  4. 性能优化别忘:用 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

最热最新
暂无评论
Git冲突患者Lv.1
0
影响力
0
文章
0
粉丝