news 2026/4/16 14:26:16

我接手了一个 10 年前的 jQuery 老项目,用 Web Components 给它续了命

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
我接手了一个 10 年前的 jQuery 老项目,用 Web Components 给它续了命

前几个月,主管把我拉进一个小黑屋,语重心长地说:那个 2015 年上线的 CRM 系统,客户想加个AI 智能客服的功能,虽然是个老项目,但这是公司的大金矿,你来负责呗!😃。

我打开代码仓库的那一刻,两眼一黑。

  • jQuery 1.8.3,古董级的版本。
  • 没有 Webpack,没有 Vite,只有无尽的<script>标签。
  • 全局变量漫天飞,一个common.js有 8000 行,里面充斥着varfunction
  • CSS 样式全剧透,随便改个div的 padding,隔壁页面的布局就崩了。

这时候,我的脑子里有两个小人在打架:

  • 全删了!用 Vue3 + Vite 重构!这代码是人写的吗?😖
  • 别冲动!这项目逻辑极其复杂,重构就是火葬场,上线出了 Bug 你负责?

现实是残酷的:业务只给了 3 天时间,重构是不可能的😒。

但在满屏$('#id').show()的代码里写现代化的 AI 对话界面?光是解决 CSS 样式冲突就能让我加班到猝死。

最后,我没有选择 React,也没用 iframe(通信太麻烦),而是掏出了浏览器原生的大杀器——Web Components

结果?我只用了一个 JS 文件,就完美地把现代 UI种进了这坨老代码里,而且没引发任何副作用。

今天就来聊聊,这套屎山续命指南

为什么我选择了 Web Components?

在老项目里加新功能,最大的痛点是什么?

不是 JS 逻辑混乱,是 CSS 污染。

老项目里通常会有这种霸道的全局样式:

/* style.css */ div { box-sizing: content-box; } input { border: 1px solid red; } /* 不知道哪位前辈留下的坑 */ .btn { float: left; }

如果你直接引入一个 Vue/React 组件,这些全局样式会瞬间摧毁你的 UI。

Web Components 的 Shadow DOM(影子 DOM),就是为此而生的。

它创造了一个完全隔离的 DOM 作用域

  • 外部的 CSS 进不来(你的组件不会乱)。
  • 内部的 CSS 出不去(你不会搞崩老页面)。

在 jQuery 页面里开始植入AI 助手

假设我们不需要任何构建工具(Webpack/Vite),直接在老页面的 HTML 里写。

定义组件

不用引入任何库,直接用原生 ES6 Class。

// ai-assistant.js class AiAssistant extends HTMLElement { constructor() { super(); // 1. 开启 Shadow DOM,这是隔离的关键 this.shadow = this.attachShadow({ mode: 'open' }); } connectedCallback() { // 2. 渲染 UI 和 独立的 CSS this.render(); // 3. 绑定事件 this.shadow.querySelector('#send-btn').addEventListener('click', () => { this.handleSend(); }); } render() { this.shadow.innerHTML = ` <style> /* 这里的样式绝对安全,不会影响外部,也不受外部影响 */ :host { position: fixed; bottom: 20px; right: 20px; z-index: 9999; } .container { background: #fff; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); width: 300px; font-family: 'Segoe UI', sans-serif; /* 我们可以用现代字体 */ } .header { background: #007bff; color: white; padding: 10px; border-radius: 12px 12px 0 0; } .content { height: 200px; padding: 10px; overflow-y: auto; color: #333; } input { width: 70%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; } button { background: #007bff; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; } </style> <div class="container"> <div class="header">🤖 AI 助手</div> <div class="content" id="chat-box"> <div>你好,我是你的智能助手,有什么可以帮您?</div> </div> <div style="padding: 10px; border-top: 1px solid #eee;"> <input type="text" id="input-msg" placeholder="输入问题..." /> <button id="send-btn">发送</button> </div> </div> `; } handleSend() { const input = this.shadow.querySelector('#input-msg'); const text = input.value; if (!text) return; // 模拟添加消息 const chatBox = this.shadow.querySelector('#chat-box'); chatBox.innerHTML += `<div style="text-align:right; margin:5px 0; color: #007bff;">${text}</div>`; input.value = ''; // 关键:如何跟外部 jQuery 通信?抛出原生 CustomEvent this.dispatchEvent(new CustomEvent('new-question', { detail: { question: text }, bubbles: true, composed: true // 允许穿透 Shadow DOM 冒泡出去 })); } } // 注册组件 customElements.define('ai-assistant', AiAssistant);

在 jQuery 老页面中使用

只需要引入 JS,然后像写<div>一样写标签。

<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="legacy-style.css"> <script src="jquery-1.8.3.min.js"></script> </head> <body> <div class="old-header">...</div> <ai-assistant></ai-assistant> <script src="ai-assistant.js"></script> <script> // jQuery 监听组件抛出的事件 $(document).ready(function() { // 这里的事件监听依然丝滑 $('ai-assistant').on('new-question', function(e) { // 注意:jQuery 的 event 对象封装了一层,原生的 detail 在 originalEvent 里 var question = e.originalEvent.detail.question; console.log("老页面收到了 AI 提问:", question); // 这里可以调用老系统的 API // $.ajax(...) }); }); </script> </body> </html>

为什么说这是屎山的终极解法?

侵入性为零

我在ai-assistant.js里写了input { width: 70% }

如果是普通的 Vue/React 组件,这行样式可能会把老页面所有的 input 都搞乱。

但因为有Shadow DOM,它只对自己生效。老页面的style.css就算写了div { display: none },也影响不到我 Shadow Root 里的布局。

技术栈框架无关

Web Components 是浏览器原生标准。

这意味着,无论这坨屎山是基于 jQuery、AngularJS 1.x、PHP 模板还是 JSP,只要它是浏览器,它就能跑这个组件

未来如果你们终于决定重构,换成了 React 19,这个<ai-assistant>标签依然可以直接复制过去用,不用改一行代码。

渐进式迁移

我们可以不用搞重构。

今天把用户卡片改成 Web Component。

明天把侧边栏改成 Web Component。

这是一种n class="nolink">岛屿架构(Astro 类似)。我们在屎山💩的海洋里,建立一个个小岛。等岛屿连成一片,屎山自然就消失了。

避坑指南(干货)

虽然很香,但也有几个坑要注意:

  1. React 兼容性: 如果你的老项目里混用了 React 16/17,它们对 Web Components 的事件绑定支持不太好(React 19 已完美修复)。
  2. 字体图标: Shadow DOM 里的字体图标(如 FontAwesome)如果定义在外部 CSS 里,内部读不到。需要在 Shadow DOM 的<style>@import进来,或者用<link>引入。
  3. 构建工具: 如果组件逻辑复杂,还是建议用 Lit 或 Stencil 这种轻量库来写,然后打包成一个bundle.js丢给老项目引用,开发体验会好很多(支持 TS、响应式数据)。

面对老旧项目,我们往往有两种极端的态度:要么摆烂(接着堆屎),要么激进点(推倒重来)。

但作为成熟的工程师,我们更需要中间平滑路线。Web Components 恰恰给了我们非常好的解决方案。

下次再遇到老板让你给 10 年前的 JSP 页面加功能,别急着提离职。

试着建一个<my-feature>,你会发现,屎山其实也没那么可怕😁。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 11:10:06

孤能子视角:“行为主义心理学“

第一步&#xff1a;分析“作者”——行为主义运动&#xff08;作为对心理学的“外科手术式”革命孤能子&#xff09; 启动&#xff1a;三力逼问&#xff0c;定位张力 1. 零预设&#xff1a;不预设行为主义是“科学”或“机械论”&#xff0c;视其为二十世纪初为拯救心理学于“…

作者头像 李华
网站建设 2026/4/16 12:42:14

Gaussian如何计算分析HOMO-LUMO

一、 什么是 HOMO-LUMO&#xff1f;它们能干什么&#xff1f;在深入计算之前&#xff0c;我们需要明确&#xff1a;算出这两个能级数据究竟能解决什么科研问题&#xff1f;1、预测化学反应活性HOMO&#xff08;最高占据轨道&#xff09;&#xff1a;代表分子的“给电子”能力。…

作者头像 李华
网站建设 2026/4/16 12:42:11

设计行业资讯精准推送工具,输入关注行业关键词,自动筛选优质资讯,过滤冗余信息,按每日/每周推送,帮职场人及时掌握行业动态。

1. 实际应用场景与痛点 场景 - 职场人&#xff08;产品经理、运营、市场、研发&#xff09;需要每天了解行业动态&#xff0c;但信息来源分散&#xff08;公众号、新闻网站、论坛&#xff09;。 - 信息过载&#xff0c;很多文章质量不高或与自己关注的领域无关。 - 手动筛选耗…

作者头像 李华
网站建设 2026/4/16 14:22:54

基于深度学习YOLOv11的花生种子霉变检测系统(YOLOv11+YOLO数据集+UI界面+登录注册界面+Python项目源码+模型)

一、项目介绍 花生种子的霉变问题直接影响其发芽率、食品安全及商业价值&#xff0c;传统人工检测方法效率低且易受主观因素影响。本项目基于YOLOv11深度学习框架&#xff0c;开发了一套高效、自动化的花生种子霉变检测系统&#xff0c;能够精准识别两类花生种子状态&#xff…

作者头像 李华
网站建设 2026/4/16 13:03:12

PostgreSQL 表达式详解

PostgreSQL 表达式详解 引言 PostgreSQL,作为一款功能强大的开源关系型数据库管理系统,以其灵活的扩展性和卓越的性能著称。在数据库操作中,表达式扮演着至关重要的角色。本文将深入探讨PostgreSQL中的各种表达式,包括算术表达式、字符串表达式、日期和时间表达式等,旨在…

作者头像 李华