構建專欄系列目錄入口javascript
劉崇楨,微醫前端技術部門戶支撐組html
Webpack
的使用目前已是前端開發工程師必備技能之一。如果想在本地環境啓動一個開發服務快速開發咱們的應用(而不是每次 coding 完,手動執行 run build,全量打包),你們只需在 Webpack
的配置中,增長 devServer 的配置便可。它的做用主要是用來伺服資源文件。 webpack-dev-server(如下簡稱 wds)
已經爲咱們封裝好了全面、豐富且可配置化的功能,配置工程師們只需經過 webpack.config
和 命令行參數
便可知足開發所需。 然而配置工程師們,發現 wds
的 hot
、live reload
實際上至關於啓用了一個 express
的 Http 服務器
+ webpack-dev-middleware(如下簡稱 wdm)
等中間件。這個 Http 服務器
和 用戶訪問服務的 client
能夠經過 websocket
通信協議創建長鏈接,在 webpack
'watch' 到原始文件做出改動後,wds
會使用 webpack
的實時編譯,再用 wdm
將 webpack
編譯後文件會輸出到內存中。每當應用程序請求一個文件時,wdm
匹配到了就把內存中緩存的對應結果以文件的格式返回給 client
,反之則進入到下一個中間件。 若是想要使用更多 wds
提供的配置功能,好比 proxy
、static
、open
等, 在 server
端增長中間件便可,這樣配置工程師搖身一變,配置開發工程師! 項目中的 devServer
咱們更可能是使用 webpack
+ express
+ webpack-dev-middleware
+ webpack-hot-middleware
的組合來完成 HMR
。在系列文章中,有更加具體詳細的學習分享來介紹這些,這裏收縮一下,咱們只關注 webpack-dev-server
。 前端
Use webpack with a development server that provides live reloading. This should be used for development only. It uses webpack-dev-middleware under the hood, which provides fast in-memory access to the webpack assets. java
webpack 配合一個開發服務器,能夠提供熱重載功能。但只用於開發模式下。 wds 的底層,集成了 wdm,能夠提供快速內存訪問打包資源的功能。node
以上是 wds 對本身的一個簡短自我介紹,咱們來搞清楚它這麼歸納的點:webpack
webpack 能夠經過 watch mode 的方式啓動,指示 webpack 'watch' 依賴圖中全部文件的更改,而且自動打包。可是每次打包後的結果將會存儲到本地硬盤中,而 IO 操做是很是耗資源時間的,沒法知足本地開發調試需求。 wds 則能夠提供一個開發服務器,而且提供 live reloading(實時重載)功能,在打包完成後通知客戶端,刷新頁面從新加載資源。git
wdm 能夠將 webpack 編譯後的資源輸出到內存中,當應用程序請求資源時,能夠直接從內存中進行響應。 開發中,咱們還會注意到,在編譯期間,客戶端的請求會被 delay 到最新的編譯結果完成以後纔會去響應。github
「wdm」: wait until bundle finished: /myapp/
複製代碼
平常開發修改完代碼後,你有沒有傻乎乎地,手動刷新調試頁面,來驗證如今的 bug 仍是不是以前的那個 bug? 其實 HMR 會在應用程序運行過程當中,替換、添加或刪除模塊,而無需從新加載整個頁面。 能夠經過如下幾種方式,來顯著提速開發效率:web
有時候服務啓動後,會打開 localhost:8080,或者打開瀏覽器的多個頁籤ajax
前端項目中,藉助 history api,能夠作到改變視圖而不向後端發出請求。若是你手動刷新一個路由中匹配不到的頁面,同時你的項目中沒有配置 404 頁面兜底邏輯,那就真的 404 Not Found 了。 wds 配置中有一項配置 historyApiFallback,能夠配置一個頁面代替全部的 404 響應。
「wds」: 404s will fallback to /index.html
複製代碼
wds 的 proxy 配置使用方法,能夠詳見 Webpack-dev-server 的 proxy 用法。
控制代碼打包編譯時出現警告和錯誤時,是否在頁面上顯示錯誤信息
output.path 打包產物的絕對路徑,沒有什麼疑問。對於 output.publicPath、devServer.publicPath,仍是有點疑惑不解吧? 假若有一個域名 example.com,可是大家應用部署在 example.com/myapp/ 。沒有指定 output.publicPath,默認爲 '/',這時 index.html 引用其餘模塊的 url 會是 /bundle.xxxhashxxx.js,這時這個資源的 url 就變成了 example.com/bundle.xxxhashxxx.js,毫無疑問,這個資源會 404 Not Found。若是指定 output.publicPath: '/myapp/',那麼 index.html 中資源的 url 就變成了 '/myapp/bundle.xxxhashxxx.js'。 同理 wds 中,指定 devServer.publicPath: '/myapp/',devServer 就會在 http://localhost:8080/myapp/ 下伺服資源訪問。模擬生產環境下的運維配置。
「wds」: webpack output is served from /myapp/
複製代碼
contentBase 呢?它只做用於 wds,只有你想要伺服靜態資源文件的時候使用。換句話說,wds 會加載這個文件夾下的靜態資源到服務器,而不須要 bundle 這個文件夾。 假如,你的 app 中須要加載一些 mp4 文件,這些文件基本不會被改動,因此你沒必要把這些資源打包到 /dist 文件下,能夠把這些文件維護在 /src、/dist 的同級目錄下的 /static。而後設置 contentBase: path.join(__dirname, 'static'),而後就能夠在代碼中這樣引用靜態資源了 。
「wds」: Content not from webpack is served from /Volumes/bomb/git/webpack-learning/webpack-demo/static
複製代碼
爲了驗證咱們構建的自洽模型,可以自洽,咱們須要一個參照物來進行修正。 咱們使用 devServer 官方配置,來伺服資源文件。 爲了避免影響體驗,自洽模型 和 參考物 的代碼都維護在第四節的參考,有興趣的能夠本身 debugger 一下。
首先咱們使用 express 啓動一個本地 server,讓瀏覽器能夠訪問本地的靜態資源。
// wds.server.js
const app = express();
const listeningApp = http.createServer(app);
listeningApp.listen('8888', '127.0.0.1', (err) => {
createSocketServer();
});
複製代碼
這裏建立 http 服務器,沒有使用 app.listen('8888', callback),而是使用 http.createServer(app) 的緣由有兩點:
wds 調用 webpack api 對文件系統進行 'watch',當文件發生改變後,webpack 會從新對文件進行編譯打包,而後保存到內存中。 這一系列操做,主要有兩點:一、watch 文件更改;二、內存響應。所幸,wdm 完成了這部分功能,咱們在自洽模型中直接引用 wdm 。
// wds.server.js
const webpack = require('webpack');
const wdm = require('webpack-dev-middleware');
const config = require('./webpack.config.js');
const compiler = webpack(config); // 將 webpack.config.js 配置文件做爲基礎配置
const app = express();
app.use(wdm(compiler)) // 告知 express 使用 webpack-dev-middleware
複製代碼
這裏不難看出,wdm(compiler) 的執行結果返回的是一箇中間件,它將 webpack 編譯後的文件存儲到內存中,而後在用戶訪問 express 服務時,將內存中對應的資源輸出返回。 那麼 wdm 內部是如何實現的呢? wdm 的源碼並很少,其核心只有 /index.js,/lib/middleware。
// webpack-dev-middleware/index.js
// 在 compiler 的 invalid、run、done、watchRun 這 4 個編譯生命週期上,註冊對應的處理方法。
// 經過 tapable 來調用插件功能,主要是 report 編譯的狀態信息以及執行 context.callbacks 回調函數
const context = createContext(compiler, options);
...
// 以監控的方式啓動 webpack,調用 compiler 的 watch 方法,以後 webpack 便會監聽文件變動,一旦檢測到文件變動,就會從新執行編譯。
context.watching = compiler.watch(options.watchOptions, (err) => { ... });
...
// 使用 memory-fs,將 webpack 的編譯內容,輸出至內存中
setFs(context, compiler);
複製代碼
// webpack-dev-middleware/lib/middleware
// 核心邏輯是:針對 request 請求,根據各類條件判斷,最終返回對應的文件
module.exports = function wrapper(context) {
// 返回 express 中間件函數的包裝函數
return function middleware(req, res, next) {
// 若是不是 SSR,直接 next,流轉到下一個中間件
// 若是是 SSR,調用 util/ready,根據 state 判斷執行回調 fn,仍是將 fn 存儲到 callbacks 隊列中
// ready 也是「在編譯期間,中止提供舊版的 bundle 而且將請求延遲到最新的編譯結果完成以後」的實現
function goNext() { ... }
// 根據請求的 req.url 地址,在 compiler 的內存文件系統中查找對應的文件,若查找不到,則直接調用 goNext() 方法處理請求
let filename = getFilenameFromUrl( ... )
if (filename === false) {
return goNext();
}
// 根據上文找到的 filename 路徑獲取到對應的文件內容,並構造 response 對象返回
// 最後也是調用 ready
return new Promise((resolve) => {
handleRequest(context, filename, processRequest, req);
function processRequest() {
...
}
})
}
}
複製代碼
使用 HMR 的過程當中,經過 network 咱們知道 client 端 是經過 websocket 和 server 端 進行通訊的。client 端 和 server 端 之間創建一個 websocket 長鏈接,將 webpack 編譯打包的各個階段的狀態信息告知 client 端。最關鍵的仍是 wds 註冊 compiler hooks(compile、done、watchRun 等),當進入 webpack compile 生命週期時調用 hooks 回調註冊方法。 compilation done 時,server 端 傳遞的最主要的信息是 'stats.hash' 和 'ok',而後 client 端 根據 hash 進行模塊熱更新。
// wds.server.js
let connect = null // 長鏈接實例
// 調用 webpack api 監聽 compile 的 done 事件
// 註冊 compiler hooks -- done
const { done } = compiler.hooks
done.tap('myappPligins', (stats) => {
if (connect) {
let _stats = stats.toJson({...})
// 將編譯打包後的新模塊 hash 值發送到 client 端
connect.write(JSON.stringify({
"type": "hash",
"data": _stats.hash
}))
// 通知 client 端編譯完成,能夠進行 reloadApp 操做
connect.write(JSON.stringify({
"type": "ok"
}))
}
});
// 建立 websocket server(wss)
// 目前 webpack-dev-server@4.X 使用 sockjs 會出錯,webpack-dev-server@3.X 使用 ws 會報錯
function createSocketServer () {
let socket = sockjs.createServer({
sockjs_url: './sockjs-client'
});
// 複用 http 服務器實例 listeningApp
socket.installHandlers(listeningApp, {
prefix: '/sockjs-node',
});
socket.on('connection', (connection) => {
connect = connection
...
});
}
複製代碼
咱們在業務代碼中並無添加接收 wss 消息的代碼,那 client 端 的邏輯怎麼實現的呢? 其實 wds 修改了 webpack.config.js 的基礎配置,它會往 chunk 中偷偷塞入兩個文件 webpack-dev-server/lib/client/index.js 和 webpack/hot/dev-server。 咱們在自洽模型中也這麼操做,這樣這兩段代碼就植入到 client 端了。
// wds.server.js
config.entry.app = [require.resolve('webpack/hot/dev-server'), './wds.client.js', config.entry.app]
// HMR 做爲一個 Webpack 內置的功能,能夠經過 HotModuleReplacementPlugin 開啓
config.plugins.push(
new webpack.HotModuleReplacementPlugin()
)
複製代碼
client 端 經過 websocket 接收 server 端 最新編輯後的模塊 hash 值,這個值會被存起來(currentHash),在接收到 ok 後纔會 reloadApp。 若是配置了 hot,開啓 HMR,會把程序控制權交給 webpack 的客戶端代碼進行 HMR。若是沒有開啓,就直接調用 location.reload() 刷新頁面。
// wds.client.js
const SockJS = require('./sockjs-client')
const socketUrl = 'http://127.0.0.1:8888/sockjs-node'
let currentHash = '' // 最新代碼模塊的 hash 值
function reloadApp () {
if (options.hot) {
let hotEmitter = require('webpack/hot/emitter');
// webpackHotUpdate 是 webpack 在 webpack/hot/dev-server.js 定義的一個事件,事件回調是獲取這次編譯的最新代碼
hotEmitter.emit('webpackHotUpdate', currentHash);
} else if (options.liveReload) { // 沒有配置 hmr,就直接 live reload 刷新頁面
location.reload();
}
}
// 處理 wss 通知
const onSocketMessage = {
...
hash: function hash(_hash) {
currentHash = _hash; // wss 端 通知 client 端 最新編輯後的模塊 hash 值,這個值會被存起來(currentHash),在接收到 ok 後纔會 reloadApp
},
ok: function ok() {
reloadApp();
}
};
const socket = (url, handlers) => {
client = new SockJS(url)
...
client.onmessage = function (data) { // 接收 wss 通知
var msg = JSON.parse(data.data);
if (handlers[msg.type]) {
handlers[msg.type](msg.data);
}
}
...
}
socket(socketUrl, onSocketMessage)
複製代碼
當 client 端 收到 ok 的通知後,開啓 hot 的 wds,會執行 reload 方法,而後調用 webpackHotUpdate
// wds.client.js
let hotEmitter = require('webpack/hot/emitter');
hotEmitter.emit('webpackHotUpdate', currentHash);
複製代碼
而後程序被 client 端 的 webpack 接管(第四步中咱們注入到 plugins 中的 webpack.HotModuleReplacementPlugin 就派上用場了),webpack 監聽到 webpackHotUpdate
事件,並獲取到最新的 hash 值,而後開始檢查更新。
// webpack/hot/dev-server.js
var hotEmitter = require("./emitter");
hotEmitter.on("webpackHotUpdate", function (currentHash) {
lastHash = currentHash;
...
check();
});
// 檢查更新
var check = function check() {
module.hot.check(true)
.then(updatedModules => {
...
})
}
複製代碼
源碼中,追蹤到 module.hot.check,就不知道路該怎麼走了,hot.check 是哪裏來的? 系列文章中有單獨介紹 HMR 的一章,這裏咱們就偷個懶,粗線條的勾勒一下大體過程。 hot.check 來自於 /webpack/lib/hmr/HotModuleReplacement.runtime.js
。
hash
值,調用 hotDownloadManifest
發送 xxx.hash.hot-update.json
的 ajax
請求c: chunkIds m: removedChunks r: removedModules
chunkId.hash.hot-update.js
請求。下面就是拿到的 hot-update.js 的內容。 JSONP 返回的 js 文件當即執行,會調用 window.webpackHotUpdatewebpack_demo
方法。此方法會把更新的模塊 moreModules
(圖中入參的第二個參數對象)賦值給全局全量 currentUpdate
。
hotApply
進行熱更新模塊替換// wds.server.js
// 添加中間件 connect-history-api-fallback,解決 history api 降級
app.use(historyApiFallback({
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'], // 只對這些類型的請求進行 rewrite
rewrites: [
{ from: /./, to: '/myapp/index.html' }
]
}))
複製代碼
proxy 配置,咱們常用,那 node 是如何代理請求的呢?
在 wds 中,藉助建立的 http 服務器,其 proxy 功能的實現就是解析配置項,並掛載 http-proxy-middleware
中間件到 http 服務上。結合 proxy 的用法,wds/lib/Server.js 中的代碼顯得一目瞭然。 http-proxy-middleware 則藉助於 node-http-proxy,用於將 node 服務端接收到的請求,轉發到目標服務器,實現代理服務器的功能。 能夠預見,整個流程的大體實現思路就是,經過配置項註冊全局的請求轉發規則,在中間件中攔截客戶端的 request 請求匹配轉發規則,而後調用 node-http-proxy 的 .web
、.ws
方法進行轉發請求。 http-proxy-middleware 將轉發規則分爲兩大類進行配置,context 和 options。
// Proxy middleware configuration.
var proxy = require('http-proxy-middleware');
var apiProxy = proxy('/api', { target: 'http://www.example.org' });
// \____/ \_____________________________/
// | |
// context options
// 'apiProxy' is now ready to be used as middleware in a server.
複製代碼
context 用於匹配須要進行轉發的客戶端請求,默認值是 '/',客戶端發起的全部請求都會被轉發;也能夠字符串 url、字符串 url 數組、通配符或者個性化方法,來決定哪些請求會被代理轉發。http-proxy-middleware 使用 options 中
// rewrite path
pathRewrite: {'^/old/api' : '/new/api'}
// remove path
pathRewrite: {'^/remove/api' : ''}
// add base path
pathRewrite: {'^/' : '/basepath/'}
// custom rewriting
pathRewrite: function (path, req) {}
router: {
'integration.localhost:3000' : 'http://localhost:8001', // host only
'staging.localhost:3000' : 'http://localhost:8002', // host only
'localhost:3000/api' : 'http://localhost:8003', // host + path
'/rest' : 'http://localhost:8004' // path only
}
複製代碼
// http-proxy-middleware/lib/index.js
function HttpProxyMiddleware(context, opts) {
...
var config = configFactory.createConfig(context, opts) // 解析獲取 context、options
...
var proxy = httpProxy.createProxyServer({}) // 建立代理服務器,由這個服務器進行轉發請求
...
var pathRewriter = PathRewriter.create(proxyOptions.pathRewrite) // 將客戶端請求路徑轉化爲目標服務器的路徑(pathname 部分),既能夠是 key-value,也能夠函數。
...
function shouldProxy(context, req) { // 判斷請求是否須要轉發
var path = req.originalUrl || req.url
return contextMatcher.match(context, path, req) // 經過多種匹配方法校驗客戶端 req 是否須要轉發
}
function prepareProxyRequest(req) {
req.url = req.originalUrl || req.url
var originalPath = req.url
var newProxyOptions = _.assign({}, proxyOptions)
__applyRouter(req, newProxyOptions) // 遍歷 options.router,校驗是否匹配客戶端 req,匹配的話就改寫此次請求的 host
__applyPathRewrite(req, pathRewriter) // 若是有 pathRewriter,就匹配當前請求,匹配的話就將設置的目標服務器路徑寫入 req.url
return newProxyOptions
}
...
function middleware(req, res, next) { // 真正的代理中間件
if (shouldProxy(config.context, req)) {
var activeProxyOptions = prepareProxyRequest(req)
proxy.web(req, res, activeProxyOptions) // node-http-proxy 進行代理轉發
} else {
next()
}
}
...
return middleware
}
複製代碼
這樣看來,http-proxy-middleware 主要作的是解析轉發規則、最終把代理轉發的事情交給了 node-http-proxy,同時配置了相關的 Logger、綁定事件。
本文從開發過程當中遇到的痛點出發,梳理了現代打包工具對咱們平常開發的幫助和提效,並自娛自樂的結合表現和源碼,照虎畫貓完成了所謂的自洽模型。其實自洽模型畫的遠不如貓,最多就是一個四支腿生物的簡筆畫了。權當梳理了一遍 wds 的工做流程,更加細節的東西,還須要你們一塊兒動手才能挖掘出來,但願能對你的理解過程起到必定的幫助做用。
"webpack": "5.24.0", "webpack-cli": "4.5.0", "webpack-dev-server": "^3.11.2"
// webpack.config.js 使用通用的 Vue 項目配置
module.exports = {
mode: 'development',
entry: {
app: './src/app.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/myapp/'
},
devtool: 'inline-source-map',
plugins: [
...some plugins
],
module: {
rules: [ ...some loaders ]
}
};
複製代碼
而後把 devServer 的配置單獨維護在 webpack.dev.js
// webpack.dev.js devServer 配置
module.exports = merge([
base, // webpack.config.js
{
mode: 'development',
devServer: {
host: '127.0.0.1', // 服務器 host,默認爲 localhost
port: 7777, // 服務器端口號,默認爲 8080
open: true, // string | boolean,啓動後是否打開瀏覽器,當爲字符串時,打開指定瀏覽器
openPage: 'myapp/', // string | Array<string>, ['', 'index.html'], 'index.html', 打開瀏覽器後默認打開的頁面,Array 打開多個頁面
compress: true,
hot: true, // 是否啓動熱更新(HMR),熱更新使用的是 webpack 中 HotModuleReplacementPlugin
http2: false, // 是否設置 HTTP/2 服務器,爲 true,則默認使用 https 做爲服務
// https: {
// key: '',//fs.readFileSync('/path/to/server.key'),
// cert: '',//fs.readFileSync('/path/to/server.crt'),
// ca: '',//fs.readFileSync('/path/to/ca.pem')
// },
proxy: {
'/api': {
target: 'http://localhost:7777',
pathRewrite: { '^/api': '' },
secure: false // HTTPS 設置爲無效證書
}
},
// 靜態文件屬性
publicPath: '/myapp/', // 掛載到服務器中間件的可訪問虛擬地址
contentBase: path.join(__dirname, 'static'), // devServer 伺服這個文件夾下的靜態資源。換句話說會加載本地 /static 目錄下的靜態文件到服務器
stats: 'minimal',
// 設置編譯出錯或警告後,頁面是否會直接顯示信息, boolean | {}
// 默認爲 false,當失敗後會顯示空白頁
// 設置爲 true 後,編譯失敗會顯示錯誤/警告的覆蓋層,也能夠設置爲 object,顯示多種類型信息
overlay: {
warnings: true,
errors: true
},
injectClient: true, // 是否要注入 WebSocket 客戶端,將此屬性設置爲 false,那麼 hot、overlay 等功能都會失效
injectHot: true, // 是否注入 HMR, 這個屬性是 injectClient 的子集。隻影響熱更新
liveReload: false, // 是否開啓自動刷新瀏覽器功能,優先級低於 hot
// 是否將全部 404 頁面都跳轉到 index.html,當此屬性設置爲 true 或爲 object 時而且使用 history API 時全部 404 頁面會跳轉到 index.html 或指定的頁面
historyApiFallback: {
rewrites: [
{ from: /./, to: '/myapp/index.html' },
]
},
// 設置 WebSocket,設置使用的 WebSocket 庫,內置爲 sockjs 或 ws
transportMode: {
// 目前 webpack-dev-server@4.X 使用 sockjs 會出錯,webpack-dev-server@3.X 使用 ws 會報錯
server: 'sockjs'
}
}
}
])
複製代碼
package.json
// package.json
"scripts": {
"start": "webpack serve --config webpack.dev.js",
"start:dev": "node wds.server.js",
}
複製代碼
// wds.server.js
const express = require('express');
const webpack = require('webpack');
const http = require('http');
const webpackDevMiddleware = require('webpack-dev-middleware');
const historyApiFallback = require('connect-history-api-fallback');
const sockjs = require('sockjs');
const app = express();
const config = require('./webpack.config.js');
config.entry.app = [require.resolve('webpack/hot/dev-server'), './wds.client.js', config.entry.app]
config.plugins.push(
new webpack.HotModuleReplacementPlugin()
)
// 告知 express 使用 webpack-dev-middleware,
// 以及將 webpack.config.js 配置文件做爲基礎配置。
const compiler = webpack(config)
// historyApiFallback
app.use(historyApiFallback({
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
rewrites: [
{ from: /./, to: '/myapp/index.html' }
]
}))
app.use(
webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath,
})
)
let connect = null
const { done } = compiler.hooks
done.tap('myappPligins', (stats) => {
if (connect) {
let _stats = stats.toJson({
all: false,
hash: true,
assets: true,
warnings: true,
errors: true,
errorDetails: false,
})
connect.write(JSON.stringify({
"type": "hash",
"data": _stats.hash
}))
connect.write(JSON.stringify({
"type": "ok"
}))
}
});
const listeningApp = http.createServer(app);
function createSocketServer () {
let socket = sockjs.createServer({
sockjs_url: './sockjs-client'
});
socket.installHandlers(listeningApp, {
prefix: '/sockjs-node',
});
socket.on('connection', (connection) => {
if (!connection) {
return;
}
connect = connection
// 通知 client enable 了哪些功能
connection.write(JSON.stringify({
"type": "hot"
}))
});
}
listeningApp.listen('8888', '127.0.0.1', (err) => {
console.log('Example app listening on port 8888!\n');
createSocketServer();
});
listeningApp.on('error', (err) => {
console.error(err);
});
複製代碼
// wds.client.js
console.log('this is from client.')
const SockJS = require('./sockjs-client')
const socketUrl = 'http://127.0.0.1:8888/sockjs-node'
const options = {
hot: true,
hotReload: true,
liveReload: false,
initial: true,
useWarningOverlay: false,
useErrorOverlay: false,
useProgress: false
}
let currentHash = ''
function reloadApp () {
if (options.hot) {
console.log('[WDS] App hot update...');
let hotEmitter = require('webpack/hot/emitter');
hotEmitter.emit('webpackHotUpdate', currentHash);
// broadcast update to window
window.postMessage("webpackHotUpdate".concat(currentHash), '*');
} else if (options.liveReload) {
location.reload();
}
}
const onSocketMessage = {
hot: function hot() {
options.hot = true;
console.info('[WDS] Hot Module Replacement enabled.')
},
liveReload: function liveReload() {
options.liveReload = true;
console.info('[WDS] Live Reloading enabled.')
},
invalid: function invalid() {
console.info('[WDS] App updated. Recompiling...')
},
hash: function hash(_hash) {
currentHash = _hash;
},
ok: function ok() {
reloadApp();
},
close: function close() {
console.error('[WDS] Disconnected!');
}
};
let retries = 0
let client = null
const socket = (url, handlers) => {
client = new SockJS(url)
client.onopen = function () {
retries = 0
}
client.onmessage = function (data) {
var msg = JSON.parse(data.data);
if (handlers[msg.type]) {
handlers[msg.type](msg.data);
}
}
client.onclose = function () {
if (retries === 0) {
handlers.close();
} // Try to reconnect.
client = null; // After 10 retries stop trying, to prevent logspam.
if (retries <= 10) {
var retryInMs = 1000 * Math.pow(2, retries) + Math.random() * 100;
retries += 1;
setTimeout(function () {
socket(url, handlers);
}, retryInMs);
}
}
}
socket(socketUrl, onSocketMessage)
複製代碼