文章目录
- 概要
- webpack 基础内容
- vue3 打包优化总结
- vue3 打包优化实例案例展示
- HMR
- 小结
- webpack`配置文件实例
- 应用优化总结
概要
webpack 基础内容
-
概念
webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。 -
核心概念
- 入口(entry):指定 webpack 从哪里开始构建依赖关系图。可以是一个或多个入口点,通常是 JavaScript 文件。例如在 webpack 配置中:
module.exports = {
entry: './src/index.js'
};
- **输出(output)**:告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件。
module.exports = {
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
- 加载器(loader):webpack 只能理解 JavaScript 和 JSON 文件。加载器用于将其他类型的文件(如 CSS、图片、字体等)转换为 webpack 能够处理的有效模块。例如,使用
css - loader
和style - loader
来处理 CSS 文件:
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
};
- 插件(plugin):用于执行范围更广的任务,比如打包优化、资源管理和注入环境变量等。例如,使用
HtmlWebpackPlugin
来自动生成 HTML 文件并引入打包后的 JavaScript 文件:
const HtmlWebpackPlugin = require('html - webpack - plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
]
};
- 模块(module)
在 webpack 中,一切皆模块。一个模块可以是一个 JavaScript 文件、CSS 文件、图片文件等。每个模块都有自己的依赖关系,webpack 会根据这些依赖关系来构建整个应用的依赖图。
vue3 打包优化总结
-
优化目标
减少打包后的文件体积、提高加载速度和运行性能。 -
优化方法
- 代码分割(Code Splitting)
- 使用动态导入(dynamic import),在 Vue 3 中,可以通过
import()
函数来实现。例如,对于一些只在特定条件下使用的组件,可以采用动态导入的方式,这样在初始加载时不会包含这些组件的代码。
- 使用动态导入(dynamic import),在 Vue 3 中,可以通过
- 代码分割(Code Splitting)
// 异步加载组件
const AsyncComponent = () => import('./AsyncComponent.vue');
- 利用 Vue 3 的异步组件 API,如
defineAsyncComponent
,它可以更好地控制组件的加载和错误处理。
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue'));
- 优化依赖树
- 尽量减少不必要的依赖安装。查看项目中使用的第三方库,删除那些没有实际使用的库。
- 对于一些大型的第三方库,如果只使用了其中的部分功能,可以考虑使用更轻量级的替代方案或者进行针对性的引入(如果库支持的话)。- 压缩和优化输出
- 使用 webpack 的生产模式(
mode: 'production'
),它会自动启用一些优化,如代码压缩。 - 配置
optimization
选项,例如,可以设置minimize: true
来确保代码被压缩,还可以配置splitChunks
来进一步分割代码块,避免将所有代码打包到一个大的 bundle 中。
- 使用 webpack 的生产模式(
- 压缩和优化输出
module.exports = {
mode: 'production',
optimization: {
minimize: true,
splitChunks: {
chunks: 'all'
}
}
};
- 图片和资源优化
- 对于图片资源,可以使用合适的加载器来进行压缩。例如,使用image - webpack - loader
可以在打包过程中对图片进行压缩。
- 使用合适的格式,如对于一些小图标可以使用 SVG 格式,它在加载和渲染上有更好的性能。- 去除不必要的 Vue 特性
如果项目中没有使用 Vue 的某些特性(如过渡效果、指令等),可以通过构建工具的配置或者 Vue 的自定义构建来去除这些不必要的代码,减少最终的打包体积。
- 去除不必要的 Vue 特性
vue3 打包优化实例案例展示
案例场景:假设我们有一个 Vue 3 项目,包含多个页面和组件,使用了一些第三方库,并且有较多的图片资源。
-
初始情况分析
在初始构建后,发现打包后的文件体积较大,加载时间较长。经过分析,发现部分第三方库有很多未使用的功能,同时图片资源没有进行优化,而且一些组件的加载没有进行合理的分割。 -
优化步骤及效果
- 代码分割
- 对于一些只在特定页面使用的组件,采用了动态导入的方式。例如,有一个只在用户登录后才显示的设置页面组件,修改代码如下:
- 代码分割
// 原代码
import SettingsComponent from './SettingsComponent.vue';
// 修改为
const SettingsComponent = () => import('./SettingsComponent.vue');
- 经过此优化,初始加载的 bundle 体积明显减小,主页面的加载速度提高了约 30%。
- 优化依赖树
- 检查项目中的第三方库,发现
moment
库被完全引入,但实际上只使用了日期格式化功能。将其替换为dayjs
,这是一个更轻量级的日期处理库。同时,删除了一些没有使用的其他第三方库。 - 这一步使得打包后的整体体积减少了约 20%。
- 检查项目中的第三方库,发现
- 压缩和优化输出
- 在 webpack 配置中,确保
mode
设置为production
,并进一步配置optimization
选项:
- 在 webpack 配置中,确保
- 优化依赖树
module.exports = {
mode: 'production',
optimization: {
minimize: true,
splitChunks: {
chunks: 'all',
minSize: 20000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
enforceSizeThreshold: 50000,
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
- 通过这些优化,代码的压缩效果更好,进一步减少了 bundle 的大小,并且由于代码块的合理分割,加载时的资源并行加载效率提高。
- 图片和资源优化
- 安装并配置
image-webpack-loader
,在 webpack 配置的module
规则中添加对图片的处理:
- 安装并配置
- 图片和资源优化
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|jpeg|gif|svg)$/,
use: [
{
loader: 'file-loader',
options: {
name: 'images/[name].[ext]'
}
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65
},
// 其他图片格式的优化选项
}
}
]
}
]
}
};
- 将一些小图标从 PNG 格式转换为 SVG 格式。这些优化措施使得图片资源的加载时间减少了约 40%。
- 去除不必要的 Vue 特性
通过分析项目,发现没有使用 Vue 的过渡效果。在构建时,通过自定义 Vue 构建(使用@vue/builder-utils
等相关工具)来去除过渡相关的代码,进一步减少了打包体积。经过一系列优化后,整个项目的加载速度和性能得到了显著提升,用户体验得到了改善。
- 去除不必要的 Vue 特性
HMR
-
什么是HMR(热模块替换)?
- HMR(Hot Module Replacement)是一种技术,它允许在运行时更新模块,而无需进行完整的页面刷新。这对于开发过程中快速查看代码更改的效果非常有用,能够显著提高开发效率。例如,当你修改了一个CSS样式或者一个JavaScript函数时,通过HMR可以在浏览器中即时看到变化,而不需要重新加载整个页面,这样可以保留当前的应用状态。
-
在webpack中配置HMR的基本步骤
- 安装必要的插件和依赖(如果需要)
- 对于大多数基于webpack的项目,
webpack-dev-server
是实现HMR的关键。如果还没有安装,使用以下命令安装:npm install webpack-dev-server --save - dev
- 对于大多数基于webpack的项目,
- 配置
webpack.config.js
文件- 基础配置修改
- 在
webpack.config.js
的devServer
选项中开启HMR。例如:
const path = require('path'); module.exports = { //...其他配置 devServer: { contentBase: path.join(__dirname, 'dist'), hot: true,//开启HMR port: 9000 }, //...其他配置 };
- 这里
contentBase
指定了服务器从哪里提供内容,hot: true
则是开启HMR的关键设置,port
是服务器运行的端口号。
- 在
- 模块热替换插件配置(如果是JavaScript模块)
- 对于JavaScript模块,还需要在
plugins
部分添加webpack.HotModuleReplacementPlugin
。
const webpack = require('webpack'); const path = require('path'); module.exports = { //...其他配置 plugins: [ new webpack.HotModuleReplacementPlugin() ], devServer: { contentBase: path.join(__dirname, 'dist'), hot: true, port: 9000 }, //...其他配置 };
- 对于JavaScript模块,还需要在
- 处理CSS模块(以style - loader为例)
- 当修改CSS文件时,为了让HMR能够正确处理CSS模块,在CSS加载器(
style-loader
和css-loader
)的配置中需要添加特定的选项。
module.exports = { //...其他配置 module: { rules: [ { test: /\.css$/, use: [ 'style - loader', { loader: 'css-loader', options: { importLoaders: 1, modules: false } } ] } ] }, //...其他配置 };
- 对于
style-loader
,它本身就支持HMR。在css-loader
的配置中,importLoaders
指定了在@import
的CSS文件中应用多少个加载器,modules
设置为false
表示不使用CSS模块(如果你的项目不需要的话)。
- 当修改CSS文件时,为了让HMR能够正确处理CSS模块,在CSS加载器(
- 基础配置修改
- 安装必要的插件和依赖(如果需要)
-
在代码中使用HMR(以JavaScript模块为例)
- 当一个模块被更新时,你需要在模块中编写一些代码来处理更新。例如,在一个JavaScript模块中:
if (module.hot) { module.hot.accept('./anotherModule.js', function() { // 当`./anotherModule.js`模块更新时,这里的代码会执行 console.log('Another module has been updated.'); }); }
- 这里
module.hot.accept
用于监听指定模块的更新。当./anotherModule.js
模块被更新时,回调函数中的代码会被执行,你可以在这个回调函数中执行一些操作来更新应用的状态,比如重新渲染组件等。
-
注意事项
- 兼容性问题
- HMR并不是在所有浏览器和环境中都能完美工作。一些旧版本的浏览器可能不支持某些HMR功能。在实际开发中,需要考虑到目标浏览器的兼容性。
- 状态管理的复杂性
- 当使用HMR更新模块时,应用的状态管理可能会变得复杂。如果模块中有状态相关的代码(如Vuex或Redux中的状态),需要小心处理状态的更新和持久化,以避免出现意外的行为。例如,在更新一个包含状态管理的模块后,可能需要重新初始化或者合并状态,以确保应用的正常运行。
- 兼容性问题
小结
webpack`配置文件实例
const path = require('path');
module.exports = {
// 入口文件
entry: './src/index.js',
// 输出配置
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
// 模块处理规则
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
},
// 开发服务器配置(用于开发环境)
devServer: {
contentBase: path.join(__dirname, 'dist'),
port: 8080,
hot: true
},
// 插件配置
plugins: [
new webpack.HotModuleReplacementPlugin()
]
};
应用优化总结
- 代码分割优化
- 动态导入:对于大型项目,可以使用动态导入来分割代码。例如,如果有一些仅在特定用户操作或页面中才需要的模块,可以使用
import()
语法。假设在一个电商应用中,有一个复杂的订单管理模块,仅在用户进入订单页面时才需要加载。
- 动态导入:对于大型项目,可以使用动态导入来分割代码。例如,如果有一些仅在特定用户操作或页面中才需要的模块,可以使用
const loadOrderModule = () => import('./orderModule.js');
这样,在初始加载时不会加载订单模块的代码,减小了初始包的大小。
- SplitChunks 插件:在webpack
配置的optimization
部分配置splitChunks
。它可以自动将公共模块和第三方库提取到单独的文件中,避免重复打包。
optimization: {
splitChunks: {
chunks: 'all'
}
}
例如,多个页面都使用了lodash
库,通过splitChunks
,lodash
会被提取到一个单独的文件中,多个页面可以共享这个文件,减少了总的打包体积。
- 优化加载器(Loader)
- 缓存加载器结果:对于一些计算成本高的加载器(如
babel - loader
),可以启用缓存。在babel - loader
的配置中添加cacheDirectory: true
。这样,在重复构建时,如果模块没有变化,就可以直接使用缓存结果,加快构建速度。
- 缓存加载器结果:对于一些计算成本高的加载器(如
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
cacheDirectory: true
}
}
}
- 优化图片加载器:如果项目中有大量图片,可以使用
image - webpack - loader
对图片进行压缩和优化。同时,根据图片的类型和使用场景选择合适的加载器和输出格式。例如,对于小图标可以使用url - loader
将其转换为 base64 编码嵌入到 JavaScript 或 CSS 文件中,减少额外的网络请求。
{
test: /\.(png|jpg|jpeg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192, // 小于8kb的图片转换为base64
name: 'images/[name].[ext]'
}
},
'image-webpack-loader'
]
}
- 优化插件(Plugin)使用
- 压缩插件:在生产环境中,使用
terse - webpack - plugin
等压缩插件来压缩 JavaScript 代码。这可以显著减小打包后的文件大小,提高加载速度。
- 压缩插件:在生产环境中,使用
const TerserPlugin = require('terse-webpack-plugin');
module.exports = {
//...其他配置
optimization: {
minimize: true,
minimizer: [new TerserPlugin()]
}
};
- 去除无用代码:使用
webpack - bundle - analyzer
插件可以分析打包后的文件,查看哪些模块占用了大量空间,是否有未使用的代码。如果发现有未使用的第三方库或代码,可以将其移除或优化。例如,通过分析发现项目中引入了一个完整的 UI 库,但只使用了其中几个组件,那么可以考虑只引入需要的组件,减少打包体积。
- 优化开发环境和生产环境配置差异
- 开发环境注重速度和调试便利性:在开发环境中,开启
source - map
可以方便调试。同时,可以使用eval - source - map
模式,它在构建速度和调试便利性之间有较好的平衡。例如:
- 开发环境注重速度和调试便利性:在开发环境中,开启
devtool: 'eval-source-map'
- 生产环境注重性能和文件大小:在生产环境中,除了上述提到的压缩代码、优化图片等操作,还可以设置
mode: 'production'
,webpack
会自动启用一些优化,如代码压缩、去除调试信息等。同时,可以关闭一些不必要的开发相关的功能,如devServer
等。
module.exports = {
mode: 'production',
//...其他生产环境特定配置
};
module.exports = {
mode: 'development',
//...其他开发环境特定配置
};
这样可以根据不同的环境需求,有针对性地优化webpack
配置,提高开发效率和应用性能。
- 优化模块解析路径
- 配置
resolve
选项:在webpack
配置中,可以通过resolve
选项来优化模块的解析路径。例如,可以设置alias
来简化模块的引用路径。假设项目中有一个src
目录,里面有很多子目录和模块,可以设置alias
如下:
- 配置
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
这样,在代码中可以使用import something from '@/path/to/module'
来代替相对复杂的import something from '../../../path/to/module'
,提高了代码的可读性和可维护性,同时在模块解析时也可能会更快。
{
test: /\.js$/,
include: [path.resolve(__dirname, 'src')], // 只处理src目录下的js文件
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
同时,通过include
或exclude
选项更精确地指定loader
处理的文件范围,避免不必要的文件处理,提高构建效率。
{
test: /\.js$/,
exclude: /node_modules/, // 不处理node_modules目录下的文件
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
{
test: /\.js$/,
include: [path.resolve(__dirname, 'src')], // 只处理src目录下的js文件
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
这样可以避免处理node_modules
中的大量文件(通常这些文件已经是编译好的),同时只处理项目中自己编写的源代码,提高加载器的处理效率。
{
test: /\.(js|jsx)$/,
include: [path.resolve(__dirname, 'src')],
use: [
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset - env', '@babel/preset - react']
}
}
]
}
如果是 React 项目,还可以这样配置babel - loader
来同时处理js
和jsx
文件,确保正确的代码转换和优化。
{
test: /\.(js|jsx)$/,
include: [path.resolve(__dirname, 'src')],
use: [
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset - env', '@babel/preset - react'],
plugins: ['react - hot - loader/babel']
}
}
]
}
并且可以添加react - hot - loader/babel
插件来实现 React 组件的热更新,提高开发效率。
{
test: /\.(scss|sass)$/,
use: [
'style - loader',
'css - loader',
'sass - loader'
]
}
对于样式文件(如scss
或sass
),合理配置加载器,确保样式的正确处理和应用,同时可以考虑添加postcss - loader
来添加一些样式的后处理功能,如自动添加浏览器前缀等。
{
test: /\.(scss|sass)$/,
use: [
'style - loader',
'css - loader',
'sass - loader',
'postcss - loader'
]
}
{
test: /\.(css)$/,
use: [
'style - loader',
'css - loader',
'postcss - loader'
]
}
对于普通css
文件也可以添加postcss - loader
,通过配置postcss.config.js
文件来定义具体的后处理规则。
module.exports = {
plugins: [
require('autoprefixer')
]
};
例如,在postcss.config.js
中添加autoprefixer
来自动为css
样式添加浏览器前缀,提高样式的兼容性。
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file - loader'
]
}
对于字体文件,使用file - loader
进行处理,确保字体在项目中的正确加载和应用。
{
test: /\.(svg)$/,
use: [
'file - loader'
]
}
对于svg
文件,同样可以使用file - loader
,或者根据具体情况使用svg - inline - loader
等其他专门处理svg
的加载器,比如如果要将svg
内联到html
或js
文件中,可以使用svg - inline - loader
。
{
test: /\.(html)$/,
use: [
'html - loader'
]
}
对于html
文件,如果需要在webpack
中进行处理(如在js
中导入html
模板),可以使用html - loader
。
{
test: /\.(html)$/,
use: [
{
loader: 'html - loader',
options: {
minimize: true
}
}
]
}
并且可以添加minimize
选项来对html
文件进行压缩,减少文件大小,提高加载速度。
{
test: /\.(pug)$/,
use: [
'pug - loader'
]
}
如果项目中使用pug
模板,可以使用pug - loader
进行处理,将pug
模板转换为html
。
{
test: /\.(jade)$/,
use: [
'jade - loader'
]
}
对于jade
模板(pug
的旧称),使用jade - loader
进行处理,确保模板的正确转换和应用。
{
test: /\.(xml)$/,
use: [
'xml - loader'
]
}
如果项目中有xml
文件需要处理,可以使用xml - loader
。
{
test: /\.(json)$/,
use: [
'json - loader'
]
}
对于json
文件,可以使用json - loader
,不过在webpack
4及以上版本,json
文件默认可以被处理,无需额外配置json - loader
。
{
test: /\.(yaml|yml)$/,
use: [
'yaml - loader'
]
}
如果项目中使用yaml
或yml
文件,可以使用yaml - loader
进行处理。
{
test: /\.(md)$/,
use: [
'markdown - loader'
]
}
对于markdown
文件,如果需要在项目中处理(如将markdown
文件渲染为html
),可以使用markdown - loader
。
{
test: /\.(txt)$/,
use: [
'raw - loader'
]
}
对于txt
文件,可以使用raw - loader
将其内容作为字符串加载到js
文件中。
{
test: /\.(csv)$/,
use: [
'csv - loader'
]
}
如果项目中有csv
文件需要处理,可以使用csv - loader
。
{
test: /\.(ts|tsx)$/,
include: [path.resolve(__dirname, 'src')],
use: [
'ts - loader'
]
}
对于TypeScript
项目,使用ts - loader
来处理ts
和tsx
文件,确保TypeScript
代码的正确编译和优化。
{
test: /\.(ts|tsx)$/,
include: [path.resolve(__dirname, 'src')],
use: [
{
loader: 'ts - loader',
options: {
transpileOnly: true
}
}
]
}
并且可以添加transpileOnly
选项,它可以加快ts - loader
的处理速度,不过需要注意的是,这种模式下不会进行类型检查,所以可以在项目中另外配置tsc
进行类型检查。
{
test: /\.(glsl|vert|frag)$/,
use: [
'webpack - glsl - loader'
]
}
如果项目涉及到WebGL
相关的glsl
、vert
、frag
等文件,可以使用webpack - glsl - loader
进行处理。
{
test: /\.(wav|mp3|midi|ogg)$/,
use: [
'file - loader'
]
}
对于音频文件,可以使用file - loader
进行处理,确保音频文件在项目中的正确加载和应用。
{
test: /\.(mp4|webm|ogg)$/,
use: [
'file - loader'
]
}
对于视频文件,同样可以使用file - loader
进行处理。
{
test: /\.(webp)$/,
use: [
'file - loader'
]
}
对于webp
格式的图像文件,使用file - loader
进行处理。
{
test: /\.(ico)$/,
use: [
'file - loader'
]
}
对于ico
图标文件,使用file - loader
进行处理。
{
test: /\.(swf)$/,
use: [
'file - loader'
]
}
对于swf
文件,可以使用file - loader
进行处理。
{
test: /\.(map)$/,
use: [
'file - loader'
]
}
对于map
文件(如source - map
文件),可以使用file - loader
进行处理,不过需要注意根据具体情况决定是否需要在生产环境中包含这些文件,通常在生产环境中可以不包含source - map
文件以减小文件大小。
{
test: /\.(dat)$/,
use: [
'file - loader'
]
}
对于dat
文件等其他特殊类型的文件,如果需要在项目中处理,可以根据文件的性质选择合适的加载器或自行编写加载器。
{
test: /\.(node)$/,
use: [
'node - loader'
]
}
对于node
模块文件(如果需要在webpack
构建的前端项目中使用),可以使用node - loader
进行处理,不过需要谨慎使用,因为node
模块可能会引入一些与前端环境不兼容的代码。
{
test: /\.(asar)$/,
use: [
'asar - loader'
]
}
如果项目涉及到asar
文件(如在Electron
应用中),可以使用asar - loader
进行处理。
{
test: /\.(asar.unpacked)$/,
use: [
'asar - unpacked - loader'
]
}
对于asar.unpacked
文件(同样在Electron
应用相关场景