webpack 详细执行过程
问:webpack 究竟解决了什么问题
- 模块化解决方案
在早前web前端只需要一个简单的 html 页面,插入几条script标签 去引用 js 文件就可以满足需求,随着项目越来越复杂,要实现的功能越来越多,文件也越来越多,全部都这么引入已经不再现实,这时候前端模块化就出现了,从AMD、CMD 到现在的 ES6 模块化写法,我们可以把代码拆成一个个 JS 文件,通过 import 去关联依赖文件,最后再通过某个打包工具把这么多 js 文件按照依赖关系最终打包成一个或多个 js 文件在html 页面去引入。
所以 webpack首要要解决的问题是将多个模块化的 js文件 按照依赖关系打包为一个或多个文件,所以我们通常都会说他是一个模块化解决方案
- 处理资源转换
随着 ES6,ES7,ES8 的出现,还有 vue、react 等前端框架的出现,我们发现这些文件浏览器是不能直接执行的,需要我们中间编译转换一下为浏览器可执行的文件,所以这时候 webpack 要做的事情又多了一项,按照依赖打包的同时,还要对源文件进行编译转换处理,也就是我们日常配置的 loader 处理。
- tree-shaking以及代码压缩
现在webpack已经支持了对文件编译转换后再进行打包,满足了我们的基本需求。这时候我们又开始对性能提出了要求,希望打包出的体积越小越好。比如有些文件虽然整个引用了,但其实真正只用了其中部分代码,没用到的部分希望可以被剔除掉。这种是通过剔除无效代码来减小总的打包体积,另外一种方式是通过代码压缩,比如空格、较长的函数名都可以被压缩。因此webpack支持了 tree-shaking和代码压缩。
- 代码拆分(异步加载 + 抽出第三方公用库)
现在 webpack 打包结果是不是做到了极致了呢?不行,我们还是嫌弃最终打包出的文件体积太大了。这时候懒加载(异步加载)出现了,你只需要把进入首页时所需要的所有资源打包为一个文件输出就行,这样进入首页我只需要加载该文件就行,其他资源文件等我真正执行的时候再去加载就可以。就这样,webpack又支持了异步加载文件的拆包功能,这时候我们最终打包出的主文件只是当前首页需要的资源。
- 开发辅助工具的提供
我们对于打包的基本需求以及性能需求终于得到了满足,又开始追求开发时的体验了,开发越便捷越好,webpack 就提供了一系列的开发辅助功能,比如 devserver,HMR 等等什么的帮助我们高效的开发。
现在我们回过头总结下看,webpack帮我们做了好多事啊。
- 作为一个模块化解决方案,帮助我们将繁多的 JS 模块化文件按照依赖关系打包 为一个或多个文件输出
- 支持针对文件指定文件进行编译转换后再打包
- 支持针对打包后的内容优化、压缩处理
来减小总的文件体积 - 支持异步加载以及其他拆包方式
- 提供一系列开发辅助工具
概述
webpack的构建从处理入口文件开始着手,首先解析入口文件,需要 经过 loader转换编译这时候就转换编译,转换完了开始分析是否有依赖文件,有依赖文件就接着处理依赖文件,流程和刚刚一致,需要编译转换就转换,然后接着解析依赖文件是否还有依赖文件,有再接着处理。就这样通过入口文件以及依赖关系,webpack 可以获取并处理所有的依赖文件。然后再基于这些文件做进一步的优化处理,比如 treeshaking 或者 代码压缩,最后生成为我们需要的一个或多个js 文件。
1. 准备工作
webpack 首先会将我们的配置文件和它自己的默认配置做一个 merge,生成最终的一个配置文件,其次会将这个最终配置文件里的所有插件plugin在这个时候都注册好,在这里要提一下 webpack 的事件机制,他是基于一个 tapable库做的事件流控制,在整个的编译过程中暴露出各种hook,而我们写的 plugin 也就是去注册监听了某个 hook,在这个 hook 触发时,去执行我们的 plugin。
2. 处理入口文件
在 webpack 的处理中多种入口最后都会转化为同一方法去处理,单入口不用说,多入口我可以先遍历,再去执行该方法,动态入口,我先执行函数再去处理,最终都会进入到 生成入口文件 module 实例阶段。
1 | // 入口文件 |
大家都说 webpack 中一切文件都是 module,那 module 是什么呢,其实他就是一个存了当前文件所有信息的一个对象而已,这个文件包含了以下信息。
1 | module = { |
3.生成文件 module实例
1. resolve 阶段
通过我们的 resolve 配置和 rules 配置去获取到当前文件的绝对路径和需要经过哪些loader 进行处理,然后将这些信息存到我们当前这个文件对应的 module 实例里面
2.执行 loader 阶段
loader 的执行是倒序
loader的执行分为 2 个阶段: pitching,执行 loader 上的 pitch 方法;normal,执行 loader 常规方法
执行完 loader 后,也就是对文件做了编译转换,使其变成了最终可以被浏览器执行的代码。
3. parse 阶段
webpack 是采用将loader 执行过后的源文件source转换为AST 去分析依赖。这里使用了acorn 库,将source生成对应的 AST。生成的 AST 划分为 3部分,ImportDeclaration、FunctionDeclaration和VariablesDeclaration,接下来遍历 AST 去收集依赖。找到 import 等关键字去达到依赖收集的目的。
4.递归处理依赖
基于我们解析到的依赖文件,我们要开始递归处理依赖了,又回到了我们处理入口文件的整个流程,去生成依赖文件的 module 实例,再执行 对应loader。就这样 webpack 递归处理了所有的依赖文件并完成了所有文件的转换
4. 生成 chunk
根据用户配置的 optimization.slitChunks 或者默认的配置,用上一步生成的module,来生成 chunk
- 生成 Module-graph
- 生成 Basic-chunk-graph
- 生成最终的 chunk-graph
5. 优化
- 首先生成对应的 moduleId ,不做任何配置的话,默认采用以自增 id 的方式,推荐 hash 的方式,有利于缓存
- 基于生成的 moduleId进行排序
- 接着类似于 module 的操作,对应生成 chunkId ,并根据 chunkId进行排序
- 分别为 module 和chunk 生成hash
6. 生成文件
webpack 把这些文件按照内置的 template 渲染生成最终的打包文件。
总结
总结一下 webpack 的整个构建打包过程,首先通过依赖关系和 loader 配置获取经过编译转换后的所有module 实例,然后再根据配置进行拆分为一个或多个chunk,最后按照内置的template 渲染出最终的文件输出。