Webpack 知识体系 | 青训营笔记
这是我参与「第四届青训营 」笔记创作活动的的第 18 天
课程简介
由浅入深,从应用技巧到组件开发,再到 Webpack 构建流程的基本原理剖析,最后总结一套学习方法论,帮助读者 0 基础搭建 Webpack 知识体系。
课前准备
安装浏览器
安装编辑器
要进行前端开发,一个趁手的编辑器必不可少。强烈推荐使用 Visual Studio Code,如果你习惯使用其它编辑器也是可以的。
提前阅读
课前建议先充分了解 Webpack 的基本功能与作用,参考资料:Webpack 官网,建议根据官网文档手动搭建若干示例。
什么是 Webpack
前端项目由资源构成。
png jpg gif webp js ts css less vue jsx sass ...
可以手动管理这些资源(09 年以前),在 HTML 中引用链接。
但这种管理方式:
- 依赖手工,比如有 50 个 js 文件的话,操作、过程繁琐
- 当代码文件之间有依赖的时候,就得严格按依赖顺序书写
- 开发与生产环境一致,难以接入 ts 或 js 新特性
- 比较难接入 Less、Sass 等工具
- js、图片、css 资源管理模型不一致
这些都是旧时代非常突出的问题,对开发效率影响非常大。
直到出现了很多工程化工具。某种程度上正是这些工具的出现,才有了‘前端工程’这一概念。
概念
Webpack 本质上是一种前端资源编译、打包工具。
- 多份资源文件打包成一个 Bundle
- 支持 Babel、Eslint、TS、CoffeScript、Less、Sass
- 支持模块化处理 CSS 、图片等资源
- 支持 HMR + 开发服务器
- 支持持续监听、持续构建
- 支持代码分离
- 支持 Tree-shaking
- 支持 Sourcemap
- ...
使用 Webpack
示例
- 安装
npm i -D webpack webpack-cli
- 编辑配置文件
webpack.config.js
module.exports = {
entry: 'main.js',
output: {
filename: "[name].js",
path: path.join(__dirname, "./dist"),
}
module: {
rules:[{
test: /\.less$/i,
use: ['style-loader', 'css-loader', 'less-loader']
}]
}
}
- 执行编译命令
npx webpack
核心流程
极度简化版:
- 入口处理 Get Start
从 `entry` 文件开始,启动编译流程
- 依赖解析 Dependencies Lookup
从 `entry` 文件开始,根据 `require` or `import` 等语句找到依赖资源
- 资源解析 Transform
根据 `module` 配置,调用资源转移器,将 png、css 等非标准 JS 资源转译为 JS 内容
递归调用 2、3 ,直到所有资源处理完毕。
- 资源合并打包 Combine Assets
将转译后的资源内容合并打包为可直接在浏览器运行的 JS 文件
模块化 + 一致性
- 多个文件资源合并成一个,减少 http 请求数
- 支持模块化开发
- 支持高级 JS 特性
- 支持 TypeScript、CoffeeScript 方言
- 统一图片、CSS、字体等其他资源的处理模型
- ...
如何使用
关于 Webpack 的使用方法,基本都围绕“配置”展开,而这些配置大致可划分为两类:
- 流程类:作用域流程中某个 or 若干个环节,直接影响打包效果的配置项
- 工具类:主流程之外,提供更多工程化能力的配置项
流程类配置
-
输入
- `entry`
- `context`
-
模块解析
- `resolve`
- `externals`
-
模块转译
- `module`
-
后处理
- `optimization`
- `mode`
- `target`
配置总览
使用频率:
- entry/output
- module/plugins
- mode
- watch/devServer/devtool
处理 CSS
文件结构
.
|---src
| |---index.js
| |---index.css
|---webpack.config.js
index.js
const styles = require('./index.css');
// or
import styles from './index.css'
- 安装 Loader
npm add -D css-loader style-loader
- 添加 `module` 处理 CSS 文件
webpack.config.js
const path = require("path");
module.exports = {
entry: './src/index',
output: {
filename: "[name].js",
path: path.join(__dirname, "./dist"),
}
module: {
// css processor
rules:[{
test: /\.css/i,
use: [
'style-loader',
'css-loader',
],
}],
},
};
接入 Babel
文件结构
.
|---src
| |---index.js
|---webpack.config.js
index.js
class Person {
constructor() {
this.name = 'Tecvan'
}
}
console.log((new Person()).name)
const say = () => {}
- 安装依赖
npm i -D @babel/core @babel/preset-env babel-loader
-
声明产物出口 output
-
执行 npx webpack
const path = require("path");
module.exports = {
entry: './src/index',
output: {
filename: "[name].js",
path: path.join(__dirname, "./dist"),
}
module: {
rules:[{
test: /\.js$/,
use: [{
loader: 'babel-loader',
options: {
presents: [
[
'@babel/preset-env'
]
]
}
}, ],
}],
},
};
生成 HTML
文件结构
.
|---src
| |---index.js
|---webpack.config.js
- 安装依赖
npm i -D html-webpack-plugin
- 声明产物出口 ouput
const path = require("path");
const HtmlWebpackPlungin = require('html-webpack-plungin');
module.exports = {
entry: './src/index',
output: {
filename: "[name].js",
path: path.join(__dirname, "./dist"),
}
plungin: [new HtmlWebpackPlugin()]
};
- 执行 npx webpack
HMR
Hot Module Replacement 模块热替换
- 开启 HMR
webpack.config.js
module.exports = {
// ...
devServer: {
hot: true
}
};
- 启动 Webpack
npx webpack serve
Tree-Shaking
树摇,用于删除 Dead Code
Dead Code:
- 代码没有被用到,不可到达
- 代码的执行结果不会被用到
- 代码只读不写
- ...
Tree-Shaking
- 模块导出了,但未被其他模块使用
开启 tree-shaking:
- mode: "production"
- optimization.usedExports: true
PS:对工具类库如 Lodash 有奇效
其他工具
- 缓存
- Sourcemap
- 性能监控
- 日志
- 代码压缩
- 分包
- ...
理解 Loader
Webpack 只认识 JS
为了处理非标准 JS 资源,设计出资源翻译模块 —— Loader
用于将资源翻译为标准 JS
链式调用
module.exports = {
module: {
rules:[{
test: /\.less%/i,
use: [
'style-loader',
'css-loader',
'less-loader',
],
}],
},
};
- less-loader:实现 less => 的转换
- css-loader:将 CSS 包装成类似 module.export = "${css}" 的内容,包装后的内容符合 JavaScript 语法
- style-loader:将 CSS 模块包进 require 语句,并在运行时调用 injectStyle 等函数将内容注入到页面的 style 标签
其他特性
特点:
- 链式执行
- 支持异步执行
- 分 normal、pitch 两种模式
常见 Loader
站在使用角度,建议掌握这些常见 Loader 的功能、配置方法
理解插件
这是一个特别复杂的过程,那么:
- 新人需要了解整个流程细节,上手成本高
- 功能迭代成本高,牵一发动全身
- 功能僵化,作为开源项目而言缺乏成长性
- ...
心智成本高 => 可维护性低 => 生命力弱
插件架构精髓:对扩展开放,对修改封闭
甚至,Webpack 本身的很多功能也是基于插件实现的
hook
在编译的某个环节触发 hook ,某种程度上可以理解为事件
hook 的核心信息:
- 时机:编译过程的特定节点,Webpack 会以 hook 形式通知插件此刻正在发生什么事情
- 上下文:通过 tapable 提供的回调机制,以参数方式传递上下文信息
- 交互:在上下文参数对象中附带了很多存在 side effect 的交互接口,插件可以通过这些接口改变
时机:compier.hooks.compilation
参数:compilation ...
交互:dependencyFactories.set
学习方法
- 入门应用
- 理解打包流程
- 熟练掌握常用配置项、Loader、插件的使用方法,能够灵活搭建集成 Vue、React、Babel、Less、Sass、图片处理等工具的 Webpack 环境
- 掌握常见脚手架工具的用法,例如:Vue-cli、creat-react-app、@angular/cli
- 进阶
- 理解 Loader、Plugin 机制,能够自行开发 Webpack 组件
- 理解常见性能优化手段,并能用于解决实际问题
- 理解前端工程化概念与生态现状
- 大师级
- 阅读源码,理解 Webpack 编译、打包原理,甚至能够参与共建