使用 webpack 构建 chrome 扩展的热更新问题

使用 webpack 构建 chrome 扩展的热更新问题

💡 原文中文,约10700字,阅读约需26分钟。
📝

内容提要

作者开发Chrome扩展时,采用webpack+TypeScript+React技术栈,解决了content script修改后自动重载扩展并刷新页面的问题。核心方案是监听webpack编译完成事件,通过SSE通知background,background调用chrome.runtime.reload()重载扩展,并向所有注入content script的页面发送消息以刷新页面。该项目已提取为模板awesome-chrome-extension-boilerplate。

🔎

延伸解读

为什么 content script 热更新需要特殊处理

文章指出,content script 无法使用 webpack devServer 自带的热更新机制,根本原因是 Chrome 限制了 content script 内无法访问其它 content script、inject script 以及网页本身的 JS 变量。而 webpack 的热更新依赖 JSONP 拉取补丁并调用更新函数,这种跨脚本调用被 Chrome 阻止,导致补丁无法应用。因此,开发者需要另辟蹊径,不能直接套用普通 SPA 的热更新方案。

自动重载方案的核心流程

作者设计的方案分为四步:监听 webpack 编译完成事件,通过 SSE 推送事件给 background,background 监听事件后调用 chrome.runtime.reload() 重载扩展,并向所有注入了 content script 的页面发送消息以刷新页面。其中,判断是否修改了 content script 依赖于编译统计信息中的 chunks 与 content scripts 目录的匹配。这套流程实现了修改 content script 后自动重载扩展和刷新页面,省去了手动点击刷新按钮的麻烦。

实现中的关键配置与注意事项

文章提到,为了支持自动重载,需要配置 webpack devServer 将 bundle 写到磁盘、设置 CORS 跨域,并修改热更新补丁的拉取路径。此外,必须添加一个默认的 content script(如 all.ts),其 matches 需覆盖其它所有 content scripts 的 matches,以便监听刷新消息并执行页面刷新。background 在收到 content script 的响应后再调用 chrome.runtime.reload(),确保刷新逻辑可靠执行。这些细节是方案落地的关键。

❓

Q&A

为什么 webpack 自带的热更新对 Chrome 扩展的 content script 不起作用?

因为 Chrome 限制了 content script 无法访问其他 content script、inject script 以及网页本身的 JS 变量。而 webpack 的热更新是通过 JSONP 拉取更新补丁的,注入网页的 content script 中的热更新代码会调用 JSONP 插入的更新函数,但由于 Chrome 的限制,无法调用,因此无法应用热更新补丁。

如何实现修改 content script 后自动重载 Chrome 扩展并刷新页面?

实现步骤:1. 监听 webpack 编译完成事件,判断是否修改了 content script 的 chunk;2. 通过 SSE 推送事件给 background;3. background 监听 SSE 事件;4. background 调用 chrome.runtime.reload() 重载扩展,并向所有注入了 content script 的页面发送消息,让它们刷新页面。

在 webpack 中开发 Chrome 扩展时,如何配置 devServer 以支持热更新?

需要配置 devServer 将 bundle 写到磁盘(writeToDisk: true),并设置 CORS 跨域(Access-Control-Allow-Origin: '*')。同时,为了修改热更新补丁的拉取路径,需要使用 express + webpack devServer 中间件的方式,配置 webpack-hot-middleware 的 path 为 devServer 地址。

为什么选择 express + webpack devServer 中间件而不是 webpack-dev-server 命令行工具?

因为 webpack-dev-server 命令行工具灵活度不够,无法配置 content script 的 webpack entry 不加载 hot reload 脚本,也无法修改默认拉取热更新补丁的 path。只有通过 webpack devServer 中间件才能实现这些配置。

background 如何监听 webpack 编译完成事件并触发扩展重载?

background 通过 EventSource 与 devServer 的 SSE 路由建立连接,监听 'compiled-successfully' 事件。当收到事件且 action 为 'reload-extension-and-refresh-current-page' 时,先向所有 tab 发送刷新页面的消息,收到 content script 的响应后调用 chrome.runtime.reload() 重载扩展。

如何确保所有注入了 content script 的页面都能接收到刷新消息?

需要默认添加一个 content script(如 all.ts),并将其 matches 配置为其他所有 content scripts 的 matches 的父集,确保它注入到所有相关页面。all.ts 中监听 background 发送的刷新消息,收到后调用 window.location.reload() 刷新页面。

🏷️

标签

➡️

继续阅读