查找打包入口文件
通过 npm scripts 运行 webpack
- 开发环境: npm run dev
- 生产环境:npm run build
通过 webpack 直接运行
- webpack entry.js bundle.js
在命令行运行以上命令后,npm会让命令行工具进入node_modules.bin 目录查找是否存在 webpack.sh 或者 webpack.cmd 文件,如果存在,就执行,不存在,就抛出错误。实际的入口文件是:node_modules\webpack\bin\webpack.js
webpack.js
process.exitCode = 0; //1\. 正常执行返回 const runCommand = (command, args) =>{...}; //2\. 运行某个命令 const isInstalled = packageName =>{...}; //3\. 判断某个包是否安装 const CLIs =[...]; //4\. webpack 可用的 CLI: webpack-cli 和 webpack-command const installedClis = CLIs.filter(cli => cli.installed); //5\. 判断是否两个 ClI 是否安装了 if (installedClis.length === 0){...}else if //6\. 根据安装数量进行处理 (installedClis.length === 1){...}else{...}.
最终找到 webpack-cli (webpack-command) 这个 npm 包,并且执行 CLI
webpack-cli做了什么
- 引入 yargs,对命令行进行定制
- 分析命令行参数,对各个参数进行转换,组成编译配置项
- 引用webpack,根据配置项进行编译和构建
主要看引用webpack做的事情
Webpack 的本质
Webpack可以将其理解是一种基于事件流的编程范例,一系列的插件运行。
class Compiler extends Tapable{ //... } class Compilation extends Tapable { // ... }
Tabable是什么
Tapable 是一个类似于 Node.js 的 EventEmitter 的库, 主要是控制钩子函数的发布与订阅,控制着 webpack 的插件系统。
Tapable库暴露了很多 Hook(钩子)类,为插件提供挂载的钩子
const { SyncHook, //同步钩子 SyncBailHook, //同步熔断钩子 SyncWaterfallHook, //同步流水钩子 SyncLoopHook, //同步循环钩子 AsyncParallelHook, //异步并发钩子 AsyncParallelBailHook, //异步并发熔断钩子 AsyncSeriesHook, //异步串行钩子 AsyncSeriesBailHook, //异步串行熔断钩子 AsyncSeriesWaterfallHook //异步串行流水钩子 } = require("tapable");
Tabable钩子类型
type function
Hook 所有钩子的后缀
Waterfall 同步方法,但是它会传值给下一个函数
Bail 熔断:当函数有任何返回值,就会在当前执行函数停止
Loop 监听函数返回true表示继续循环,返回undefined表示结束循环
Sync 同步方法
AsyncSeries 异步串行钩子
AsyncParallel 异步并行执行钩子
Tapable的使用 -new Hook 新建钩子
Tapable 暴露出来的都是类方法,new 一个类方法获得我们需要的钩子
class接受数组参数options,非必传。
const hook1 = new SyncHook(['arg1','arg2','arg3'])
Tapable的使用 -钩子的绑定与执行
Tabpack 提供了同步&异步绑定钩子的方法,并且他们都有绑定事件和执行事件对应的方法。
Async* Sync*
绑定:tapAsync/tapPromise/tap 绑定:tap
执行:callAsync/promise 执行:call
Tapable的使用 -hook基本用法示例
const hook1 = new SyncHook(['arg1','arg2','arg3']) //绑定事件到webpack事件流 hook1.tap('hook1',(arg1,arg2,arg3)=>{ console.log(arg1,arg2,arg3) }) //执行绑定的事件 hook1.call(1,2,3)
Tapable是如何和webpack联系起来的
if (Array.isArray(options)) { compiler = new MultiCompiler(options.map(options => webpack(options))); } else if (typeof options === "object") { options = new WebpackOptionsDefaulter().process(options); compiler = new Compiler(options.context); compiler.options = options; new NodeEnvironmentPlugin().apply(compiler); if (options.plugins && Array.isArray(options.plugins)) { for (const plugin of options.plugins) { if (typeof plugin === "function") { plugin.call(compiler, compiler); } else { plugin.apply(compiler); } } } compiler.hooks.environment.call(); compiler.hooks.afterEnvironment.call(); compiler.options = new WebpackOptionsApply().process(options, compiler); } ```![](https://uploadfiles.nowcoder.com/images/20190919/357373100_1568889674884_BDC23150C11D4AEE6F7FB8FAE85AE2C3) # Chunk生成 1\. webpack 先将 entry 中对应的 module 都生成一个新的 chunk 2\. 遍历 module 的依赖列表,将依赖的 module 也加入到 chunk 中 3\. 如果一个依赖 module 是动态引入的模块,那么就会根据这个 module 创建一个 新的 chunk,继续遍历依赖 4\. 重复上面的过程,直至得到所有的 chunks
HMR
webpack 对文件系统进行 watch 打包到内存中
webpack-dev-middleware 调用 webpack 的 api 对文件系统 watch,当文件发生改变后,webpack 重新对文件进行编译打包,然后保存到内存中。
devServer 通知浏览器端文件发生改变
在启动 devServer 的时候,sockjs 在服务端和浏览器端建立了一个 webSocket 长连接,以便将 webpack 编译和打包的各个阶段状态告知浏览器,最关键的步骤还是 webpack-dev-server 调用 webpack api 监听 compile的 done 事件,当compile 完成后,webpack-dev-server通过 _sendStatus 方法将编译打包后的新模块 hash 值发送到浏览器端。
webpack-dev-server/client 接收到服务端消息做出响应
webpack-dev-server 修改了webpack 配置中的 entry 属性,在里面添加了 webpack-dev-client 的代码,这样在最后的 bundle.js 文件中就会接收 websocket 消息的代码了。
webpack-dev-server/client 当接收到 type 为 hash 消息后会将 hash 值暂存起来,当接收到 type 为 ok 的消息后对应用执行 reload 操作。
在 reload 操作中,webpack-dev-server/client 会根据 hot 配置决定是刷新浏览器还是对代码进行热更新(HMR)。代码如下:
webpack 接收到最新 hash 值验证并请求模块代码
首先 webpack/hot/dev-server(以下简称 dev-server) 监听第三步 webpack-dev-server/client 发送的 webpackHotUpdate 消息,调用 webpack/lib/HotModuleReplacement.runtime(简称 HMR runtime)中的 check 方法,检测是否有新的更新。
在 check 过程中会利用 webpack/lib/JsonpMainTemplate.runtime(简称 jsonp runtime)中的两个方法 hotDownloadManifest 和 hotDownloadUpdateChunk。
hotDownloadManifest 是调用 AJAX 向服务端请求是否有更新的文件,如果有将发更新的文件列表返回浏览器端。该方法返回的是最新的 hash 值。
hotDownloadUpdateChunk 是通过 jsonp 请求最新的模块代码,然后将代码返回给 HMR runtime,HMR runtime 会根据返回的新模块代码做进一步处理,可能是刷新页面,也可能是对模块进行热更新。该 方法返回的就是最新 hash 值对应的代码块。
最后将新的代码块返回给 HMR runtime,进行模块热更新。
HotModuleReplacement.runtime 对模块进行热更新
业务代码需要做些什么?
当用新的模块代码替换老的模块后,但是我们的业务代码并不能知道代码已经发生变化,也就是说,当 hello.js 文件修改后,我们需要在 index.js 文件中调用 HMR 的 accept 方法,添加模块更新后的处理函数,及时将 hello 方法的返回值插入到页面中。代码如下
// index.js if(module.hot) { module.hot.accept('./hello.js', function() { div.innerHTML = hello() }) }