求助:基于Chrome Manifest V3扩展实现类似DevTools本地覆盖的功能(替换Next.js JS Chunk中的字符串)
求助:基于Chrome Manifest V3扩展实现类似DevTools本地覆盖的功能(替换Next.js JS Chunk中的字符串)
兄弟,我太懂你这种被CSP和竞态条件卡得头大的感觉了!你想做的是用Chrome Manifest V3扩展实现类似DevTools本地覆盖的功能,替换Next.js打包后的JS Chunk里的特定字符串对吧?我之前也折腾过类似的需求,给你分享几个靠谱的解决方案和踩坑心得!
一、先搞定Manifest V3的配置(解决CSP和权限问题)
你的代码没起作用,第一个可能的原因是Manifest配置没到位——Manifest V3的内容脚本默认在隔离世界运行,根本访问不到页面的__webpack_require__这类全局变量,而且权限不够也会触发CSP拦截。
先把你的manifest.json改成这样:
{ "manifest_version": 3, "name": "Next.js Chunk字符串替换工具", "version": "1.0", "permissions": ["scripting"], "host_permissions": ["https://你的目标网站域名/*"], "content_scripts": [ { "matches": ["https://你的目标网站域名/*"], "js": ["content.js"], "world": "MAIN", "run_at": "document_start" } ] }
重点说明:
world: 'MAIN':让内容脚本进入页面的主世界,能访问页面的全局变量(比如webpack相关对象)run_at: "document_start":在页面脚本加载前注入,避免竞态条件host_permissions:必须指定目标网站,不然没权限拦截请求或修改页面内容
二、最稳定的方案:拦截Fetch请求修改Chunk内容
我试过很多方案,最终觉得这个最靠谱——直接在Fetch层面拦截JS Chunk请求,下载后替换字符串再返回,完全绕开webpack的内部机制,也不容易触发CSP。
把你的注入脚本改成这样(保存为content.js):
(() => { const targetPatterns = ["foo"]; const replacement = "bar"; // 批量替换字符串的工具函数 const replaceTargetStrings = (str) => { return targetPatterns.reduce((processedStr, pattern) => { return processedStr.replaceAll(pattern, replacement); }, str); }; // 拦截原生fetch方法 const originalFetch = window.fetch; window.fetch = async (...args) => { const response = await originalFetch(...args); // 判断是否是Next.js的JS Chunk文件(通常路径包含_chunks且后缀为.js) if (response.url.includes("_chunks") && response.url.endsWith(".js")) { // 克隆响应体(因为响应体只能读取一次) const clonedResp = response.clone(); let chunkText = await clonedResp.text(); // 替换目标字符串 chunkText = replaceTargetStrings(chunkText); // 返回修改后的新响应 return new Response(chunkText, { headers: response.headers, status: response.status, statusText: response.statusText }); } // 非Chunk文件直接返回原响应 return response; }; console.log("[Chunk替换工具] Fetch拦截器已加载"); })();
这个方案的优势:
- 不依赖页面的webpack内部实现,适配几乎所有版本的Next.js
- 从源头修改Chunk内容,彻底避免竞态条件(Chunk执行前就被修改了)
- 几乎不会触发CSP,因为只是修改请求返回的文本内容,没有执行危险操作
三、适配Webpack钩子的改进方案(针对你的原有思路)
如果你坚持想用webpack钩子的方式,我也帮你优化了代码,解决了原代码找不到Chunk数组、时机不对的问题:
(() => { const targetPatterns = ["foo"]; const replacement = "bar"; const isTargetString = str => targetPatterns.some(pattern => str.includes(pattern)); const fixTargetString = str => targetPatterns.reduce((s, p) => s.replaceAll(p, replacement), str); // 遍历模块exports替换字符串 function patchModuleExports(obj) { if (!obj) return; if (typeof obj === "string" && isTargetString(obj)) return fixTargetString(obj); if (typeof obj === "object") { Object.keys(obj).forEach(key => { if (typeof obj[key] === "string" && isTargetString(obj[key])) { obj[key] = fixTargetString(obj[key]); } }); } } // 修补已加载的模块 function patchLoadedModules() { if (typeof __webpack_require__ !== "function" || !__webpack_require__.c) return; const moduleCache = __webpack_require__.c; let hitCount = 0; Object.keys(moduleCache).forEach(id => { if (patchModuleExports(moduleCache[id].exports)) hitCount++; }); console.log(`[patcher] 已修补 ${hitCount} 个已加载模块`); } // 扫描并包装Chunk里的模块工厂函数 function scanAndWrapChunk(chunk) { const modules = chunk[1] || {}; Object.keys(modules).forEach(id => { const factory = modules[id]; if (typeof factory !== "function") return; const factorySrc = factory.toString(); if (!isTargetString(factorySrc)) return; const originalFactory = factory; modules[id] = function (module, exports, require) { originalFactory(module, exports, require); patchModuleExports(exports); }; console.log(`[patcher] 已包装模块 ${id}`); }); } // 钩子webpack的加载机制 function hookWebpack() { if (typeof __webpack_require__ !== "function") return false; // 钩子模块加载函数,实时修补新加载的模块 const originalRequire = __webpack_require__; __webpack_require__ = function (moduleId) { const moduleExports = originalRequire(moduleId); patchModuleExports(moduleExports); return moduleExports; }; // 修补已加载的模块 patchLoadedModules(); // 钩子Next.js的webpackJsonp Chunk加载数组 if (window.webpackJsonp) { const originalPush = window.webpackJsonp.push.bind(window.webpackJsonp); window.webpackJsonp.push = function (chunk) { scanAndWrapChunk(chunk); return originalPush(chunk); }; // 处理已存在的Chunk window.webpackJsonp.forEach(scanAndWrapChunk); } console.log("[patcher] Webpack钩子已安装"); return true; } // 等待webpack初始化完成再执行钩子 (function waitForWebpack() { if (!hookWebpack()) { setTimeout(waitForWebpack, 50); } })(); })();
改进点说明:
- 直接钩子
__webpack_require__函数,不管模块是同步还是异步加载都能实时修补 - 针对Next.js专用的
webpackJsonp数组做钩子,不用盲目遍历window上的所有数组 - 增加延迟等待逻辑,确保webpack初始化后再执行钩子,避免找不到对象的问题
四、关键踩坑点提醒
- 如果目标网站的CSP禁止
unsafe-eval,那Webpack钩子方案可能会触发拦截,这时候优先用Fetch拦截方案 - 一定要确保内容脚本在
document_start时机注入,不然可能错过Chunk加载的时机 - Next.js的Chunk路径可能会变,比如新版本可能用
_next/static/chunks,可以根据实际情况调整Fetch拦截的判断条件
内容来源于stack exchange
相关产品推荐
相关产品推荐

