middleware 源码解读

Webpack 的使用目前已经是前端开发工程师必备技能之一。若是想在本地环境启动一个开发服务,大家只需在 Webpack 的配置中,增加 devServer 的配置来启动。devServer 配置的本质是 webpack-dev-server 这个包提供的功能,而 webpack-dev-middleware 则是这个包的底层依赖。

截至本文发表前,webpack-dev-middleware 的最新版本为 webpack-dev-middleware@3.7.2,本文的源码来自于此版本。本文会讲解 webpack-dev-middleware 的核心模块实现,相信大家把这篇文章看完,再去阅读源码,会容易理解很多。

webpack-dev-middleware 是什么?

要回答这个问题,我们先来看看如何使用这个包:

const wdm = require('webpack-dev-middleware'); const express = require('express'); const webpack = require('webpack'); const webpackConf = require('./webapck.conf.js'); const compiler = webpack(webpackConf); const app = express(); app.use(wdm(compiler)); app.listen(8080);

通过启动一个 Express 服务,将 wdm(compiler) 的结果通过 app.use 方法注册为 Express 服务的中间函数。从这里,我们不难看出 wdm(compiler) 的执行结果返回的是一个 express 的中间件。它作为一个容器,将 webpack 编译后的文件存储到内存中,然后在用户访问 express 服务时,将内存中对应的资源输出返回。

为什么要使用 webpack-dev-middleware

熟悉 webpack 的同学都知道,webpack 可以通过watch mode 方式启动,那为何我们不直接使用此方式来监听资源变化呢?答案就是,webpack 的 watch mode 虽然能监听文件的变更,并且自动打包,但是每次打包后的结果将会存储到本地硬盘中,而 IO 操作是非常耗资源时间的,无法满足本地开发调试需求。

而 webpack-dev-middleware 拥有以下几点特性:

以 watch mode 启动 webpack,监听的资源一旦发生变更,便会自动编译,生产最新的 bundle

在编译期间,停止提供旧版的 bundle 并且将请求延迟到最新的编译结果完成之后

webpack 编译后的资源会存储在内存中,当用户请求资源时,直接于内存中查找对应资源,减少去硬盘中查找的 IO 操作耗时

本文将主要围绕这三个特性和主流程逻辑进行分析。

源码解读

让我们先来看下 webpack-dev-middleware 的源码目录:

... ├── lib │ ├── DevMiddlewareError.js │ ├── index.js │ ├── middleware.js │ └── utils │ ├── getFilenameFromUrl.js │ ├── 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 的源码实现

入口文件 index.js

从上文我们已经得知 wdm(compiler) 返回的是一个 express 中间件,所以入口文件 index.js 则为一个中间件的容器包装函数。它接收两个参数,一个为 webpack 的 compiler、另一个为配置对象,经过一系列的处理,最后返回一个中间件函数。下面我将对 index.js 中的核心代码进行讲解:

... 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 个部分的执行,分别完成了我们上文提到的两点特性:

以监控的方式启动 webpack

将 webpack 的编译内容,输出至内存中

setupHooks

此函数的作用是在 compiler 的 invalid、run、done、watchRun 这 4 个编译生命周期上,注册对应的处理方法

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); } );

在 done 生命周期上注册 done 方法,该方法主要是 report 编译的信息以及执行 context.callbacks 回调函数

在 invalid、run、watchRun 等生命周期上注册 invalid 方法,该方法主要是 report 编译的状态信息

compiler.watch

此部分的作用是,调用 compiler 的 watch 方法,之后 webpack 便会监听文件变更,一旦检测到文件变更,就会重新执行编译。

setupOutputFileSystem

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/ad0b1d2a12ec36cc06c1d9a756c2fc14.html