有没有人知道 webpackdevserver-dev-middleware 这个东西是用来干嘛的

登录 SF-backyard在 SegmentFault,学习技能、解决问题
每个月,我们帮助 1000 万的开发者解决各种各样的技术问题。并助力他们在技术能力、职业生涯、影响力上获得提升。
问题对人有帮助,内容完整,我也想知道答案
问题没有实际价值,缺少关键内容,没有改进余地
我认真地看了一下github上面的介绍(,应该是用来做服务端开发的时候,在内存中生成打包好的js文件,不用真正写到硬盘上。但我非常疑问,用这个生成的打包好的js文件,怎么让express或者koa引用到??官网没有一个例子说明,并不太懂。
还是说这个项目已经废弃掉?我想达到的目的是,写node服务端的程序时,在开发阶段让webpack与express/koa结合在一起,改变源代码的时候express实时更新,要用什么组件去实现这个功能?
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
先從 webpack dev server 是什麼談起?
webpack dev server 是一個開發伺服器,內建 webpack 使用的 live reloading 功能。
那 webpack dev middleware 是啥?
它就是一個用來組織包裝 webpack 使其可以變成中介軟體,或稱中間件的容器。回想一下 express 你大概可以明白關於 middleware 的用途,就是在輸入到輸出的過程中 加工 的一種手段。單純說 middleware 的話我們可以想成一系列任務, 動作(actions stack),不只 express 有,在 Ruby 中的 rake 也具備這種機制。
先看看的說明
The webpack-dev-server is a little node.js Express server, which uses the webpack-dev-middleware to serve a webpack bundle.
從頭說起的話就是 webpack 本身只負責打包編譯的功能 bundle, webpack-dev-server 當然就是協助我們開發的伺服器,這個伺服器底層是靠 express 來實作的,接著思考一下我們要如何更新(live reload)呢? 當然是需要取得 webpack 編好的資料啊,於是就需要在從 request 到 response 的過程中透過 express 的 middleware 取得資料,而方法就是透過 webpack-dev-middleware 。
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
webpack 文档写的非常糟糕。请认真阅读文档里的设置,output里有个叫publicPath的属性。
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
可以看看我的 里面有用到
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
可以看下我写的关于webpack-dev-server使用方法的文章。里面有你想要的答案
该答案已被忽略,原因:
同步到新浪微博
分享到微博?
关闭理由:
删除理由:
忽略理由:
推广(招聘、广告、SEO 等)方面的内容
与已有问题重复(请编辑该提问指向已有相同问题)
答非所问,不符合答题要求
宜作评论而非答案
带有人身攻击、辱骂、仇恨等违反条款的内容
无法获得确切结果的问题
非开发直接相关的问题
非技术提问的讨论型问题
其他原因(请补充说明)
我要该,理由是:
在 SegmentFault,学习技能、解决问题
每个月,我们帮助 1000 万的开发者解决各种各样的技术问题。并助力他们在技术能力、职业生涯、影响力上获得提升。在用Node.js+Webpack构建的方式进行开发时,
我们希望能实现修改代码能实时刷新页面UI的效果.
这个特性webpack本身是支持的, 而且基于koa也有现成的koa-webpack-hot-middleware 和 koa-webpack-dev-middleware 封装好的组件支持.
不过这里如果需要支持Node.js服务器端修改代码自动重启webpack自动编译功能就需要cluster来实现.
今天这里要讲的是如何在koa和egg应用实现Node.js应用重启中的webpack热更新功能. 要实现egg项目中webpack友好的开发体验, 需要解决如下三个问题.
如何解决Node.js服务器端代码修改应用重启避免webpack重新编译.
如何访问js,css,image等静态资源.
如何处理本地开发webpack热更新内存存储读取和线上应用本机文件读取逻辑分离.
基于koa的webpack编译和热更新实现
在koa项目中, 通过koa-webpack-dev-middleware和koa-webpack-hot-middleware可以实现webpack编译内存存储和热更新功能, 代码如下:
const compiler = webpack(webpackConfig);
const devMiddleware = require('koa-webpack-dev-middleware')(compiler, options);
const hotMiddleware = require('koa-webpack-hot-middleware')(compiler, options);
app.use(devMiddleware);
app.use(hotMiddleware);
如果按照上面实现, 可以满足修改修改客户端代码实现webpack自动变编译和UI界面热更新的功能, 但如果是修改Node.js服务器端代码重启后就会发现webpack会重新编译,
这不是我们要的效果.原因是因为middleware是依赖app的生命周期, 当app销毁时, 对应webpack compiler实例也就没有了, 重启时会重新执行middleware初始化工作.
针对这个我们可以通过Node.js cluster实现, 大概思路如下:
通过cluster worker 启动App应用
if (cluster.isWorker) {
const koa = require('koa');
app.listen(8888, () =&{
app.logger.info('The server is running on port: 9999');
通过cluster master 启动一个新的koa应用, 并启动 webpack 编译.
const cluster = require('cluster');
const chokidar = require('chokidar');
if (cluster.isMaster) {
const koa = require('koa');
const app = koa();
const compiler = webpack([clientWebpackConfig,serverWebpackConfig]);
const devMiddleware = require('koa-webpack-dev-middleware')(compiler);
const hotMiddleware = require('koa-webpack-hot-middleware')(compiler);
app.use(devMiddleware);
app.use(hotMiddleware);
let worker = cluster.fork();
chokidar.watch(config.dir, config.options).on('change', path =&{
console.log(`${path} changed`);
worker.kill();
worker = cluster.fork().on('listening', (address) =&{
console.log(`[master] listening: worker ${worker.id}, pid:${worker.process.pid} ,Address:${address.address } :${address.port}`);
通过chokidar库监听文件夹的文件修改, 然后重启worker, 这样就能保证webpack compiler实例不被销毁.
const watchConfig = {
dir: [ 'controller', 'middleware', 'lib', 'model', 'app.js', 'index.js' ],
options: {}
let worker = cluster.fork();
chokidar.watch(watchConfig.dir, watchConfig.options).on('change', path =&{
console.log(`${path} changed`);
worker && worker.kill();
worker = cluster.fork().on('listening', (address) =&{
console.log(`[master] listening: worker ${worker.id}, pid:${worker.process.pid} ,Address:${address.address } :${address.port}`);
worker 通过process.send 向 master 发现消息, process.on 监听 master返回的消息
首先我们看看本地文件读取的实现, 在context上面挂载readFile方法, 进行view render时, 调用app.context.readFile 方法.
app.context.readFile = function(fileName){
const filePath = path.join(config.baseDir, config.staticDir, fileName);
return new Promise((resolve, reject) =&{
fs.readFile(filePath, CHARSET, function(err, data){
if (err) {
reject(err);
resolve(data);
通过覆写worker app.context.readFile 方法, 这样进行本地开发时,开启该插件就可以无缝的从webpack编译内存系统里面读取文件
app.context.readFile = (fileName) =&{
return new Promise((resolve, reject) =&{
process.send({ action: Constant.EVENT_FILE_READ, fileName });
process.on(Constant.EVENT_MESSAGE, (msg) =&{
resolve(msg.content);
master 通过监听worker发过来的消息, 获取webpack编译进度和读取webpack compiler内存系统文件内容
cluster.on(Constant.EVENT_MESSAGE, (worker, msg) =&{
switch (msg.action) {
case Constant.EVENT_WEBPACK_BUILD_STATE: {
const data = {
action: Constant.EVENT_WEBPACK_BUILD_STATE,
state: app.webpack_client_build_success && app.webpack_server_build_success
worker.send(data);
case Constant.EVENT_FILE_READ: {
const fileName = msg.fileName;
const compiler = app.compiler;
const filePath = path.join(compiler.outputPath, fileName);
const content = app.compiler.outputFileSystem.readFileSync(filePath).toString(Constant.CHARSET);
worker.send({ fileName, content });
} catch (e) {
console.log(`read file ${fileName} error`, e.toString());
基于egg的webpack编译和热更新实现
通过上面koa的实现思路, egg实现就更简单了. 因为egg已经内置了worker和agent通信机制以及自动重启功能.
worker和agent通信机制: https://eggjs.org/zh-cn/core/cluster-and-ipc.html
实现egg项目服务器代码修改项目自动重启的功能可以使用插件.
app.js (worker) 通过 检测webpack 编译进度
通过app.messenger.sendToAgent 向agent发送消息
通过app.messenger.on 监听agent发送过来的消息
app.use(function* (next) {
if (app.webpack_server_build_success && app.webpack_client_build_success) {
yield* next;
const serverData = yield new Promise(resolve =& {
this.app.messenger.sendToAgent(Constant.EVENT_WEBPACK_SERVER_BUILD_STATE, {
webpackBuildCheck: true,
this.app.messenger.on(Constant.EVENT_WEBPACK_SERVER_BUILD_STATE, data =& {
resolve(data);
app.webpack_server_build_success = serverData.state;
const clientData = yield new Promise(resolve =& {
this.app.messenger.sendToAgent(Constant.EVENT_WEBPACK_CLIENT_BUILD_STATE, {
webpackBuildCheck: true,
this.app.messenger.on(Constant.EVENT_WEBPACK_CLIENT_BUILD_STATE, data =& {
resolve(data);
app.webpack_client_build_success = clientData.state;
if (!(app.webpack_server_build_success && app.webpack_client_build_success)) {
if (app.webpack_loading_text) {
this.body = app.webpack_loading_text;
const filePath = path.resolve(__dirname, './lib/template/loading.html');
this.body = app.webpack_loading_text = fs.readFileSync(filePath, 'utf8');
yield* next;
app.messenger.on(Constant.EVENT_WEBPACK_SERVER_BUILD_STATE, data =& {
app.webpack_server_build_success = data.state;
app.messenger.on(Constant.EVENT_WEBPACK_CLIENT_BUILD_STATE, data =& {
app.webpack_client_build_success = data.state;
agent.js 启动koa实例和webpack编译流程
这里client和server编译单独启动koa实例, 而不是一个是因为在测试时发现编译会导致热更新冲突.
启动webpack client 编译模式, 负责编译browser运行文件(js,css,image等静态资源)
'use strict';
const webpack = require('webpack');
const koa = require('koa');
const cors = require('kcors');
const app = koa();
app.use(cors());
const Constant = require('./constant');
const Utils = require('./utils');
module.exports = agent =& {
const config = agent.config.webpack;
const webpackConfig = config.clientConfig;
const compiler = webpack([webpackConfig]);
compiler.plugin('done', compilation =& {
// Child extract-text-webpack-plugin:
compilation.stats.forEach(stat =& {
stat.compilation.children = stat.compilation.children.filter(child =& {
return child.name !== 'extract-text-webpack-plugin';
agent.messenger.sendToApp(Constant.EVENT_WEBPACK_CLIENT_BUILD_STATE, { state: true });
agent.webpack_client_build_success = true;
const devMiddleware = require('koa-webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
colors: true,
children: true,
modules: false,
chunks: false,
chunkModules: false,
watchOptions: {
ignored: /node_modules/,
const hotMiddleware = require('koa-webpack-hot-middleware')(compiler, {
log: false,
reload: true,
app.use(devMiddleware);
app.use(hotMiddleware);
app.listen(config.port, err =& {
if (!err) {
agent.logger.info(`start webpack client build service: http://127.0.0.1:${config.port}`);
agent.messenger.on(Constant.EVENT_WEBPACK_CLIENT_BUILD_STATE, () =& {
agent.messenger.sendToApp(Constant.EVENT_WEBPACK_CLIENT_BUILD_STATE, { state: agent.webpack_client_build_success });
agent.messenger.on(Constant.EVENT_WEBPACK_READ_CLIENT_FILE_MEMORY, data =& {
const fileContent = Utils.readWebpackMemoryFile(compiler, data.filePath);
if (fileContent) {
agent.messenger.sendToApp(Constant.EVENT_WEBPACK_READ_CLIENT_FILE_MEMORY_CONTENT, {
fileContent,
agent.logger.error(`webpack client memory file[${data.filePath}] not exist!`);
agent.messenger.sendToApp(Constant.EVENT_WEBPACK_READ_CLIENT_FILE_MEMORY_CONTENT, {
fileContent: '',
启动webpack server 编译模式, 负责编译服务器端Node运行文件
'use strict';
const webpack = require('webpack');
const koa = require('koa');
const cors = require('kcors');
const app = koa();
app.use(cors());
const Constant = require('./constant');
const Utils = require('./utils');
module.exports = agent =& {
const config = agent.config.webpack;
const serverWebpackConfig = config.serverConfig;
const compiler = webpack([serverWebpackConfig]);
compiler.plugin('done', () =& {
agent.messenger.sendToApp(Constant.EVENT_WEBPACK_SERVER_BUILD_STATE, { state: true });
agent.webpack_server_build_success = true;
const devMiddleware = require('koa-webpack-dev-middleware')(compiler, {
publicPath: serverWebpackConfig.output.publicPath,
colors: true,
children: true,
modules: false,
chunks: false,
chunkModules: false,
watchOptions: {
ignored: /node_modules/,
app.use(devMiddleware);
app.listen(config.port + 1, err =& {
if (!err) {
agent.logger.info(`start webpack server build service: http://127.0.0.1:${config.port + 1}`);
agent.messenger.on(Constant.EVENT_WEBPACK_SERVER_BUILD_STATE, () =& {
agent.messenger.sendToApp(Constant.EVENT_WEBPACK_SERVER_BUILD_STATE, { state: agent.webpack_server_build_success });
agent.messenger.on(Constant.EVENT_WEBPACK_READ_SERVER_FILE_MEMORY, data =& {
const fileContent = Utils.readWebpackMemoryFile(compiler, data.filePath);
if (fileContent) {
agent.messenger.sendToApp(Constant.EVENT_WEBPACK_READ_SERVER_FILE_MEMORY_CONTENT, {
fileContent,
// agent.logger.error(`webpack server memory file[${data.filePath}] not exist!`);
agent.messenger.sendToApp(Constant.EVENT_WEBPACK_READ_SERVER_FILE_MEMORY_CONTENT, {
fileContent: '',
挂载 webpack 内存读取实例到app上面, 方便业务扩展实现, 代码如下:
我们通过worker向agent发送消息, 就可以从webpack内存获取文件内容, 下面简单封装一下:
class FileSystem {
constructor(app) {
this.app = app;
readClientFile(filePath, fileName) {
return new Promise(resolve =& {
this.app.messenger.sendToAgent(Constant.EVENT_WEBPACK_READ_CLIENT_FILE_MEMORY, {
this.app.messenger.on(Constant.EVENT_WEBPACK_READ_CLIENT_FILE_MEMORY_CONTENT, data =& {
resolve(data.fileContent);
readServerFile(filePath, fileName) {
return new Promise(resolve =& {
this.app.messenger.sendToAgent(Constant.EVENT_WEBPACK_READ_SERVER_FILE_MEMORY, {
this.app.messenger.on(Constant.EVENT_WEBPACK_READ_SERVER_FILE_MEMORY_CONTENT, data =& {
resolve(data.fileContent);
在app/extend/application.js 挂载webpack实例
const WEBPACK = Symbol('Application#webpack');
module.exports = {
get webpack() {
if (!this[WEBPACK]) {
this[WEBPACK] = new FileSystem(this);
return this[WEBPACK];
本地开发webpack热更新内存存储读取和线上应用文件读取逻辑分离
基于上面编译流程实现和webpack实例, 我们很容易实现koa方式的本地开发和线上运行代码分离. 下面我们就以vue 服务器渲染render实现为例:
在egg-view插件开发规范中,我们会在ctx上面挂载render方法, render方法会根据文件名进行文件读取, 模板与数据编译, 从而实现模板的渲染.如下就是controller的调用方式:
exports.index = function* (ctx) {
yield ctx.render('index/index.js', Model.getPage(1, 10));
其中最关键的一步是根据文件名进行文件读取, 只要view插件设计时, 把文件读取的方法暴露出来(例如上面的koa的readFile),就可以实现本地开发webpack热更新内存存储读取.
vue view engine设计实现:
const Engine = require('../../lib/engine');
const VUE_ENGINE = Symbol('Application#vue');
module.exports = {
get vue() {
if (!this[VUE_ENGINE]) {
this[VUE_ENGINE] = new Engine(this);
return this[VUE_ENGINE];
class Engine {
constructor(app) {
this.app = app;
this.config = app.config.vue;
this.cache = LRU(this.config.cache);
this.fileLoader = new FileLoader(app, this.cache);
this.renderer = vueServerRenderer.createRenderer();
this.renderOptions = Object.assign({
cache: this.cache,
}, this.config.renderOptions);
createBundleRenderer(code, renderOptions) {
return vueServerRenderer.createBundleRenderer(code, Object.assign({}, this.renderOptions, renderOptions));
* readFile(name) {
return yield this.fileLoader.load(name);
render(code, data = {}, options = {}) {
return new Promise((resolve, reject) =& {
this.createBundleRenderer(code, options.renderOptions).renderToString(data, (err, html) =& {
if (err) {
reject(err);
resolve(html);
ctx.render 方法
class View {
constructor(ctx) {
this.app = ctx.app;
* render(name, locals, options = {}) {
// 我们通过覆写app.vue.readFile即可改变文件读取逻辑
const code = yield this.app.vue.readFile(name);
return this.app.vue.render(code, { state: locals }, options);
renderString(tpl, locals) {
return this.app.vue.renderString(tpl, locals);
module.exports = View;
服务器view渲染插件实现
通过webpack实例覆写app.vue.readFile 改变从webpack内存读取文件内容.
if (app.vue) {
app.vue.readFile = fileName =& {
const filePath = path.isAbsolute(fileName) ? fileName : path.join(app.config.view.root[0], fileName);
if (/\.js$/.test(fileName)) {
return app.webpack.fileSystem.readServerFile(filePath, fileName);
return app.webpack.fileSystem.readClientFile(filePath, fileName);
app.messenger.on(app.webpack.Constant.EVENT_WEBPACK_CLIENT_BUILD_STATE, data =& {
if (data.state) {
const filepath = app.config.webpackvue.build.manifest;
const promise = app.webpack.fileSystem.readClientFile(filepath);
promise.then(content =& {
fs.writeFileSync(filepath, content, 'utf8');
webpack + vue 编译插件实现
egg+webpack+vue工程解决方案
基于Vue多页面和单页面服务器渲染同构工程骨架项目
egg view plugin for vue
vue server side render solution for egg-view-vue
webpack dev server plugin for egg, support read file in memory and hot reload
egg webpack building solution for vue
programming instead of configuration, webpack is no longer complex

我要回帖

更多关于 webpack devserver 的文章

 

随机推荐