webpack 核心知识点总结
Webpack模块化打包工具出现的意义
- 新特性打包编译: ESM有环境兼容问题,需要打包工具将开发阶段的一些文件在生产阶段直接编译成兼容绝大多数环境的代码。
- 模块化JS打包: 将模块文件打包到bundle.js文件中,解决了浏览器中频繁对模块文件发出请求的问题。
- 支持不同种类型的资源模块
Webpack快速上手
- 初始化生成package.json文件
webpack本质是npm的一个工具模块,通过yarn init初始化的方式生成配置文件
yarn init --yes
- 安装webpack核心模块及webpack-cli模块
yarn add webpack webpack-cli --dev
安装完成之后,对应的webpack和webpack-cli文件在modules的bin目录中。
3. yarn webpack运行打包
自动通过src目录下的index.js开始打包。打包文件默认在dist的main.js。
yarn webpack
Webpack自定义配置文件
在根目录下新建webpack.config.js文件
- entry 入口
- output出口
- loader用于对模块的源代码进行转换,转换为js代码
工作模式
通过mode指定打包模式(可以在配置文件中直接设置mode值,也可以打包命令中通过--mode 指定):
- development
会将 process.env.NODE_ENV 的值设为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin。
- production
会将 process.env.NODE_ENV 的值设为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin.
loader
三种使用方式:
- **配置:**在webpack.config.js文件中指定loader
module: {
rules: [
{
test: /\.css$/,
//多个loader时,执行顺序为从下到上
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
modules: true
}
}
]
}
]
}
- **内联:**在每个import语句中显示指定loader
使用!将资源中的loader分开。?用于传递查询参数
import Styles from 'style-loader!css-loader?modules!./styles.css';
- **CLI:**在shell命令中指定
webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'
文件资源加载器
file-loader将资源文件加载打包到指定文件中,一般默认在dist目录中。而加载解析时默认从根目录取资源。因此需要通过publicPath设置打包输入文件路径。打包文件会在资源读取时在前面拼接上配置的路径(因为时拼接的路径,所以最后的/不可省略)。
output:{
publicPath:'dist/'
}
除了file-loader打包加载资源,通过路径使用的方式。还有URL加载器,直接将文件转换为data文件形式,放在打包文件中。但有些文件转换为data文件形式后很大,所以一般采用以下处理方式:
- 小文件使用Data URLs,减少请求次数
- 大文件单独提取存放,提高加载速度
一般loader都有一些额外的配置来处理需要对应loader处理的文件。比如url-loader中limit:
{
test:/.png$/,
use:{
loader:'url-loader',
options:{
limit:10 * 1024 //10kb以下的文件才会被url-loader处理为data文件形式
}
}
}
常用loader大致分类
- 编译转换类:css-loader、babel-loader
- 文件操作类:file-loader
- 代码检查类:eslint-loader
babel用于将ES6编译成ES5处理新特性。通常需要一起安装依赖的核心模块@babel/code @babel/preset-env
webpack加载资源的方式
- 遵循ES Modules标准的import声明
- 遵循CommonJs标准的require函数
- 遵循AMD标准的define函数和require函数
- 样式代码中的@import指令和url函数
- HTML代码中图片标签的src属性
webpack核心工作原理
- 通过配置文件找到打包文件入口,一般都是一个js文件
- 在入口文件中通过import或者require关键词推断解析出依赖的模块
- 分别解析每个模块对应的依赖,形成体现整个项目依赖关系的依赖树。
- 递归依赖树,找到每个节点对应的资源文件
- 通过rules中的配置找到每个资源文件对应的loader,对资源进行加载
- 将通过loader加载的结果放到打包结果bundel.js中
webpack插件机制
webpack插件是一个具有apply属性的JavaScript对象。apply属性会被webpack compiler调用,并且compiler对象可在整个编译生命周期访问。
常用的插件:
clean-webpack-plugin自动清除输入目录插件。
html-webpack-plugin通过webpack输出HTML文件。
copy-webpack-plugin复制文件。
接收一个数组参数,数组中为需要拷贝的文件目录
html-webpack-plugin可配置title、meta等参数。还可以通过模板文件的形式生成html文件。
html-webpack-plugin是将由依赖树加载后的完整文件关系生成的,因此rules中只需要处理真正的依赖文件,node_modules文件应该排除。
rules: [
{
test:/.js$/,
exclude:/node_modules/,
use:{
loader:'babel-loader',
options:{
presets:['@babel/preset-env']
}
}
}
]
plugins:[
new HtmlWebpackPlugin({
title:'自定义title',
meta:{
viewport:'width= device-width'
},
template:'./src/index.html'
})
]
./src/index.html模板文件通过lodash模板语法输出
<h><%= htmlWebpackPlugin.options.title %></h>
每个htmlWebpackPlugin实例对象都会生成一个对应的页面文件。如果需要多个页面,可实例多个对象,filename指定页面文件的名称。
plugin通过钩子机制实现
自动编译
--watch模式下,文件发生变化时会进行自动编译打包
webpack --watch
自动刷新浏览器
先安装browser-sync,命令行中以browser-sync命令运行,当文件变化时会自动刷新浏览器。
原理是监听 --flies指定文件,发生变化时,刷新浏览器,渲染dist目录下的index.html文件。因此index.html在每次打包时不可删除,如果使用了CleanWebpackPlugin插件,需要配置不删除index文件。
new CleanWebpackPlugin({cleanStaleWebpackAssets: false})
browser-sync dist --files "**/*"
webpack dev serve
提供用于开发的HTTP Server,集成自动编译和自动刷新浏览器等功能。
首先通过yarn add安装
tips:注意与webpack版本兼容问题,最好安装低版本。webpack4.x ,webpack-cli3.x,webpack-dev-server2.x的组合能很好地兼容。
yarn add webpack-dev-server --dev
命令行运行
yarn webpack-dev-server --config ./webpack.config.js --open
默认打包生成的文件都是服务器资源文件,服务器都可直接访问。但是其他静态资源文件如果也需要作为服务器文件,需要额外配置。具体通过devServerde contentBase设置静资源文件的位置。
module.exports = {
sevServer:{
contentBase:'./public'
}
}
Source Map
Source Map顾名思义,源代码地图,用于映射打包转换后的代码与源代码的关系。
devtool用于设置开发工具设置。
devtool:'source-map'
主要有以下几种区分devtool模式的关键词:
- eval-是否使用eval执行模块代码
- cheap-Source Map是否包含行信息
- module-是否能够得到Loader处理前的源代码
一般的选择规律:
- 开发环境:cheap-module-eval-source-map
- 生产环境:none
HMR模块热替换
实现HMR的几种方式:
- 命令行运行时添加--hot参数
webpack-dev-server --hot
- 通过配置文件开启
1、配置devServer开启hot热更新
devServer:{
hot:true
}
2、运用webpack内置插件
const webpack = require('webpack')
module.exports = {
plugins:[
new webpack.HotModuleReplacementPlugin()
]
}
HMR优化使用
由于替换逻辑无规律,因此webpack中的HMR还需手动处理替换逻辑。
如果已经通过 HotModuleReplacementPlugin 启用了模块热替换(Hot Module Replacement),则它的接口将被暴露在 module.hot 属性下面。
tips:额外处理之后的文件不再会自动刷新,而是会执行设置的热替换逻辑。
accept
接受(accept)给定依赖模块的更新,并触发一个 回调函数 来对这些更新做出响应。
module.hot.accept(
dependencies, // 可以是一个字符串或字符串数组
callback // 用于在模块更新后触发的函数
)
注意事项:
- 处理HMR代码报错会导致自动刷新
解决方式:设置devServer的hotOnly:true。处理HMR错误不会回退刷新。
2. 没启用HMR的情况下,HMR API报错
解决方式:HMR的处理代码前先判断是否开启了hot插件module.hot
不同环境下的配置
一般情况下开发环境追求开发效率,生产环境追求渲染速度,影刺不同的环境有不同的打包要求。对不同的环境应该进行不同的配置。主要有以下两种实现方式:
1. 配置文件根据环境不同导出不同配置
2. 一个环境对应一个配置文件
1.配置文件根据环境不同导出不同配置(中小型项目)
webpack配置文件可支持导出一个函数,函数接收两个参数。env环境参数,argv传递的所有参数。
const path = require('path')
const webpack = require('webpack')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = (env,argv) => {
const config = {
mode:"development",
entry:'./src/main.js',
output:{
filename:'js/bundle.js'
},
devtool:'source-map',
module:{
rules: [
{
test:/.js$/,
exclude:/node_modules/,
use:{
loader:'babel-loader',
options:{
presets:['@babel/preset-env']
}
}
},
{
test:/.css$/,
use:[
'style-loader',
'css-loader'
]
},
{
test:/.png$/,
use:{
loader:'url-loader',
options:{
limit:10 * 1024 //小于10kb的图片转换为Data URLs,大于的仍然需要file-loader进行处理,所以也需要安装
}
}
}
]
},
plugins:[
new HtmlWebpackPlugin({
title:'Webpack',
template:'./src/index.html'
}),
new webpack.HotModuleReplacementPlugin()
]
}
if(env === 'production'){
config.mode = 'production'
config.devtool = false
config.plugins = [
...config.plugins,
new CleanWebpackPlugin(),
// new CopyWebpackPlugin({patterns: [{ from: "public", to: "public" }] }
]
}
return config
}
2、一个环境对应一个配置文件
通常会有一个common配置。通过webpack-merge模块将通用设置merge到不同配置文件中。
- 安装webpack-merge模块
- 在单独的配置文件中通过webpack-merge将通用配置和环境特殊配置合并。
merge([common,{环境配置}])
DefinePlugin
生产环境下有很多默认的优化配置。DefinePlugin是webpack内置的插件,使用前需要先引入webpack.
const webpack = require('webpack')
module.exports = {
mode:'none',
entry:'./src/main.js',
output:{
filename:'bundle.js'
},
plugins:[
new webpack.DefinePlugin({
API_BASE_URL:JSON.stringify('http://api.com')
})
]
}
生产模式优化
optimization:{
usedExports:true, //只导出使用到的成员
minimize:true, //压缩掉无用代码
concatenateModules:true,//合并模块。
sideEffects:true,//是否有副作用
splitChunks:{//公共模块提取
chunks:'all'
},
minimizer:[//代码压缩配置
new TerserWebpackPlugin()//js压缩插件
new OptimizeCssAssetsWebpackPlugin()//css压缩插件
]
}
Tree Shaking
生产模式下会自动开启。
其余模式下可通过optimization集中配置优化选项。
合并模块Scope Hoisting:尽可能将所有模块合并输出到一个函数中,既提升了运行效率,又减少了代码的体积
Tree Shaking前提是ES Modules,只能处理ESM。而babel-loader转换新特性时如果插件使用到CommonJs,会导致Tree Shaking失效。可通过配置关闭babel对ESM的转换。
{
test:/\.js$/,
use:{
loader:'babel-loader',
options:[
presets:[
['@bable/preset-env',{modules:false}]
]
]
}
}
sideEffects副作用
生产模式下也会自动开启副作用检查。
副作用:模块执行时除了导出成员之外所做的事情,一般用于npm包标记是否有副作用。package.json中sideEffects设置为false没有副作用,未使用的模块将不会被打包。如果模块除了导出成员还做了其他事情,就表示有副作用,应该将有副作用的文件添加到sideEffects数组中,以免有用模块被误删。
//package.json文件中
"sideEffects":[
'./src/global.css',
'./src/extend.js'
]
代码分割Code Splitting
分包,按需加载。
- 多入口打包
- 动态导入
多入口打包
一般用于多页面程序。一个页面对应一个打包入口,公共部分单独提取。公共模块的提取也是在optimization中设置。
entry配置为一个对象,对象的每个属性对应一个打包入口。多个汶口文件会对应多个出口和htmll文件,也需要同步配置。
entry:{
index:'./src/index.js',
album:'./src/album.js'
},
output:{
filename:[name].bundle.js
},
plugins:[
new HtmlWebpackPlugin({
title:'Multi Entry',
template:'./src/index.html',
filename:'index.html',
chunks:['index']//设置引用的打包文件
}},
new HtmlWebpackPlugin({
title:'Multi Entry',
template:'./src/album.html',
filename:'album.html',
chunks:['album']//设置引用的打包文件
}}
]
动态导入
import(模块).then()的方式动态导入模块。
动态导入的模块可通过魔法注释的方式给分包所生成的bundle命名。
相同bundle文件名对应动态导入的模块会被打包到同一个Bundle文件中。
import(/*webpackChunkName:'posts'*/'./posts').then(({default:posts}) => {
document.body.append(posts())
})
MiniCssExtracPlugin
提取css到单个文件,一般只有样式文件过大时建议单独提取,单独提取会增加请求次数。
当css打包为单独文件时,便不在需要使用style-loader将css文件通过style标签注入到js文件中,而应该使用MiniCssExtracPlugin自带的loader通过link的方式注入。
{
test:/.css$/,
use:[
// 'style-loader',
MiniCssExtracPlugin.loader,
'css-loader'
]
},
单独css文件的压缩
使用插件:optimize-css-assets-webpack-plugin
建议在optimization中使用,以便只用在minimizer开启时才使用该插件。一旦设置了minimizer,会默认压缩配置都需手动配置,因此默认的js压缩会失效,也需要手动配置。
输出文件名 Hash
一般部署前端的资源文件时,会启动服务器的静态资源缓存。而缓存失效时间不好把控,所以建议生产模式下,文件名使用Hash,只有文件改变时缓存失效,重新请求资源。
有三种级别的Hash值:
[hash:num]//可截取指定num长度的hash值
- hash:项目级别的hash,只要项目文件改变,hash值就会改变。
- chunkhash打包级别的hash:同一打包chunk文件改变时,chunkhash会改变
- contenthash文件级别的hash