这是我参与「第四届青训营 」笔记创作活动的的第6天
前言
1. 为什么学习 Webpack?
Webpack 是一种用于构建 JavaScript 应用程序的静态模块打包器,它能够以一种相对一致且开放的处理方式,加载应用中的所有资源文件(图片、CSS、视频、字体文件等),并将其合并打包成浏览器兼容的 Web 资源文件。
在旧时代,我们只能用原生 JavaScript(ES5)、CSS、HTML 方式编写页面代码,开发与生产环境代码基本一致,开发与运行效率都非常低;其次,页面的图片、代码、CSS 等资源都能且只能通过 img、 script、link 等标签插入到页面中,我们需要非常精细地管理、设计各个标签出现的位置、顺序,这也会占用我们非常多的精力与注意力;并且,开发环境和生产环境一致,难以接入 TS 或 JS 新特性,以及CSS预处理工具。如下图代码片段:
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="shortcut icon" type="image/x-icon" href="./images/log.ico" media="screen">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>嗨.音乐</title>
<link rel="stylesheet" type="text/css" href="./fonts/font-awesome-4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" type="text/css" href="./plugins/swiper/swiper-bundle.min.css">
<link rel="stylesheet" type="text/css" href="./css/base.css">
<link rel="stylesheet" type="text/css" href="./css/index.css">
<link rel="stylesheet" type="text/css" href="./css/header.css">
<link rel="stylesheet" type="text/css" href="./css/login.css">
<link rel="stylesheet" type="text/css" href="./css/main_show.css">
<script src="./plugins/swiper/swiper-bundle.min.js"></script>
<script src="./js/flexible.js"></script>
<script src="./js/jquery.min.js"></script>
<script src="./js/index.js"></script>
</head>
<body>
<!--网页logo 介绍 首行-->
<div class="head">
<div class="head_nav">
<a href="index.html">发现音乐</a>
<a href="index.html">我的音乐</a>
<a href="sub/shop.html" target="_blank">商城</a>
</div>
<!--导航部分-->
<div class="nav" id="nav">
2. 为什么是Webpack?
- 深入学习 Webpack,不仅能帮助你更快解决具体的工程技术问题,还能形成属于你个人的,极具区分度的核心竞争力!
- Vite 一类 Unbundle 工具定位于解决特定问题,而 Webpack 则几乎无所不能,功能覆盖小程序、桌面应用、微前端、WASM 等诸多场景,许多情况下 Webpack 依然是最优解。
- 同类工具或多或少都有借鉴 Webpack 之处,虽然具体实现差异很大,但解决工程化问题的思路基本一致,所谓一通百通,深入理解 Webpack 底层逻辑,以及处理具体问题的方式方法后,相同的知识必然也能套用到同类工具中。
- Webpack 还在持续迭代发展,V5 之后推出的持久化缓存、
lazyCompilation等特性极大强化了构建性能,未来虽不大可能超越 Unbundle 方案的性能优势,但相信会逐渐缩小差距,直至可被用户接受。
3. Webpack 的优点
- 所有资源都是 Module,所以可以用同一套代码实现诸多特性,包括:代码压缩、Hot Module Replacement、缓存等;
- 打包时,资源与资源之间非常容易实现信息互换,例如可以轻易在 HTML 插入 Base64 格式的图片;
- 借助 Loader,Webpack 几乎可以用任意方式处理任意类型的资源,例如可以用 Less、Stylus、Sass 等预编译 CSS 代码。
- Webpack 具有极强的开放性,也让它得以成为前端工程化环境的 基座,我们可以围绕 Webpack 轻易接入一系列工程化工具,例如 TypeScript、CoffeScript、Babel 一类的 JavaScript 编译工具;或者 Less、Sass、Stylus、PostCSS 等 CSS 预处理器;或者 Jest、Karma 等测试框架,等等。
- 自 2012 年首次发布至今,Webpack 还处于快速迭代成长阶段,社区依然保持极大活力;Webpack 已经发布了最新的 5.73.0 版本,经过 5 个大版本迭代以及社区的不断努力,现如今的 Webpack 已经非常非常成熟。
4. 如何学习 Webpack?
1. 入门应用
- 理解打包流程
- 熟练掌握常用配置项、Loader、插件的使用方法,能够灵活搭建继承 Vue、React、Babel、Eslint、Less、Sass、图片处理等工具的 Webpack 环境。
- 掌握常见脚手架的用法,例如:Vue-cli,create-react-app、@angular/cli
2. 进阶
- 理解 Loader、Plugin 机制,能够自行开发 Webpack 组件
- 理解常见性能优化手段、并能解决实际问题
- 理解前端工程化概念与生存现状
3. 大师级
- 阅读源码,理解 Webpack 编译、打包原理,甚至参与共建
1、使用 Webpack
1. 第一个 Webpack项目
首先新建文件夹 wepack-test, 通过编辑器打开,文件结构:
├── src
| └── index.js
└── webpack.config.js
填写内容: webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js', //项目入口文件
mode: 'development', //环境
devtool: false,
output: { //输出目录
filename: '[name].js',
path: path.join(__dirname, './dist')
}
}
index.js
document.write("hello Webpack!");
进行项目初始化,安装 Webpack:在项目根目录下,打开终端,执行命令:
npm init # 一路回车
npm i -D webpack webpack-cli
在项目根目录下,打开终端,执行编译命令,构建项目:
npx webpack
执行成功输出生产文件:
2. 理解 Webpack配置项
核心流程 -- 极度简化版
Webpack 原生提供了上百种配置项,这些配置最终都会作用于 Webpack 打包过程的不同阶段,因此我们可以从Webpack 编译流程来了解各项配置的作用。
- 入口处理:从
entry配置项开始,启动编译流程 - 从
entry配置项所指定的入口文件开始,根据require或import等语句找到依赖资源 - 根据
moudle配置项,调用资源转移器,将 png、css 等非标准 JS 资源转译为 JS 内容 - 递归调用 1 2,指定所有引用的文件和非标准 JS 资源处理完毕
- 将转译后的资源内容合并打包为可直接在浏览器运行的 JS 文件
webpack 特点 模块化 + 一致性
- 多个文件资源合并成一个,减少 http 请求数
- 支持模块化开发
- 支持高级 JS 特性
- 支持 TypeScript CoffeeScript 方言
- 统一图片、CSS、字体等其他资源的处理模型
- ...
那么,怎么使用 Webpack?
Webpack 的使用方法,基本都围绕“配置”展开,而这些配置大致可分为两类:
- 流程类:作用于流程中某个或若干个环节,直接影响打包效果的配置项
- 工具类:主流程之外,提供更多工程化能力的配置项
流程类配置项综述
与打包流程强相关的配置项有:
-
输入输出:
entry:用于定义项目入口文件,Webpack 会从这些入口文件开始按图索骥找出所有项目文件;context:项目执行上下文路径;output:配置产物输出路径、名称等;
-
模块处理:
resolve:用于配置模块路径解析规则,可用于帮助 Webpack 更精确、高效地找到指定模块module:用于配置模块加载规则,例如针对什么类型的资源需要使用哪些 Loader 进行处理externals:用于声明外部资源,Webpack 会直接忽略这部分资源,跳过这些资源的解析、打包操作
-
后处理:
optimization:用于控制如何优化产物包体积,内置 Dead Code Elimination、Scope Hoisting、代码混淆、代码压缩等功能target:用于配置编译产物的目标运行环境,支持 web、node、electron 等值,不同值最终产物会有所差异mode:编译模式短语,支持development、production等值,可以理解为一种声明环境的短语
这里的重点是,Webpack 首先需要根据输入配置(entry/context) 找到项目入口文件;之后根据按模块处理(module/resolve/externals 等) 所配置的规则逐一处理模块文件,处理过程包括转译、依赖分析等;模块处理完毕后,最后再根据后处理相关配置项(optimization/target 等)合并模块资源、注入运行时依赖、优化产物结构等。
这些配置项与打包流程强相关,建议学习时多关注它们对主流程的影响,例如 entry 决定了项目入口,而 output 则决定产物最终往哪里输出;resolve 决定了怎么找到模块,而 module 决定了如何解读模块内容,等等。
工具类配置项综述
除了核心的打包功能之外,Webpack 还提供了一系列用于提升研发效率的工具,大体上可划分为:
-
开发效率类:
watch:用于配置持续监听文件变化,持续构建devtool:用于配置产物 Sourcemap 生成规则devServer:用于配置与 HMR 强相关的开发服务器功能
-
性能优化类:
cache:Webpack 5 之后,该项用于控制如何缓存编译过程信息与编译结果performance:用于配置当产物大小超过阈值时,如何通知开发者
-
日志类:
stats:用于精确地控制编译过程的日志内容,在做比较细致的性能调试时非常有用infrastructureLogging:用于控制日志输出方式,例如可以通过该配置将日志输出到磁盘文件
逻辑上,每一个工具类配置都在主流程之外提供额外的工程化能力,例如 devtool 用于配置产物 Sourcemap 生成规则,与 Sourcemap 强相关;devServer 用于配置与 HMR 相关的开发服务器功能;watch 用于实现持续监听、构建。
工具类配置内聚性较强,通常一个配置项专注于解决一类工程问题,学习时建议先对配置项按其功能做个简单分类,例如上述开发效率类、性能优化类等,之后再展开研究其可选值与效果。
3. 通过例子了解 Webpack 配置过程
引入CSS预处理器,处理CSS资源
首先搭建项目,示例文件结构:
.
├── src
| ├── index.js
| └── index.css
└── webpack.config.js
填写内容
index.js
import styles from './index.css'
index.css
.box{
width: 100px;
height: 100px;
}
初始化项目,安装 Webpack
npm init # 一路回车
npm i -D webpack webpack-cli
安装 CSS 处理器 loader
npm add -D css-loader style-loader
配置 webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
mode: 'development',
devtool: false,
output: {
filename: '[name].js',
path: path.join(__dirname, './dist')
},
module: {
//CSS处理器
rules: [{
test: /.css/i,
use: [
'style-loader',
'css-loader'
]
}]
}
}
在项目根目录下,打开终端,执行编译命令,构建项目:
npx wabpack
问题
- Loader 有什么作用?为什么这里需要用到 css-loader、style-loader?
- 与旧时代 ---- 在 HTML 文件中维护 css 相比,这种方式会有什么优劣处?
- 有没有接触过 Less、Sass、Stylus 这一类 CSS 预编译框架?如何在 Webpack 接入这些工具?
回答
Loader有什么作用?
Webpack Loader 最核心的只能是实现内容转换器 —— 将各式各样的资源转化为标准 JavaScript 内容格式,例如:
css-loader将 css 转换为__WEBPACK_DEFAULT_EXPORT__ = ".a{ xxx }"格式html-loader将 html 转换为__WEBPACK_DEFAULT_EXPORT__ = "<!DOCTYPE xxx"格式vue-loader更复杂一些,会将.vue文件转化为多个 JavaScript 函数,分别对应 template、js、css、custom block
为什么这里需要用到 css-loader、style-loader ?
- 本质上是因为 Webpack 只认识符合 JavaScript 规范的文本(Webpack 5之后增加了其它 parser):在构建(make)阶段,解析模块内容时会调用
acorn将文本转换为 AST 对象,进而分析代码结构,分析模块依赖;这一套逻辑对图片、json、Vue SFC等场景就不工作了,就需要 Loader 介入将资源转化成 Webpack 可以理解的内容形态。
与旧时代 ---- 在 HTML 文件中维护 css 相比,这种方式会有什么优劣处?
- 优点:统一管理
- 缺点:耦合度高
有没有接触过 Less、Sass、Stylus 这一类 CSS 预编译框架?如何在 Webpack 接入这些工具?
- 有,接入 less 示例,在理解loader章节叙述
引入 Babel 处理 JS 兼容性问题
Babel 的作用: 用于将 JS 语法降级, 处理兼容性, 如将 ES6 规范,转换为 ES5 规范
为什么这样做:
- JavaScript 语法标准繁多, 浏览器支持程度不一
- 开发者需要用到高级语法
安装 babel 依赖
npm i -D @babel/core @babel/preset-env babel-loader
配置 webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
mode: 'development',
devtool: false,
output: {
filename: '[name].js',
path: path.join(__dirname, './dist')
},
module: {
rules: [{ //less处理
test: /.less$/i,
use: [
'style-loader',
'css-loader',
'less-loader'
]
},{ //js处理 babel
test: /.js?$/,
use:[{
loader: 'babel-loader',
options:{
presets:[
['@babel/preset-env']
]
}
}]
}]
}
}
执行 npx webpack
问题
- Babel 具体有什么功能?
- Babel 与 Webpack 分别解决了什么问题? 为何能协作到一起?
回答
Babel 具体有什么功能 ?
- Babel 插件大致分为两种:语法插件和转换插件。语法插件作用于 @babel/parser,负责将代码解析为抽象语法树(AST)(官方的语法插件以 babel-plugin-syntax 开头);转换插件作用于 @babel/core,负责转换 AST 的形态。
Babel 与 Webpack 分别解决了什么问题? 为何能协作到一起?
- Babel 解决了项目的兼容性问题
- Webpack 使项目模块化, 解决依赖问题
- Webpack 主要是将非标准 JS 资源转译为 JS 内容,进行统一资源管理,而 JS 可能在不同浏览器上存在兼容问题,所以需要 Babel 来进行协作
引入HTMLWebPackPlugin 生成 HTML
安装 HTMLWebPackPlugin 插件
npm i -D html-webpack-plugin
配置 webpack.config.js
const path = require('path');
//引入 依赖
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
mode: 'development',
devtool: false,
output: {
filename: '[name].js',
path: path.join(__dirname, './dist')
},
module: {
rules: [{ //less处理
test: /.less$/i,
use: [
'style-loader',
'css-loader',
'less-loader'
]
},{ //js处理
test: /.js?$/,
use:[{
loader: 'babel-loader',
options:{
presets:[
['@babel/preset-env']
]
}
}]
}]
},
//插件
plugins:[new HtmlWebpackPlugin()]
}
执行 npx webpack
问题
- 相比于手工维护 HTML 内容,这种自动生成的方式有什么优缺点?
回答
相比于手工维护 HTML 内容,这种自动生成的方式有什么优缺点?
- 优点:简化了HTML文件的创建,无需手工维护 HTML 文件
- 缺点:配置繁琐
开启模块热替换工具,进行项目的实时预览
HMR Hot Moudle Replacement --- 模块热替换工具:热更新代码,当你修改代码保存后,会自动更新,实时显示
安装 devserver
npm install -D webpack-dev-server
配置 webpack.config.js
const path = require('path');
//引入 依赖
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
mode: 'development',
devtool: false,
devServer:{
hot: true, //开启热加载
open:true
},
watch:true, //持续监听代码改变生成新版本
output: {
filename: '[name].js',
path: path.join(__dirname, './dist')
},
module: {
rules: [{ //less处理
test: /.less$/i,
use: [
'style-loader',
'css-loader',
'less-loader'
]
},{ //js处理
test: /.js?$/,
use:[{
loader: 'babel-loader',
options:{
presets:[
['@babel/preset-env']
]
}
}]
}]
},
//插件
plugins:[new HtmlWebpackPlugin()]
}
执行命令,运行项目至浏览器
npx webpack serve
使用 Tree-Shaking 删除没有使用的代码
Tree-Shaking 树摇 工具
把定义的代码,但没有进行使用,进行删掉
使用 Tree-Shaking, 配置 webpack.config.js
const path = require('path');
//引入 依赖
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
mode: 'production', //使用树摇,注意把 模式改为 生产环境
devtool: false,
devServer:{
hot: true, //开启热加载
open:true
},
optimization:{
usedExports:true //开启 树摇
},
watch:true, //持续监听代码改变生成新版本
output: {
filename: '[name].js',
path: path.join(__dirname, './dist')
},
module: {
rules: [{ //less处理
test: /.less$/i,
use: [
'style-loader',
'css-loader',
'less-loader'
]
},{ //js处理
test: /.js?$/,
use:[{
loader: 'babel-loader',
options:{
presets:[
['@babel/preset-env']
]
}
}]
}]
},
//插件
plugins:[new HtmlWebpackPlugin()]
}
验证: 编写 index.js bar.js
//bar.js
let bar1 = '1';
let bar2 = '2';
export {
bar1,
bar2
}
//index.js
import {
bar,
bar2
} from './bar'
console.log(bar);
注意此时是生产环境,执行 npx webpack
以下效果是开发环境和生产环境的对比
其他工具
- 缓存
- Sourcemap
- 性能监控
- 日志
- 代码压缩
- 分包
3、进阶篇:理解 Loader
1. 概述
问题:Webpack 只认识JS
为了处理非标准 JS 资源,设计出资源翻译模块---Loader,用于将资源翻译为标准JS
2. 使用Loader
文件结构
├── src
| ├── a.less
| ├── b.less
| └── index.js
└── webpack.config.js
引入 less
npm install less less-loader -D
配置 webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
mode: 'development',
devtool: false,
output: {
filename: '[name].js',
path: path.join(__dirname, './dist')
},
module: {
//less处理器
rules: [{
test: /.less$/i,
use: [
'style-loader',
'css-loader',
'less-loader'
]
}]
}
}
index.js
import styles from './a.less'
认识Loader -- 链式调用
使用上,可以为某种资源文件配置多个 Loader,Loader 之间按照配置的顺序从前到后(pitch),再从后到前依次执行,从而形成一套内容转译工作流,例如对于下面的配置:
module.exports = {
module: {
rules: [
{
test: /.less$/i,
use: [
"style-loader",
"css-loader",
"less-loader",
],
},
],
},
};
上述示例中,三个 Loader 分别起如下作用:
less-loader:实现 less => css 的转换,输出 css 内容,无法被直接应用在 Webpack 体系下css-loader:将 css 内容包装成类似module.exports = "${css}"的内容,包装后的内容符合 JavaScript 语法style-loader:做的事情非常简单,就是将 css 模块包进 require 语句,并在运行时调用 injectStyle 等函数将内容注入到页面的 style 标签
认识Loader :其它特性
- 链式执行
- 支持异步执行
- 分 normal ,pitch 两种模式
2. 编写loader
3. 常见 Loader
站在使用角度,建议掌握这些常见的 Loader 的功能、配置方法
4、进阶篇:理解插件
1. 概述
插件是什么?
- 插件是为了给程序提供一种扩展能力
- 比如 Vs Code、Vue
甚至、Webpack 本身的很多功能都是插件来实现的,我们可以通过下图看出:
2. 使用插件
- 安装插件依赖 npm
- 在 webpack 配置,引入插件
- 创建插件实例
3. 写webpack插件
首先:插件围绕 “钩子“ 展开
钩子的核心信息:
- 时机 :编译过程的特点阶段,Webpack 会以钩子形式通知插件此刻正在发生什么事情
- 上下文:通过 tapable 提供的回调机制,以参数形式传递上下文信息
- 交互:在上下文参数对象中附带很多存在 side effect 的交互接口,插件可以通过这些接口改变
时机: compier.hooks.compliation
参数:compliation
交互:dependencyFactories.set
5、总结
我们前面学了:
- Webpack 的作用
- 理解 Webpack 配置结构,学习关键配置项
- Loader 的作用与常用 Loader
- 插件基本形态与作用
推荐学习网址: