Webpack学习目标
- 能够使用
webpack
搭建任意的前端架构环境(v4
、v5
都要会使用) - 尽可能多地了解
webpack
工作流程、底层原理,以及与vite
、gulp
、rollup
等工具的差异与优劣 - 尽可能多地实践一些
webpack
构建优化技巧(运行速度、代码质量)
Webpack认识
- 目前最流行的前端构建工具
- 作用就是将模块及其依赖编译打包成浏览器能够普遍兼容的优质代码或静态资源
- 理论上
webpack
可以对任意的文件进行编译打包。 webpack
是运行到node
环境中的,支持CommonJS
模块化语法webpack
它是基于配置文件的(定制化特点强)- 版本:目前大部分公司中用的是
v4
版本,最近新出了v5
版本
webpack安装
webpack-cli
- 全局安装+本地安装
- 有很多好用的
webpack
相关命令
webpack
- 全局安装+本地安装
- 它是
webpack
的核心代码包括webpack API
和核心插件所在的包。
webpack配置
- 配置入口和出口,实现
build
打包功能 - 配置本地服务,以便开发环境使用
- 自动清除
dist
目录,添加文件hash
值,开启编译进度条、开启代码压缩 - 区分开发环境和生产环境
- 把
src
中的js
代码编译成浏览器能够普遍兼容的ES5
代码 - 在当前环境下支持
jsx
语法 - 配置
eslint
,实现对代码的检测
webpack文件配置
- 代码语法是
CommonJs
模块 - 导出方式
module.exports={}
module.exports=function(){return{}}
module.exports=()=>{return {}}
module.exports=()=>({})
module.exports=()=>({
entry:{//入口配置(建议在配置入口文件时使用绝对路径)
app:path.resolve(__dirname,'../src/main.js')
},
output:{//出口配置
filename:'js/[]'
}
})
自定义loader
- 每一个
loader
都是一个函数 - 接收一种文件(对象或字符串),最终返回另外一种文件(对象、字符串或
JS
代码)
module.exports=function(source){
//使用任何第三方js模版do something
//如果一个loader不用与webpack规则的最后一个loader,不要抛出js代码直接return 一个值就可以了
//不是最后一个loader
return result
//es6模块化
return `export default ${JSON.stringify(result)}`
//CommonJs
return `module.exports=${JSON.stringify(result)}`
}
自定义plugin
- 每一个
plugin
本质上都是一个class
类 - 插件函数或类的
prototype
上定义一个apply
方法- 指定要绑定到的
webpack
事件钩子 - 处理
webpack
内部实例数据 - 功能完成后调到
webpack
提供的回调 - 最重要的两个资源compliler和compilation对象
- 指定要绑定到的
// 一个 JavaScript 类
class MyExampleWebpackPlugin {
// 在插件函数的 prototype 上定义一个 apply 方法,以 compiler 为参数。
apply(compiler) {
// 指定一个挂载到 webpack 自身的事件钩子。
compiler.hooks.emit.tap(
'MyExampleWebpackPlugin',
(compilation, callback) => {
console.log('这是一个示例插件!');
console.log(
'这里表示了资源的单次构建的 `compilation` 对象:',
compilation
);
// 用 webpack 提供的插件 API 处理构建过程
compilation.addModule(/* ... */);
callback();
}
);
}
}
webpack开发环境优化
开发环境优化标准:运行速度尽量快
-
devtool:inline-source-map
- 便于调试代码(直接定位源代码位置)
- 使用
devtool
会减低运行速度
-
关闭
watch
监听代码依赖图的变化- 在
v5
中使用hot:true
实现热更新 - 提高运行速度
- 在
-
开启
memory
缓存- 提高运行速度
-
在使用
loader
和plugin
时,使用exclude
、inculde
- 缩小搜索范围
- 提高运行速度
-
巧用
resolve
属性- 对路径进行优化,缩小搜索范围
- 在不使用
npm run link
时候,设置symlinks:false
提升解析速度
-
使用
thread-loader
- 开启多线程构建,提高构建速度
- 需要注意硬件配置
-
使用
cache-loader
- 对“指定文件模块”进行缓存
- 但对于经常要更新的不要缓存,会导致热更新失效
- 提高运行速度
-
使用
speed-measure-webpack-plugin
- 测量你的
webpack
构建速度
- 测量你的
webpack生产环境优化
生产环境优化标准:提高打包后代码的质量
-
devtool:source-map
- 打包后会为每一个
bundle
生成.map
文件 - 用于打包后代码之间关系映射
- 打包后会为每一个
-
chunks
拆分-
前端代码使用
()=>import
动态导入技术- 需要
@babel/plugin-syntax-dynamic-import
- 推荐使用
- 需要
-
optimization.splitChunks
或split-chunks-plugin
- 实现手动代码分离,用得很少
-
-
- 在
entry
入口中对多个chunk
中重复代码进行抽离
- 在
entry:{
vendor:['react'],
app:{
dependOn:'vendor',
import:path.resolve(__dirname,'../foo/main.js')
}
}
-
bundle
分析技术- 使用
webpack-bundle-analyzer
对代码依赖图进行人工分析
- 使用
-
使用
Tree Shaking
技术- 用于把
src
中的“死代码”(未使用的代码)移除掉,以节省打包后代码的体积 - 在
package.json
中添加sideEffects:false
,该功能只对mode:production
起作用
- 用于把
-
抽离
css
并压缩- 使用
mini-css-extract-plugin
抽离css
- 使用
css-minimizer-webpack-plugin
- 使用
-
terser
压缩- 使用
terser-webpack-plugin
集成terser
高性能压缩
- 使用
常见loader
babel-loader
使用babel
加载es6
代码并将其转换为es5
html-loader
将HTML
导出为字符串,需要传入静态资源的引用路径markdown-loader
将markdown
编译为HTML
syle-loader
将模块导出的内容作为样式添加到DOM
中sass-loader
加载·并编译sass/scss
文件为css
文件vue-loader
编译vue
文件
常见plugin
eslint配置
-
语法是
CommonJS
语法 -
意义:协同开发保证代码规范和一致性
-
使用的插件配置
eslint
- 在
v4
中,使用eslint-loader
- 在
v5
中,使用eslint-webpack-plugin
- 在
-
不同的项目,我们需要安装不同的
ESLint
检测器 -
EsLint
的配置文件有六种,.eslintrc.js
优先级最高 -
解决
ESLint
报错或警告的问题- 找到报错的地方或警告的地方进行修改
- 找到
ESLint
的规则名称并修改它的检测级别 - 使用
ESLint
注释包裹代码,忽略代码的检测 - 找到
webpack
配置文件将ESLint
注释掉//eslint-disable-line
/*eslint-disable*//*eslint-enable*/
/*eslint-disable no-var*/ /* eslint-enable no-var*/
- 在项目的根目录添加,
.eslintigore
文件(推荐)
-
配置文件
module.exports={ //配置解析器 "parserOptions": { "ecmaVersion": 6,//默认设置为3,5可以使用6,7,8,9也可以指定年号(设置ES版本) "sourceType": "module",//设置代码类型,默认是“script”如果使用ESMmodule,设置为"module" "ecmaFeatures": {//配额外的语言特性 "jsx": true,//使用"jsx" "impliedStrict":true//启用全局strict mode(要求ecmaVersion是5或更高) } }, //默认使用esprima作为解析器,可以指定为其他解析器(1.必须为一个node模块2.必须符合parser interface) "parser":"esprima", "rules": { "semi": "error" }, "plugins":["a-plugin"],//配置插件 "processor":"a-plugin/a-processor"//指定配置处理器(从另一种文件提取或在预处理中转换为JavaScript代码) , "overrides":[//为特定类型的文件指定处理器 { "files":["*.md"], "processor":"a-plugin/markdown" } ], "env":{//配置预定义全局变量,环境 "browser":true, "node":true } }
babel配置
-
配置预设
- 什么是预设?就是一些比较大的
javaScript
语法版本。预设不一定能够编译所有的小语法 @babel/core
是babel
编译器的核心代码,最新版本v7
@babel/preset-env
是一个babel
预设,用于编译ES6+
语法@babel/preset-react
是一个babel
预设,用于编译jsx
语法@babel/preset-typescript
,是一个babel
预设,用于编译ts
语法
- 什么是预设?就是一些比较大的
-
配置插件
- 什么是插件?用于弥补预设不能编译的小语法问题
-
babel
的配置文件babel.config.js
用于全项目范围的配置.babelrc.json
用于package
级别的配置
-
配置文件
module.exports={ presets:[//配置预设 ['@babel/preset-env',{}] ], plugins:[//配置插件,用于弥补预设不能编译的小语法问题 ["@babel/plugin-proposal-decorators",{"legacy":true}], ["@babel/plugin-proposal-class-properties",{}] ], env:{//基于环境配置babel development:{ plugins:[...] }, production:{ plugins:[...] } } }
webpack 面试题
-
Loader
和Plugin
的区别loader
本质就是一个函数用于对对应的资源进行转换,因为webpack
只认识JavaScript
plugin
就是插件(具有apply
实例方法的类),是基于事件流的框架Tapable
的- 插件可以扩展
webpack
的功能,原理:在webpack
运行的生命周期中会广播出许多事件,plugin
可以监听这些事件,并通过webpack
提供的api
改变输出结果 loader
在module.rules
中配置,plugin
在plugins
数组中单独配置,每一项都是plugin
的一个实例
-
Webpack
构建流程webpack
的运行流程是一个串行的过程,具体流程如下:-
初始化参数
- 合并配置文件和
shell
语句中的参数得到最终参数
- 合并配置文件和
-
开始编译
- 用上一步得到的参数初始化
Compiler
对象 - 加载所有配置的插件
- 执行对象的
run
方法开始执行编译
- 用上一步得到的参数初始化
-
确定入口
- 根据配置中的
entry
找出所有的入口文件
- 根据配置中的
-
编译模块
- 调用所有配置的
loader
对模块进行翻译 - 再对模块的依赖模块进行翻译直到所有模块都翻译完毕
- 调用所有配置的
-
完成模块编译
- 经过
loader
翻译后得到最终翻译内容,以及它们的依赖关系
- 经过
-
输出资源
- 根据入口和模块的依赖关系,组装为一个个的
chunk
文件 - 再把一个个的
chunk
文件加入到输出列表中
- 根据入口和模块的依赖关系,组装为一个个的
-
输出完成
- 再确定好输出内容后,根据配置确定输出的路径和路径名,写入到文件系统
-
-
提高效率的插件
webpack-merge
:提取公共配置,减少重复配置代码webpack-dashboard
:可以更友好的展示相关打包信息size-plugin
:监视资源体积变化,尽早发现问题
-
source map
source map
是将编译、打包、压缩后的代码 映射回源代码的过程,打包压缩后的代码不具有良好的可读性,调试源码就需要soucre map
map
文件只要不打开开发者工具,浏览器是不会加载的。- 线上环境有三种处理方案
hidden-source-map
:借助第三方错误监控平台Sentry
使用nosources-source-map
:只会显示具体行数以及查看源码的错误栈,安全性比sourcemap
高sourcemap
:通过nginx
设置将.map
文件只对白名单开发(公司内网)
-
模块打包原理
Webpack
实际上为每个模块创造了一个可以导出和导入的环境,本质上并没有修改 代码的执行逻辑,代码执行顺序与模块加载顺序也完全一致。
-
文件监听原理
-
当发现源码发生变化时,自动构建出新的输出文件
-
开启监听模式
- 启动
webpack
命令时,带上--watch
参数 - 在配置
webpack.config.js
中设置watch:true
- 启动
-
缺点:每次需要手动刷新浏览器
-
原理:轮询判断文件的最后编辑时间是否变化,如果某个文件发生了变化,并不会立刻告诉监听者,而是先缓存起来,等
aggregateTimeout
后再执行。module.export = { // 默认false,也就是不开启 watch: true, // 只有开启监听模式时,watchOptions才有意义 watchOptions: { // 默认为空,不监听的文件或者文件夹,支持正则匹配 ignored: /node_modules/, // 监听到变化发生后会等300ms再去执行,默认300ms aggregateTimeout:300, // 判断文件是否发生变化是通过不停询问系统指定文件有没有变化实现的,默认每秒问1000次 poll:1000 } }
-
-
热更新原理
- 热更新缩写为
HMR
,这个机制可以做到不用刷新浏览器就可以将旧模块更新为新模块 webpack dev serve(WDS)
与浏览器之间维护了一个websocket
长连接- 当本地资源发生变化时,
WDS
会向浏览器推送更新,并带上构建时的hash
,让客户端与上一次资源进行对比 - 如果客户端比对出差异会向
WDS
发起Ajax
请求来获取更改内容(文件列表、hash
),这样客户端再借助这些信息继续向WDS
发起jsonp
请求获取该chunk
的增量更新 - 拿到增量更新之后,由
HotModulePlugin
来完成,提供了相关API
以供开发者根据自身场景进行处理。像react-hot-loader
和vue-loader
都是借助这些API
实现的HMR
- 当本地资源发生变化时,
- 热更新缩写为
-
bundle
体积进行监控和分析- 使用
VSCode
插件Import Cost
帮助我们对引入模块的大小进行实时监控 - 使用
webpack-bundle-analyzer
生成bundle
的模块组成图 bundlesize
工具包可以进行自动化资源体积监控。
- 使用
-
文件指纹
文件指纹指的是打包后输出的文件名的后缀
hash
:只要项目有变化,整个项目的hash
值就会改变chunkhash
:和webpack
打包的chunk
有关,不同的entry
会出不同的chunkhash
contenthash
:根据文件内容来定义hash
,文件内容不变,则contenthash
不变
-
CSS
的文件指纹 -
图片的文件指纹
-
保证各个
loader
按照预想方式工作 -
优化
Webpack
的构建速度 -
代码分割
-
编写
loader
-
编写
Plugin
-
Babel
原理