摘要:一直感覺的特性挺神奇,所以這里初步探究下,這個模塊首先地址,,當前版本為,為了配合吧,肯定也做了些更新,不過這個是個非官方的庫。
一直感覺hot module replacement的特性挺神奇,所以這里初步探究下webpack-hot-middleware,這個模塊
首先地址,https://github.com/glenjamin/...,當前版本為2.13.2,為了配合webpack2吧,肯定也做了些更新,不過這個是個非官方的庫。
項目結構: 簡單使用他的用法,大家也很熟悉,可以參考文檔以及example,下面僅展示了example里核心部分
從中能看出他似乎是和webpack-dev-middleware配套使用的,具體是不是這樣子? 以后有空再探究下webpack-dev-middleware嘍,在此也暫時不用太關心
server.js
var http = require("http"); var express = require("express"); var app = express(); app.use(require("morgan")("short")); (function() { // Step 1: Create & configure a webpack compiler var webpack = require("webpack"); var webpackConfig = require(process.env.WEBPACK_CONFIG ? process.env.WEBPACK_CONFIG : "./webpack.config"); var compiler = webpack(webpackConfig); // Step 2: Attach the dev middleware to the compiler & the server app.use(require("webpack-dev-middleware")(compiler, { noInfo: true, publicPath: webpackConfig.output.publicPath })); // Step 3: Attach the hot middleware to the compiler & the server app.use(require("webpack-hot-middleware")(compiler, { log: console.log, path: "/__webpack_hmr", heartbeat: 10 * 1000 })); })(); // Do anything you like with the rest of your express application. app.get("/", function(req, res) { res.sendFile(__dirname + "/index.html"); }); if (require.main === module) { var server = http.createServer(app); server.listen(process.env.PORT || 1616, "127.0.0.1", function() { console.log("Listening on %j", server.address()); }); }
webpack.config.js
entry: { index: [ "webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000", "./src/index.js" ] } plugins: [ new webpack.HotModuleReplacementPlugin() ] ...
src/index.js
... var timeElem = document.getElementById("timeElement"); var timer = setInterval(updateClock, 1000); function updateClock() { timeElem.innerHTML = (new Date()).toString(); } // ... if (module.hot) { // 模塊自己就接收更新 module.hot.accept(); // dispose方法用來定義一個一次性的函數,這個函數會在當前模塊被更新之前調用 module.hot.dispose(function() { clearInterval(timer); }); }source code分析
middleware.js
webpackHotMiddleware函數
function webpackHotMiddleware(compiler, opts) { opts = opts || {}; opts.log = typeof opts.log == "undefined" ? console.log.bind(console) : opts.log; opts.path = opts.path || "/__webpack_hmr"; opts.heartbeat = opts.heartbeat || 10 * 1000; var eventStream = createEventStream(opts.heartbeat); var latestStats = null; compiler.plugin("compile", function() { latestStats = null; if (opts.log) opts.log("webpack building..."); eventStream.publish({action: "building"}); }); compiler.plugin("done", function(statsResult) { // Keep hold of latest stats so they can be propagated to new clients latestStats = statsResult; publishStats("built", latestStats, eventStream, opts.log); }); var middleware = function(req, res, next) { if (!pathMatch(req.url, opts.path)) return next(); eventStream.handler(req, res); if (latestStats) { // Explicitly not passing in `log` fn as we don"t want to log again on // the server publishStats("sync", latestStats, eventStream); } }; middleware.publish = eventStream.publish; return middleware; }
這里主要使用了sse(server send event),具體協議的內容及其用法,可以文末給出的資料 1) - 4),也不算什么新東西,不過感覺還不錯,可以理解為基于http協議的服務器"推送",比websocket要簡便一些
稍微強調的一下的是,服務端可以發送個id字段(似乎必須作為首字段),這樣連接斷開時瀏覽器3s后會自動重連,其中服務端可以通過發送retry字段來控制這個時間,這樣重連時客戶端請求時會帶上一個Last-Event-ID的字段,然后服務端就能知道啦(不過也看到有人說可以new EventSource("srouce?eventId=12345"),我試好像不行啊,這個我就母雞啦)
如果你不自動想重連,那么客戶端eventsource.close(),比如這里就是這樣
這里就是webpack的plugin的簡單寫法, compile和done鉤子,正常webpack一下plugin是不會運行的,要調用其run或watch方法,webpack-dev-middleware好像調用了watch方法,所以配合使用就沒問題,難道這就解釋上面配合使用的疑問?
這里webpack的compile的回調,為啥只在rebuild的時候觸發哩?難道又被webpack-dev-middleware吸收傷害了...?
createEventStream內部函數
function createEventStream(heartbeat) { var clientId = 0; var clients = {}; function everyClient(fn) { Object.keys(clients).forEach(function(id) { fn(clients[id]); }); } setInterval(function heartbeatTick() { everyClient(function(client) { client.write("data: uD83DuDC93 "); }); }, heartbeat).unref(); return { handler: function(req, res) { req.socket.setKeepAlive(true); res.writeHead(200, { "Access-Control-Allow-Origin": "*", "Content-Type": "text/event-stream;charset=utf-8", "Cache-Control": "no-cache, no-transform", "Connection": "keep-alive" }); res.write(" "); var id = clientId++; clients[id] = res; req.on("close", function(){ delete clients[id]; }); }, publish: function(payload) { everyClient(function(client) { client.write("data: " + JSON.stringify(payload) + " "); }); } }; }
setInterval的unref可以看資料5),我想說,我用你這模塊,肯定要createServer,我肯定有event loop啊,不明白為啥還調用unref()方法
req.socket.setKeepAlive(true)可以看資料6),雖然我也沒太懂,而且我看注釋掉這行,好像運行也沒問題啊,難道是我人品好,2333
這里呢,就是每10秒向客戶端發送心跳的unicode碼,chrome控制臺Network里的__webpack_hmr,可以看到
extractBundles內部函數
function extractBundles(stats) { // Stats has modules, single bundle if (stats.modules) return [stats]; // Stats has children, multiple bundles if (stats.children && stats.children.length) return stats.children; // Not sure, assume single return [stats]; }
將webpack的bundle,統一成數組形式
buildModuleMap內部函數
function buildModuleMap(modules) { var map = {}; modules.forEach(function(module) { map[module.id] = module.name; }); return map; }
轉成key為module.id,value為module.name的map
publishStats內部函數
function publishStats(action, statsResult, eventStream, log) { // For multi-compiler, stats will be an object with a "children" array of stats var bundles = extractBundles(statsResult.toJson({ errorDetails: false })); bundles.forEach(function(stats) { if (log) { log("webpack built " + (stats.name ? stats.name + " " : "") + stats.hash + " in " + stats.time + "ms"); } eventStream.publish({ name: stats.name, action: action, time: stats.time, hash: stats.hash, warnings: stats.warnings || [], errors: stats.errors || [], modules: buildModuleMap(stats.modules) }); }); }
這個函數就是打印下built的信息,并調用eventStream.publish
pathMatch助手函數
function pathMatch(url, path) { if (url == path) return true; var q = url.indexOf("?"); if (q == -1) return false; return url.substring(0, q) == path; }
為 /__webpack_hmr 或 /__webpack_hmr?xyz=123 均返回true
process-update.js
這塊主要是調用webpack內部hot的一些api,如module.hot.status, module.hot.check, module.hot...
作者基本也是參考webpack的hot目錄下一些js文件寫法以及HotModuleReplacement.runtime.js
由于是初探嘛,偷偷懶,有空補全下吧,請不要丟?
client.js
client.js是與你的entry開發時打包到一起的一個文件,當然它還引入了client-overlay.js就是用來展示build錯誤時的樣式
__resourceQuery是webpack的一個變量,這里其值為?path=/__webpack_hmr&timeout=20000
// 選項,參數 var options = { path: "/__webpack_hmr", timeout: 20 * 1000, overlay: true, reload: false, log: true, warn: true }; if (__resourceQuery) { var querystring = require("querystring"); var overrides = querystring.parse(__resourceQuery.slice(1)); if (overrides.path) options.path = overrides.path; if (overrides.timeout) options.timeout = overrides.timeout; if (overrides.overlay) options.overlay = overrides.overlay !== "false"; if (overrides.reload) options.reload = overrides.reload !== "false"; if (overrides.noInfo && overrides.noInfo !== "false") { options.log = false; } if (overrides.quiet && overrides.quiet !== "false") { options.log = false; options.warn = false; } if (overrides.dynamicPublicPath) { options.path = __webpack_public_path__ + options.path; } } // 主要部分 if (typeof window === "undefined") { // do nothing } else if (typeof window.EventSource === "undefined") { console.warn( "webpack-hot-middleware"s client requires EventSource to work. " + "You should include a polyfill if you want to support this browser: " + "https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events#Tools" ); } else { connect(window.EventSource); } function connect(EventSource) { var source = new EventSource(options.path); var lastActivity = new Date(); source.onopen = handleOnline; source.onmessage = handleMessage; source.onerror = handleDisconnect; var timer = setInterval(function() { if ((new Date() - lastActivity) > options.timeout) { handleDisconnect(); } }, options.timeout / 2); function handleOnline() { if (options.log) console.log("[HMR] connected"); lastActivity = new Date(); } function handleMessage(event) { lastActivity = new Date(); if (event.data == "uD83DuDC93") { return; } try { processMessage(JSON.parse(event.data)); } catch (ex) { if (options.warn) { console.warn("Invalid HMR message: " + event.data + " " + ex); } } } function handleDisconnect() { clearInterval(timer); source.close(); setTimeout(function() { connect(EventSource); }, options.timeout); } } // 導出一些方法 if (module) { module.exports = { subscribeAll: function subscribeAll(handler) { subscribeAllHandler = handler; }, subscribe: function subscribe(handler) { customHandler = handler; }, useCustomOverlay: function useCustomOverlay(customOverlay) { if (reporter) reporter.useCustomOverlay(customOverlay); } }; }
這里,每10s鐘檢查當前時間和上次活躍(onopen, on message)的時間的間隔是否超過20s,超過20s則認為失去連接,則調用handleDisconnect
eventsource主要監聽3個方法:
onopen,記錄下當前時間
onmessage,記錄下當前時間,發現心跳就直接返回,否則嘗試processMessage(JSON.parse(event.data))
onerror,調用handleDisconnect,停止定時器,eventsource.close,手動20s后重連
module.exports的方法,主要給自定義用的
其中useCustomeOverlay,就是自定義報錯的那層dom層
createReporter函數
var reporter; // the reporter needs to be a singleton on the page // in case the client is being used by mutliple bundles // we only want to report once. // all the errors will go to all clients var singletonKey = "__webpack_hot_middleware_reporter__"; if (typeof window !== "undefined" && !window[singletonKey]) { reporter = window[singletonKey] = createReporter(); } function createReporter() { var strip = require("strip-ansi"); var overlay; if (typeof document !== "undefined" && options.overlay) { overlay = require("./client-overlay"); } return { problems: function(type, obj) { if (options.warn) { console.warn("[HMR] bundle has " + type + ":"); obj[type].forEach(function(msg) { console.warn("[HMR] " + strip(msg)); }); } if (overlay && type !== "warnings") overlay.showProblems(type, obj[type]); }, success: function() { if (overlay) overlay.clear(); }, useCustomOverlay: function(customOverlay) { overlay = customOverlay; } }; }
createReport就是有stats有warning或error的時候,讓overlay顯示出來
如果build succes那么在有overlay的情況下,將其clear掉
如下圖,故意在src/index.js弄個語法錯誤,讓其編譯不通過
processMessage函數
var processUpdate = require("./process-update"); var customHandler; var subscribeAllHandler; function processMessage(obj) { switch(obj.action) { case "building": if (options.log) console.log("[HMR] bundle rebuilding"); break; case "built": if (options.log) { console.log( "[HMR] bundle " + (obj.name ? obj.name + " " : "") + "rebuilt in " + obj.time + "ms" ); } // fall through case "sync": if (obj.errors.length > 0) { if (reporter) reporter.problems("errors", obj); } else { if (reporter) { if (obj.warnings.length > 0) reporter.problems("warnings", obj); reporter.success(); } processUpdate(obj.hash, obj.modules, options); } break; default: if (customHandler) { customHandler(obj); } } if (subscribeAllHandler) { subscribeAllHandler(obj); } }
參數obj其實就是后端傳過來的data,JSON.parse里一下
action分為"building", built", "sync",均為middleware.js服務端傳過來的
至于其他,應該是用戶自定義處理的
資料:1) http://cjihrig.com/blog/the-s...
2) https://www.html5rocks.com/en...
3) http://cjihrig.com/blog/serve...
4) http://www.howopensource.com/...
5) https://cnodejs.org/topic/570...
6) http://tldp.org/HOWTO/TCP-Kee...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/86630.html
摘要:把處理后的配置文件傳遞給服務器,不過我們在使用它之前,需要把它改造成中間件。因為通過生成的模塊是寫入到內存中的,所以我們需要修改開發環境中的配置項修改此配置項安裝封裝成中間件。 前言 webpack提供了webpack-dev-server模塊來啟動一個簡單的web服務器,為了更大的自由度我們可以自己配置一個服務器,下面介紹如何用koa2來實現。 wepack-dev-middlewa...
摘要:如果修改的是里的文件,保存后,服務器將自動重啟,瀏覽器會在服務器重啟完畢后自動刷新。從開始首先,已經想到了開發流程中的自動刷新,這就是。 在以前的一篇文章BrowserSync,迅捷從免F5開始中,我介紹了BrowserSync這樣一個出色的開發工具。通過BrowserSync我感受到了這樣一個理念:如果在一次ctrl + s保存后可以自動刷新,然后立即看到新的頁面效果,那會是很棒的開...
摘要:前言由于博主最近又閑下來了,之前覺得的官方文檔比較難啃一直放到現在。文章會逐步分析每個處理的用意當然是博主自己的理解,不足之處歡迎指出溝通交流。后續將會補上構建生產的配置分析,案例參考。前端臨床手札構建逐步解構下 前言 由于博主最近又閑下來了,之前覺得webpack的官方文檔比較難啃一直放到現在。細心閱讀多個webpack配置案例后覺得還是得自己寫個手腳架,當然這個案例是基于vue的,...
摘要:的架構設計促使第三方開發者讓核心發揮出無限的潛力。當然建置比起開發是較進階的議題,因為我們必須要理解內部的一些事件。這個編譯結果包含的訊息包含模組的狀態,編譯後的資源檔,發生異動的檔案,被觀察的相依套件等。 本文將對 webpack 周邊的 middleware 與 plugin 套件等作些介紹,若您對於 webpack 還不了解可以參考這篇彙整的翻譯。 webpack dev ser...
摘要:此處用到跑服務器命令行輸入即可,會忽略的改變,其余時候都會自動重啟服務器不用的話,就用啟動服務器在此處是用的做后臺,并且配置了的信息,不然,在跑服務器前,要先輸入命令來生成文件。并且也用到了熱加載,在代碼改變后,立馬更新頁面 package.json: 此處用到nodemon跑服務器:命令行輸入:npm run serve即可,會忽略components的改變,其余時候都會自動重啟服務...
閱讀 3110·2021-11-10 11:36
閱讀 3312·2021-10-13 09:40
閱讀 6051·2021-09-26 09:46
閱讀 662·2019-08-30 15:55
閱讀 1410·2019-08-30 15:53
閱讀 1580·2019-08-29 13:55
閱讀 2997·2019-08-29 12:46
閱讀 3204·2019-08-29 12:34