Webpack学习笔记
一、Webpack
Webpack 是一种前端资源构建工具,一个静态模块打包器(module bundler)
在 Webpack 中前端所有资源文件(js/json/css/img/less/…)都会作为模块处理,它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源(bundle)
1、Webpack 五个核心概念
(1)Entry
入口(Entry)指示 Webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图
(2)Output
输出(Output)指示 Webpack 打包后的资源 bundles 输出到哪里,以及如何命名
(3)Loader
Loader 让 Webpack 能够处理那些非 JavaScript 文件(如样式 css 文件、图片等)(Webpack 自身只能理解 JavaScript)
使用 loader 需要:1)下载 2)使用(配置loader)
(4)Plugins
插件(Plugins)可以用于执行范围更广的任务,插件的范围包括从打包优化和压缩,到重新定义环境中的变量等
使用 plugins 需要:1)下载 2)引入 3)使用
(5)Mode
模式(Mode)指示 Webpack 使用相应模式的配置,模式分为 development 开发模式和 production 生产模式
development
development 会将 process.env.NODE_ENV 的值设置为 development,启用 NamedChunksPlugin 和 NamesModulesPlugin
特点:能让代码本地调试运行的环境
production
production 会将 process.env.NODE_ENV 的值设置为 production,启用 FlagDependencyUsagePlugin、FlagIncludedChunksPlugin、ModuleConcatenationPlugin、NoEmitOnErrorsPlugin、OccurenceOrderPlugin、SideEffectsFlagPlugin、UglifyJsPlugin/TerserPlugin(压缩 js 代码)
特点:能让代码优化上线运行的环境
开发环境和生产环境的异同
生产环境和开发环境能将 ES6 模块化编译成浏览器能识别的模块化
生产环境生成的打包文件压缩了 js 代码,而开发环境没有,因此生产环境打包后生成的文件较小
2、使用 Webpack
新建项目文件夹,在文件夹下使用命令 npm init
初始化生成 package.json 文件
全局安装 webpack 和 webpack-cli npm i webpack webpack-cli -g
本地安装 webpack 和 webpack-cli npm i webpack webpack-cli -D
在项目下新建文件夹 src 存放源码,并新建 index.js 文件作为入口文件,在项目下新建文件夹 build 存放打包后的文件
在 index.js 中编写相关代码
function add(x,y){
return x+y;
}
console.log(add(1,2))
运行指令
开发环境中
webpack ./src/index.js -o ./build/built.js --mode=development
webpack 会以 ./src/index.js 为入口文件开始打包,打包后输出到 ./build/built.js,整体打包环境是开发环境
生产环境
webpack ./src/index.js -o ./build/built.js --mode=production
webpack 会以 ./src/index.js 为入口文件开始打包,打包后输出到 ./build/built.js,整体打包环境是生产环境
打包后会生成 built.js 文件,且命令行输出中有个 Hash 相当于文件的 id,后面可以利用它作为文件命名的方式
在生产环境生成的 built.js 中压缩了 js 代码
使用 node .\build\built.js
运行代码,也可在 html 文件中使用 <script>
标签引入打包后的 built.js 文件
3、使用 webpack 打包样式资源
要打包样式资源需要用到 loader,而 loader 需要定义配置文件
在和 src 同级目录下新建配置文件 webpack.config.js,运行 webpack 指令时会加载里面的配置指示 webpack 干哪些活,内容如下
//resolve 用来拼接绝对路径的方法
const {resolve} = require('path');
module.exports = {
//webpack 配置
//入口起点
entry:'./src/index.js',
//输出
output:{
//输出文件名
filename: 'built.js',
//输出路径,__dirname 是 node.js 的变量,代表当前文件的目录绝对路径
path: resove(__dirname,'build')
},
//loader 的配置
module:{
rules:[
//详细 loader 配置,不同类型文件必须配置不同 loader 处理
//处理 css 文件
{
//匹配哪些文件
test:/\.css$/, //匹配以 .css 结尾的文件
//使用哪些loader进行处理
use:[
//use 数组中 loader 执行顺序:从右到左,从下到上依次执行,下列 loader 先执行 css-loader 再执行 style-loader
'style-loader', //创建 style 标签,将 js 中的样式资源插入进去,添加到 head 中生效
'css-loader' //将 css 文件变成 CommonJS 模块加载到 js 中,里面内容是样式字符串
]
},
//处理 less 文件
{
test:/\.less$/, //匹配以 .less 结尾的文件
use:[
'style-loader', //创建 style 标签,将 js 中的样式资源插入进去,添加到 head 中生效
'css-loader', //将 css 文件变成 CommonJS 模块加载到 js 中,里面内容是样式字符串
'less-loader' //将 less 文件编译成 css 文件,注意这里需要下载 less-loader 和 less
]
}
]
},
//plugins 的配置
plugins:[
//详细 plugins 的配置
],
//模式
mode: 'development',
//mode:'production'
}
注意:要使用多个 loader 处理时用 use,若只需一个 loader 时再 rules 的对象中直接使用 loader:
进行配置
注意:所有构建工具都是基于 node.js 平台运行的,模块化默认采用 CommonJS 规范,而 src 文件夹中写的项目相关代码所使用的模块化是 ES6 规范的
4、使用 webpack 打包 html 资源
const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry:'./src/index.js',
output:{
filename: 'built.js',
path: resove(__dirname,'build')
},
module:{
rules:[
]
},
//plugins 的配置
plugins:[
//详细 plugins 的配置
//html-webpack-plugin 默认会创建一个空的 html 文件,自动引入打包输出的所有资源(js/css)
new HtmlWebpackPlugin({
template:'./src/index.html' //复制 ./src/index.html 文件并自动引入打包输出的所有资源
})
],
//模式
mode: 'development'
}
5、使用 webpack 打包图片资源
const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry:'./src/index.js',
output:{
filename: 'built.js',
path: resove(__dirname,'build')
},
module:{
rules:[
{
test:/\.less$/,
use:['style-loader','css-loader','less-loader']
},
//处理图片资源
{
test:/\.(jpg|png|gif)$/,
loader:'url-loader', //需要下载 url-loader 和 file-loader 两个包
options:{
//这里设置图片大小小于 8kb 就会被转换为 base64 编码的一种字符串,以 base64 处理
//优点:减少请求数量,减轻服务器压力
//缺点:图片体积会更大,文件请求速度更慢(如原先图片 10kb,转为 base64 编码后可能有 14kb)
//所以一般不会对大图片进行 base64 处理,会对 8-12kb 图片进行 base64 处理
limit:8*1024,
esModule: false,
//给图片重命名
name:'[hash:10].[ext]'//取图片 hash 的前 10 位以及文件原扩展名来命名,这样文件也会小一点
}
},
//注意:上面的方式默认处理不了 html 中的 img 图片(通过 img 标签的 src 属性引入的图片)
{
test:/\.html$/,
//处理 html 文件的 img 图片(负责引入 img,从而能被 url-loader 进行处理)
loader:'html-loader'
}
]
},
//plugins 的配置
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html'
})
],
//模式
mode: 'development'
}
注意:打包图片资源需要下载 url-loader 和 file-loader 两个包,因为 url-loader 依赖于 file-loader
问题:url-loader 默认处理不了 html 中的 img 图片,因为 url-loader 是使用 ES6 中的 module 去处理模块,而 html-loader 打包后引入的 img 是通过 CommonJS 规范引入,所以通过 url-loader 去解析 html-loader 引入的图片解析不了会报错,即打包后 img 标签中 src 会变成 [object Module]
解决:esModule: false
关闭 url-loader 的 ES6 模块化,使用 CommonJS 解析
注意:当一个文件多次使用时,webpack 只会打包一次,不会重复打包一个文件
6、使用 webpack 打包其他资源(如字体图标)
const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry:'./src/index.js',
output:{
filename: 'built.js',
path: resove(__dirname,'build')
},
module:{
rules:[
{
test:/\.css$/,
use:['style-loader','css-loader']
},
//打包其他资源(除了 html/js/css/less 以外的资源)
{
//排除 css/js/html 资源
exclude:/\.(css|js|html|less)$/,
loader:'file-loader'
},
options:{
name:'[hash:10].[ext]'
}
]
},
//plugins 的配置
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html'
})
],
//模式
mode: 'development'
}
7、devServer
开发服务器 devServer 用来自动化,可自动编译、自动打开浏览器、自动刷新浏览器
特点:只会在内存中编译打包,不会有任何输出
需下载包 webpack-dev-server,启动 devServer 指令:npx webpack-dev-server
(若没有全局安装 webpack-dev-server 需要使用 npx 启动)
const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry:'./src/index.js',
output:{
filename: 'built.js',
path: resove(__dirname,'build')
},
module:{
rules:[
{
test:/\.css$/,
use:['style-loader','css-loader']
},
//打包其他资源(除了 html/js/css/less 以外的资源)
{
//排除 css/js/html 资源
exclude:/\.(css|js|html|less)$/,
loader:'file-loader'
},
options:{
name:'[hash:10].[ext]'
}
]
},
//plugins 的配置
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html'
})
],
//模式
mode: 'development',
devServer:{
contentBase:resolve(__dirname,'build'), //要运行的项目构建后的目录
compress:true, //启动 gzip 压缩,使代码更小运行更快
port:3000, //端口号
open:true //自动打开浏览器
}
}
8、开发环境基本配置
const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry:'./src/index.js',
output:{
filename: 'js/built.js',
path: resove(__dirname,'build')
},
module:{
rules:[
//处理 css
{
test:/\.less$/,
use:['style-loader','css-loader','less-loader']
},
//处理 less
{
test:/\.css$/,
use:['style-loader','css-loader']
},
//处理图片资源
{
test:/\.(jpg|png|gif)$/,
loader:'url-loader',
options:{
limit:8*1024,
esModule: false,
name:'[hash:10].[ext]',
outputPath:'imgs' //打包后图片会输出到打包目录下的 imgs 文件夹中
}
},
//处理 html 中 img 资源
{
test:/\.html$/,
loader:'html-loader'
},
//处理其他资源
{
exclude:/\.(css|js|html|less|jpg|png|gif)/,
loader:'file-loader',
options:{
name:'[hash:10].[ext]',
outputPath:'media' //打包后图片会输出到打包目录下的 media 文件夹中
}
}
]
},
//plugins 的配置
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html'
})
],
devServer:{
contentBase:resolve(__dirname,'build'),
compress:true,
port:3000,
open:true
},
//模式
mode: 'development'
}
运行项目指令:
1)webpack
会将打包结果输出出去
2)npx webpack-dev-server
只会在内存中编译打包,没有输出
9、生产环境
生产环境中需解决的问题
(1)经过上面的配置,css 文件在打包后都会添加到 js 代码中,使得 js 代码很大,且由于要先加载 js 代码才能解析出其中所需的 css 样式,因此会出现闪屏现象
(2)在生产环境中需要对代码进行压缩
(3)还需要考虑一些样式代码(如一些 css3 的效果)和 js 的兼容性问题
……
提取 css 成单独文件
使用插件 mini-css-extract-plugin 可将 css 提取成单独文件
const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry:'./src/index.js',
output:{
filename: 'js/built.js',
path: resove(__dirname,'build')
},
module:{
rules:[
{
test:/\.css$/,
use:[
//'style-loader', //创建 style 标签,将样式放入
MiniCssExtractPlugin.loader, //这个 loader 取代 style-loader,提取 js 中的 css 成单独文件
'css-loader' //将 css 文件整合到 js 文件中
]
}
]
},
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html'
}),
new MiniCssExtractPlugin({
filename: 'css/built.css' //对输出的 css 文件进行重命名,生成的 css 文件会在 build/css/built.css
})
],
mode: 'development'
}
通过上述方式从打包生成的 js 文件中提取 css 样式成单独文件后,生成的 build/index.html 中引入样式的方式就不是通过 style 标签引入,而是通过 <link ref="css/built.css" rel="stylesheet">
引入样式,这样也解决了闪屏现象,并且生成的 js 文件体积也没那么大了
css 兼容性处理
css 兼容性处理需要使用库 postcss,在 webpack 中通过 postcss-loader 以及插件 postcss-preset-env(帮助 postcss 识别某些环境,从而加载指定的配置,能让兼容性精确到某一个浏览器的版本)使用
其中 postcss-preset-env 帮助 postcss 找到 package.json 中 browserlist 里面的配置,通过配置加载指定的 css 兼容性样式
安装 postcss-loader 和 postcss-preset-env npm i postcss-loader postcss-preset-env -D
在 webpack.config.js 中内容如下
//设置 Nodejs 环境变量,决定使用 browserlists 的哪个环境,因为默认是看 package.json 中的生产环境
process.env.NODE_ENV = 'development';
const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry:'./src/index.js',
output:{
filename: 'js/built.js',
path: resove(__dirname,'build')
},
module:{
rules:[
{
test:/\.css$/,
use:[
MiniCssExtractPlugin.loader,
'css-loader',
//使用 loader 的默认配置
//'postcss-loader' //相当于{loader:'postcss-loader'}
//修改 loader 的配置
{
loader:'postcss-loader',
options:{
ident: 'postcss',
plugins: () => [
//postcss 的插件
require('postcss-preset-env')()
]
}
}
]
}
]
},
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html'
}),
new MiniCssExtractPlugin({
filename: 'css/built.css'
})
],
mode: 'development'
}
在 package.json 文件中添加如下内容,在开发环境中兼容最新版本的各浏览器,在生产环境中兼容 99.8% 的浏览器,不要已经 dead 和 op_mini 浏览器
"browserslist": {
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
}
压缩 css
使用插件 optimize-css-assets-webpack-plugin 进行压缩
安装 npm i optimize-css-assets-webpack-plugin -D
const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
entry:'./src/index.js',
output:{
filename: 'js/built.js',
path: resove(__dirname,'build')
},
module:{
rules:[
{
test:/\.css$/,
use:[
MiniCssExtractPlugin.loader,
'css-loader',
{
loader:'postcss-loader',
options:{
ident: 'postcss',
plugins: () => [
//postcss 的插件
require('postcss-preset-env')()
]
}
}
]
}
]
},
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html'
}),
new MiniCssExtractPlugin({
filename: 'css/built.css'
}),
//压缩 css
new OptimizeCssAssetsWebpackPlugin()
],
mode: 'development'
}
js 语法检查 eslint
在 webpack 中通过 eslint-loader 进行语法检查,eslint-loader 依赖于 eslint 库,因此需要安装两者,npm i eslint-loader eslint -D
注意:语法检查只检查自己写的源代码,第三方的库是不用检查的
const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry:'./src/index.js',
output:{
filename: 'js/built.js',
path: resove(__dirname,'build')
},
module:{
rules:[
{
test:/\.js$/,
exclude: /node_modules/, //不检查 node_modules 中的第三方库
loader:'eslint-loader',
options:{
fix:true //自动修复 eslint 的错误
}
}
]
},
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html'
})
],
mode: 'development'
}
这里使用airbnb 代码规范对 eslint 进行配置,还需要下载库 eslint-config-airbnb-base 和 eslint-plugin-import
在 package.json 中的 eslintConfig 中设置
“eslintConfig”:{
"extends":"airbnb-base"
}
项目上线时无需 console.log 语句,可在源代码中的 console.log 语句的上一行加上 //eslint-disable-next-line
使得下一行 eslint 所有规则都失效,即不对下一行进行 eslint 检查
js 兼容性处理 eslint
在 IE 浏览器中不能识别 ES6 语法,因此需要对 js 进行兼容性处理,将 ES6 语法转换为 ES5 及以下的语法
在 webpack 中通过使用 babel-loader,同时还需要下载 @babel/core 和 @babel/preset-env
const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry:'./src/index.js',
output:{
filename: 'js/built.js',
path: resove(__dirname,'build')
},
module:{
rules:[
{
test:/\.js$/,
exclude: /node_modules/, //不检查 node_modules 中的第三方库
loader:'babel-loader',
options:{
presets:['@babel/preset-env'] //预设:指示 babel 做怎样的兼容性处理
}
}
],
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html'
})
],
mode: 'development'
}
}
其中在 babel-loader 的配置 presets 中
1、@babel/preset-env
实现基本 js 兼容性处理
问题:只能转换基本语法,如 promise 等高级不能转换
2、@babel/polyfill
可对全部 js 做兼容性处理,注意它不是作为插件在配置中设置,只需在 js 源代码中使用 import '@babel/polyfill' 引入即可
问题:往往只需解决部分兼容性问题,但它会将所有兼容性代码全部引入,使得代码文件体积太大了
3、core-js
按需加载,需要做兼容性处理的就做,同时 presets 设置需改为
presets:[
[
'@babel/preset-env',
{
//按需加载
useBuiltIns: 'usage',
//指定 core-js 版本
corejs: {
version: 3
},
//指定兼容性做到哪个版本浏览器
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
]
]
通过 1、3 或 1、2(一般不用) 搭配使用
压缩 html 和 js
要完成 js 的压缩只需将 webpack.config.js 文件中的 mode 设置为 production 即可,因为生产环境下会自动压缩 js 代码
要完成 html 代码的压缩只需在 webpack.config.js 文件中的 plugin 中进行设置
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html',
//压缩 html 代码
minify: {
//移除空格
collapseWhitespace: true,
//移除注释
removeComments: true
}
})
]
注意:html 代码无需做兼容性处理,浏览器认识就认识,不认识就不认识
生产环境基本配置
const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
//复用 loader
const commonCssLoader = [
MiniCssExtractPlugin.loader,
'css-loader',
{
//还需要在 package.json 中定义 browserslist
loader:'postcss-loader',
options:{
ident: 'postcss',
plugins: () => [
require('postcss-preset-env')()
]
}
}
]
module.exports = {
entry:'./src/index.js',
output:{
filename: 'js/built.js',
path: resove(__dirname,'build')
},
module:{
rules:[
//处理 css
{
test:/\.less$/,
use:[...commonCssLoader]
},
//处理 less
{
test:/\.css$/,
use:[...commonCssLoader,'less-loader']
},
//js 语法检查
{
//还需在 package.json 中配置 eslintConfig
test:/\.js$/,
exclude: /node_modules/,
enforce: 'pre', //优先执行
loader:'eslint-loader',
options:{
fix:true
}
},
// js 兼容性处理
{
test:/\.js$/,
exclude: /node_modules/,
loader:'babel-loader',
options:{
presets:[
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3
},
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
]
]
}
},
//处理图片资源
{
test:/\.(jpg|png|gif)$/,
loader:'url-loader',
options:{
limit:8*1024,
esModule: false,
name:'[hash:10].[ext]',
outputPath:'imgs' //打包后图片会输出到打包目录下的 imgs 文件夹中
}
},
//处理 html 中 img 资源
{
test:/\.html$/,
loader:'html-loader'
},
//处理其他资源
{
exclude:/\.(css|js|html|less|jpg|png|gif)$/,
loader:'file-loader',
options:{
name:'[hash:10].[ext]',
outputPath:'media' //打包后图片会输出到打包目录下的 media 文件夹中
}
}
]
},
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
});
new MiniCssExtractPlugin({
filename: 'css/built.css'
}),
//压缩 css
new OptimizeCssAssetsWebpackPlugin()
],
//模式
mode: 'production'
}
还需在 package.json 文件中添加如下内容
"browserslist": {
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
},
“eslintConfig”:{
"extends":"airbnb-base"
}
注意:正常来讲一个文件只能被一个 loader 处理,当一个文件要被多个 loader 处理时,一定要指定 loader 的执行现后顺序,如 js 的语法检查和兼容性处理需要先执行 eslint 再执行 babel
原因1:先做语法检查,若语法检查出现错误后续工作无意义
原因2:使用 babel 后会把 ES6 语法转为 ES5 及以下语法,若此时再使用 eslint 进行语法检查又会报错,如 var 等不好用
10、性能优化
webpack 的性能优化包括开发环境性能优化和生产环境性能优化
开发环境性能优化
开发环境性能优化包括(1)优化打包构建速度(通过 HMR)(2)优化代码调试(通过 source-map)
(1)优化打包构建速度 ———— HMR
问题1:在开发环境中若只修改 css 样式文件或只修改了某个文件,其他文件没有变动,但 webpack 会把 css、js 等文件一起全部重新打包一次
解决:可通过 webpack 的 HMR(hot module replacement 热模块替换/模块热替换)功能,HMR 是基于 devServer 的
作用:一个模块发生变化,只会重新打包这个模块,而不是打包所有模块,极大提升了构建速度
只需修改 webpack.config.js 文件中的 devServer 设置,添加语句 hot: true
开启 HMR 功能
devServer:{
contentBase: resolve(__dirname,'build'),
compress: true,
port: 3000,
open: true,
//开启 HMR 功能
hot: true
},
启动 webpack 服务:npx webpack-dev-server
问题2:
样式文件:可以使用 HMR 功能,因为 style-loader 内部实现了
js 文件:默认不能使用 HMR 功能
html 文件:默认不能使用 HMR 功能,同时会导致 html 文件不能热更新了
解决:修改 webpack.config.js 文件中的 entry 入口,将 html 文件加入,此时若修改 html 文件能热更新了,整个页面重新刷新,依然不能使用 HMR 功能(但是 html 无需 HMR 功能,因为项目中只会创建一个 html 文件,当该文件发生变化时自然也需要重新加载这个文件)
entry:['./src/js/index.js','./src/index.html']
在 index.js 文件中添加如下代码让 js 文件的 HMR 功能生效
if(module.hot){
//一旦 module.hot 为 true,说明开启了 HMR 功能
module.hot.accept(./xxx1.js,function(){
//方法会监听 xxx.js 文件的变化,一旦发生变化,其他模块不会重新打包构建,会执行回调函数
print();
});
module.hot.accept(./xxx2.js,function(){
print();
})
}
此时若修改 xxx1.js 文件或 xxx2.js 文件只会重新加载相应文件
注意:HMR 功能对 js 的处理,只能处理非入口 js 文件的其他文件
注意:当修改了 webpack 配置,新配置要想生效,必须重新启动 webpack 服务
(2)优化代码调试 ———— source-map
source-map 是一种提供源代码到构建后代码映射的技术,若构建后代码出错了,会通过映射关系可以追踪源代码错误
在 webpack.config.js 文件中添加
devtool: 'source-map'
打包后会在 js 的输出文件夹下生成 built.js.map 文件
devtool 的参数有[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
,其中
source-map:外部,能提供错误代码的准确信息和源代码的错误位置(第几行)
inline-source-map:内联,只生成一个 source-map,能提供错误代码的准确信息和源代码的错误位置
hidden-source-map:外部,能提供代码错误的原因,不能追踪源代码错误,只能提示构建后代码的错误位置(可以半隐藏源代码)
eval-source-map:内联,每个文件都生成对应的 source-map,都在 eval 中,能提供错误代码的准确信息和源代码的错误位置(第几行),只是在错误文件名中多了个哈希值
nosources-source-map:外部,能提供错误代码的准确信息,但没有任何源代码信息(可以隐藏源代码)
cheap-source-map:外部,能提供错误代码的准确信息和源代码的错误位置,但只能精确到行,而前面的方式(如 source-map 可精确到行列)
cheap-module-source:外部,能提供错误代码的准确信息和源代码的错误位置,但只能精确到行,module 会将 loader 的 source-map 加入
外部方式和内联方式的区别:(1)使用外部方式会生成 built.js.map 文件,而使用内联方式不会生成文件,而是嵌入在 built.js 文件中,(2)但内联的构建速度更快
开发环境下:需要速度快,调试更友好
速度快(eval > inline > cheap > ...)
最快:eval-cheap-source-map
其次:eval-source-map
调试更友好
最友好:source-map
其次:cheap-module-source-map
再其次:cheap-source-map
综合:可选择 eval-source-map 或 eval-cheap-module-source-map
生产环境下:需要考虑源代码要不要隐藏?调试要不要更友好?
由于内联会让代码体积变大,所以在生产环境不用内联
考虑源代码隐藏
nosources-source-map(全部隐藏)
hidden-source-map(只隐藏源代码,会提示构建后代码的错误信息和位置)
调试更友好
source-map
同时速度更快点:cheap-module-source-map
生产环境性能优化
生产环境性能优化包括(1)优化打包构建速度(oneOf、babel缓存、多进程打包、externals、DLL)(2)优化代码运行的性能(文件资源缓存、tree shaking、code split、懒加载/预加载、PWA)
(1)优化打包构建速度
问题:一般一个文件只会使用一种 loader 进行处理(除了 js 文件需要 eslint-loader 和 babel-loader 两种进行处理),但是每加载一个文件都会把所有 loader 过一遍,影响速度
oneOf
解决1:通过将 webpack.config.js 中的各种 loader 放在 oneOf 中,并把 eslint-loader 单独放到 oneOf 前面
module:{
rules:[
//js 语法检查
{
//还需在 package.json 中配置 eslintConfig
test:/\.js$/,
exclude: /node_modules/,
enforce: 'pre', //优先执行
loader:'eslint-loader',
options:{
fix:true
}
},
{
//以下 loader 只会匹配一个,不能有两个配置处理同一种类型文件
oneOf:[
//处理 css
{
test:/\.less$/,
use:[...commonCssLoader]
},
//处理 less
{
test:/\.css$/,
use:[...commonCssLoader,'less-loader']
},
// js 兼容性处理
{
test:/\.js$/,
exclude: /node_modules/,
loader:'babel-loader',
options:{
presets:[
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3
},
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
]
]
}
},
//处理图片资源
{
test:/\.(jpg|png|gif)$/,
loader:'url-loader',
options:{
limit:8*1024,
esModule: false,
name:'[hash:10].[ext]',
outputPath:'imgs' //打包后图片会输出到打包目录下的 imgs 文件夹中
}
},
//处理 html 中 img 资源
{
test:/\.html$/,
loader:'html-loader'
},
//处理其他资源
{
exclude:/\.(css|js|html|less|jpg|png|gif)/,
loader:'file-loader',
options:{
name:'[hash:10].[ext]',
outputPath:'media' //打包后图片会输出到打包目录下的 media 文件夹中
}
}
]
}
]
},
缓存 ———— babel 缓存
问题:修改某个 js 文件后其他未变动的 js 无需重新编译,但 HMR 是基于 devServer 的,所以在生产环境中无法使用
解决:babel 缓存————让第二次打包构建速度更快
只需在 babel-loader 中进行设置
{
test:/\.js$/,
exclude: /node_modules/,
loader:'babel-loader',
options:{
presets:[
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3
},
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
]
],
//开启 babel 缓存,第二次构建时会读取之前的缓存
cacheDirectory: true
}
},
多进程打包
通过 thread-loader 库进行多进程打包,在webpack.config.js 配置文件的 babel-loader 中添加 thread-loader
进程启动大概为 600ms,进程通信也有开销,只有工作消耗事件比较长,才需要多进程打包,若乱用反而会增加打包时间
一般 js 代码较多 babel 干的活久时使用多进程打包的加速效果更明显
{
test:/\.js$/,
exclude: /node_modules/,
use: [
//开启多进程打包
{
loader:'thread-loader',
options:{
workers: 2 //2个进程
}
},
{
loader:'babel-loader',
options:{
presets:[
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3
},
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
]
],
//开启 babel 缓存,第二次构建时会读取之前的缓存
cacheDirectory: true
}
}
]
},
externals
externals 防止将某些包打包到最终输出的 bundle 中,如想要 jQuery 通过 CDN 链接引入,就要禁止将它打包
在 webpack.config.js 文件中添加 externals
externals: {
//忽略库名(npm 包名)
jquery: 'jQuery' //拒绝 jQuery 被打包进来
}
不打包就要在 html 中通过 <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
引入 jQuery 才能使用
DLL
DLL 可以对代码进行单独打包
和 externals 类似,DLL 也是禁止一些库被打包进来,不同的是 DLL
会对一些库进行单独打包,在上面的 code split 代码分割中是把 node_modules 所有第三方库打包成一个 chunk,而 DLL 会把不同库分开打包
externals 中还需要通过 CDN 引入第三方库,而 DLL 会自动引入自己打包的第三方库
使用 dll 技术对某些第三方库(如 jQuery、react、vue…)进行单独打包
需要新建文件 webpack.dll.js 写入如下内容
const {resolve} = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
//打包生成的名字[name]: ['要打包的库']
jquery: ['jquery']
},
output: {
filename: '[name].js',
path: resolve(__dirname,'dll'),
library: '[name]_[hash]', //打包的库里向外暴露出去的内容叫什么名字
},
plugins: [
//打包生成一个 manifest.json 告诉 webpack 说 jQuery 不需要打包,并且提供和 jQuery 映射
new webpack.DllPlugin({
name: '[name]_[hash]', //映射库的暴露的内容名称
path: resolve(__dirname,'dll/manifest.json') //最终这个库输出到哪里去
})
],
mode: 'production'
}
运行打包 webpack --config webpack.dll.js
,因为运行 webpack 时默认查找 webpack.config.js 配置文件,要想运行 webpack.dll.js 配置,需要使用 –config 参数指定
打包后会生成 dll/jquery.js 和 dll/manifest.json,只需打包一次,此后 jQuery 无需再重复打包
下载 add-asset-html-webpack-plugin 包
npm i add-asset-html-webpack-plugin -D
在 webpack.config.js 文件中同样引入 webpack 和 add-asset-html-webpack-plugin
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
在 webpack.config.js 文件 的 plugins 中添加
//告诉 webpack 哪些库不参与打包,同时使用时的名称也得变
new webpack.DllReferencePlugin({
manifest: resolve(__dirname,'dll/manifest.json')
})
//将某个文件打包输出出去,并在 html 中自动引入该资源
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname,'dll/jquery.js')
})
通过上述配置就会把 jQuery 库单独打包,并在 html 中自动引入自己打包的 jQuery 库,无需再手动通过 script 标签通过 CDN 引入 jQuery
(2)优化代码运行的性能
缓存 ———— 文件资源缓存
文件资源缓存————让代码上线运行缓存更好使用
可在打包输出的 built.js 文件名以及 css 文件名中添加 webpack 每次构建时生成的唯一 hash 值,当文件改变重新打包后生成的文件名改变则必须重新请求,不会读取缓存中的文件
有三种 hash 值:hash、chunkhash、contenthash
hash:每次 webpack 构建时会生成一个 hash 值
问题:因为 js 和 css 同时使用一个 hash 值,若重新打包会导致所有缓存失效(可能只改动了一个文件)
chunkhash:根据 chunk 生成的 hash 值,若打包来源于同一个 chunk,则 hash 值就一样
问题:因为 css 是在 js 中被引入的,所以同属于一个 chunk,打包后都在 built.js 中,所以 js 和 css 文件的 chunkhash 还是一样的
contenthash:根据文件内容生成 hash 值,不同文件的 hash 值一定不一样
更改后配置文件如下
const {resolve} = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
process.env.NODE_ENV = 'production';
//复用 loader
const commonCssLoader = [
MiniCssExtractPlugin.loader,
'css-loader',
{
//还需要在 package.json 中定义 browserslist
loader:'postcss-loader',
options:{
ident: 'postcss',
plugins: () => [
require('postcss-preset-env')()
]
}
}
]
module.exports = {
entry:'./src/index.js',
output:{
filename: 'js/built.[contenthash:10].js', //添加 contenthash
path: resove(__dirname,'build')
},
module:{
rules:[
//js 语法检查
{
//还需在 package.json 中配置 eslintConfig
test:/\.js$/,
exclude: /node_modules/,
enforce: 'pre', //优先执行
loader:'eslint-loader',
options:{
fix:true
}
},
{
//以下 loader 只会匹配一个,不能有两个配置处理同一种类型文件
oneOf:[
//处理 css
{
test:/\.less$/,
use:[...commonCssLoader]
},
//处理 less
{
test:/\.css$/,
use:[...commonCssLoader,'less-loader']
},
// js 兼容性处理
{
test:/\.js$/,
exclude: /node_modules/,
loader:'babel-loader',
options:{
presets:[
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3
},
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
]
],
//开启 babel 缓存,第二次构建时会读取之前的缓存
cacheDirectory: true
}
},
//处理图片资源
{
test:/\.(jpg|png|gif)$/,
loader:'url-loader',
options:{
limit:8*1024,
esModule: false,
name:'[hash:10].[ext]',
outputPath:'imgs' //打包后图片会输出到打包目录下的 imgs 文件夹中
}
},
//处理 html 中 img 资源
{
test:/\.html$/,
loader:'html-loader'
},
//处理其他资源
{
exclude:/\.(css|js|html|less|jpg|png|gif)/,
loader:'file-loader',
options:{
name:'[hash:10].[ext]',
outputPath:'media' //打包后图片会输出到打包目录下的 media 文件夹中
}
}
]
}
]
},
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
});
new MiniCssExtractPlugin({
filename: 'css/built.[contenthash:10].css' //添加contenthash
}),
//压缩 css
new OptimizeCssAssetsWebpackPlugin()
],
//模式
mode: 'production',
devtool: 'source-map'
}
服务器端代码
const express = require('express');
//创建应用对象
const app = express();
app.use(express.static('build',{maxAge:1000 * 3600}));
//监听端口启动服务
app.listen(8000)
tree shaking
tree shaking 用于去除无用代码
tree shaking 前提:(1)必须使用 ES6 模块化(2)mode 使用 production 环境即可自动开启 tree shaking
tree shaking 作用:减少代码体积,从而使得请求和加载速度更快
注意:在旧版本 webpack 中或设置 "sideEffects": false
表示所有代码都没有副作用,即都可以进行 tree shaking,这可能把 css 或 @babel/polyfill 等副作用文件都消除掉,因此需在 package.json 中配置 "sideEffects": ["*.css","*.less"]
code split
通过 code split 代码分割把打包后的文件分成多个,这样在加载时可并行加载速度更快,且可按需加载
代码分割主要是关注 js 文件
方式一:多入口
通过在 entry 中设置多入口来拆分文件(之前的单入口方式一般用在单页面应用,多入口用于多页面应用)
entry:{
//多入口:每个入口最终都输出一个 bundle
index: './src/js/index.js',
test: './src/js/test.js'
},
output:{
filename: 'js/[name].[contenthash:10].js'
path: resolve(__dirname,'build')
}
方式二:splitChunks
若使用单入口 + splitChunks 可以将 node_modules 中代码(第三方代码)单独打包一个 chunk 输出
若使用多入口 + splitChunks 还会自动分析多入口 chunk 中有没有公共的文件,若有则只会把该公共文件打包成一个单独的 chunk 不会重复打包多次
entry:{
index: './src/js/index.js',
test: './src/js/test.js'
},
output:{
filename: 'js/[name].[contenthash:10].js'
path: resolve(__dirname,'build')
},
optimization: {
splitChunks: {
chunks: 'all'
}
},
上述配置中若 index.js 和 test.js 都引入了 jQuery,则打包后会生成三个 bundle 分别对应 index.js、test.js 和 jQuery
方式三:import 动态导入语法
修改 js 代码,使用 impor 动态导入语法,让某个文件被单独打包成一个 chunk
在某 js 文件(如 index.js)中引入另一 js 文件时不使用 import {mul,red} from './xxx.js'
,而使用如下方式
import(/* webpackChunkName: '打包后的文件名' */'./xxx.js)
.then(({mul,red}) => {
//eslint-disable-next-line
console.log(mul(1,2));
})
.cache(() => {
//eslint-disable-next-line
console.log('文件加载失败');
});
其中 xxx.js 中定义了 mul 和 red 两个方法
export function mul(x, y) {
return x * y;
}
export function red(x, y) {
return x - y;
}
打包后会生成两个 bundle 分别对应 index.js 和 xxx.js
注意:一般多入口使用较少,会使用单入口 + splitChunks,并把其他要单独打包的 js 文件使用 import 动态导入语法来完成
js 的懒加载和预加载
懒加载:当文件需要使用时才加载,是通过把代码分割的 import 动态导入语法放在一个异步函数中来实现
预加载:在使用之前提前加载 js 文件,但兼容性较差
正常加载是并行加载,即同一时间加载多个文件,而预加载是等其他资源加载完毕,浏览器空闲了再偷偷加载资源
如在 index.js 中编写点击按钮触发 xxx.js 中定义的方法
document.getElementById('btn').onclick = function(){
//懒加载和预加载(webpackPrefetch)
import(/* webpackChunkName: 'xxx', webpackPrefetch: true */'./xxx.js)
.then(({mul}) => {
//eslint-disable-next-line
console.log(mul(1,2));
})
}
PWA
PWA(渐进式网络应用开发程序)让网页向 app 一样可以离线访问,性能也会更好
PWA 通过 workbox 实现,在 webpack 中通过使用 workbox-webpack-plugin 插件
下载后,在 webpack.config.js 文件中引入 workbox-webpack-plugin
const WorkboxWebpackPlugin = require('workbox-webpack-plugin')
在 webpack.config.js 文件中的 plugins 里添加
new WorkboxWebpackPlugin.GenerateSW({
clientsClaim: true, //删除旧的 serviceworker
skipWaiting: true //帮助 serviceworker 快速启动
})//会生成一个 serviceworker 配置文件
在 index.js 中添加如下代码
if('serviceWorker' in navigator){
window.addEventListener('load',() => {
navigator.serviceWorker
.register('/service-worker.js') //注册 serviceworker
.then(() => {
console.log('sw注册成功');
})
.catch(() => {
console.log('sw注册失败');
});
});
}
eslint 不认识 window、navigator 全局变量,因此需要在 package.json 中 eslintConfig 修改为
“eslintConfig”:{
"extends":"airbnb-base",
"env": {
"browser": true
}
}
serviceworker 代码必须运行在服务器上,这里通过 serve 包快速构建一个服务器进行测试,下载 serve 包 npm -i serve -g
,再启动服务器 serve -s build
其中 build 表示打包后的目录,将 build 目录下所有资源作为静态资源暴露出去
二、webpack 配置详解
1、entry
entry 的值可以是 string、array、object,一般用 string 和 object 较多
string:单入口 ———— ‘./src/index.js’
打包形成一个 chunk,输出一个 bundle 文件,此时 chunk 名称默认是 main
array:多入口 ———— [‘./src/index.js’,’./src/xxx.js’]
所有入口文件最终只会形成一个 chunk,输出只有一个 bundle 文件 默认是 main.js(当 output 中 filename 是 [name].js 时)
一般用在 HMR 功能中让 html 热更新生效
object:多入口 ———— {index: ‘./src/index.js’, xxx: ‘./src/xxx.js’}
有几个入口文件就形成几个 chunk,输出几个 bundle 文件,此时 chunk 名称是 key
特殊用法:
entry: {
index:['./src/index.js','./src/mul.js'], //mul 和 index 形成一个 chunk 打包输出在一个 bundle 文件中
add: './src/add.js' //形成一个 chunk,输出一个 bundle
}
2、output
output 中可以设置
filename ———— 文件名称(指定该名称 + 目录)
filename: 'js/[name].js'
path ———— 输出文件目录(将来所有资源输出的公共目录)
path: resolve(__dirname,'build')
publicPath ———— 所有资源引入时的公共路径前缀,一般用于生产环境
publicPath: '/' //当引入 imgs/a.jpg 时路径为 /imgs/a.jpg
chunkFilename ———— 非入口 chunk 的名称(entry 中指定的就是入口文件)
如使用 import 动态导入语法进行代码单独打包时,打包输出的文件按 chunkFilename 指定的规则命名,若不修改名称默认使用 filename,这会与入口文件的输出文件名称冲突,webpack 会自动修改导入文件的打包后文件名称,命名方式可能不方便使用
chunkFilename: 'js/[name]_chunk.js'
library ———— 整个库向外暴露的变量名,一般配合 dll 使用
library: '[name]'
libraryTarget ———— 变量名添加到哪里
libraryTarget: 'window' //将库添加到浏览器的 window 上
libraryTarget: 'global' //添加到 node 上
libraryTarget: 'commonjs'
libraryTarget: 'amd'
3、module
module: {
rules: [
//loader 配置
{
test: /\.css$/
//多个 loader 用 use
use: ['style-loader',''css-loader]
},
{
test: /\.js$/
//排除 node_modules 下的 js 文件
exclude: /node_modules/,
//只检查 src 目录下的 js 文件
include: resolve(__dirname, 'src'),
//优先执行
enforce: 'pre',
//延后执行 enforce: 'post',
//单个 loader 用 loader
loader: 'eslint-loader',
options: {}
},
{
//以下配置只会生效一个
oneOf: []
}
]
}
4、resolve
resolve 用于解析模块的规则
参数 alias ———— 配置解析模块路径别名:优点简写路径,缺点路径没有提示
参数 extensions ———— 配置省略文件路径的后缀名,默认值是 .js 和 .json
参数 modules ———— 告诉 webpack 解析模块是去哪个目录找加快解析速度
在 webpack.config.js 中添加
resolve:{
alias: {
$css: resolve(__dirname, 'src/css')
},
extensions: ['.js','.json', '.jsx', '.css']
modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
}
5、devServer
devServer 用于开发环境
devServer:{
contentBase:resolve(__dirname,'build'), //要运行的项目构建后的目录
watchContentBase: true, //监视 contentBase 目录下的所有文件,一旦文件变化就会 reload
watchOptions: {
//忽略文件
ignored: /node_modules/
}
compress:true, //启动 gzip 压缩,使代码更小运行更快
port:5000, //端口号
open:true, //自动打开浏览器
hot: true, //开启 HMR 功能
clientLogLevel: 'none', //不显示启动服务器日志信息
quiet: true, //除了一些基本启动信息外,其他内容都不要显示
overlay: false, //如果出错不要全屏提示,在日志中打印出来就好
proxy: { //服务器代理,解决开发环境跨域问题(浏览器和服务器间有跨域问题,服务器和服务器间没有跨域问题)
'/api': {
target: 'http://localhost:3000', //一旦 devServer(5000)服务器接收到 /api/xxx 的请求,就会把请求转发到另一个服务器(3000)
pathRewrite: { //发送请求时,请求路径重写
'^/api':'' //将 /api/xxx 重写为 /xxx(去掉 /api)
}
}
}
}
6、optimization
optimization 在生产环境中才有意义
参数 splitChunks 用于提取公共代码成单独 chunk 打包
参数 runtimeChunk 将当前模块中记录其他模块 hash 的部分单独打包为一个文件,当 a 模块发生变化时 a 文件以及对应的 runtime 文件重新打包,而其他引用 a 的模块不重新打包 ———— 解决修改 a 文件导致 b 文件的 contenthash 变化产生的缓存失效需要重新打包 b 的问题
因此写代码分割时一定要加上 runtimeChunk,否则会导致缓存失效
参数 minimizer 配置生产环境的压缩方案(针对 js 和 css)
在 webpack 4.26 之前是使用 UglifyJsPlugin 插件进行 js 压缩,之后的版本是使用 terser-webpack-plugin 插件进行压缩
const TerserWebpackPlugin = require('terser-webpack-plugin');
optimization: {
splitChunks: {
chunks: 'all',
//下面设置的这些都是用的默认值
minSize: 30 * 1024, //分割的 chunk 最小为 30kb(即文件大于 30kb 才会分割)
minChunks: 1, //要提取的 chunk 最少被引用 1 次
maxAsyncRequests: 5, //按需加载时并行加载在的文件最大数量(所以最多也只会打包成相应数量 chunk)
maxInitialRequests: 3, //入口 js 文件最大并行请求数量
automaticNameDelimiter: '~', //名称连接符
name: true, //可以使用命名规则
cacheGroups: { //分割 chunk 的组
vendors: {
test: /[\\/]node_modules[\\/]/, //node_modules 文件(也需满足上面规则如大小超过 30kb,至少被引用一次)会被打包到 vendors 组的 chunk 中,即会输出 vendors~xxx.js
priority: -10 //打包的优先级
},
default:{
minChunks: 2, //要提取的 chunk 最少被引用 2 次
priority: -20, //打包优先级
reuseExistingChunk: true //若当前要打包的模块和之前已经被提取的模块是同一个就会复用,而不是重新打包模块
}
}
},
runtimeChunk: {
name: entrypoint => `runtime-${entrypoint.name}`
},
minimizer: [
new TerserWebpackPlugin({
cache: true, //开启缓存
parallel: true, //开启多进程打包,速度更快
sourceMap: true //启动 source-map
})
]
},
三、webpack 5
1、使用 webpack 5
首先初始化 npm npm init
下载 webpack 5 npm i webpack@next webpack-cli -D
在 webpack 5 中 webpack.config.js 配置文件中只有 mode 是需要设置的其他可使用默认值,这不同于 webpack 4,在 webpack 4 中需要设置 entry、output、mode
同样使用命令 webpack
打包
webpack 5 中默认输出的目录是 dist
其中 webpack 5 中使用的默认值如下
entry: "./src/index.js"
output.path: path.resolve(__dirname, "dist")
output.filename: "[name].js"
2、webpack 5 重点关注的内容
(1)通过持久缓存提高构建性能
(2)使用更好的算法和默认值来改善长期缓存(如 hash 值的相关算法)
(3)通过更好的 tree shaking 和代码生产来改善 bundle 大小
(4)清除处于怪异状态的内部结构,同时在 v4 中实现功能而不引入任何重大更改
(5)通过引入重大更改来为将来的功能做准备,使我们能尽可能长时间使用 v5
3、一些特点
自动删除 Node.js Polyfills
早期 webpack 的目标是允许在浏览器中允许大多数 node.js 模块,但模块格局发生了变化,许多模块用途现在主要是为前端目的而编写的,webpack <= 4 附带了许多 node.js 核心模块的 polyfill,一旦模块使用任何核心模块(即 crypto 模块),这些模块就会自动应用,尽管这使使用 node.js 编写的模块变得容易,但它会将这些巨大的 polyfill 添加到包中,许多情况下,这些 polyfill 是不必要的,webpack 5 会自动停止填充这些核心模块,并专注于与前端兼容的模块
在 webpack 5 中尽可能尝试使用与前端兼容的模块。可为 node.js 核心模块手动添加一个 polyfill,错误消息会提示如何实现
chunk 和 模块 ID
添加了用于长期缓存的新算法(更优化的 hash 值算法),在生产模式下默认启用这些功能
chunkIds:"deterministic", moduleIds:"deterministic"
chunk ID
可以不使用 import(/* webpackChunkName:"name" */ "module")
在开发环境来为 chunk 命名,生产环境还是有必要的
webpack 内部有 chunk 命名规则,不再是以 id(0,1,2) 命名了
tree shaking 功能更加强大
(1) webpack 5 能处理嵌套模块的 tree shaking
//a.js
export const aa = 1;
export const bb = 2;
//b.js
import * as a from './a';
export { a };
//c.js
import * as b from './b';
console.log(b.a.aa);
在生产环境中,a 模块暴露的 bb 会被删除(因为没有被用到)
(2) webpack 5 能处理多个模块间的关系
import { something } from './something';
function usingSomething(){
return something;
}
export function test(){
return usingSomething();
}
当设置了 “sideEffects”: false 时,一旦发现 test 方法没有使用,不但删除 test,还会删除 ./something
(3) webpack 5 能处理 Commonjs 模块化的 tree shaking(之前 webpack 4 中都是以 ES6 模块化和生产环境为例)
ouput
webpack 4 默认只能输出 ES5 代码,webpack 5 开始新增属性 output.ecmaVersion 可以生成 ES5 和 ES6/ES2015 代码
output.ecmaVersion: 2015
optimization 中 splitChunk
webpack 4 中
minSize: 30000;
webpack 5 中,可进行更精确的划分
minSize: {
javascript: 30000,
style: 50000
}
caching 缓存
webpack 4 中设置了 babel 缓存等
webpack 5 在 cache 中设置缓存
//配置缓存
cache: {
//磁盘存储
type:'filesystem',
buildDependencies: {
//当配置修改时,缓存失效
config: [__filename]
}
}
缓存将存储到 node_modules/.cache/webpack
监视输出文件
之前 webpack 总是在第一次构建时输出全部文件,重新构建时会只更新修改的文件
webpack 5 在第一次构建时会找到输出文件看是否有变化,从而决定要不要输出全部文件
其他
webpack 可以处理 js、json 资源,不能处理 css、img 等其他资源