1.css 优化
- 提取
- 压缩
- 去除无用的css
cnpm i mini-css-extract-plugin optimize-css-assets-webpack-plugin purgecss-webpack-plugin glob -D
const path = require("path");
const glob = require("glob");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { PurgeCSSPlugin } = require("purgecss-webpack-plugin");
const { VueLoaderPlugin } = require('vue-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const PATHS = {
src: path.join(__dirname, "src"),
};
module.exports = {
mode: 'development',
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.join(__dirname, "dist"),
},
optimization: {
splitChunks: {
cacheGroups: {
styles: {
name: "styles",
test: /\.css$/,
chunks: "all",
enforce: true,
},
},
},
},
module: {
rules: [
{
test: /\.vue$/,
use: "vue-loader",
},
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
"@babel/preset-env"
]
}
}
]
},
// {
// test: /\.css$/,
// use: ["css-loader"],
// },
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
}),
new PurgeCSSPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
}),
new HtmlWebpackPlugin({
template: './public/index.html', // 指定模板文件
filename: 'index.html', // 输出的HTML文件名
title: 'webpack5新特性', // HTML文件的标题,可以在模板中使用 <%= htmlWebpackPlugin.options.title %>
// 其他配置选项
}),
new OptimizeCssAssetsWebpackPlugin(),
new VueLoaderPlugin(),
],
};
2.图片优化
- 压缩
- 转Base64
- 上传到其他的CDN服务器
- 懒加载和预加载
- 文件名哈希和缓存
- 使用webP格式
3. js文件
3.1. DLL动态链接库
DllPlugin和DllReferencePlugin提供了拆分包的方法,可以极大地提高构建时性能。术语DLL代表动态链接库,它最初是由Microsoft引入的。.dll为后缀的文件称为动态链接库,在一个动态链接库中可以包含给其他模块调用的函数和数据- 把基础模块独立出来打包到单独的动态连接库里
- 当需要导入的模块在动态连接库里的时候,模块不能再次被打包,而是去动态连接库里获取
- dll 打包文件
const path = require('path');
const { DllPlugin } = require('webpack');
module.exports = {
mode: 'production',
entry: {
vendor: ['lodash', 'vue', 'isarray', 'is-promise'] // 这里列出你希望打包的第三方库
},
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, 'dist'),
library: '[name]_library' // 生成的 DLL 文件中暴露的全局变量名
},
plugins: [
new DllPlugin({
name: '[name]_library',
path: path.resolve(__dirname, 'dist/[name]-manifest.json')
})
]
};
- webpack 主打包文件代码
new DllReferencePlugin({
manifest: require('./dist/vue-manifest.json'), // 指向生成的 DLL 文件的 manifest 文件
// context: __dirname
})
- 再去index.html 中加入
'
3.2 多进程打包
- thread-loader
- 把这个 loader 放置在其他 loader 之前, 放置在这个 loader 之后的 loader 就会在一个单独的 worker 池(worker pool)中运行
- 开启多进程打包
{
test: /\.js$/,
use: [
{
loader: 'thread-loader',
options: {
workers: 3
}
},
{
loader: 'babel-loader',
options: {
presets: ["@babel/preset-env"]
}
}]
}
- 没有开启多进程打包
总结:多进程打包不一定比没有开启的快,因为去开启多进程打包的时候也是需要一定的时间。
3.3 开启(webpack5)内存和文件系统缓存
-
- 使用默认缓存配置:
Webpack 5 默认会在缓存目录(默认为
node_modules/.cache/webpack)中存储缓存。这意味着对于相同的输入,Webpack 会尝试重用之前的构建结果,加快构建速度。
- 使用默认缓存配置:
Webpack 5 默认会在缓存目录(默认为
-
- 手动配置缓存:
如果需要更精细的控制,你可以手动配置缓存选项。
在 webpack 配置文件中,可以通过
cache配置项来设置缓存选项:
- 手动配置缓存:
如果需要更精细的控制,你可以手动配置缓存选项。
在 webpack 配置文件中,可以通过
module.exports = {
// 其他配置项
cache: {
type: 'filesystem', // 指定使用文件系统缓存
cacheDirectory: path.resolve(__dirname, '.webpack_cache') // 自定义缓存目录
},
// 其他配置项
};
'memory':存储在内存中,适用于开发模式。'filesystem':存储在文件系统中,适用于生产模式和长时间运行的构建过程。
3.4 代码分割和提取公共模块
代码分割的意义
- 对于大的Web应用来讲,将所有的代码都放在一个文件中显然是不够有效的,特别是当你的某些代码块是在某些特殊的时候才会被用到。
- webpack有一个功能就是将你的代码库分割成chunks语块,当代码运行到需要它们的时候再进行加载。 适用的场景
- 抽离相同代码到一个共享块
- 脚本懒加载,使得初始下载的代码更小
3.4.1. 入口起点代码分割
- 缺点:如果两个入口或者多个入口代码重复的话会被各自打包到各自的文件中去
- 无法有效的进行重复代码的拆分
- 不够灵活无法对核心应用程序逻辑进行动态分割
3.4.2. 动态导入和懒加载
- 对网站功能进行划分,每一类一个chunk
- 对于首次打开页面需要的功能直接加载,尽快展示给用户,某些依赖大量代码的功能点可以按需加载
- 被分割出去的代码需要一个按需加载的时机
3.4.2.1动态导入
-
webpack提供了两种实现动态导入的方式:
- 第一种,使用ECMAScript中的 import() 语法来完成,也是目前推荐的方式;
- 第二种,使用webpack遗留的 require.ensure,目前已经不推荐使用;
-
比如我们有一个模块 bar.js:
- 该模块我们希望在代码运行过程中来加载它(比如判断一个条件成立时加载);
- 因为我们并不确定这个模块中的代码一定会用到,所以最好拆分成一个独立的js文件;
- 这样可以保证不用到该内容时,浏览器不需要加载和处理该文件的js代码;
- 这个时候我们就可以使用动态导入;
-
动态导入前
- 动态导入后
异步导入的模块,不管文件的大小,都会进行分包处理的。
动态导入的文件命名
- 因为动态导入通常是一定会打包成独立的文件的,所以并不会再cacheGroups中进行配置;
- 那么它的命名我们通常会在output中,通过 chunkFilename 属性来命名;(设置异步加载的打包文件名)
- 也可以通过webpack 的魔法注释来修改名字
/* webpackChunkName:"bar" */
3.4.2.2代码懒加载
动态import使用最多的一个场景是懒加载(比如路由懒加载):
- 封装一个component.js,返回一个component对象;
- 我们可以在一个按钮点击时,加载这个对象;
3.4.3. 提取公共代码
- 相同的资源被重复的加载,浪费用户的流量和服务器的成本;
- 每个页面需要加载的资源太大,导致网页首屏加载缓慢,影响用户体验。
- 如果能把公共代码抽离成单独文件进行加载能进行优化,可以减少网络传输流量,降低服务器成本
optimization: {
splitChunks: {
chunks: "all",//默认作用于异步chunk,值为all/initial/async
minSize: 30000, //默认值是30kb,代码块的最小尺寸
minChunks: 2, //被多少模块共享,在分割之前模块的被引用次数
maxAsyncRequests: 5, //按需加载最大并行请求数量
maxInitialRequests: 3, //一个入口的最大并行请求数量
// name: true, //打包后的名称,默认是chunk的名字通过分隔符(默认是~)分隔开,如vendor~
automaticNameDelimiter: '~',//默认webpack将会使用入口名和代码块的名称生成命名,比如 'vendors~main.js'
cacheGroups: { //设置缓存组用来抽取满足不同规则的chunk,下面以生成common为例
styles: {
name: 'styles',
test: /\.css$/,
chunks: 'all',
enforce: true,
},
vendors: {
chunks: "initial",
test: /node_modules/,//条件
priority: -10 ///优先级,一个chunk很可能满足多个缓存组,会被抽取到优先级高的缓存组中,为了能够让自定义缓存组有更高的优先级(默认0),默认缓存组的priority属性为负值.
},
commons: {
chunks: "initial",
minSize: 0,//最小提取字节数
minChunks: 2, //最少被几个chunk引用
priority: -20,
reuseExistingChunk: true// 如果该chunk中引用了已经被抽取的chunk,直接引用该chunk,不会重复打包代码
}
}
},
},
chunks
- async:当设置chunks的值为async时,只有在异步加载模块的时候,才会进行分包处理该模块
- initial:同步加载模块的时候,也会进行分包处理。
- all:同步异步都会进行分包处理
minSize和 maxSize
1.minSize:拆分出的包的大小,至少是minSize的值,默认是3000,如果拆分出去的包没有这个大,就不会拆分
2.maxSize:将大于maxSize的包,再次拆分为大于minSize,但是不会大于maxSize的包。
minChunks
在模块中,使用(import,require)等关键字引入其他模块的时候,只有引入次数大于等于该值时,才会进行分包。
- 至少被引入的次数,默认是1;
- 如果我们写一个2,但是引入了一次,那么不会被单独拆分 缓存组,出现在缓存组中的模块,不会直接分包,而是在所有模块加载完毕以后再根据缓存组配置的内容进行分包处理。
cacheGroups
用于对拆分的包就行分组,比如一个lodash在拆分之后,并不会立即打包,而是会等到有没有其他符合规则的包一起来打 包
- test属性:匹配符合规则的包;
- name属性:拆分包的name属性;
- filename属性:拆分包的名称,可以自己使用placeholder属性;
js
cacheGroups: {
// 第三方库 将匹配到的 node_modules下加载的库 都打包到vendors下面
vendors: {
test: /[\/]node_modules[/\]/,
filename: "[id]_vendors.js",
},
// 将自己的 utils文件夹下的文件 打包
format:{
test:/[\/]utils[/\]/,
filename:"[id]_utils_format.js"
}
},
缓存组优先级
如果一个模块同时满足多个缓存组,那么就将模块分包到优先级高的缓存组中。优先级可以为负数。
chunkIds
告诉webpack,配置分包的时候,生成的分包文件的id采用什么算法。 optimization.chunkIds配置用于告知webpack模块的id采用什么算法生成。
-
有三个比较常见的值:
- natural:按照数字的顺序使用id;
- named:development下的默认值,一个可读的名称的id;
- deterministic:确定性的,在不同的编译中不变的短数字id 。确定的文件名一定有确定的短数字id。
- size: 根据模块大小生成的数字id
-
最佳实践:
- 开发过程中,我们推荐使用named;
- 打包过程中,我们推荐使用deterministic;
optimization. runtimeChunk
配置runtime相关的代码是否抽取到一个单独的chunk中:
-
runtime相关的代码指的是在运行环境中,对模块进行解析、加载、模块信息相关的代码;
-
比如我们的component、bar两个通过import函数相关的代码加载,就是通过runtime代码完成的;
-
- 抽离出来后,有利于浏览器缓存的策略:
-
比如我们修改了业务代码(main),那么runtime和component、bar的chunk是不需要重新加载的;
-
比如我们修改了component、bar的代码,那么main中的代码是不需要重新加载的;
- 通过将
optimization.runtimeChunk设置为object,对象中可以设置只有name属性,其中属性值可以是名称或者返回名称的函数,用于为 runtime chunks 命名
- 通过将
js
module.exports = {
//...
optimization: {
runtimeChunk: {
// name: "runtime.module", // single的别名配置
// name: (entrypoint) => "runtime.module" // single的别名配置
name: (entrypoint) => `runtime.module-${entrypoint.name}`, // true multiple 的别名配置
}
},
};
3.5 开启Scope Hoisting
- 这个功能在mode为production下默认开启,开发环境要用
webpack.optimize.ModuleConcatenationPlugin插件 - 也要使用ES6 Module,CJS不支持
module.exports = {
resolve: {
// 针对 Npm 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件
mainFields: ['jsnext:main', 'browser', 'main'] },
plugins: [
// 开启 Scope Hoisting
new webpack.optimize.ModuleConcatenationPlugin(),
], };
3.6 压缩js
- terser-webpack-plugin是一个优化和压缩JS资源的插件
const path = require('path');
const { VueLoaderPlugin } = require('vue-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
main: './src/index.js',
index: './src/index.js',
vue: ['vue'],
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader',
},
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
],
},
{
test: /\.(jpg|png|gif)$/,
type: 'asset',
generator: {
filename: '[path][name].[hash:8][ext]',
},
},
{
test: /\.css$/,
use: ['css-loader'],
},
],
},
optimization: {
minimize: true, // 开启压缩,默认为 true
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true // 删除 console.log
}
}
})
],
splitChunks: {
chunks: "all",//默认作用于异步chunk,值为all/initial/async
minSize: 30000, //默认值是30kb,代码块的最小尺寸
minChunks: 2, //被多少模块共享,在分割之前模块的被引用次数
maxAsyncRequests: 5, //按需加载最大并行请求数量
maxInitialRequests: 3, //一个入口的最大并行请求数量
// name: true, //打包后的名称,默认是chunk的名字通过分隔符(默认是~)分隔开,如vendor~
automaticNameDelimiter: '~',//默认webpack将会使用入口名和代码块的名称生成命名,比如 'vendors~main.js'
cacheGroups: { //设置缓存组用来抽取满足不同规则的chunk,下面以生成common为例
styles: {
name: 'styles',
test: /\.css$/,
chunks: 'all',
enforce: true,
},
vendors: {
chunks: "initial",
test: /node_modules/,//条件
priority: -10 ///优先级,一个chunk很可能满足多个缓存组,会被抽取到优先级高的缓存组中,为了能够让自定义缓存组有更高的优先级(默认0),默认缓存组的priority属性为负值.
},
commons: {
chunks: "initial",
minSize: 0,//最小提取字节数
minChunks: 2, //最少被几个chunk引用
priority: -20,
reuseExistingChunk: true// 如果该chunk中引用了已经被抽取的chunk,直接引用该chunk,不会重复打包代码
}
}
},
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html',
title: 'webpack5新特性',
}),
new VueLoaderPlugin(),
]
};
3.7 文件名哈希缓存
- 在webpack5之前,没有从entry打包的chunk文件,都会以1、2、3...的文件命名方式输出,删除某些些文件可能会导致缓存失效
- 在生产模式下,默认启用这些功能chunkIds: "deterministic", moduleIds: "deterministic",此算法采用确定性的方式将短数字 ID(3 或 4 个字符)短hash值分配给 modules 和 chunks
| 可选值 | 含义 | 示例 |
|---|---|---|
| false | 不应使用任何内置算法,插件提供自定义算法 | Path variable [name] not implemented in this context: [name].js |
| natural | 按使用顺序的数字ID | 1 |
| named | 方便调试的高可读性id | src_two_js.js |
| deterministic | 根据模块名称生成简短的hash值 | 915 |
| size | 根据模块大小生成的数字id | 0 |
3.8 Tree Shaing
- 一个模块可以有多个方法,只要其中某个方法使用到了,则整个文件都会被打到bundle里面去,tree shaking就是只把用到的方法打入bundle,没用到的方法会uglify阶段擦除掉
- 原理是利用es6模块的特点,只能作为模块顶层语句出现,import的模块名只能是字符串常量
tree-shaking 有以下几种情况不会打包进去最终结果(production 会自动开启)
1.没有导入和使用 
2.代码不会被执行,不可到达 
3.代码执行的结果不会被用到 
4,代码中只写不读的变量 
3.9 预加载预拉取
preload(预先加载)
-
preload通常用于本页面要用到的关键资源,包括关键js、字体、css文件
-
preload将会把资源得下载顺序权重提高,使得关键数据提前下载好,优化页面打开速度
-
在资源上添加预先加载的注释,你指明该模块需要立即被使用
-
一个资源的加载的优先级被分为五个级别,分别是
- Highest 最高
- High 高
- Medium 中等
- Low 低
- Lowest 最低
-
异步/延迟/插入的脚本(无论在什么位置)在网络优先级中是
Low
js
<link rel="preload" as="script" href="utils.js">
js
import( `./utils.js` /* webpackPreload: true */ /* webpackChunkName: "utils" */ )
prefetch(预先拉取)
- prefetch 跟 preload 不同,它的作用是告诉浏览器未来可能会使用到的某个资源,浏览器就会在闲时去加载对应的资源,若能预测到用户的行为,比如懒加载,点击到其它页面等则相当于提前预加载了需要的资源
js
<link rel="prefetch" href="utils.js" as="script">
button.addEventListener('click', () => {
import(
`./utils.js`
/* webpackPrefetch: true */
/* webpackChunkName: "utils" */
).then(result => {
result.default.log('hello');
})
});
4. CDN
- CDN 又叫内容分发网络,通过把资源部署到世界各地,用户在访问时按照就近原则从离用户最近的服务器获取资源,从而加速资源的获取速度。
- HTML文件不缓存,放在自己的服务器上,关闭自己服务器的缓存,静态资源的URL变成指向CDN服务器的地址
- 静态的JavaScript、CSS、图片等文件开启CDN和缓存,并且文件名带上HASH值
- 为了并行加载不阻塞,把不同的静态资源分配到不同的CDN服务器上
4.1 使用缓存
- 由于 CDN 服务一般都会给资源开启很长时间的缓存,例如用户从 CDN 上获取到了 index.html 这个文件后, 即使之后的发布操作把 index.html 文件给重新覆盖了,但是用户在很长一段时间内还是运行的之前的版本,这会新的导致发布不能立即生效 解决办法
- 针对 HTML 文件:不开启缓存,把 HTML 放到自己的服务器上,而不是 CDN 服务上,同时关闭自己服务器上的缓存。自己的服务器只提供 HTML 文件和数据接口。
- 针对静态的 JavaScript、CSS、图片等文件:开启 CDN 和缓存,上传到 CDN 服务上去,同时给每个文件名带上由文件内容算出的 Hash 值
- 带上 Hash 值的原因是文件名会随着文件内容而变化,只要文件发生变化其对应的 URL 就会变化,它就会被重新下载,无论缓存时间有多长。
- 启用CDN之后 相对路径,都变成了绝对的指向 CDN 服务的 URL 地址
4.2 域名限制
- 同一时刻针对同一个域名的资源并行请求是有限制
- 可以把这些静态资源分散到不同的 CDN 服务上去
- 多个域名后会增加域名解析时间
- 可以通过在 HTML HEAD 标签中 加入
<link rel="dns-prefetch" href="http://img.zhufengpeixun.cn">去预解析域名,以降低域名解析带来的延迟
5 缩小范围
5.1 extensions
指定extensions之后可以不用在require或是import的时候加文件扩展名,会依次尝试添加扩展名进行匹配
resolve: { extensions: [".js",".jsx",".json",".css"] },
5.2alias
配置别名可以加快webpack查找模块的速度
- 每当引入bootstrap模块的时候,它会直接引入
bootstrap,而不需要从node_modules文件夹中按模块的查找规则查找
const bootstrap = path.resolve(__dirname,'node_modules/bootstrap/dist/css/bootstrap.css') resolve: {
alias:{ bootstrap
} },
5.3 modules
-
对于直接声明依赖名的模块(如 react ),webpack 会类似 Node.js 一样进行路径搜索,搜索
node_modules目录 -
这个目录就是使用
resolve.modules字段进行配置的 默认配置resolve: { modules: ['node_modules'], }如果可以确定项目内所有的第三方依赖模块都是在项目根目录下的 node_modules 中的话
resolve: { modules: [path.resolve(__dirname, 'node_modules')], }
5.4 mainFields
默认情况下package.json 文件则按照文件中 main 字段的文件名来查找文件
resolve: {
// 配置 target === "web" 或者 target === "webworker" 时 mainFields 默认值是:
mainFields: ['browser', 'module', 'main'],
// target 的值为其他时,mainFields 默认值为:
mainFields: ["module", "main"],
}
5.5 mainFiles
当目录下没有 package.json 文件时,我们说会默认使用目录下的 index.js 这个文件,其实这个也是可以配置的
resolve: {
mainFiles: ['index'], // 你可以添加其他默认使用的文件名
},
5.6 resolveLoader
resolve.resolveLoader用于配置解析 loader 时的 resolve 配置,默认的配置:
module.exports = {
resolveLoader: {
modules: [ 'node_modules' ],
extensions: [ '.js', '.json' ],
mainFields: [ 'loader', 'main' ]
}
};
5.7 noParse
-
module.noParse字段,可以用于配置哪些模块文件的内容不需要进行解析 -
不需要解析依赖(即无依赖) 的第三方大型类库等,可以通过这个字段来配置,以提高整体的构建速度
module.exports = { // ... module: { noParse: /jquery|lodash/, // 正则表达式 // 或者使用函数 noParse(content) { return /jquery|lodash/.test(content) }, } }...使用 noParse 进行忽略的模块文件中不能使用 import、require、define 等导入机制
5.8 IgnorePlugin
IgnorePlugin用于忽略某些特定的模块,让 webpack 不把这些指定的模块打包进去
src/index.js
import moment from 'moment';
import 'moment/locale/zh-cn'
console.log(moment().format('MMMM Do YYYY, h:mm:ss a'));
webpack.config.js
import moment from 'moment';
console.log(moment);
new webpack.IgnorePlugin({
//A RegExp to test the context (directory) against.
contextRegExp: /moment$/,
//A RegExp to test the request against.
resourceRegExp: /^./locale/
new MiniCSSExtractPlugin({
filename:'[name].css'
})
- 第一个是匹配引入模块路径的正则表达式
- 第二个是匹配模块的对应上下文,即所在目录名
6.webpack费时分析
cnpm i speed-measure-webpack-plugin
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin'); const smw = new SpeedMeasureWebpackPlugin(); module.exports =smw.wrap({ });
webpack.config.js
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
const smw = new SpeedMeasureWebpackPlugin();
const { VueLoaderPlugin } = require('vue-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path')
module.exports = smw.wrap({
mode: 'development',
entry: {
main: './src/index.js',
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader',
},
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
],
},
{
test: /\.(jpg|png|gif)$/,
type: 'asset',
generator: {
filename: '[path][name].[hash:8][ext]',
},
},
{
test: /\.css$/,
use: ['css-loader'],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html',
title: 'webpack5新特性',
}),
new VueLoaderPlugin(),
]
});
7.码分析报告 webpack-bundle-analyzer
- 是一个webpack的插件,需要配合webpack和webpack-cli一起使用。这个插件的功能是生成代码分析报告,帮助提升代码质量和网站性能
cnpm i webpack-bundle-analyzer -D
const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer');
module.exports={
plugins: [
new BundleAnalyzerPlugin()
]
}