We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
他人原文:https://juejin.im/post/5e7782dbf265da57584dc95e
Webpack 的使用目前已经是前端开发工程师必备技能之一。若是想在本地环境启动一个开发服务,大家只需在 webpack 的配置中,增加 devServer 的配置来启动。而 devServer 配置的本质是 webpack-dev-server 这个包提供的功能,而 webpack-dev-middleware 则是这个包的底层依赖。 截止2020.03.23,本文解读的
webpack-dev-server
webpack-dev-middleware
webpack-dev-server@3.10.3
webpack-dev-middleware@3.7.2
先看看webpack-dev-server主要做了啥:
const wdm = require('webpack-dev-middleware'); const express = require('express'); const webpack = require('webpack'); const webpackConf = require('./webapck.conf.js'); // 引入webpack配置 const compiler = webpack(webpackConf); // 首先创建一个webpack compiler const app = express(); // 建一个express服务 app.use(wdm(compiler)); // 用wdm(compiler)生成中间件并注册为express的中间函数 app.listen(8080); // 开启监听端口
一句话概括,webpack-dev-middleware把webpack编译后的文件存在内存,并在用户访问express时将对应资源输出。
webpack
express
webpack可以通过watch mode启动,实现实时监听项目文件变化并编译输出,但输出结果需要存储到本地硬盘,而I/O操作非常消耗资源时间,无法满足本地开发调试需求(此处匿名吐槽gitbook的本地调试)。
而 webpack-dev-middleware 拥有以下几点特性:
以 watch mode 启动 webpack,监听的资源一旦发生变更,便会自动编译,生产最新的 bundle 在编译期间,停止提供旧版的 bundle 并且将请求延迟到最新的编译结果完成之后 webpack 编译后的资源会存储在内存中,当用户请求资源时,直接于内存中查找对应资源,减少去硬盘中查找的 I/O 操作耗时(这也是webpack-dev-middleware的核心部分)
watch mode
bundle
到这一步了,其实不看源码,也能写出 webpack-dev-middleware 的大概流程:
webpack-dev-middleware源码目录:
... ├── lib │ ├── DevMiddlewareError.js │ ├── index.js // 主文件 │ ├── middleware.js │ └── utils │ ├── getFilenameFromUrl.js // 解析url得到文件名 │ ├── handleRangeHeaders.js │ ├── index.js │ ├── ready.js │ ├── reporter.js │ ├── setupHooks.js // 挂钩子 │ ├── setupLogger.js // 日志设置 │ ├── setupOutputFileSystem.js // 输出文件系统设置 │ ├── setupRebuild.js // 重构建逻辑设置 │ └── setupWriteToDisk.js // 写入磁盘设置 ├── package.json ...
其实看文件名就知道干了些啥了。 其中 lib 目录下为源代码,一眼望去有近 10 多个文件要解读。但刨除 utils 工具集合目录,其核心源码文件其实只有两个 index.js、middleware.js 下面我们就来分析核心文件 index.js、middleware.js 的源码实现
lib
utils
index.js
middleware.js
从上文我们已经得知 wdm(compiler) 返回的是一个 express 中间件,所以入口文件 index.js 则为一个中间件的容器包装函数。它接收两个参数,一个为 webpack 的 compiler、另一个为配置对象,经过一系列的处理,最后返回一个中间件函数。下面对 index.js 中的核心代码进行讲解:
wdm(compiler)
compiler
... setupHooks(context); ... // start watching context.watching = compiler.watch(options.watchOptions, (err) => { if (err) { context.log.error(err.stack || err); if (err.details) { context.log.error(err.details); } } }); ... setupOutputFileSystem(compiler, context);
index.js 最为核心的是以上 3 个部分的执行,分别完成了我们上文提到的两点特性:
此函数的作用是在 compiler 的 invalid、run、done、watchRun 这 4 个编译生命周期上,注册对应的处理方法
invalid
run
done
watchRun
context.compiler.hooks.invalid.tap('WebpackDevMiddleware', invalid); context.compiler.hooks.run.tap('WebpackDevMiddleware', invalid); context.compiler.hooks.done.tap('WebpackDevMiddleware', done); context.compiler.hooks.watchRun.tap( 'WebpackDevMiddleware', (comp, callback) => { invalid(callback); } );
report
context.callbacks
此部分的作用是,调用 compiler 的 watch 方法,之后 webpack 便会监听文件变更,一旦检测到文件变更,就会重新执行编译。
其作用是使用 memory-fs 对象替换掉 compiler 的文件系统对象,让 webpack 编译后的文件输出到内存中。
fileSystem = new MemoryFileSystem(); compiler.outputFileSystem = fileSystem;
通过以上 3 个部分的执行,我们以 watch mode 的方式启动了 webpack,一旦监测的文件变更,便会重新进行编译打包,同时我们又将文件的存储方法改为了内存存储,提高了文件的存储读取效率。最后,我们只需要返回 express 的中间件就可以了,而中间件则是调用 middleware(context) 函数得到的。下面,我们来看看 middleware 是如何实现的。
middleware(context)
middleware
此文件返回的是一个 express 中间件函数的包装函数,其核心处理逻辑主要针对 request 请求,根据各种条件判断,最终返回对应的文件内容:
request
function goNext() { if (!context.options.serverSideRender) { return next(); } return new Promise((resolve) => { ready( context, () => { res.locals.webpackStats = context.webpackStats; res.locals.fs = context.fs; resolve(next()); }, req ); }); }
首先,middleware 中定义了一个 goNext() 方法,该方法判断是否是服务端渲染。如果是,则调用 ready() 方法(此方法即为 ready.js 文件,作用为根据 context.state 状态判断直接执行回调还是将回调存储 callbacks 队列中)。如果不是,则直接调用 next() 方法,流转至下一个 express 中间件。
goNext()
ready()
ready.js
context.state
callbacks
next()
const acceptedMethods = context.options.methods || ['GET', 'HEAD']; if (acceptedMethods.indexOf(req.method) === -1) { return goNext(); }
接着,判断 HTTP 协议的请求的类型,若请求不包含于配置中(默认 GET、HEAD 请求),则直接调用 goNext() 方法处理请求:
HTTP
GET
HEAD
let filename = getFilenameFromUrl( context.options.publicPath, context.compiler, req.url ); if (filename === false) { return goNext(); }
然后,根据请求的 req.url 地址,在 compiler 的内存文件系统中查找对应的文件,若查找不到,则直接调用 goNext() 方法处理请求:
req.url
return new Promise((resolve) => { function processRequest() { ... } ... ready(context, processRequest, req); });
最后,中间件返回一个 Promise 实例,而在实例中,先是定义一个 processRequest 方法,此方法的作用是根据上文中找到的 filename 路径获取到对应的文件内容,并构造 response 对象返回,随后调用 ready(context, processRequest, req) 函数,去执行 processRequest 方法。这里我们着重看下 ready 方法的内容:
Promise
processRequest
filename
response
ready(context, processRequest, req)
ready
if (context.state) { return fn(context.webpackStats); } context.log.info(`wait until bundle finished: ${req.url || fn.name}`); context.callbacks.push(fn);
非常简单的方法,判断 context.state 的状态,将直接执行回调函数 fn,或在 context.callbacks 中添加回调函数 fn。这也解释了上文提到的另一个特性 “在编译期间,停止提供旧版的 bundle 并且将请求延迟到最新的编译结果完成之后”。若 webpack 还处于编译状态,context.state 会被设置为 false,所以当用户发起请求时,并不会直接返回对应的文件内容,而是会将回调函数 processRequest 添加至 context.callbacks 中,而上文中我们说到在 compile.hooks.done 上注册了回调函数 done,等编译完成之后,将会执行这个函数,并循环调用 context.callbacks。
fn
false
compile.hooks.done
整体来看其实还挺简单的,前提是对 webpack 的完整打包流程及Hook 有了解。
Hook
强烈建议项目内使用 webpack 的同学完整读一遍 webpack 源码,那么像这些 webpack 的插件看用途就知道怎么实现的。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
前言
他人原文:https://juejin.im/post/5e7782dbf265da57584dc95e
Webpack 的使用目前已经是前端开发工程师必备技能之一。若是想在本地环境启动一个开发服务,大家只需在 webpack 的配置中,增加 devServer 的配置来启动。而 devServer 配置的本质是
webpack-dev-server
这个包提供的功能,而webpack-dev-middleware
则是这个包的底层依赖。截止2020.03.23,本文解读的
webpack-dev-server
版本:webpack-dev-server@3.10.3
,webpack-dev-middleware
版本:webpack-dev-middleware@3.7.2
webpack-dev-middleware
是什么?先看看
webpack-dev-server
主要做了啥:一句话概括,
webpack-dev-middleware
把webpack
编译后的文件存在内存,并在用户访问express
时将对应资源输出。为什么要使用
webpack-dev-middleware
webpack
可以通过watch mode启动,实现实时监听项目文件变化并编译输出,但输出结果需要存储到本地硬盘,而I/O操作非常消耗资源时间,无法满足本地开发调试需求(此处匿名吐槽gitbook的本地调试)。而
webpack-dev-middleware
拥有以下几点特性:以
watch mode
启动webpack
,监听的资源一旦发生变更,便会自动编译,生产最新的bundle
在编译期间,停止提供旧版的
bundle
并且将请求延迟到最新的编译结果完成之后webpack
编译后的资源会存储在内存中,当用户请求资源时,直接于内存中查找对应资源,减少去硬盘中查找的 I/O 操作耗时(这也是webpack-dev-middleware
的核心部分)到这一步了,其实不看源码,也能写出
webpack-dev-middleware
的大概流程:watch mode
启动webpack
webpack
上挂编译中、编译完成、编译出错这些钩子webpack
的bundle
输出地址从原本的文件系统换成内存系统(关键)express
用源码解读
webpack-dev-middleware
源码目录:其实看文件名就知道干了些啥了。
其中
lib
目录下为源代码,一眼望去有近 10 多个文件要解读。但刨除utils
工具集合目录,其核心源码文件其实只有两个index.js
、middleware.js
下面我们就来分析核心文件
index.js
、middleware.js
的源码实现入口文件
index.js
从上文我们已经得知
wdm(compiler)
返回的是一个express
中间件,所以入口文件index.js
则为一个中间件的容器包装函数。它接收两个参数,一个为webpack
的compiler
、另一个为配置对象,经过一系列的处理,最后返回一个中间件函数。下面对index.js
中的核心代码进行讲解:index.js
最为核心的是以上 3 个部分的执行,分别完成了我们上文提到的两点特性:watch mode
启动webpack
webpack
的编译内容,输出至内存中setupHooks
此函数的作用是在
compiler
的invalid
、run
、done
、watchRun
这 4 个编译生命周期上,注册对应的处理方法done
生命周期上注册done
方法,该方法主要是report
编译的信息以及执行context.callbacks
回调函数invalid
、run
、watchRun
等生命周期上注册invalid
方法,该方法主要是report
编译的状态信息compiler.watch
此部分的作用是,调用
compiler
的 watch 方法,之后webpack
便会监听文件变更,一旦检测到文件变更,就会重新执行编译。setupOutputFileSystem
其作用是使用 memory-fs 对象替换掉
compiler
的文件系统对象,让webpack
编译后的文件输出到内存中。通过以上 3 个部分的执行,我们以
watch mode
的方式启动了webpack
,一旦监测的文件变更,便会重新进行编译打包,同时我们又将文件的存储方法改为了内存存储,提高了文件的存储读取效率。最后,我们只需要返回express
的中间件就可以了,而中间件则是调用middleware(context)
函数得到的。下面,我们来看看middleware
是如何实现的。middleware.js
此文件返回的是一个
express
中间件函数的包装函数,其核心处理逻辑主要针对request
请求,根据各种条件判断,最终返回对应的文件内容:首先,
middleware
中定义了一个goNext()
方法,该方法判断是否是服务端渲染。如果是,则调用ready()
方法(此方法即为ready.js
文件,作用为根据context.state
状态判断直接执行回调还是将回调存储callbacks
队列中)。如果不是,则直接调用next()
方法,流转至下一个express
中间件。接着,判断
HTTP
协议的请求的类型,若请求不包含于配置中(默认GET
、HEAD
请求),则直接调用goNext()
方法处理请求:然后,根据请求的
req.url
地址,在compiler
的内存文件系统中查找对应的文件,若查找不到,则直接调用goNext()
方法处理请求:最后,中间件返回一个
Promise
实例,而在实例中,先是定义一个processRequest
方法,此方法的作用是根据上文中找到的filename
路径获取到对应的文件内容,并构造response
对象返回,随后调用ready(context, processRequest, req)
函数,去执行processRequest
方法。这里我们着重看下ready
方法的内容:非常简单的方法,判断
context.state
的状态,将直接执行回调函数fn
,或在context.callbacks
中添加回调函数fn
。这也解释了上文提到的另一个特性 “在编译期间,停止提供旧版的bundle
并且将请求延迟到最新的编译结果完成之后”。若webpack
还处于编译状态,context.state
会被设置为false
,所以当用户发起请求时,并不会直接返回对应的文件内容,而是会将回调函数processRequest
添加至context.callbacks
中,而上文中我们说到在compile.hooks.done
上注册了回调函数done
,等编译完成之后,将会执行这个函数,并循环调用context.callbacks
。总结
整体来看其实还挺简单的,前提是对
webpack
的完整打包流程及Hook
有了解。强烈建议项目内使用
webpack
的同学完整读一遍webpack
源码,那么像这些webpack
的插件看用途就知道怎么实现的。The text was updated successfully, but these errors were encountered: