Open lzwaiwai opened 5 years ago
在编写 chrome 插件开发模板 的时候,遇到了 webpack 的 热更新不完全生效 的问题。
不生效情景:
当 chrome 插件的 manifest.json 中的 background, content_scripts 被配置为 js 的形式时(如下),这些脚本必须在文件目录中存在(注意:background 的 scripts 和 content_scripts 的 js 两个属性是不能使用远程链接的)。
{ "background": { "persistent": false, "scripts": ["background.js"] }, "content_scripts": [ { "matches": ["http://*/*", "https://*/*"], "js": ["content.js"], "all_frames": true } ], }
生效情景: 当 chrome 插件的 manifest.json 中的 background, popup 被配置为 html(js 在 html 中被以 远程链接 的形式引入) 的形式时(如下),
{ "browser_action": { "default_popup": "pages/popup.html" }, "background": { "persistent": false, "page": "pages/background.html" }, }
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="http://127.0.0.1:3000/js/background.js"></script> </head> </html>
<!DOCTYPE html> <html> <head><meta charset="UTF-8"></head> <body> <div id="chrome-popup-root"></div> <script src="http://127.0.0.1:3000/js/popup"></script> </body> </html>
分析原因:
在配置 webpack 的热更新的时候,我们都会在 entry 配置如下配置: webpack-hot-middleware/client?path=http://127.0.0.1:3000/__webpack_hmr&noInfo=false&reload=true&quiet=false
webpack-hot-middleware/client?path=http://127.0.0.1:3000/__webpack_hmr&noInfo=false&reload=true&quiet=false
然后开发过程中,我们会在 network 中发现很多 http://127.0.0.1:3000/\_\_webpack_hmr/xxx 这样的网络请求,这些请求就是用来获取对应脚本的修改部分,并触发其发生热更新,最后局部进行重新渲染。所以这样的热更新只能支持远程链接配置的形式。这就是 chrome 插件开发时的热更新不完全生效的原因。
上面我们提到 manifest.json 中的 background 的 scripts 和 content_scripts 的 js 两个属性是不能使用远程链接的,所以 webpack 的热更新并不适合 js 脚本配置的 chrome 插件开发。
而开发过 chrome 插件的同学都知道,对于使用本地脚本配置的方式,在修改了脚本后,需要去应用扩展后台手动更新插件,否则插件不会更新。(这里推荐另一个插件,可以重载其他当前正在开发的 chrome 插件:https://github.com/arikw/chrome-extensions-reloader)
所以,这里就衍生出了开发 chrome 插件过程中修改代码后能否自动重载的问题。
我们逆向思考:要重载插件,需要获得代码的修改时机;要获得代码的修改时机,需要了解 webpack 在每次修改编译后如何通知到客户端。
于是继续衍生出以下几个问题:
我们一个一个来解决
查阅 webpack 文档我们可以找到如下方法:
const compiler = webpack(webpackConfig) compiler.hooks.done.tap('name', () => { // todo })
首先请详细看一下这几篇文章和源码:《Server-Sent Events》、《Webpack 热更新实现原理分析》、《EventSource client for Node.js and Browser (polyfill)》
SSE 就是一种由服务端直接将信息推送给客户端的单向通道,相比 websocket 则会更加轻量。
客户端:
if ('EventSource' in window) { const source = new EventSource(`http://localhost:3000/sse`) source.addEventListener('open', (event) => { console.log('Auto Reload Listen: ', event); }, false); source.addEventListener('compiled', (event) => { // 自定义事件 console.log('compiled: ', event); // todo }, false); source.addEventListener('error', (event) => { console.log('error: ', event); }, false); }
服务端:
const SseStream = require('ssestream'); const compiler = webpack(webpackConfig); app.use('/sse', webpackChromeExtensionsReloadMiddleware(compiler)); function webpackChromeExtensionsReloadMiddleware(compiler, opts = {}) { opts.heartbeat = opts.heartbeat || 5 * 1000; const middleware = function(req, res, next) { res.header('Access-Control-Allow-Origin', '*'); const sseStream = new SseStream(req) // 构造 sse 的请求 sseStream.pipe(res) if (compiler.hooks) { compiler.hooks.done.tap('webpack-chrome-extensions-reload-middleware', () => { sseStream.write({ // 请求数据 event: 'compiled', data: 'compiled' }) }) } res.on('close', () => { console.log('close connection') sseStream.unpipe(res) }) next(); }; return middleware; }
在 2 中客户端的 todo 中,我们在这里的回调里就可以实时监听到代码的改动,这里我们只需要执行如下代码即可。
chrome.runtime.reload();
以上机制已被我之前编写的 vue 和 react 版本的 chrome 插件开发模板 中用到,欢迎围观~
1. chrome插件开发时,热更新不完全生效发现问题
在编写 chrome 插件开发模板 的时候,遇到了 webpack 的 热更新不完全生效 的问题。
不生效情景:
当 chrome 插件的 manifest.json 中的 background, content_scripts 被配置为 js 的形式时(如下),这些脚本必须在文件目录中存在(注意:background 的 scripts 和 content_scripts 的 js 两个属性是不能使用远程链接的)。
生效情景: 当 chrome 插件的 manifest.json 中的 background, popup 被配置为 html(js 在 html 中被以 远程链接 的形式引入) 的形式时(如下),
分析原因:
在配置 webpack 的热更新的时候,我们都会在 entry 配置如下配置:
webpack-hot-middleware/client?path=http://127.0.0.1:3000/__webpack_hmr&noInfo=false&reload=true&quiet=false
然后开发过程中,我们会在 network 中发现很多 http://127.0.0.1:3000/\_\_webpack_hmr/xxx 这样的网络请求,这些请求就是用来获取对应脚本的修改部分,并触发其发生热更新,最后局部进行重新渲染。所以这样的热更新只能支持远程链接配置的形式。这就是 chrome 插件开发时的热更新不完全生效的原因。
2. 解决开发chrome 插件过程中修改代码后自动重载的问题
上面我们提到 manifest.json 中的 background 的 scripts 和 content_scripts 的 js 两个属性是不能使用远程链接的,所以 webpack 的热更新并不适合 js 脚本配置的 chrome 插件开发。
而开发过 chrome 插件的同学都知道,对于使用本地脚本配置的方式,在修改了脚本后,需要去应用扩展后台手动更新插件,否则插件不会更新。(这里推荐另一个插件,可以重载其他当前正在开发的 chrome 插件:https://github.com/arikw/chrome-extensions-reloader)
所以,这里就衍生出了开发 chrome 插件过程中修改代码后能否自动重载的问题。
我们逆向思考:要重载插件,需要获得代码的修改时机;要获得代码的修改时机,需要了解 webpack 在每次修改编译后如何通知到客户端。
于是继续衍生出以下几个问题:
我们一个一个来解决
1. 如何获取 webpack 每次触发修改编译后的钩子
查阅 webpack 文档我们可以找到如下方法:
2. 如何在 webpack 钩子回调中及时通知浏览器代码已更新
首先请详细看一下这几篇文章和源码:《Server-Sent Events》、《Webpack 热更新实现原理分析》、《EventSource client for Node.js and Browser (polyfill)》
SSE 就是一种由服务端直接将信息推送给客户端的单向通道,相比 websocket 则会更加轻量。
客户端:
服务端:
3. 浏览器如何接受到修改通知,并更新重载插件和页面
在 2 中客户端的 todo 中,我们在这里的回调里就可以实时监听到代码的改动,这里我们只需要执行如下代码即可。
以上机制已被我之前编写的 vue 和 react 版本的 chrome 插件开发模板 中用到,欢迎围观~