webpack进阶之路
Webpack进阶之路
前言
谨此一文记录webpack5学习之路。 本文主供自己学习和日后复习所用,参考借鉴 构建webpack5知识体系【近万字总结】所写
Webpack基础篇
1、什么是webpack
本质上讲:webpack是一个现代JavaScript应用程序的静态打包工具,当webpack处理应用程序的时候,它会递归的形成一个依赖关系图,其中包含应用程序需要的每个模块,然后将这些模块打包成一个或者多个bundle。
简单来说: webpack就是一个静态资源打包工具,负责将项目中依赖的各个模块,打包成一个或多个bundle
2、webpack的优点
- 拥有依赖管理、动态打包、代码分离、按需加载、代码压缩、静态资源压缩、缓存等配置
- webpack扩展性强,插件机制完善,开发者可自定义插件(plugin)、loader
- webpack社区庞大,更新速度快,轮子丰富
3、基础应用
3.1、entry(入口文件)
入口是依赖关系图的开始,从入口开始寻找依赖,打包构建,webpack允许一个或者多个入口配置:
单入口配置
module.exports = {
entry:"indexjs"
}
多入口配置
module.exports = {
entry :{
index: path.join(srcPath,"index.js"),
other:path.join(scrPath,"other.js")
}
}
3.2、output(出口文件)
输出用于配置webpack构建打包的出口,如打包的位置,打包的文件名:
单个出口起点
对于单出口起点,filename
回事一个静态的名称
module.exports = {
output:{
path: path.resolve(__dirname,"dist"),
filename:"bundle.js",
}
}
多个出口起点
对于多个出口起点,代码拆分(code splitting)、或各种插件(plugin) 创建多个bundle
,应该使用一下替换方式,来赋予每个bundle
一个唯一的名称
使用出口名称
module.exports = {
output:{
filename:"[name].bundle.js"
}
}
使用内部chunk id
module.exports = {
output:{
filename:"[hash].bundle.js"
}
}
使用chunkhash
module.exports = {
output:{
filename:"[name].[chunkhash].bundle.js"
}
}
使用contenthash
module.exports = {
output:{
filename:"[name].[contenthash:8],bundle.js"
}
}
TODO webpack三种hash的区别,webpack缓存相关 output更多配置详情参考webpack文档
3.3、loader(编译转换)
webpack自带JavaScript和JSON文件的打包构建能力,无需格外配置,对于其他类型的文件如CSS文件等,则需要安装loader处理; loader让webpack能够去处理其他类型的文件,并将他们转换为有效模块,以供应用程序使用,以及被添加到依赖图中
module.exports = {
module:{
rules:[
{
test:/\.css$/i,
use:["style-loader","css-loader"]
}
]
}
}
常用的loader
- style-loader
- css-loader
- less-loader、sass-loader
- thread-loader
具体loader配置看loader文档
3.4、plugin(插件)
插件则是用于扩展webpack的能力
module.exports = {
plugin:[new HtmlWebpackPlugin({
template:'./src/index.html'
})]
}
具体plugin配置看具体plugin文档
3.5、mode模式
webpack提供了模式选择,包括开发模式、生产模式、空模式,并对不同模式做出了对应的内置优化。可以通过配置模式让项目更优。 配置如下:
module.exprots = {
mode:'development'
}
3.6、resolve(解析)
resolve用于配置模块如何解析,常用配置如下:
- alias:配置别名,简化模块引入
- extensions:在引入模块时可不带后缀
- symlinks:用于配置
npm link
是否生效,禁用可提升编译速度
module.exports = {
resolve:{
extensions:['.js','.jsx','.ts','.json','.d.ts'],
alias:{
'@':"./src"
},
symlinks:false
}
}
3.7、optimization(优化)
optimization用于自定义webpack的内置优化配置,一般用于生产模式(mode:production) 提升性能,常配置项如下:
- minimize:是否压缩budle
- minimizer:是否配置压缩工具,如TerserPlugin、OptimizeCssAssetsPlugin
- splitChunk:拆分bundle
- runtimeChunk:是否需要将所有生成chunk之间共享运的运行时文件拆分出来
module.exports = {
optimization:{
minimizer:[
new CssMinimizerPlugin()
],
splitChunks:{
chunks:'all',
//重复打包问题
cacheGroup:{
vendors:{//node_modules里面的代码
test:/[\\/]node_modules[\\/]/,
chunks:"all",
name:"vendors",//chunks name
priority:10, //优先级
enforce:true
}
}
}
}
}
完整代码
简单示例
module.exports = {
mode:'development'
entry:"index.js",
output:{
filename:[contenthash:8].bundle.js
path:path.resolve(__dirname,"dist")
},
module:{
rule:[
{
test:/\.css$/i,
use:["style-loader","css-loader"]
}
]
},
plugin:[new HtmlWebpackPlugin({
template:'./src/index.html'
})],
resolve:{
extensions:[".js",".ts",".d.ts",".jsx"],
symlinks:false
alias:{ '@':"./src" },
},
optimization:{
minimizer:[
new CssMinimizerPlugin()
],
splitChunks:{
chunks:'all',
//重复打包问题
cacheGroup:{
vendors:{//node_modules里面的代码
test:/[\\/]node_modules[\\/]/,
chunks:"all",
name:"vendors",//chunks name
priority:10, //优先级
enforce:true
}
}
}
}
}
总结
本片主要学习webpack的基本配置模块,具体配置参照官方文档。
Webpack 进阶篇之一实践基础配置
1、实现目标
- 分离开发环境、生产配置环境
- 模块化开发
- sourceMap定位警告和错误
- 动态引入bundle.js的HTML5文件
- 实时编译
- 封装编译、打包命令
2、基本配置
2.1、新建项目目录
//新建webpack-init文件夹
mkdir webpack-init
//进入webpack-init目录
cd ./webpack-init
//初始化项目
npm init -y
新建config文件夹
mkdir config
//新建 webpack.common.js webpack.dev.js webpack.prod.js
2.2、 下载插件
npm install webpack webpack-cli --save-dev
npm i webpack-merge -D
2.3、构建配置文件内容
webpack.common.js
webpack的基本配置,开发和生产环境下均可生效
//webpack.common.js
module.exports = {}
webpack.dev.js
webpack开发配置,仅在开发环境下生效
//webpack.dev.js
const { merge } = require('webpack-merge')
const common = require('./webpack.common')
module.exports = merge(common,{})
webpack.prod.js
webpack生产配置,仅在生产环境下生效
//webpack.prod.js
const { merge } = require('webpack-merge')
const common = require('./webpack.common')
module.exports = merge(common,{})
2.4、入口文件的配置
修改 webpack.commom.js:
module.exports = {
//入口
entry:{
index:'./src/index.js'
}
}
2.5、出口文件的配置
output
属性输出他创建的bundle
的位置和命名
生产环境的output
需要通过contenthash
值来区分版本和变动,可达到清除缓存的效果,而本地开发环境为了构建效率,则不引入contenthash
。
修改webpack.dev.js:
//webpack.dev.js
const { merge } = require('webpack-merge')
const common = require('./webpack.common')
const {resolveApp} = require('./path') //封装的路径工具函数
module.exports = merge(common,{
output:{
filename:'[name].bundle.js',
path:resolveApp('dist'),
clean:true //编译前清除目录
})
修改webpack.prod.js
//webpack.dev.js
const { merge } = require('webpack-merge')
const common = require('./webpack.common')
const {resolveApp} = require('./path') //封装的路径工具函数
module.exports = merge(common,{
output:{
filename:'[name][contenthash:8].bundle.js', // 【只有这里和开发环境不一样】
path:resolveApp('dist'),
clean:true //编译前清除目录
})
path.js
文件,封装路径工具函数
const fs = require('fs')
const path = require('path')
const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath =>path.resolve(appDirectory, relativePath);
module.exports = { resolveApp }
2.6、模式选择
module.exports = merge(common, {
// 生产模式
mode: 'production',
})
module.exports = merge(common, {
// 生产模式
mode: 'development',
})
2.7、 source-map配置
source-map用于快速追踪定位error和warning,将编译后的代码映射回原始代码,提高开发效率
修改webpack.dev.js
module.exports = merge(common, {
// 生产模式
mode: 'development',
devtool:'eval-cheap-module-source-map',启 source map,编译调试
})
打包测试
- 开发环境:
npx webpack --config config/webpack.dev.js
- 生产环境 :
npx webpack --config config/webpack.prod.js
2.8 HtmlWepackPlugin插件
引入 HtmlWebpackPlugin 插件,生成一个 HTML5 文件,其中包括使用 script 标签的 body 中的所有 webpack 包;
安装:
npm install --save-dev html-webpack-plugin
修改webpack.common.js
module.exports = {
plugins:[
new HtmlWebpackPlugin({
title:"webpack",
})
]
}
2.9、DevServer
每次编译代码时,手动运行npx webpack --config config/webpack.prod.js
会显得麻烦,webpack-dev-server
帮助我们在代码发生变化后自动编译代码。
webpack-dev-server
提供了一个基本的 web server,并且具有实时重新加载功能。
webpack-dev-server
默认配置 conpress: true
,为每个静态文件开启gizp commpress
安装:
npm install --save-dev webpack-dev-server
修改webpack.dev.js
module.exports = merge(common, {
devServer:{
//告诉服务器从哪里提供内容,只有在你想要提供静态资源文件时才需要
static: {
directory: resolveApp('dist'),
},
port:8880,
hot:true
}
})
输入命令启动:
npx webpack serve --open --config config/webpack.dev.js
2.10、区分环境变量变量,执行命令
通过cross-env
配置环境变量,生产环境和开发环境
安装npm install --save-dev cross-env
修改package.json
{
"scripts": {
"dev": "cross-env NODE_ENV=development webpack serve --open --config config/webpack.dev.js",
"build": "cross-env NODE_ENV=production webpack --config config/webpack.prod.js"
},
}
webpack进阶篇之二 -实践进阶配置
本篇将继续完善配置,在上述的基础上,使用webpack搭建一个VUE3-TS的项目 将支持以下功能:
- 加载图片
- 加载字体
- 加载CSS/LESS
- 使用Postcss,并自动为CSS规则添加前缀,解析最新的CSS语法
- 引入css-modlues解决命名冲突的问题
- 使用Vue3
- 使用Typescript
1、加载图片(image)
在webpack4中,需要下载并配置特定的loader
(url-loader
、file-laoder
)来加载图片,本篇主要学习webpack5内容,所以不使用webpack4配置
在webpack5,内置了特定的用于资源模块Asset-Module
,它允许使用资源文件(字体、图标等)而无需配置loader
修改webpack.common.js
module.export = {
module:{
rules:[
{
test:/\.(png|jpg|svg|jpeg|gif)$/i,
include:[
resloveApp("src").
],
type:"asset/resource",
generator: {
filename: "img/[hash][ext][query]", // 局部指定输出位置
},
}
]
}
}
在实际开发过程中,推荐将大图片上传至 CDN,提高加载速度。
2、加载字体
同样使用Asset Module
接收字体文件
修改webpack.common.js
module.exports = {
module:{
rules:[
{
test: /.(woff|woff2|eot|ttf|otf)$/i,
include: [resolveApp("src")],
type: "asset/resource",
generator: {
filename: "iconfont/[hash][ext][query]", // 局部指定输出位置
},
},
}
TODO 还有些bug 后面在填坑
在实际开发过程中,推荐将字体文件压缩上传至 CDN,提高加载速度。如配置字体的文字是固定的,还可以针对固定的文字生成字体文件,可以大幅缩小字体文件体积。
3、加载css、less
安装相关配置
//less相关
npm install --save-dev less less-loader
//css相关
npm install --save-dev style-loader css-loader
修改webpack.common.js
module.exports = {
module: {
rules: [
{
test: /\.less$/i,
include: resolveApp("src),
use: ["style-loader", "css-loader", "less-loader"],
},
]
}
}
4、 使用PostCss
postcss
是一个JavaScript工具和插件转换css的工具
- 可以自动为css规则添加前缀
- 将最新的css语法转换成大多数浏览器都能理解的语法
- css-modules解决全局命冲突
postcss-loader
使用postcss
处理CSS的loader
安装Postcss相关依赖
npm install --save-dev postcss-loader postcss postcss-preset-env
修改webpack.common.js:
{
test:/\.less$/i,
include:resolveApp("src"),
use:[
'style-loader',
{
loader:"css-loader",
options:{
modules:true,
importLoaders:2,
},
},
{
loader:'postcss-loader",
options:{
postcssOptions:{
plugins:[
[
// postcss-preset-env 包含 autoprefixer
'postcss-preset-env',
]
]
}
}
},
"less-loader",
],
}
具体配置参考PostCSS配置文件
5、vue3 + TypeScript
5.1、TypeScript配置
安装Typescript
npm install typescript -D
添加ts解析工具
ts
文件并不是webpack
能够直接解析的,所以我们需要使用ts-loader
来专门处理
npm i ts-loader -D
修改webpack.common.js
module.exports = {
entry: "./src/index.ts",
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
use: "ts-loader",
},
],
},
};
babel
使用 ts-loader
处理 ts
文件,但是这会存在一个问题:该 loader
是把 typeScript
转换成 javaScript
, 只负责新语法的转换,新增的 API 不会自动添加 polyfill
了解决这个问题,我们还是要祭出 babel
。babel
是一个工具链,主要用于旧浏览器或者环境中将 ECMAScript 2015+
代码转换为向后兼容版本的 javaScript
,包括语法转换、源代码转换等。关注社区的小伙伴可能知道,从 babel7
开始 babel
已经支持 ts
的编译,所以 ts-loader
可以弃用了
安装依赖
npm i babel-loader @babel/core @babel/preset-env @babel/preset-typescript -D
修改webpack.common.js
module.epxorts = {
module: {
rules: [
{
test: /\.(t|j)s$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: { cacheDirectory: true,
},
},
}
]
},
};
在根目录添加babel.config.js
文件
module.exports = {
presets: [
"@babel/preset-env",
[
"@babel/preset-typescript",
{
allExtensions: true, //支持所有文件扩展名
},
],
],
};
5.2、vue3配置
安装vue3
//vue3
npm install vue
此时webpack
不能认识.vue
文件,它需要我们提供一个loader
对其进行处理,这个loader在官方文档中提到
其中
@vitejs/plugin-vue
这个是 vite
才需要的,因此我们只需要 vue-loader@next
、@vue/compiler-sfc
安装相关依赖
npm install vue-loader@next @vue/compiler-sfc -D
修改webpack.common.js配置
const {VueLoaderPlugin} = require("vue-loader")
modules.exports = {
module:{
rules:[
{
test:/\.vue$/i,
use:"vue-loader"
}
]
},
plugins:[
new VueLoaderPlugin()
]
}
webpack进阶之优化
本章将从优化开发体验,加快编译速度,减少打包体积,加快加载等对webpack项目进行优化
1、效率工具
1.1、编译进度条(不重要)
www.npmjs.com/package/pro…
安装
npm i -D progress-bar-webpack-plugin
webpack.common.js 配置方式如下:
const chalk = require('chalk')
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
module.exports = {
plugins: [
// 进度条
new ProgressBarPlugin({
format: ` :msg [:bar] ${chalk.green.bold(':percent')} (:elapsed s)`
})
],
}
1.2、编译速度分析(不重要)
安装
npm i -D speed-measure-webpack-plugin
webpack.common.js 配置方式如下:
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
module.exports = smp.wrap({
// ...webpack config...
})
1.3、 打包提及分析
安装
npm i -D webpack-bundle-analyzer
webpack.prod.js 配置方式如下:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
// 打包体积分析
new BundleAnalyzerPlugin()
],
}
2、优化开发
2.1、 热更新
热更新指的是,在开发过程中,修改代码后,仅更新修改部分的内容,无需刷新整个页面;
webpack.dev.js 配置方式如下:
module.export = {
devServer: {
static: {
directory: resolveApp("dist"),
},
port: 8880,
hot: true,
},
}
3、构建速度优化
3.1 cache
webpack5
较于webpack4
,新增了持久化缓存、改进缓存算法的优化,webpack5新特性可查看参考资料
通过配置webpack 持久化缓存,来缓存生成的webpack模块和chunk,改善构建速度,可提速90%左右
修改webpack.common.js
module.exports = {
cache:{
type:"filesystem",//使用文件缓存
}
引入缓存后,首次构建速度将增加15%,二次构建事件将减少90%
3.2、减少loader、plugins
每个loader、plugin都有其启动时间,尽量减少的使用工具,将非必须的loader、plugin删除
3.3、 指定include
为 loader 指定 include,减少 loader 应用范围,仅应用于最少数量的必要模块。
例如:
module.exports = {
rules: [
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
include: [
resolveApp('src'),
],
type: 'asset/resource',
}
]
}
3.4、资源管理
使用 webpack 资源模块(asset module) 代替旧的 assets loader(如 file-loader/url-loader/raw-loader 等),减少 loader 配置数量。
module.exports = {
rules: [
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
include: [
resolveApp('src'),
],
type: 'asset/resource',
}
]
}
3.5、优化resolve配置
resolve用来配置 webpack 如何解析模块,可通过优化 resolve 配置来覆盖默认配置项,减少解析范围;
alias 别名
alias可以创建import或require的别名,用来简化模块引入
webpack.common.js 配置方式如下:
module.exports = {
resolve:{
alias:{
"@":resolve("src")
}
}
}
extensions 解析文件类型列表
extensions表示需要解析的文件类型列表。
根据项目的文件类型,定义extensions,以覆盖webpack默认的extensions,加快解析速度。
webpackwebpack 的解析顺序是从左到右,因此要将使用频率高的文件类型放在左侧,如下我将 .vue
放在最左侧
修改webpack.common.js配置
module.exports = {
resolve: {
extensions: ['.tsx', '.ts', '.js'],
}
}
modules
modules 表示 webpack 解析模块时需要解析的目录;
指定目录可缩小 webpack 解析范围,加快构建速度
webpack.common.js配置
module.exports = {
resolve{
modules: [
'node_modules',
paths.appSrc,
]
}
}
symlinks
如果项目不使用 symlinks(例如 npm link 或者 yarn link),可以设置 resolve.symlinks: false
,减少解析工作量。
webpack.common.js配置
module.exports = {
resolve: {
symlinks: false,
},
}
3.6、 多线程(thread-loader)
通过 thread-loader将耗时的 loader 放在一个独立的 worker 池中运行,加快 loader 构建速度
安装npm i -D thread-loader
配置
{
test: /.js$/,
include: path.resolve('src'),
use: [
'thread-loader',
// your expensive loader (e.g babel-loader)
],
},
3.7 区分环境
切忌在开发环境使用生产环境才会用到的工具,如在开发环境下,应该排除 [fullhash]/[chunkhash]/[contenthash] 等工具。
在生产环境,应该避免使用开发环境才会用到的工具,如 webpack-dev-server 等插件;
3.8、devtool
不同的 devtool 设置,会导致性能差异。在多数情况下,最佳选择是 eval-cheap-module-source-map;
webpack.dev.js配置如下:
export.module = {
devtool: 'eval-cheap-module-source-map',
}
3.9、输出结果不携带路径信息
默认 webpack 会在输出的 bundle 中生成路径信息,将路径信息删除可小幅提升构建速度
module.exports = {
output: {
pathinfo: false,
},
};
}
3.10、 IgnorePlugin
IgnorePlugin在构建模块时候直接剔除那些需要被排除的模块,常用于国家化和moment
new webpack.IgnorePlugin(/\.\/locale/, /moment/)
3.11、DllPugin
核心思想是将项目依赖的框架等模块单独构建打包,与普通构建流程区分开。
output: {
filename: '[name].dll.js',
// 输出的文件都放到 dist 目录下
path: distPath,
library: '_dll_[name]',
},
plugins: [
// 接入 DllPlugin
new DllPlugin({
// 动态链接库的全局变量名称,需要和 output.library 中保持一致
// 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
// 例如 react.manifest.json 中就有 "name": "_dll_react"
name: '_dll_[name]',
// 描述动态链接库的 manifest.json 文件输出时的文件名称
path: path.join(distPath, '[name].manifest.json'),
}),
],
3.12、Externals
Webpack 配置中的 externals 和 DllPlugin 解决的是同一类问题:将依赖的框架等模块从构建过程中移除。
他们区别在于:
- 在 Webpack 的配置方面,externals 更简单,而 DllPlugin 需要独立的配置文件。
- DllPlugin 包含了依赖包的独立构建流程,而 externals 配置中不包含依赖框架的生成方式,通常使用已传入 CDN 的依赖包。
- externals 配置的依赖包需要单独指定依赖模块的加载方式:全局对象、CommonJS、AMD 等。
- 在引用依赖包的子模块时,DllPlugin 无须更改,而 externals 则会将子模块打入项目包中。
// 引入cdn jq为例
<script
src="https://code.jquery.com/jquery-3.1.0.js"
integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="
crossorigin="anonymous"
></script>
//vue
<script
crossorigin="anonymous"
integrity="sha512-9eYPYYqSLRRJlQVcobBpNgDNq7ui/VtXRO/abRajYVXlxLFnV6sBNGfro0+/Us2pqE8DLC2ymO5XT4LIyJZbvQ=="
src="https://lib.baomitu.com/vue/3.2.31/vue.cjs.prod.min.js"></script>
// webpack配置
module.exports = {
//...
externals: {
jquery: 'jQuery',
vue:'Vue'
},
};
// 页面
import $ from 'jquery';
$('.my-element').animate(/* ... */);
import {Vue} from "vue"
new Vue({})
4.减少打包体积
4.1、代码压缩
4.1.1、js压缩
使用TerserWebpackPlugin来压缩 JavaScript
Webpack5自带最新的terser-webpack-plugin,无需手动安装
terser-webpack-plugin
默认开启了 parallel: true
配置,并发运行的默认数量: os.cpus().length - 1
,本文配置的parallel
数量为4,使用多进程并发运行压缩以提高构建速度;
修改webpack.prod.js
cosnt TerserPlugin = require('terser-webpack-plugin')
module.exports = {
optimization:{
minimizer:{
new TerserPlugin({
parallel:4,
terserOptions:{
parse:{
ecma:8
},
compress:{
ecma:5,
warnings:false,
comparisons:false,
inline:2
},
mangle: { safari10: true, },
output: {
ecma: 5,
comments: false,
ascii_only: true,
},
}
})
}
}
}
4.1.2、CSS压缩
使用 CssMinimizerWebpackPlugin压缩 CSS 文件
安装:npm install -D css-minimizer-webpack-plugin
webpack.prod.js配置
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
optimization: {
minimizer: [
new CssMinimizerPlugin({
parallel: 4,
}),
],
}
}
4.2代码分离
代码分离能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,可以缩短页面加载时间;
4.2.1、抽离重复代码
SplitChunksPlugin插件开箱即用,可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk;
webpack 将根据以下条件自动拆分 chunks:
- 新的chunk可以被共享,或者模块来自
node_modules
文件夹, - 新的chunk体积大于20kb(在进行min+gz之前的体积)
- 按需加载chunks,并行请求的最大数量小于或者等于30
- 当加载初始化页面时,并发请求的最大数量小于或者等于30;
注意:切记不要为 cacheGroups 定义固定的 name,因为 cacheGroups.name 指定字符串或始终返回相同字符串的函数时,会将所有常见模块和 vendor 合并为一个 chunk。这会导致更大的初始下载量并减慢页面加载速度;
webpack.prod.js
module.exports = {
optimization: {
splitChunks: {
// include all types of chunks
chunks: 'all',
// 重复打包问题
cacheGroups:{
// node_modules里的代码
// 第三方模块
vendors:{
test: /[\\/]node_modules[\\/]/,
chunks: "all",
// name: 'vendors', 一定不要定义固定的name
priority: 10, // 优先级
enforce: true
},
// 公共的模块
common: {
name: 'common', // chunk 名称
priority: 0, // 优先级
minSize: 0, // 公共模块的大小限制
minChunks: 2 // 公共模块最少复用过几次
}
}
}
}
}
4.2.2 css文件分离
MiniCssExtractPlugin插件将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载;
安装:npm install -D mini-css-extract-plugin
webpack.common.js 配置方式如下:
注意:MiniCssExtractPlugin.loader 要放在 style-loader 后面
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const isEnvProduction = process.env.NODE_ENV === "production";
module.exports = {
plugins: [new MiniCssExtractPlugin()],
module: {
rules: [
{
test: /\.module\.(scss|sass|less)$/,
include: paths.appSrc,
use: [
'style-loader',
isEnvProduction && MiniCssExtractPlugin.loader, // 仅生产环境
{
loader: 'css-loader',
options: {
modules: true,
importLoaders: 2,
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
[
'postcss-preset-env',
],
],
},
},
},
{
loader: 'thread-loader',
options: {
workerParallelJobs: 2
}
},
'less-loader',
]
},
]
},
};
4.2.3、最小化entry chunk
通过配置 optimization.runtimeChunk = true,为运行时代码创建一个额外的 chunk,减少 entry chunk 体积,提高性能 ; webpack.prod.js 配置方式如下:
module.exports = {
optimization: {
runtimeChunk: true,
},
};
}
4.3 Tree Shaking(摇树)
1个模块可能有多个⽅法,只要其中的某个方法使⽤到了,则整个⽂件都会被打到 bundle 里面去,tree shaking 就是只把⽤到的方法打入 bundle ,没⽤到的方法会在uglify阶段被擦除掉;
4.3.1、 js
JS Tree Shaking将 JavaScript 上下文中的未引用代码(Dead Code)移除,通过 package.json 的 "sideEffects" 属性作为标记,向 compiler 提供提示,表明项目中的哪些文件是 "pure(纯正 ES2015 模块)",由此可以安全地删除文件中未使用的部分;
Dead Code一般具有以下几个特征:
- 代码不会被执行,不可到达
- 代码执行的结果不会被用到
- 代码只会影响死变量(只写不读)
webpack5 sideEffects设置 通过 package.json 的 "sideEffects" 属性,来实现这种方式;
{
"name": "your-project",
"sideEffects": false
}
需注意的是,当代码有副作用时,需要将 sideEffects 改为提供一个数组,添加有副作用代码的文件路径:
{
"name": "your-project",
"sideEffects": ["./src/some-side-effectful-file.js"]
}
对组件库引用的优化
webpack5 sideEffects 只能清除无副作用的引用,而有副作用的引用则只能通过优化引用方式来进行 Tree Shaking;
loadsh
类似 import { throttle } from 'lodash' 就属于有副作用的引用,会将整个 lodash 文件进行打包;
优化方式是使用 import { throttle } from 'lodash-es' 代替 import { throttle } from 'lodash', lodash-es将 Lodash库导出为 ES模块,支持基于 ES modules 的 tree shaking,实现按需引入;
ant-design
ant-design默认支持基于 ES modules 的 tree shaking,对于 js 部分,直接引入 import { Button } from 'antd' 就会有按需加载的效果;
假如项目中仅引入少部分组件,import { Button } from 'antd' 也属于有副作用,webpack不能把其他组件进行tree-shaking。这时可以缩小引用范围,将引入方式修改为 import { Button } from 'antd/lib/button' 来进一步优化。
4.3.2、css
使用 purgecss-webpack-plugin对 CSS Tree Shaking。
安装 :npm i purgecss-webpack-plugin -D
因为打包时 CSS 默认放在 JS 文件内,因此要结合 webpack 分离 CSS 文件插件 mini-css-extract-plugin 一起使用,先将 CSS 文件分离,再进行 CSS Tree Shaking;
webpack.prod.js 配置方式如下:
const glob = require('glob')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const PurgeCSSPlugin = require('purgecss-webpack-plugin')
const paths = require('paths')
module.exports = {
plugins: [
// 打包体积分析
new BundleAnalyzerPlugin(),
// 提取 CSS
new MiniCssExtractPlugin({
filename: "[name].css",
}),
// CSS Tree Shaking
new PurgeCSSPlugin({
paths: glob.sync(`${resolveApp("src")}/**/*`, { nodir: true }),
}),
]
}
4.5.3 CDN
通过 CDN 来减小打包体积;
将大的静态资源上传至 CDN:
- 字体:压缩并上传至CDN
- 图片:压缩并上传至CDN
5 加快加载速度
5.1、按需加载
通过 webpack 提供的 import() 语法动态导入功能进行代码分离,通过按需加载,大大提升网页加载速度;
<template>
<button@click = clickHandle>加载loadsh</button>
</tepmlate>
<script lang=:ts setup>
const clickHandle = ()=>{
import("lodash")
}
</script>
5.2浏览器缓存
浏览器缓存,就是进入某个网站后,加载的静态资源被浏览器缓存,再次进入该网站后,将直接拉取缓存资源,加快加载速度。 webpack 支持根据资源内容,创建 hash id,当资源内容发生变化时,将会创建新的 hash id 配置 JS bundle hash,webpack.prod.js 配置方式如下
module.exports = {
// 输出
output: {
// 仅在生产环境添加 hash
filename: '[name].[contenthash].bundle.js'
},
}
配置 CSS bundle hash,webpack.prod.js 配置方式如下:
module.exports = {
plugins: [
// 提取 CSS
new MiniCssExtractPlugin({
filename: "[hash].[name].css",
}),
],
}
配置 optimization.moduleIds,让公共包 splitChunks 的 hash 不因为新的依赖而改变,减少非必要的 hash 变动,webpack.prod.js 配置方式如下:
module.exports = {
optimization: {
moduleIds: 'deterministic',
}
}
通过配置 contenthash/hash,浏览器缓存了未改动的文件,仅重新加载有改动的文件,大大加快加载速度
5.3、使用CDN
上面讲过了
6、总结
- 在小型项目中,添加过多的优化配置,作用不大。反而会因为额外的loader、plugin增加构建时间
- 在加快构建时间方面。作用最大的配置时
cache
配置,可大大加快二次构建速度 - 在减少打包体积方面,作用最大的时压缩代码,分离重复代码、tree-shaking,可大幅度减少打包体积
- 在加快加速度方面,按需加载、浏览器缓存、CDN效果都很显著。
未完待续
转载自:https://juejin.cn/post/7071935716824973349