用 webpack 开发 Chrome 扩展时,我们会遇到一些热更新(HMR)的问题。碍于 Chrome 的安全策略限制,background 和 content script 无法使用常规的 HMR 方案。如果直接启用 HMR,会给不适用的 chunk 也加入 HMR 的 modules,造成不必要的报错信息。因此,我们需要有选择性地给 chunk 启用 HMR。另外,我们还需要给 background 和 content script 启用一个 HMR 的替代方案。
webpack-dev-server
官网上有说明,启动 webpack-dev-server
时,禁用默认的行为:
在配置 chunk 时,则需要手动加上 HMR modules。同时,由于 Chrome 扩展的页面都是直接通过 Chrome 访问(均为 chrome://
开头),并不通过 webpack-dev-server
访问,我们还需要手动指定 hostname
和 port
:
react-refresh
用 @pmmmwh/react-refresh-webpack-plugin
这个包就可以在 webpack 中使用 react-refresh
了。
根据官方文档,需要先启用 webpack-dev-server
的 hot
选项。由于前面我们已经有选择性地对 chunk 启用了 HMR,我们可以跳过这个步骤。
但是如何有选择性地对 chunk 添加 react-refresh
modules 呢?可以知道的是,是 ReactRefreshPlugin
自动注入了相关 modules,因此我们查看 @pmmmwh/react-refresh-webpack-plugin
的 package.json
,得到入口文件:
打开 lib/index.js
,发现如下代码:
可以知道是由 getAdditionalEntries()
函数返回了需要注入的 modules。可以得知 getAdditionalEntries()
函数定义位于 lib/utils/getAdditionalEntries.js
,查看后发现如下代码:
可以知道注入了两个 modules:一个是主功能,路径为 client/ReactRefreshEntry.js
;另一个是错误显示,路径为 options.overlay.entry
,并带有 query string。这里的 options
是调用 getAdditionalEntries()
时传入的,回到 lib/index.js
发现:
normalizeOptions()
函数位于 lib/utils/normalizeOptions.js
,查看代码发现:
可以得知默认情况下,错误显示 module 的路径为 client/ErrorOverlayEntry
。不过,它还需要 query string,这是在 getAdditionalEntries()
函数中定义的,查看发现如下代码:
可以知道默认是从 webpack-dev-server
配置中读取选项,由于我们没有配置,则需要在 query string 中手动指定。查看 sockets/utils/getSocketUrlParts.js
可以知道,sockPath
和 sockProtocol
两个选项采用默认值即可。我们只需要补全 sockHost
和 sockPort
。
因此,首先我们注释掉 lib/index.js
中注入 modules 的逻辑:
按照官方文档,加入 plugin 和 loader:
然后给适用的 chunk 手工加入 modules(别忘了 webpack-dev-server
的 modules):
这样就可以了!
background 和 content script 的替代方案
由于是 content script 无法连接 devServer,采用 devServer -> background -> content script
的方案。
给 webpack-dev-server
增加一个 middleware:
给 background 注入(可能需要提供 query string):
给 content script 注入:
这样就可以实现自动重载扩展和刷新页面了(不过可能需要打开 background 的开发者工具来保持脚本的活跃)。