为什么要学习 Webpack
- 理解前端"工程化"概念、工具、目标
- 一个团队总要有那么几个熟悉 Webpack,某种程度上可以成为个人的核心竞争力
- 高阶前端必经之路
什么是 Webpack
一个前端项目由很多的资源构成
旧时代的资源引入十分麻烦,对开发的效率影响很大
- 依赖手动引入
- 代码文件之间相互依赖时,需要严格按照依赖顺序书写
- 比较难接入 Less、Sass 等工具
- JS、图片、CSS 资源管理模型不一致
09年之后出现了很多的工具,"前端工程化"的概念逐渐出现
Webpack 本质上是一种前端资源编译、打包工具:
- 多份资源文件打包成一个 Bundle
- 支持 Babel、ESlint、TS、CoffeeScript、Less、Sass
- 支持模块化处理 css、图片等资源文件
- 支持 HMR + 开发服务器
- 支持持续监听、持续构建
- 支持 Tree-shaking
- 支持 Sourcemap
- ......
使用 Webpack
基本步骤
- 安装
bash
npm install -D webpack webpack-cli
- 编辑配置文件
后续的配置可以做到非常非常复杂,实际上核心的地方就是这里的配置文件
js
// webpack.config.js
const path = require('path')
module.exports = {
entry: './src/index', // 入口
mode: "development",
devtool: false,
output: { // 产物出口
filename: 'bundle.js', // 产物名称
path: path.join(__dirname, './dist') // 产物路径
}
}
- 执行编译命令
bash
npx webpack
Webpack 打包核心流程
- 入口处理
从 entry
文件开始,启动编译流程
- 依赖解析
从 entry
文件开始,根据 require
或 import
等语句找到依赖资源
- 资源解析
根据 module
配置,调用资源转移器,将 png、css 等非标准 JS 资源编译为 JS 内容
第二步和第三步实际上是递归循环执行的,也就是说解析资源的过程中可能遇到新的资源引入,从而触发新的依赖解析
- 资源合并打包
将转义后的资源内容合并打包为可以直接在浏览器运行的 JS 文件
效果:模块化和一致性
- 多个文件资源合并成一个,减少 http 请求数
- 支持模块化挨罚
- 支持高级 JS 特性
- 支持 Typescript、CoffeeScript 方言
- 统一图片、CSS、字体 等其他资源的处理模型
- ......
怎么使用 Webpack
基本上都是围绕 配置
展开的,这些配置大致可以分为两类:
- 流程类:作用于流程中某个 or 若干个环节,直接影响打包效果的配置项
- 工具类:主流程之外,提供更多工程化能力的配置项(比如 TreeShaking、devTool)
配置文件
webpack 可以设置配置文件 webpack.config.js
这个文件是在 node 中运行,遵循 node 的规范
mode
区分:src中需要在前端运行,因此使用前端规范,其他位置在 node 环境下运行,使用 node 规范
js
// webpack.config.js
module.exports = {
mode: "production" // 设置打包模式为生产模式,另一个模式为开发模式 development
}
只打包有使用到的模块的部分代码,但是如果模块内有执行代码,则会整个模块都打包(webpack不知道有些属性/方法是否使用,因此整个模块都打包)
entry
配置打包时的主文件,默认为 "./src/index.js"
一般不改
可以传入数组,元素为多个打包的文件的路径字符串
可以传入对象,各属性名为打包后的文件名,各属性值为各文件的路径字符串
传入数组打包:将所有模块打包为一个文件,传入对象打包:多文件打包,分别打包各个模块
output
配置代码打包后的地址
传入一个对象
filename
属性表示打包后的文件名,可以使用 "[name].js"
在多文件打包时自动使用模块的名字,类似的还有 [id]
[hash]
clean: true
打包前清空打包目录
path
指定打包的目录(默认为 dist),绝对路径,最好在使用时引入 path 模块
处理 CSS
CSS文件 不在 JS 的模块化规范中,所以需要使用 Loader 进行处理(在 module 属性中配置)
安装处理 CSS 的 loader
bash
npm add -D css-loader style-loader
js
// in webpack.config.js
module.exports = {
// ...
module: {
rules: [{
test: /\.css$/, // 资源的正则匹配
use: ['style-loader', 'css-loader'] // 使用的 Loader
}]
}
}
打包后,打包文件中会插入很多运行时的代码
接入 Babel
对高级的 JS 特性(ES6+)进行转义,让不支持高级特性的浏览器能供兼容这些 JS 代码
安装依赖
bash
npm i -D @babel/core @babel/preset-env babel-loader
配置文件
js
// in webpack.config.js
module.exports = {
// ...
module: {
rules:[{
test: /.\js$/,
options: {
presets: [
{'@babel/preset-env'} // 使用 preset-env
]
}
}]
}
}
其余常用的 babel 依赖:
@babel/preset-react
@babel/preset-typescript
HTMLWebpack Plugin
用于直接生成一个 HTML 文件(index.html),并自动插入资源( JS 文件等)
js
// in webpack.config.js
module.exports = {
// ...
plugin: [new HTMLWebpackPlugin()]
}
可以通过配置项配置更多功能
上面的三个配置可以归属为流程类配置,用于配置三板斧(HTML、CSS、JS)
下面开始介绍的是工具类的配置
HMR
Hot Module Replacement,模块热替换
更新的代码不需要刷新就能应用,主要在 devServer 这个配置项
js
// in webpack.config.js
module.exports = {
// ...
devServer: {
hot: true, // 开启 HMR
open: true,
},
watch: true, // webpack 持续监听代码修改
}
详细的原理可以看这篇文章:HMR 原理全解析
Tree-Shaking
Dead Code:
- 代码没有被用到,不可到达
- 代码的执行结果不会被用到
- 代码只读不写
Tree-Shaking
- 删除掉一些模块(模块导出了,但未被其他模块使用)
- 删除 Dead Code
开启 tree-shaking 的一种方式
js
// in webpack.config.js
module.exports = {
// ...
mode: "production", // 如果是"development"则不会 tree-shaking
optimization: {
useExports: true
}
}
还有其他工具:
- 缓存
- Sourcemap
- 性能监控
- 日志
- 代码压缩
- 分包
- ......
Loader
为了处理非标准 JS 资源,设计出资源翻译模块 ------ Loader,用于将资源翻译为标准 JS
使用的步骤:
- 安装需要的 loader
- 在 webpack.config.js 中配置 module 中的 rules
loader 的链式调用
以 less-loader 举例
可以在 loader 的源码中插入 debugger;
,从而了解 loader 的执行过程
使用 ndb 工具,输入命令 ndb npm webpack
进行 debugger
通过 console
每一个 loader 的产出来观察转换的过程(一个 loader 的输出往往是另一个 loader 的输入)
loader 执行的阶段
loader 的标准模板
js
// loader.js
module.exports = function(source, sourceMap?, data?) {
// source 为 laoder 的输入
// 可能是文件内容,也可能是上一个 loader 的处理结果
return source;
};
常见的 loader
待后续学习补充
cache-loader
可以在一些性能开销较大的 Loader 之前添加,目的是将结果缓存到磁盘里。
file-loader
把文件输出到一个文件夹,在代码中通过相对 URL 去引用输出的文件(处理图片、字体、图标)
url-loader
和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去(大的文件由 file-loader 处理)
source-map-loader
加载额外的 Source Map 文件,方便断点调试
image-loader
加载并且压缩图片
babel-loader
把 ES6 转换成 ES5
css-loader
加载 CSS,支持模块化、压缩、文件导入等特性
style-loader
将 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS
sass-loader
将 scss/sass 代码转换成 css
less-loader
将 less 代码转换成 css
eslint-loader
通过 ESLint 检查 JavaScript 代码
Plugin
Webpack 的很多能力都是基于插件的
很多知名的工具都设计了"插件"架构
插件架构的精髓:对扩展开放,对修改封闭
使用的过程:
- 在 webpack.config.js 中引入插件
- 在 webpack.config.js 的 pulgins 配置数组中新建插件对象
js
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: "./src/index",
output: {
filename: "[name].js",
path: path.join(__dirname, "./dist"),
},
plugins: [
new HtmlWebpackPlugin(),
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true),
VERSION: JSON.stringify('5fa3b9'),
}),
]
}
Hook
插件围绕 钩子(Hook)
展开
可以打开 webpack 的源码来查看有哪些 Hook,以下是简化后常用的 Hook
钩子的核心信息:
- 时机:编译过程的特定节点,Webpack 会以钩子形式通知钩子此刻正在发生什么事情
- 上下文:通过 tapable 提供的回调机制,以参数方式传递上下文
- 交互:在上下文参数对象中附带了很多存在 side effect 的交互接口,插件可以通过这些接口改变
编写 Plugin
Plugin 中的核心:
- 时机:
compiler.hooks.compilation
- 参数:
compilation
等 - 交互:
dependencyFactories.set
插件的编写是非常复杂的
可以参考以下文章:
常见的 Plugin
define-plugin
定义环境变量
ignor-plugin
忽略部分文件
clean-webpack-plugin
目录清理
html-webpack-plugin
简化 html 文件创建
web-webpack-plugin
方便地为单页面应用输出 html,比 html-webpack-plugin 好用
uglifyjs-webpack-plugin
通过 UglifyES 压缩 ES6 代码
optimize-css-assets-webpack-plugin
压缩 css 代码
webpack-parallel-uglify-plugin
多核压缩,提高压缩速度
webpack-bundle-analyzer
可视化 webpack 输出文件的体积
mini-css-extract-plugin
CSS 提取到单独的文件中,支持按需加载
HotModuleReplacementPlugin
模块热替换
如何学习 Webpack
知识体系图:
学习方法
- 入门应用
- 理解打包流程
- 熟练掌握常用配置项、Loader、插件的使用方法,能够灵活搭建集成 Vue、React、Babel、Eslint、Less、Sass、图片处理等工具的 Webpack 环境
- 掌握常见脚手架工具的用法,例如:Vue-cli、create-react-app、@angular/cli
- 进阶
- 理解 Loader、Plugin 机制,能够自行开发 Webpack 组件
- 理解常见性能优化手段,并能用于解决实际问题
- 理解前端工程化概念与生态现状
- 大师级
阅读源码,理解 Webpack 编译、打包原理,甚至能够参与共建
入门应用的知识体系: