Description
Webpack 是一个 JavaScript 及其周边的模块打包工具。它所做的工作其实很简单:将项目中的资源文件进行打包,生成一个大的 bundle 文件,然后交由服务器返回给客户端解析展现。
为什么需要 Webpack
在以前,传统的 Web 开发页面都是 DOM 直出的,客户端发起请求,服务器进行一系列逻辑处理,返回 HTML 和 CSS 资源文件。这种模式就是每次请求都要刷新一次页面来获取最新的资源文件,体验并不好。后来出现了 单页面应用(SPA)
,它允许我们在不刷新的情况下更新页面获取新资源,单页面应用的主要技术就是 Ajax
,它的特点就是自始至终只有一个 URL,很多处理工作都是在前端完成的,也就是所谓前后端分离。这里顺便提下,由于单页面一个 url ,意味着很难感知用户的操作行为,为此又有了 前端路由
的解决方案。具体可以参考 这篇 。
前面说了,单页面应用很多工作都是在前端完成的,具体来说就是前端开始往工程化发展了,我们需要承担一些以前在服务端才处理的逻辑,这样项目就开始庞大起来了,这就意味着需要划分模块,而 JS 在模块化这方面支持不够,所以才涌现出了诸如立即执行函数,AMD,CMD,UMD,ES6 module 等模块化解决方案。可以说,这些模块化方案解决了代码如何组织的问题,但是它们没有解决我们项目多个模块之间的处理引入问题。于是 Webpack ,Gulp 这种模块化打包工具就出现了,它们解决了以下问题:
- 针对不同模块标准所带来的环境兼容问题
- 由于项目细分了很多模块,意味着网络文件请求增多,需要解决效率问题
- 不仅 JS 代码可以模块化,HTML ,CSS 和图片资源也可以模块化
Loader 机制
Loader 简单来说就是针对不同类型资源的一个加载器,因为 webpack 默认是把所有文件当做 JS 来解析处理的,假如你需要处理 CSS 或其他资源,那就需要有辅助工具来做这项工作,loader 就是干这个的,比如针对 css,有 css-loader
和 style-loader
;针对 sass,有 sass-loader
。针对 ES6 转 ES5 代码,有 babel-laoder
等等。
loader 是跑在 Node.js 上的,如果你要实现一个 loader,例如 function myLoader(source) {}
,它会接收源码内容作为参数,然后在里面做你自定义的转换操作,一个 loader 最终必须返回 JavaScript 代码,还可以返回第二个参数,是一个可选的 source map 对象。
export default function myLoader(source) {
source.replace('/\s/', '')
return `export default ${JSON.stringify(source)}`
}
官方还给出了写 loader 的几个指导原则:
- 保持简洁
- 利用 chaining 机制
- 返回模块化输出
- 确保无状态 stateless
- 使用 loader 的加载工具
- 标记 loader 依赖
- 解析模块依赖
- 提取公共代码
- 避免绝对路径
- 使用 peer dependencies
plugin 机制
插件是 webpack 提供的另一种能力,可以让我们方便地做一些自动化处理操作。比如:
- 打包构建前清空 dist 目录
- 自动生成根 html 文件
- 压缩打包的文件
- 将静态目录下的资源文件复制到 dist 中
插件的原理:钩子。webpack 会在打包构建的过程每个环节预留一些钩子,在这些钩子里就可以执行我们自定义的逻辑了。
class Test {
apply (compiler) {
compiler.hooks.emit.tap('函数', compilation => {
// 执行逻辑
})
}
}
webpack 打包的过程
初始化阶段
:
- 由 Webpack CLI 启动打包流程,合并命令行参数和用户的配置文件
- 载入 Webpack 核心模块,创建 Compiler 对象
- 初始化编译环境:注入内置插件,注册各种模块工厂等
构建阶段
:
- 执行 Compiler 对象的 run 方法,创建 Compilation 对象
- 进入 make 阶段,调用 Compilation.addEntry 方法,开始解析入口
- 通过 Compilation 对象的 buildModule 方法进行模块构建,这个过程中,对模块调用各种 loader 进行转译处理,使用 JS 解释器 Acorn 将模块代码转换为 AST 语法树,根据 AST 判断是否还有依赖的模块,有则继续循环 build 整个模块
生成阶段
:
- 合并生成需要输出的 bundle.js 写入到 dist 目录
与 Vite 的对比
在开发阶段:
Webpack 的思路是先打包整个应用文件,然后生成 bundle 文件,再启动开发服务器。当有文件修改时,有 HMR 热替换机制,需要找到对应模块的依赖,然后依次重新编译,如果随着文件越来越多的话也需要很长时间重新编译。
而 Vite 的话,它分为依赖和源码两大模块,思路是先分析,使用 ESBuild 预构建依赖,把 CommonJS/UMD 转换为 ESM,还会使用 Cache-Control 进行强缓存请求。