webpack-dev-server
服務器 webpack
實例 Server
服務器 webpack
的done
事件回調 編譯完成向客戶端發送消息(hash和描述文件oldhash.js和oldhash.json)
express
應用app
webpack-dev-middleware
中間件 負責返回生成的文件 http
服務 啓動 socket
實現瀏覽器和服務器的通訊(這裏先發送一次hash
,將socket
存入到第四步,初次編譯完第四步中的socket
是空,不會觸發hash
下發) 客戶端: html
.webpack-dev-server/client-src
下文件監聽hash
,保存此hash
值 ok
消息執行reload
更新 reload
中進行判斷,若是支持熱更新執行webpackHotUpdate
,不支持的話直接刷新頁面 webpack/hot/dev-server.js
監聽webpackHotUpdate
而後執行 check()
方法進行檢測 check
方法裏面調用module.hot.check
JsonpMainTemplate.runtime
的hotDownloadmainfest
方法,向server
端發送ajax
請求,服務端返回一個Mainfest
文件,該文件包含全部要更新模塊的hash
值和chunk
名 JsonpMainTemplate.runtime
的 hotDownloadUpdateChunk
方法經過jsonp
請求獲取到最新的模塊代碼 JsonpMainTemplate.runtime
的 webpackHotUpdate
方法,裏面會調用hotAddUpdateChunk
方法,用心的模塊替換掉舊的模塊 HotMoudleReplacement.runtime.js
的 hotAddUpdateChunk
方法動態更新模塊代碼 hotApply
方法熱更新客戶端代碼輔助流程理解:
webpack
socket.on("hash")和socket.on("ok")
拿到服務端首次生成的hash
值reloadApp
這個函數 這裏派發 hotEmitter.emit('webpackHotUpdate')
事件hotEmitter.on('webpackHotUpdate')
這個函數,hotCurrentHash 爲 undefined
而後將首次拿到的 currentHash
賦值給 hotCurrentHash
socket.on("hash")和socket.on("ok")
拿到最新的代碼編譯後的 hash
hotCheck
會經過ajax請求服務端拉取最新的 hot-update.json
描述文件 說明哪些模塊哪些chunk
(大集合)發生了更新改變hotDownloadUpdateChunk
去建立jsonp
拉取到最新的更新後的代碼,返回形式爲: webpackHotUpdate(id, {...})
window.webpackHotUpdate
函數來處理render
函數執行一下hotCurrentHash = currentHash
置舊hash
方便下次比較2、根據流程實現代碼:web
客戶端:ajax
//發佈訂閱
class Emitter{
constructor(){
this.listeners = {}
}
on(type, listener){
this.listeners[type] = listener
}
emit(){
this.listeners[type] && this.listeners[type]()
}
}
let socket = io('/');
let hotEmitter = new Emitter();
const onConnected = () => {
console.log('客戶端鏈接成功')
}
//存放服務端傳給的hash 本次的hash 和 上一次的hash
let currentHash, hotCurrentHash;
socket.on("hash",(hash)=>{
currentHash = hash
});
//收到ok事件以後
socket.on('ok',()=>{
//true表明熱更新
reloadApp(true);
})
hotEmitter.on('webpackHotUpdate',()=>{
if(!hotCurrentHash || hotCurrentHash == currentHash){
return hotCurrentHash = currentHash
}
hotCheck()
})
function hotCheck(){
hotDownloadMainfest().then((update)=>{
let chunkIds = Object.keys(update.c)
chunkIds.forEach(chunkId=>{
hotDownloadUpdateChunk(chunkId);
})
})
}
function hotDownloadUpdateChunk(chunkId){
let script = document.createElement('script');
script.charset = 'utd-8'
script.src = '/'+chunkId+'.'+hotCurrentHash+'.hot-update.js'
document.head.appendChild(script);
}
//此方法用來詢問服務器到底這一次編譯相對於上一次編譯改變了哪些chunk、哪些模塊
function hotDownloadMainfest(){
return new Promise(function(resolve){
let request = new XMLHttpRequest()
let requestPath = '/'+hotCurrentHash+".hot-update.json"
request.open('GET', requestPath, true)
request.onreadystatechange = function(){
if(request.readyState === 4){
let update = JSON.parse(request.responseText)
resolve(update)
}
}
request.send()
})
}
function reloadApp(hot){
if(hot){
//發佈
hotEmitter.emit('webpackHotUpdate')
}else{
//不支持熱更新直接刷新
window.location.reload()
}
}
window.hotCreateModule = function(){
let hot = {
_acceptedDependencies:{},
accept: function(deps, callback){
//callback 對應render回調
for(let i = 0; i < deps.length; i++){
hot._acceptedDependencies[deps[i]] = callback
}
}
}
return hot
}
//經過jsonp獲取的最新代碼 jsonp中有webpackHotUpdate這個函數
window.webpackHotUpdate = function(chunkId, moreModules){
for(let moduleId in moreModules){
//從模塊緩存中取到老的模塊定義
let oldModule - __webpack_requrie__.c[moduleId]
let {parents, children} = oldModule
//parents哪些模塊引用和這個模塊 children這個模塊用了哪些模塊
//更新緩存爲最新代碼
let module = __webpack_requrie__.c[moduleId] = {
i: moduleId,
l: false,
exports: {},
parents,
children,
hot: window.hotCreateModule(moduleId)
}
moreModules[moduleId].call(module.exports, module, module.exports, __webpack_requrie__)
module.l = true
//index.js ---import a.js import b.js a.js和b.js的父模塊(index.js)
parents.forEach(par=>{
//父中的老模塊的對象
let parModule = __webpack_requrie__.c[par]
parModule && parModule.hot && parModule.hot._acceptedDependencies[moduleId] && parModule.hot._acceptedDependencies[moduleId]()
})
//熱更新以後 本次的hash變爲上一次的hash 置舊操做
hotCurrentHash = currentHash
}
}
socket.on("connect", onConnected);
複製代碼
服務端實現:express
const path = require('path');
const express = require('express');
const mime = require('mime');
const webpack = require('webpack');
const MemoryFileSystem = require('memory-fs');
const config = require('./webpack.config');
//compiler表明整個webpack編譯任務,全局只有一個
const compiler = webpack(config);
class Server{
constructor(compiler){
this.compiler = compiler;
let sockets = [];
let lasthash;//每次編譯完成後都會產生一個stats對象,其中有一個hash值表明這一次編譯結果hash就是一個32的字符串
compiler.hooks.done.tap('webpack-dev-server',(stats)=>{
lasthash = stats.hash;
//每當新一個編譯完成後都會向客戶端發送消息
sockets.forEach(socket=>{
//先向客戶端發送最新的hash值
//每次編譯都會產生一個hash值,另外若是是熱更新的話,還會產出二個補丁文件。
//裏面描述了從上一次結果到這一次結果都有哪些chunk和模塊發生了變化
socket.emit('hash',stats.hash);
//再向客戶端發送一個ok
socket.emit('ok');
});
});
let app = new express();
//以監控的模塊啓動一次webpack編譯,當編譯成功以後執行回調
compiler.watch({},err=>{
console.log('又一次編譯任務成功完成了')
});
let fs = new MemoryFileSystem();
//若是你把compiler的輸出文件系統改爲了 MemoryFileSystem的話,則之後再產出文件都打包內存裏去了
compiler.outputFileSystem = fs;
function middleware(req, res, next) {
// /index.html dist/index.html
let filename = path.join(config.output.path,req.url.slice(1));
let stat = fs.statSync(filename);
if(stat.isFile()){//判斷是否存在這個文件,若是在的話直接把這個讀出來發給瀏覽器
let content = fs.readFileSync(filename);
let contentType = mime.getType(filename);
res.setHeader('Content-Type',contentType);
res.statusCode = res.statusCode || 200;
res.send(content);
}else{
// next();
return res.senStatus(404);
}
}
//express app 實際上是一個請求監聽函數
app.use(middleware);
this.server = require('http').createServer(app);
let io = require('socket.io')(this.server);
//啓動一個 websocket服務器,而後等待鏈接來到,鏈接到來以後socket
io.on('connection',(socket)=>{
sockets.push(socket);
socket.emit('hash',lasthash);
//再向客戶端發送一個ok
socket.emit('ok');
});
}
listen(port){
this.server.listen(port,()=>{
console.log(`服務器已經在${port}端口上啓動了`)
});
}
}
let server = new Server(compiler);
server.listen(8000);複製代碼