本想做一次 webpack5 的分享,准备过程中发现内容很多,所以分了上篇/下篇,上篇主要讲解 Webpack 重要的基本概念、以及开发中遇到的 webpack 配置。
When & What
是何时出现的 解决了什么问题
首先,webpack 是为什么出现的呢?一般来说,新技术都是由当下的问题而催生出来的。
自从前端引入了模块化,很好的解决了这些历史问题:
- 避免命名冲突
- 更好的分离代码,按需加载
- 高复用性
- 高可维护性
但是,与此同时,它又造成了新的问题:
- 浏览器环境兼容问题
- 模块化划分出的文件会比较多,零散的模块文件将导致浏览器频繁发出请求
- 不仅 Javascript 代码需要模块化,HTML/CSS 这些资源文件同样需要
这时候,我们需要一种工具可以让我们在开发阶段,享受模块化所带来的便利,同时也不用担心以上举例出的种种问题。 于是,很多模块打包工具应运而生,其中就有我们所熟知的webpack。
基础概念/配置
如果准备搭建一个基础的 React 开发环境,看看我们需要哪些基础配置:
- 加载资源文件
- HtmlWebpackPlugin 文件模版
- HMR 模块热替换
- 代码分割 性能
- SourceMap 定位报错
- webpack-dev-server 开发环境
- babel 解析 ES6/JSX
这是已完成基础配置的 config 文件: webpack.config.js
1 |
|
下面根据配置文件,说说 webpack 最主要的几个基础概念:
- Entry 告诉 Webpack 从哪儿开始构建内部关系图
- Output 告诉 Webpack 在哪儿输出打包好的文件
- Mode 让 Webpack 可以根据相应的环境做内置优化
- Loader 让 Webpack 能够解析非 JS 类型文件,将它们转化为应用中的有效模块。
- Plugin 让 Webpack 可以执行更广泛的任务,如打包优化/静态资源管理/环境变量注入
运行流程
-
初始化————启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler
-
编译————从 Entry 出发,针对每个 Module 串行调用对应的 Loader 去翻译文件内容,再找到该 Module 依赖的 Module,递归地进行编译处理。
-
输出————对编译后的 Module 组合成 Chunk,把 Chunk 转换成文件,输出。
在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件可以监听事件执行逻辑,改变 Webpack 的运行结果。
自定义 Loader/Plugin
Loader 基础
Loader 就像是一个翻译员,能转化源文件然后输出新的结果,并且一个文件还可以链式的经过多个 loader 翻译。
下面以处理 SCSS 文件为例: .scss –> sass-loader –> css –> css-loader –> 找出 css 中依赖的资源并压缩 css –> style-loader –> 通过脚本加载的 JS 代码
可以看出以上的处理过程需要有顺序的链式执行,先 sass-loader 再 css-loader 再 style-loader。
配置:
1 |
|
加载顺序: 由右到左 ⬅️,由下到上 ⬆️ 由上面的例子可以看出:每个 Loader 的职责是单一的,只需要完成一种转换。
如果一个源文件需要经历多步转换才能使用,就使用多个 Loader 去转换。
所以,在开发自定义 Loader 时,请保持其职责的单一性,你只需关心输入和输出。
自定义 Loader
一个 Loader 其实就是一个 Node.js 模块,这个模块需要导出一个函数。 这个函数的工作就是获得处理前的内容,返回处理后的内容。
一个最简单的 Loader 的源码如下:
1 |
|
Webpack 还提供一些 API 供 Loader 调用:
获得 Loader 的 options: loader-utils
返回 SourceMap: this.callback(err:Error | null, content: string | Buffer, souceMap?: SourceMap, abstractSyntaxTree?: AST)
异步返回: const callback = this.async()
处理二进制文件: module.exports.raw = true
关闭缓存功能: this.cacheable(false)
这是实现的一个自定义 Loader-国际化 Loader:
1 |
|
config
1 |
|
html
1 |
|
Plugin 基础
Webpack 源码中 80%的代码都是基于 Plugin 机制编写的,随着 Pulgin 越来越多,webpack 能做的也越来越多。可以说 Plugin 是 webpack 的灵魂。对于 Pulgin 的实现机制,是我们熟知的一个设计模式——事件驱动。—— 《深入浅出 Webpack》
Compiler & Compilation
它们是 Plugin 和 Webpack 之间的桥梁。
Compiler
代表了 Webpack 从启动到关闭的生命周期,而 Compilation
只是代表了一次新的编译。
Compiler: Compiler 模块是通过 CLI 或 Node API 传递的所有配置项创建的编译实例,包含了 options、loaders、plugins 这些信息。 它继承了 Tapable 类从而可以注册和调用插件。
Compilation: Compilation 实例可以访问所有模块及其依赖项。当 Webpack 以开发模式运行时,每当监测到一个文件变化,一次新的 Compilation 将被创建。 它继承了 Tapable 类从而可以注册和调用插件。
自定义 Plugin
Webpack 通过 Plugin 机制让其更加灵活,以适应各种应用场景。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
一个最基础的 Plugin 的代码是这样的:
1 |
|
在使用这个 Plugin 时,相关配置代码如下:
1 |
|
当为 webpack 开发插件时,需要知道每个 hook 在什么阶段被调用,具体需查阅文档
Webpack5 新特性介绍
-
持久性缓存 尝试用持久性缓存来提高构建性能
-
改进长期缓存 尝试用更好的算法和默认值来改进长期缓存
-
Tree Shaking 尝试用更好的 Tree Shaking 和代码生成来改善包大小
-
平台兼容性 尝试改善与网络平台的兼容性