今天来看一下hot middleware的原理
创建express 的middleware
12345678910111213141516171819202122232425262728293031323334353637383940414243function 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;var closed = false;if (compiler.hooks) {compiler.hooks.invalid.tap('webpack-hot-middleware', onInvalid);compiler.hooks.done.tap('webpack-hot-middleware', onDone);} else {compiler.plugin('invalid', onInvalid);compiler.plugin('done', onDone);}function onInvalid() {if (closed) return;latestStats = null;if (opts.log) opts.log('webpack building...');eventStream.publish({ action: 'building' });}function onDone(statsResult) {if (closed) return;// Keep hold of latest stats so they can be propagated to new clientslatestStats = statsResult;publishStats('built', latestStats, eventStream, opts.log);}var middleware = function(req, res, next) {if (closed) return next();if (!pathMatch(req.url, opts.path)) return next();eventStream.handler(req, res);if (latestStats) {publishStats('sync', latestStats, eventStream);}Ï};}来看看createEventStream是啥东西
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364function createEventStream(heartbeat) {var clientId = 0;// 记录当前有几个客户端连接着hot servervar clients = {};function everyClient(fn) {Object.keys(clients).forEach(function(id) {fn(clients[id]);});}// 这个可以在浏览器中看到 10s有一次星星发送var interval = setInterval(function heartbeatTick() {everyClient(function(client) {client.write('data: wjjwjwj');});}, heartbeat).unref();return {// 关闭所有客户端和hot server的连接close: function() {clearInterval(interval);everyClient(function(client) {if (!client.finished) client.end();});clients = {};},// 建立hot server的连接handler: function(req, res) {var headers = {'Access-Control-Allow-Origin': '*','Content-Type': 'text/event-stream;charset=utf-8','Cache-Control': 'no-cache, no-transform',// While behind nginx, event stream should not be buffered:// http://nginx.org/docs/http/ngx_http_proxy_module.html#proxy_buffering'X-Accel-Buffering': 'no',};var isHttp1 = !(parseInt(req.httpVersion) >= 2);if (isHttp1) {req.socket.setKeepAlive(true);Object.assign(headers, {Connection: 'keep-alive',});}res.writeHead(200, headers);res.write('\n');var id = clientId++;// 缓存客户端的resclients[id] = res;req.on('close', function() {if (!res.finished) res.end();delete clients[id];});},// 向客户端发送消息publish: function(payload) {everyClient(function(client) {client.write('data: ' + JSON.stringify(payload) + '\n\n');});},};}
3.再来看看这个函数publishStats
- webpack插件系统发布消息1234567function onDone(statsResult) {if (closed) return;// Keep hold of latest stats so they can be propagated to new clientslatestStats = statsResult;console.log(statsResult, 'statsResultstatsResultstatsResult');publishStats('built', latestStats, eventStream, opts.log);}
这就是服务端大概的内容了
- webpack的entery “webpack-hot-middleware/client.js”是啥123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129var options = {path: '/__webpack_hmr',timeout: 20 * 1000,overlay: true,reload: false,log: true,warn: true,name: '',autoConnect: true,overlayStyles: {},overlayWarnings: false,ansiColors: {},};function EventSourceWrapper() {var source;var lastActivity = new Date();var listeners = [];init();// 长时间没有活动会断线重连var timer = setInterval(function() {if (new Date() - lastActivity > options.timeout) {handleDisconnect();}}, options.timeout / 2);function init() {source = new window.EventSource(options.path);source.onopen = handleOnline;source.onerror = handleDisconnect;source.onmessage = handleMessage;}function handleOnline() {if (options.log) console.log('[HMR] connected');lastActivity = new Date();}// eventsource 从服务器发来的消息 客户端打印出来function handleMessage(event) {lastActivity = new Date();for (var i = 0; i < listeners.length; i++) {listeners[i](event);}}// 断线重连function handleDisconnect() {clearInterval(timer);source.close();setTimeout(init, options.timeout);}return {addMessageListener: function(fn) {listeners.push(fn);},};}getEventSourceWrapper().addMessageListener(handleMessage);function handleMessage(event) {if (event.data == '\uD83D\uDC93') {return;}try {processMessage(JSON.parse(event.data));} catch (ex) {if (options.warn) {console.warn('Invalid HMR message: ' + event.data + '\n' + ex);}}}// 客户端打印消息function processMessage(obj) {console.log(obj, 'obj obj');switch (obj.action) {case 'building':if (options.log) {console.log('[HMR] bundle ' +(obj.name ? "'" + obj.name + "' " : '') +'rebuilding');}break;case 'built':if (options.log) {console.log('[HMR] bundle ' +(obj.name ? "'" + obj.name + "' " : '') +'rebuilt in ' +obj.time +'ms');}// fall throughcase 'sync':if (obj.name && options.name && obj.name !== options.name) {return;}var applyUpdate = true;if (obj.errors.length > 0) {if (reporter) reporter.problems('errors', obj);applyUpdate = false;} else if (obj.warnings.length > 0) {if (reporter) {var overlayShown = reporter.problems('warnings', obj);applyUpdate = overlayShown;}} else {if (reporter) {reporter.cleanProblemsCache();reporter.success();}}if (applyUpdate) {processUpdate(obj.hash, obj.modules, options);}break;default:if (customHandler) {customHandler(obj);}}}
以后有机会在介绍eventsource