应使用 HTML 解析器(如 cheerio 或 parse5)而非正则替换 src 属性,以避免误改注释、脚本、data-*、srcset 等非目标内容;需校验路径前缀并单独处理 srcset 和 picture 中的响应式路径。

直接用正则替换 src 属性会炸掉整个 HTML
很多用户一上来就写 sed -i 's/src="\.\/images\//src="https:\/\/cdn.com\//g',结果发现注释里、<script></script> 里的字符串也被替了,甚至 data-src、srcset 全中招。HTML 不是纯文本,硬搜硬替等于在雷区扫盲。
常见错误现象:
-
变成,但注释不该动 -
console.log('src="./old.jpg"')被误改,JS 报错 -
被改,懒加载逻辑失效
用 cheerio 批量改多文件最省心
如果你要一次处理几十个 HTML 文件(比如静态站生成后统一换 CDN),cheerio 的 jQuery 风格 API 最直观,而且能天然跳过注释、脚本和 style 标签里的内容。
实操建议:
- 安装:
npm install cheerio - 只改真实 DOM 中的
src和href,不碰data-*或srcset(除非你明确要) - 路径判断必须加前缀检查:
if (src.startsWith('./') || src.startsWith('../')),避免把https://或/api/也替了 - 读取文件时加
{ xmlMode: false },否则含的页面可能漏匹配
示例片段(改单个字符串):
const cheerio = require('cheerio');
const html = '<p>@@##@@</p>';
const $ = cheerio.load(html, { xmlMode: false });
$('img[src]').each((i, el) => {
const src = $(el).attr('src');
if (src && (src.startsWith('./') || src.startsWith('../'))) {
$(el).attr('src', src.replace(/^\.\//, 'https://cdn.example.com/'));
}
});
console.log($.html()); // <p>@@##@@</p>
用 parse5 更可控,适合需要保留原始格式的场景
cheerio 默认会“美化”HTML(比如把 自动补成 
),而 
parse5 序列化时严格按输入格式还原,适合对空格、换行、自闭合标签有洁癖的项目。
关键差异点:
-
parse5不自动识别data-src,也不帮你过滤上下文——你得自己遍历节点、判断node.tagName和attr.name - 它不会把
改成,原始怎么写,输出就怎么写 - 不支持 CSS 选择器,得手写递归遍历,代码略长但逻辑更透明
示例核心逻辑(只改 、<script></script>、 的路径):
const { parse, serialize } = require('parse5');
function walk(node) {
if (node.tagName && node.attrs) {
for (const attr of node.attrs) {
if ((attr.name === 'src' || attr.name === 'href') &&
(attr.value.startsWith('./') || attr.value.startsWith('../'))) {
attr.value = attr.value.replace(/^\.\//, 'https://cdn.example.com/');
}
}
}
if (node.childNodes) node.childNodes.forEach(walk);
}
const doc = parse(htmlString);
walk(doc);
const newHtml = serialize(doc);
别忘了 srcset 和 picture 里的路径
响应式站点里,光改 src 不够。如果用了 或 srcset,里面每个 URL 都得单独处理,否则高 DPI 设备或小屏仍加载旧路径。
容易被忽略的地方:
-
—— 每个逗号分隔的 URL 都要检查是否为相对路径 -
——srcset在里也要扫 - 正则根本不敢碰
srcset,因为语法允许空格、括号、描述符,只有 AST 能安全解析
用 cheerio 处理 srcset 的最小改动:
$('img[srcset], source[srcset]').each((i, el) => {
const srcset = $(el).attr('srcset');
if (srcset) {
const newSrcset = srcset.replace(/(?:^|\s+)([^,\s]+)(?=\s+[0-9w]+)/g, (_, url) => {
return url.startsWith('./') ? url.replace(/^\.\//, 'https://cdn.example.com/') : url;
});
$(el).attr('srcset', newSrcset);
}
});
真正麻烦的不是改一行 src,而是确认哪些路径该动、哪些不该动,以及改完后是否还符合浏览器加载规则。尤其当 HTML 来自富文本编辑器(如 KindEditor、Quill),里面混着 data- 属性、内联样式、甚至 JS 字符串,这时候 AST 就不是“可选”,而是必须。

前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











