1. chrome外掛開發時,熱更新不完全生效發現問題
在編寫 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
然後開發過程中,我們會在 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 外掛:github.com/arikw/chrom…)
所以,這裡就衍生出了開發 chrome 外掛過程中修改程式碼後能否自動過載的問題。
我們逆向思考:要過載外掛,需要獲得程式碼的修改時機;要獲得程式碼的修改時機,需要了解 webpack 在每次修改編譯後如何通知到客戶端。
於是繼續衍生出以下幾個問題:
- 如何獲取 webpack 每次觸發修改編譯後的鉤子
- 如何在 webpack 鉤子回撥中及時通知瀏覽器程式碼已更新
- 瀏覽器接受到修改通知後,如何更新過載外掛和頁面
我們一個一個來解決
1. 如何獲取 webpack 每次觸發修改編譯後的鉤子
查閱 webpack 文件我們可以找到如下方法:
const compiler = webpack(webpackConfig)
compiler.hooks.done.tap('name', () => {
// todo
})
複製程式碼
2. 如何在 webpack 鉤子回撥中及時通知瀏覽器程式碼已更新
首先請詳細看一下這幾篇文章和原始碼:《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;
}
複製程式碼
3. 瀏覽器如何接受到修改通知,並更新過載外掛和頁面
在 2 中客戶端的 todo 中,我們在這裡的回撥裡就可以實時監聽到程式碼的改動,這裡我們只需要執行如下程式碼即可。
chrome.runtime.reload();
複製程式碼
以上機制已被我之前編寫的 vue 和 react 版本的 chrome 外掛開發模板 中用到,歡迎圍觀~