前言
先来说说为什么要优化?当然如果你的项目很小,构建很快,其实不需要特别关注性能方面的问题。
但是随着项目涉及到的页面越来越多,功能和业务代码也会越来越多,相应的 webpack
的构建时间也会越来越久,这个时候我们就不得不考虑性能优化的事情了。
webpack
的性能优化较多,我们考虑从两方面入手:
优化一:打包后的结果,上线时的性能优化。(比如分包处理、减小包体积、CDN服务器等)
优化二:优化打包速度,开发或者构建时优化打包速度。(比如 exclude
、cache-loader
等)
因为这个上线时的性能是直接影响到用户使用体验的,而构建时间与我们的日常开发是密切相关,当我们本地开发启动 devServer
或者 build
的时候,如果时间过长,会大大降低我们的工作效率。
性能优化 - 代码分离
代码分离(Code Splitting)是 webpack
一个非常重要的特性:
它主要的目的是将代码分离到不同的 bundle
中,之后我们可以按需加载,或者并行加载这些文件;
比如默认情况下,所有的 JavaScript
代码(业务代码、第三方依赖、暂时没有用到的模块)在首页全部都加载,就会影响首页的加载速度;
代码分离可以分出更小的 bundle
,以及控制资源加载优先级,提供代码的加载性能;
Webpack中常用的代码分离有三种:
- 入口起点:使用entry配置手动分离代码;
- 防止重复:使用Entry Dependencies或者SplitChunksPlugin去重和分离代码;
- 动态导入:通过模块的内联函数调用来分离代码;
入口起点优化-Entry Dependencies(入口依赖)
当项目拥有多个入口点(entry points)时,可能会遇到一些重复依赖的问题。某些模块可能在多个入口点中被引用,导致这些模块被重复打包,增加了最终输出文件的体积。
dependon-shared模块解决重复依赖
module.exports = {
entry: {
page1: {
import: './src/page1.js',
dependOn: 'shared',
},
page2: {
import: './src/page2.js',
dependOn: 'shared',
},
shared: './src/shared.js',
},
output: {
filename: '[name].bundle.js',
path: __dirname + '/dist',
},
};
动态导入(dynamic import)
动态导入是一种在Webpack中实现按需加载(Lazy Loading)的技术,允许在运行时异步加载模块,而不是在应用初始化时就把所有模块打包到一个大文件中。可以提高应用的初始加载速度,并且减小了初始包的体积。
const path = require('path');
module.exports = {
entry: {
main: './src/index.js',
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
},
module: {
rules: [
// 添加你的Loader规则
],
},
optimization: {
splitChunks: {
chunks: 'all',
},
},
};
在上述配置中,通过 optimization.splitChunks
进行代码分割,它的 chunks: 'all'
选项表示对所有模块进行代码分割。
然后,在代码中使用 import()
函数进行动态导入:
// 在需要的地方使用动态导入
const loadModule = () => import('./Module');
loadModule().then(module => {
// 使用加载的模块
});
Webpack会将使用 import()
函数引入的模块进行代码分割,生成单独的文件。
在运行时,这些文件会在需要的时候异步加载。
自定义分包-SplitChunks
分包(code splitting)是一种优化策略,它允许将代码分割成小块,使得应用在加载时能够更快地显示内容。
Webpack提供了多种分包的模式,其中一种是使用SplitChunksPlugin
插件来实现的,这个模式叫做splitChunks
。
module.exports = {
// ...其他配置
optimization: {
splitChunks: {
chunks: 'all',
minSize: 30000, // 模块的最小体积
minChunks: 1, // 模块的最小被引用次数
maxAsyncRequests: 5, // 按需加载时的最大并行请求数
maxInitialRequests: 3, // 入口点的最大并行请求数
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};
性能优化-CDN
CDN称之为内容分发网络(Content Delivery Network或Content Distribution Network,缩写:CDN), 它是指通过相互连接的网络系统,利用最靠近每个用户的服务器; 更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户; 来提供高性能、可扩展性及低成本的网络内容传递给用户;
在开发中,我们使用CDN主要是两种方式:
- 打包的所有静态资源,放到CDN服务器, 用户所有资源都是通过CDN服务器加载的;
- 一些第三方资源放到CDN服务器上;
使用CDN(Content Delivery Network,内容分发网络)是一种非常有效的性能优化策略,特别是在Webpack中。CDN可以加速网站的加载速度,减轻服务器负担,并提高用户体验。以下是如何在Webpack中配置和使用CDN的方法:
将第三方库引入CDN
将你的项目中用到的第三方库(例如React、Vue、jQuery等)引入CDN。可以选择在HTML文件中直接引入CDN链接:
<script src="https://cdn.jsdelivr.net/npm/react@版本号/dist/react.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@版本号/dist/react-dom.min.js"></script>
在Webpack中配置externals
在Webpack的配置中使用externals字段,告诉Webpack哪些模块是外部引入的,不需要打包。
module.exports = {
// ...其他配置
externals: {
react: 'React',
'react-dom': 'ReactDOM',
},
};
然后在HTML文件中通过script标签引入CDN:
<script src="https://cdn.jsdelivr.net/npm/react@版本号/dist/react.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@版本号/dist/react-dom.min.js"></script>
配置CDN的publicPath
在Webpack的output字段中配置publicPath,指定在引入资源时使用的URL前缀,通常设置为CDN的地址:
module.exports = {
// ...其他配置
output: {
// ...其他output配置
publicPath: 'https://cdn.example.com/',
},
};
这样在Webpack构建时,所有的资源引用路径都会加上CDN的地址前缀。
性能优化-提取css文件
将CSS文件从JavaScript打包文件中提取出来是一种常见的性能优化策略。这样做的好处是可以减小JavaScript文件的体积,加快页面加载速度,并且使浏览器能够并行下载CSS和JavaScript文件,提高加载性能。在Webpack中,你可以使用mini-css-extract-plugin
插件来实现CSS文件的提取。
配置Webpack
在Webpack配置文件中引入mini-css-extract-plugin
插件,然后配置module.rules
来处理CSS文件。
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
// ...其他配置
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
// 可以加入其他的CSS处理loader,比如postcss-loader和sass-loader
],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'styles.css', // 提取出的CSS文件的文件名
}),
],
};
引入CSS文件
在JavaScript文件或者入口文件中引入CSS文件:
import './styles.css';
或者在HTML文件中使用link标签引入提取出来的CSS文件:
<link rel="stylesheet" href="styles.css">
性能优化-打包文件命名(Hash,ContentHash,ChunkHash)
在Webpack中,打包文件的命名是一个重要的性能优化策略。合适的命名方案可以确保浏览器能够正确地缓存文件,避免不必要的网络请求,提高应用的加载速度。以下是三种常见的打包文件命名方式:Hash、ContentHash 和 ChunkHash。
Hash(哈希)
Hash 是根据文件内容生成的哈希值,当文件内容发生改变时,其对应的 Hash 值也会改变。在Webpack中,可以使用 [hash]
占位符来表示 Hash 值。
output: {
filename: 'bundle.[hash].js',
}
ContentHash(内容哈希)
ContentHash 是根据文件内容生成的哈希值,但是不同于 Hash 的是,ContentHash 只会受到文件内容的影响,不会受到文件名或路径等其他因素的影响。在Webpack中,可以使用 [contenthash]
占位符来表示 ContentHash 值。
output: {
filename: 'bundle.[contenthash].js',
}
ChunkHash(块哈希)
ChunkHash 是根据模块内容生成的哈希值,不同模块的内容不同,它们的 ChunkHash 值也会不同。在Webpack中,可以使用 [chunkhash]
占位符来表示 ChunkHash 值。
output: {
filename: '[name].[chunkhash].js',
}
性能优化-webpack实现Tree Shaking
JavaScript的Tree Shaking:
对JavaScript进行Tree Shaking是源自打包工具rollup(后面我们也会讲的构建工具);
这是因为Tree Shaking依赖于ES Module的静态语法分析(不执行任何的代码,可以明确知道模块的依赖关系);
webpack2正式内置支持了ES2015模块,和检测未使用模块的能力;
在webpack4正式扩展了这个能力,并且通过 package.json的 sideEffects属性作为标记,告知webpack在编译时,哪里文 件可以安全的删除掉;
webpack5中,也提供了对部分CommonJS的tree shaking的支持;
JS实现Tree Shaking
webpack实现Tree Shaking采用了两种不同的方案:
- usedExports:通过标记某些函数是否被使用,之后通过Terser来进行优化的;
- sideEffects:跳过整个模块/文件,直接查看该文件是否有副作用;
CSS进行Tree Shaking
CSS的Tree Shaking需要借助于一些其他的插件;
在早期的时候,我们使用PurifyCss插件来完成CSS的tree shaking,但是目前该库已经不再维护;
目前我们可以使用另外一个库来完成CSS的Tree Shaking:PurgeCSS,也是一个帮助我们删除未使用的CSS的工具: PurgeCss
評論