cnpm i [email protected] [email protected] [email protected] mime html-webpack-plugin express socket.io -S
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
filename: "main.js",
path: path.join(__dirname, "dist"),
},
devServer: {
contentBase: path.join(__dirname, "dist"),
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
filename: "index.html",
}),
],
};
index.js
var root = document.getElementById("root");
function render() {
let title = require("./title").default;
root.innerHTML = title;
}
render();
title.js
export default "hello";
index.html
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>
<div id="root">div>
body>
html>
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
filename: "main.js",
path: path.join(__dirname, "dist"),
},
devServer: {
hot: true,
contentBase: path.join(__dirname, "dist"),
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
filename: "index.html",
}),
new webpack.HotModuleReplacementPlugin(),
],
};
var root = document.getElementById("root");
function render() {
let title = require("./title").default;
root.innerHTML = title;
}
render();
if(module.hot){
module.hot.accept(['./title'],render)
}
client.js
let socket = io("/");
class Emitter {
constructor() {
this.listeners = {};
}
on(type, listener) {
this.listeners[type] = listener;
}
emit(type) {
this.listeners[type] && this.listeners[type]();
}
}
let hotEmitter = new Emitter();
const onConnected = () => {
console.log("客户端连接成功");
};
socket.on("connect", onConnected);
let hotCurrentHash; //lastHash 上一次 hash值
let currentHash; //这一次的hash值
socket.on("hash", (hash) => {
console.log(hash);
currentHash = hash;
});
socket.on("ok", () => {
reloadApp(true);
});
hotEmitter.on("webpackHotUpdate", () => {
if (!hotCurrentHash || hotCurrentHash == currentHash) {
return (hotCurrentHash = currentHash);
}
hotCheck();
});
function hotCheck() {
hotDownloadManifest().then((update) => {
let chunkIds = Object.keys(update.c);
chunkIds.forEach((chunkId) => {
hotDownloadUpdateChunk(chunkId);
});
});
}
function hotDownloadUpdateChunk(chunkId) {
let script = document.createElement("script");
script.charset = "utf-8";
// /main.xxxx.hot-update.js
script.src = "/" + chunkId + "." + hotCurrentHash + ".hot-update.js";
document.head.appendChild(script);
}
//此方法用来去询问服务器到底这一次编译相对于上一次编译改变了哪些chunk?哪些模块?
function hotDownloadManifest() {
return new Promise(function (resolve) {
let request = new XMLHttpRequest();
//hot-update.json文件里存放着从上一次编译到这一次编译
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();
});
}
//当收到ok事件后,会重新刷新app
function reloadApp(hot) {
if (hot) {
//如果hot为true 走热更新的逻辑
hotEmitter.emit("webpackHotUpdate");
} else {
//如果不支持热更新,则直接重新加载
window.location.reload();
}
}
window.hotCreateModule = function () {
let hot = {
_acceptedDependencies: {},
dispose() {
//销毁老的元素
},
accept: function (deps, callback) {
for (let i = 0; i < deps.length; i++) {
//hot._acceptedDependencies={'./title',render}
hot._acceptedDependencies[deps[i]] = callback;
}
},
};
return hot;
};
//当客户端把最新的代码拉到浏览之后
window.webpackHotUpdate = function (chunkId, moreModules) {
//循环新拉来的模块
for (let moduleId in moreModules) {
//从模块缓存中取到老的模块定义
let oldModule = __webpack_require__.c[moduleId];
//parents哪些模块引用这个模块 children这个模块引用了哪些模块
//parents=['./src/index.js']
let { parents, children } = oldModule;
//更新缓存为最新代码 缓存进行更新
let module = (__webpack_require__.c[moduleId] = {
i: moduleId,
l: false,
exports: {},
parents,
children,
hot: window.hotCreateModule(moduleId),
});
moreModules[moduleId].call(
module.exports,
module,
module.exports,
__webpack_require__
);
module.l = true; //状态变为加载就是给module.exports 赋值了
parents.forEach((parent) => {
let parentModule = __webpack_require__.c[parent];
//_acceptedDependencies={'./src/title.js',render}
parentModule &&
parentModule.hot &&
parentModule.hot._acceptedDependencies[moduleId] &&
parentModule.hot._acceptedDependencies[moduleId]();
});
hotCurrentHash = currentHash;
}
};
服务端
const path = require("path");
const express = require("express");
const mime = require("mime");
const webpack = require("webpack");
//1. 启动webpack-dev-server服务器
//2. 创建webpack实例
let config = require("./webpack.config");
let compiler = webpack(config);
class Server {
constructor(compiler) {
this.compiler = compiler;
//4. 添加webpack的`done`事件回调,在编译完成后会向浏览器发送消息
let lastHash;
let sockets = [];
compiler.hooks.done.tap("webpack-dev-server", (stats) => {
lastHash = stats.hash;
console.log(lastHash);
sockets.forEach((socket) => {
socket.emit("hash", stats.hash);
socket.emit("ok");
});
});
//5. 创建express应用app
let app = new express();
//6. 使用监控模式开始启动webpack编译,在 webpack 的 watch 模式下,文件系统中某一个文件发生修改,webpack 监听到文件变化,根据配置文件对模块重新编译打包,并将打包后的代码通过简单的 JavaScript 对象保存在内存中
compiler.watch(config.watchOptions || {}, (err) => {
console.log("编译成功");
});
//7. 设置文件系统为内存文件系统
const MemoryFileSystem = require("memory-fs");
const fs = new MemoryFileSystem();
compiler.outputFileSystem = fs;
//8. 添加webpack-dev-middleware中间件
const devMiddleware = (req, res, next) => {
if (req.url === "/favicon.ico") {
return res.sendStatus(404);
}
let filename = path.join(config.output.path, req.url.slice(1));
console.error(filename);
if (fs.statSync(filename).isFile()) {
let content = fs.readFileSync(filename);
res.header("Content-Type", mime.getType(filename));
res.send(content);
} else {
next();
}
};
app.use(devMiddleware);
//8. 创建http服务器并启动服务
this.server = require("http").createServer(app);
// 10. 使用sockjs在浏览器端和服务端之间建立一个 websocket 长连接,将 webpack 编译打包的各个阶段的状态信息告知浏览器端,浏览器端根据这些`socket`消息进行不同的操作。当然服务端传递的最主要信息还是新模块的`hash`值,后面的步骤根据这一`hash`值来进行模块热替换
let io = require("socket.io")(this.server);
io.on("connection", (socket) => {
sockets.push(socket);
if (lastHash) {
socket.emit("hash", lastHash);
socket.emit("ok");
}
});
}
//9. 创建http服务器并启动服务
listen(port) {
this.server.listen(port, () => {
console.log(port + "服务启动成功!");
});
}
}
//3. 创建Server服务器
let server = new Server(compiler);
server.listen(8000);