You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:基于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初始化后再执行钩子,避免找不到对象的问题

四、关键踩坑点提醒

  1. 如果目标网站的CSP禁止unsafe-eval,那Webpack钩子方案可能会触发拦截,这时候优先用Fetch拦截方案
  2. 一定要确保内容脚本在document_start时机注入,不然可能错过Chunk加载的时机
  3. Next.js的Chunk路径可能会变,比如新版本可能用_next/static/chunks,可以根据实际情况调整Fetch拦截的判断条件

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 10:13:01