GitHub Demo 地址
在线预览
Vue - 项目编译速度、性能优化、打包体积优化
- 序
- 一、编译速度优化
- 1、使用缓存
- 1.1、缓存插件 - HardSourceWebpackPlugin
- 1.2、`webpack5` 配置cache
- 1.3、cache-loader 插件
- 2、合理使用source-map
- 3、多线程打包
- 3.1、thread-loader
- 3.2、parallel-webpack
- 3.3、HappyPack
- 4、开启 热更新 / 热替换
- 5、使用Dll预编译(DllPlugin / autodll-webpack-plugin)
- 6、优化Loader
- 7、exclude & include
- 8、构建区分环境
- 9、提升webpack版本
- 二、打包体积优化
- 分析插件 `webpack-bundle-analyzer `
- 1、三方库按需引用
- 1.1、element-ui按需引用
- 1.2、echart按需引用
- 1.3、lodash按需引用
- 2、生产环境去除console.log
- 2.1、 `babel-plugin-transform-remove-console`
- 2.2、`terser-webpack-plugin`
- 2.3、 `uglifyjs-webpack-plugin`
- 3、合理使用source-map
- 4、Code Splitting(代码拆分)
- 5、Tree shaking技术
- 6、大模块cdn引入
- 7、压缩css、js、图片资源,删除无用代码
- 8、gzip压缩
- 三、性能、用户体验优化
- 3.1、路由懒加载
- 3.2、vue-lazyload插件,图片懒加载
- 3.3、使用缓存
- 3.4、watch & computed
- 3.5、防抖 & 节流
- 3.6、v-if与v-show的使用
webpack中文文档
文章 :
项目优化的方法以及优缺点
HardSourceWebpackPlugin配置和其他优化方案
Vue 项目性能优化方案分享
vue-cli 项目可以做的优化
Vue首评加载速度及白屏时间优化详解
webpack5 和 webpack4 的区别有哪些 ?
webpack5升级指南(附打包性能优化大全)
序
个人觉得优化方案选择合适的就好,没必要全上,可以把更多的心思花在业务代码的优化上。
项目环境基于webpack4x,可能有些已经过时了,自行判断
下面的方法只有部分使用,更多的是一种记录!!!
webpack版本查看
npm list webpack
或者可以找node_modules目录下面的webpack/package.json文件,查看文件中的version,如下图
一、编译速度优化
优化编译的文章:
vue项目编译、运行过慢如何提升编译(编译包括运行编译与打包编译)速度
开发过程中vue项目保存编译慢优化方法
webpack5 配置多线程和缓存加快构建速度
1、使用缓存
- 在webpack3中为了提高构建速度,我们往往会用到 DllPlugin 和 DllReferencePlugin 插件,但是配置复杂,更新文件还需要手动重新生成dll,比较繁琐。还有一种autodll-webpack-plugin会好一些
- 在webpack4之后可以使用
HardSourceWebpackPlugin
,它通过在磁盘中设置缓存来提升编译加载速度,第一次正常编译并缓存,第二次有缓存后能减少80%-90%的时间。需要安装使用 - webpack5内置的
cache
配置。
1.1、缓存插件 - HardSourceWebpackPlugin
hard-source-webpack-plugin 插件GitHub地址
HardSourceWebpackPlugin是 webpack 的插件,为模块提供中间缓存步骤。为了查看结果,您需要使用此插件运行 webpack 两次:第一次构建将花费正常的时间。第二个构建将明显更快。
速度大概有80%-90%的提升
tips:
默认缓存位置:nodemodules/.cache,需要清除缓存的话可删除后再编译一次
HardSourceWebpackPlugin 和 speed-measure-webpack-plugin 不能一起使用 !!!
安装 hard-source-webpack-plugin
npm install --save-dev hard-source-webpack-plugin
配置
// webpack.config.js 或 vue.config.js
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
const path = require('path')
module.exports = {
// ...
plugins: [
// 默认缓存位置:node_modules/.cache路径下
new HardSourceWebpackPlugin()
// new HardSourceWebpackPlugin({
// cacheDirectory: path.resolve(__dirname, 'node_modules/.cache/hard_source_cache')
// })
]
}
1.2、webpack5
配置cache
webpack5
内置了 cache 缓存机制。直接配置即可。
cache 会在开发模式下被设置成 type: memory 而且会在生产模式把cache 给禁用掉。
// webpack.config.js 或 vue.config.js
const path = require('path')
module.exports = {
transpileDependencies: true,
configureWebpack: {
cache: {
type: 'filesystem',
// cacheDirectory: path.resolve(__dirname, '.webpack_cache') // 设置缓存目录(根路径下)
cacheDirectory: path.join(__dirname, 'node_modules/.cache/webpack_cache') // 设置缓存目录(node_modules/.cache路径下)
}
}
}
1.3、cache-loader 插件
和第一种类似,这里不展开说
hard-source-webpack-plugin 安装前后对比
第一次编译 36340ms
第二次编译 3760ms
2、合理使用source-map
productionSourceMap的作用在于定位问题,打包时会生成.map文件,在生产环境就可以在浏览器查看到输出的信息具体是在哪一行,但相应的包的体积也会变大,也会影响构建速度,将其设置为false则不生成.map文件
把productionSourceMap 置为false,既可以减少包大小,也可以加密源码
module.exports = {
productionSourceMap: false
}
3、多线程打包
3.1、thread-loader
文章
【Webpack 性能优化系列(9) - 多进程打包】极大的提升项目打包构建速度
thread-loader 是一个 Webpack 的 loader,它可以将一些开销较大的工作放到 worker 池中,并在 worker 池中执行,以提高构建速度。
例如,在构建 Vue.js 项目时,由于需要编译 .vue 单文件组件、解析模板等操作,构建的时间可能会很长,特别是对于大型项目来说。可以使用 thread-loader 将这些操作放到 worker 池中执行,从而显著缩短构建时间。
使用时,需将此 loader 放置在其他 loader 之前。放置在此 loader 之后的 loader 会在一个独立的 worker 池中运行。
在 worker 池中运行的 loader 是受到限制的。例如:
这些 loader 不能生成新的文件。
这些 loader 不能使用自定义的 loader API(也就是说,不能通过插件来自定义)。
这些 loader 无法获取 webpack 的配置。
每个 worker 都是一个独立的 node.js 进程,其开销大约为 600ms 左右。同时会限制跨进程的数据交换。
请仅在耗时的操作中使用此 loader!
注意,thread-loader 并不是适用于所有场景的,它只对一些开销较大的任务有效。如果任务本身就非常快速并且非常简单,则使用 thread-loader 可能会比直接在主线程中执行更慢。
安装
npm install thread-loader --save-dev
下面是 thread-loader 的使用示例(将会被应用到所有 .js 和.vue 文件中,除了 node_modules 中的文件。):
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.js$/,
include: path.resolve('src'),
exclude: /node_modules/,
use: [
'thread-loader', // 将后续 loader 放在 worker 池中执行
// 耗时的 loader (例如 babel-loader)
'babel-loader'
]
}
]
}
}
vue.config.js
module.exports = {
// ...
configureWebpack: {
module: {
rules: [
{
test: /\.js$/,
include: path.resolve('src'),
exclude: /node_modules/,
use: [
'thread-loader', // 将后续 loader 放在 worker 池中执行
// 耗时的 loader (例如 babel-loader)
'babel-loader'
]
}
]
}
}
}
options
use: [
{
loader: "thread-loader",
// 有同样配置的 loader 会共享一个 worker 池
options: {
// 产生的 worker 的数量,默认是 (cpu 核心数 - 1),或者,
// 在 require('os').cpus() 是 undefined 时回退至 1
workers: 2,
// 一个 worker 进程中并行执行工作的数量
// 默认为 20
workerParallelJobs: 50,
// 额外的 node.js 参数
workerNodeArgs: ['--max-old-space-size=1024'],
// 允许重新生成一个僵死的 work 池
// 这个过程会降低整体编译速度
// 并且开发环境应该设置为 false
poolRespawn: false,
// 闲置时定时删除 worker 进程
// 默认为 500(ms)
// 可以设置为无穷大,这样在监视模式(--watch)下可以保持 worker 持续存在
poolTimeout: 2000,
// 池分配给 worker 的工作数量
// 默认为 200
// 降低这个数值会降低总体的效率,但是会提升工作分布更均一
poolParallelJobs: 50,
// 池的名称
// 可以修改名称来创建其余选项都一样的池
name: "my-pool"
},
},
// 耗时的 loader(例如 babel-loader)
];
3.2、parallel-webpack
parallel-webpack 是一个基于 Webpack 的插件,它能够将构建任务分解为多个子进程并行运行,以此来提高构建速度。与其他类似的插件相比,parallel-webpack 最大的特点是支持异步和同步模块的并行处理,而不仅仅局限于 Loader 和 Plugin。
npm install --save-dev parallel-webpack
// webpack.config.js
const ParallelWebpack = require('parallel-webpack').Plugin
module.exports = {
// ...
plugins: [
new ParallelWebpack({
// 配置选项
}),
/* 其他插件 */
],
// ...
}
以上配置会启用 parallel-webpack 插件,从而实现多进程打包。您可以根据自己的需求在配置选项中对插件进行配置。
需要注意的是,使用多线程打包时可能会增加系统的 CPU 和内存消耗,因此建议在配合与机器性能相匹配的线程数,并且测试多线程打包效果时,应该同时观察构建时间和构建过程中的 CPU 和内存消耗情况。
3.3、HappyPack
HappyPack 是一个用于将 Webpack 进行多线程编译的工具,它能够将一个复杂的 Loader 或者 Plugin 任务分解成多个子进程并行执行,以此来加快构建速度。
npm install happypack --save-dev
const HappyPack = require('happypack');
module.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'happypack/loader?id=js'
}
]
},
plugins: [
new HappyPack({
id: 'js',
threads: 4,
loaders: ['babel-loader']
})
],
// ...
};
在上述代码中,我们定义了一个名为 js 的 HappyPack 实例,使用了 4 个线程并行编译 babel-loader 处理的 JavaScript 文件。
注:2和3插件相关的代码没有经过实际使用测试效果!!!
4、开启 热更新 / 热替换
webpack 中文文档 HMR 加载样式
webpack 热加载 使用module.hot.accept和不使用的区别
模块热替换(HMR)
- 热更新(Hot Update):在代码文件被修改后,自动编译出新的代码,并将新代码注入到浏览器运行的 JavaScript 中。热更新并没有重新加载页面,而是更新了部分改动的内容,保留了页面的状态信息,因此可以保持用户的操作状态。
- 热重载(Hot Reload):在代码文件被修改后,以最快的速度重新编译、打包,并刷新浏览器窗口。相比于热更新,热重载不仅更新了 JavaScript 代码,还重新渲染了整个页面,因此会有一短暂的闪屏,更适用于 UI 相关的修改。
- 模块热替换(Hot Module Replacement):类似于热更新,但是只替换变化的部分,不会重载整个模块或者应用程序,因此速度更快,体验更好。模块热替换不仅适用于 JavaScript 模块,还可以用于 CSS、图片等资源的替换。
HotModuleReplacementPlugin
是 Webpack 提供的一个插件,用于启用模块热替换(HMR)功能。HMR 是一种开发模式,它可以在不刷新整个页面的情况下,只更新修改过的部分,提高开发效率。
注:
只对js生效
修改css的时候,页面不会自动刷新,只会更新我们修改后的css
const webpack = require('webpack');
module.exports = {
// ...
mode: 'development',
devtool: 'eval-source-map', // 使用 source-map 方便调试
devServer: {
port: 8080,
hot: true, // 开启 HMR 功能
open: true // 自动打开浏览器
},
plugins: [
new webpack.HotModuleReplacementPlugin() // 启用 HMR 功能
]
}
其中,hot 选项设置为 true,表示开启 HMR 功能;plugins 中添加了 HotModuleReplacementPlugin 插件,以启用 HMR 功能。
在js 代码中添加如下代码:
// js 代码中添加 HMR 相关的代码
if (module.hot) {
// 如果支持HMR,告诉 Webpack,当前模块更新不用刷新
module.hot.accept()
// 如果支持HMR,告诉 Webpack,当前模块更新时一定要刷新
// module.hot.decline()
}
通过上述配置,就可以开启 Webpack 的模块热替换功能,使得在开发过程中能够实时预览修改的效果,提高开发效率。需要注意的是,HMR 功能只适用于开发环境,在生产环境中不应该使用。
5、使用Dll预编译(DllPlugin / autodll-webpack-plugin)
DllPlugin是webpack的一个插件,可以将一些不常变化的代码或三方库打包成一个单独的动态链接库(DLL 文件),这样在每次编译时就不需要重新打包这些代码,从而提高编译速度。
使用 DllPlugin 可以将一些稳定的、难以修改的库,如 vue、vue-router、vuex 等预先编译,并通过构建工具在项目编译时直接使用预先编译的 dll 库文件,从而提升了打包速度。
推荐使用 autodll-webpack-plugin
更推荐直接使用 hard-source-webpack-plugin
npm install autodll-webpack-plugin --save-dev
vue.config.js
const AutoDllPlugin = require('autodll-webpack-plugin');
module.exports = {
// ...
plugins: [
new AutoDllPlugin({
inject: true,
debug: true,
filename: '[name]_[hash].js',
path: './dll',
entry: {
vendor: [
'vue',
'vue-router',
'vuex'
]
}
})
]
}
以上配置中,我们使用了 AutoDllPlugin 插件,并传入了以下参数:
- inject: true 表示自动将生成的 DLL 引用到 HTML 中;
- debug: true 表示在 webpack 日志中输出调试信息;
- filename: ‘[name]_[hash].js’ 表示生成的 DLL 文件名,其中 [name] 对应 entry 中的 key,[hash] 表示文件 hash 值;
- path: ‘./dll’ 表示生成的 DLL 文件存放路径;
- entry 表示要编译成 DLL 的第三方库列表。
以上配置将会生成一个名为 vendor_XXXX.js 的 DLL 文件,其中 XXXX 表示文件哈希值。该文件包含了 vue、vue-router 和 vuex 这三个库的代码。
在 HTML 中添加以下代码来引用生成的 DLL 文件, 其中,XXXX 应替换为生成的 DLL 文件的哈希值。
<script src="./dll/vendor_XXXX.js"></script>
如果使用 webpack-dev-server 进行开发,需要在 DevServer 配置中添加以下内容:
这样,在启动 DevServer 后,autodll-webpack-plugin 会自动将生成的 DLL 引入到 HTML 中,从而提升构建速度。
const AutoDllPlugin = require('autodll-webpack-plugin');
module.exports = {
// ...
devServer: {
// ...
before(app, server) {
AutoDllPlugin({
debug: true,
filename: '[name]_[hash].js',
path: './dll',
entry: {
vendor: [
'vue',
'vue-router',
'vuex'
]
}
}).apply(new webpack.Compiler());
}
}
}
6、优化Loader
Loader是webpack的一个重要组成部分,它对文件进行处理的过程中可能会影响编译速度。优化Loader的配置可以大大提高编译速度。
-
减少 Loader 数量:尽可能地避免重复的 Loader 使用,或者在必要的情况下尽可能合并多个 Loader,从而减少需要编译的 Loader 数量。
-
缓存 Loader 的编译结果:使用 Webpack 的 cache-loader 插件可以将 Loader 的编译结果缓存到硬盘中,在不改变源文件的基础上可以大大缩短 Loader 的重新编译时间。
-
启用多线程编译:使用多线程编译工具可以将 Webpack 构建任务分解成多个子进程并行执行,这样可以充分利用 CPU 多核并行计算的优势,加快编译速度。可以采用 happyPack 或 thread-loader 等工具来实现。
-
避免使用正则表达式作为 Loader 规则:在定义 Loader 规则时,尽量避免使用正则表达式做为规则匹配方式,这样会导致构建过程变慢。如果必须使用,也可以尝试限制正则表达式的匹配范围,提高匹配效率。
7、exclude & include
在 Vue 项目中使用 exclude 和 include 可以帮助提升编译速度,这是因为它们可以减少不必要的文件检查和编译。
exclude 和 include 是通过 webpack 的 module.rules 配置项来设置的。其中,exclude 表示排除某些目录或文件不被编译,而 include 表示包含某些目录或文件进行编译。
下面以 Vue 项目的 Babel 编译为例,介绍如何使用 exclude 和 include。
安装依赖
npm install --save-dev babel-loader @babel/core @babel/preset-env
其中,babel-loader 是用于编译 JavaScript 代码的工具,@babel/core 是 Babel 核心库,@babel/preset-env 是一种预设包,可以根据目标环境自动选择转换插件。
配置 webpack.config.js
以下配置中,exclude: /node_modules/ 表示排除 node_modules 目录下的 JavaScript 文件不被编译,以提高编译速度。
module.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
}
有时候,如果我们只需要编译项目中某个目录下的 JavaScript 文件,可以使用 include 来指定相应的目录。
例如,我们只需要编译 src/components 目录下的 JavaScript 文件,可以将配置改为:
module.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
include: path.resolve(__dirname, 'src/components'),
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
}
以上配置中,include: path.resolve(__dirname, ‘src/components’) 表示只包含 src/components 目录下的 JavaScript 文件进行编译,以减少不必要的文件检查和编译。
8、构建区分环境
比如,在开发环境中,我们通常需要启用 source map 和热更新等功能,而在生产环境中,我们则需要启用代码压缩和 Tree Shaking 等优化技术。因此,我们可以在 Webpack 的配置文件中使用以下代码来根据环境配置不同的插件和优化:
const isProd = process.env.NODE_ENV === 'production';
module.exports = {
// ...
plugins: [
isProd ? new UglifyJSPlugin() : null,
// ...
].filter(Boolean),
};
9、提升webpack版本
升级 Webpack 版本是提高构建速度的一个重要方式,每个新版本都会带来新的优化和性能改进。
更新 Webpack 到最新版本:
首先需要确定当前使用的 Webpack 版本并检查是否需要更新,可以通过 npm 命令安装最新版本的 Webpack:
npm install webpack@latest --save-dev
注意事项:
- 检查 Webpack 相关的插件和 loader 的版本:
升级 Webpack 时,应该同时检查相关的插件和 loader 是否需要更新,需要确保其与 Webpack 的版本兼容。
- 移除废弃的插件和 loader:
升级可能有些插件和loader 已经随着升级废弃了,因此需要移除对应的插件和 loader。
二、打包体积优化
分析插件 webpack-bundle-analyzer
首先推荐一个分析插件,使用这个插件可以在编译和打包时打开一个分析页面,可以帮助我们优化打包后的文件大小和性能。
它可以生成一个可视化的报告,展示各个模块的大小、依赖关系和构成。通过这个报告,我们可以清晰地了解每个模块的大小和依赖关系,从而确定哪些模块对打包结果的大小贡献较大,进而进行优化。
webpack-bundle-analyzer 插件配置
安装 webpack-bundle-analyzer
插件
npm install --save-dev webpack-bundle-analyzer
在 Webpack 配置文件中使用插件
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
// ...
plugins: [
new BundleAnalyzerPlugin({
openAnalyzer: true
}),
]
}
vue cli 生成的项目下面的就不需要了,直接编译打包会打开一个分析页面
运行 Webpack 构建
运行 Webpack 的构建命令,例如:
webpack --config webpack.config.js
查看分析报告
上述命令执行完成之后,会生成一个报告页面,端口号会打印在控制台中,例如:
Webpack Bundle Analyzer is started at http://127.0.0.1:8888
在浏览器中打开相应的地址,即可查看分析报告。
需要注意的是,如果你的构建配置是基于 Vue CLI 或者 Create React App 等工具生成的,则无需手动添加 webpack-bundle-analyzer 插件,这些工具已经默认包含了该插件。可以直接编译打包或者通过相应的命令来查看分析报告,例如:
vue-cli-service build --mode production --report
1、三方库按需引用
1.1、element-ui按需引用
完整组件列表以 components.json(引用版本的)为准
- 步骤1:安装 babel-plugin-component 插件:
npm i babel-plugin-component -D
- 步骤2:在
babel.config.js
或.babelrc
文件中配置:
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
['@babel/preset-env', { 'modules': false }]
],
plugins: [
[
'component',
{
libraryName: 'element-ui',
styleLibraryName: 'theme-chalk'
}
]
]
}
- 步骤3:创建element.js
// element-ui 按需引入
// 注:完整组件列表以 components.json (引用版本的)为准
// https://github.com/ElemeFE/element/blob/master/components.json
import {
Pagination,
Dialog,
Autocomplete,
Dropdown,
DropdownMenu,
DropdownItem,
Menu,
Submenu,
MenuItem,
MenuItemGroup,
Input,
InputNumber,
Radio,
RadioGroup,
RadioButton,
Checkbox,
CheckboxButton,
CheckboxGroup,
Switch,
Select,
Option,
OptionGroup,
Button,
ButtonGroup,
Table,
TableColumn,
DatePicker,
TimeSelect,
TimePicker,
Popover,
Tooltip,
Breadcrumb,
BreadcrumbItem,
Form,
FormItem,
Tabs,
TabPane,
Tag,
Tree,
Alert,
Slider,
Icon,
Row,
Col,
Upload,
Progress,
Spinner,
Badge,
Card,
Rate,
Steps,
Step,
Carousel,
Scrollbar,
CarouselItem,
Collapse,
CollapseItem,
Cascader,
ColorPicker,
Transfer,
Container,
Header,
Aside,
Main,
Footer,
Timeline,
TimelineItem,
Link,
Divider,
Image,
Calendar,
Backtop,
PageHeader,
CascaderPanel,
Avatar,
Drawer,
Popconfirm,
// 单独设置
Loading,
MessageBox,
Message,
Notification
} from 'element-ui'
const components = [
Pagination,
Dialog,
Autocomplete,
Dropdown,
DropdownMenu,
DropdownItem,
Menu,
Submenu,
MenuItem,
MenuItemGroup,
Input,
InputNumber,
Radio,
RadioGroup,
RadioButton,
Checkbox,
CheckboxButton,
CheckboxGroup,
Switch,
Select,
Option,
OptionGroup,
Button,
ButtonGroup,
Table,
TableColumn,
DatePicker,
TimeSelect,
TimePicker,
Popover,
Tooltip,
Breadcrumb,
BreadcrumbItem,
Form,
FormItem,
Tabs,
TabPane,
Tag,
Tree,
Alert,
Slider,
Icon,
Row,
Col,
Upload,
Progress,
Spinner,
Badge,
Card,
Rate,
Steps,
Step,
Carousel,
Scrollbar,
CarouselItem,
Collapse,
CollapseItem,
Cascader,
ColorPicker,
Transfer,
Container,
Header,
Aside,
Main,
Footer,
Timeline,
TimelineItem,
Link,
Divider,
Image,
Calendar,
Backtop,
PageHeader,
CascaderPanel,
Avatar,
Drawer,
Popconfirm
]
const element = {
install: (Vue) => {
components.forEach(component => {
Vue.component(component.name, component)
})
// 单独设置
Vue.use(Loading.directive)
Vue.prototype.$loading = Loading.service
Vue.prototype.$msgbox = MessageBox
Vue.prototype.$alert = MessageBox.alert
Vue.prototype.$confirm = MessageBox.confirm
Vue.prototype.$prompt = MessageBox.prompt
Vue.prototype.$notify = Notification
Vue.prototype.$message = Message
}
}
export default element
- 步骤4:main.js引用(引用方式从引用官方组件换成引用自定义的element.js)
// import ElementUI from 'element-ui' // 全局引用
import ElementUI from '@/utils/element' // 按需引用
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)
之后正常使用即可
1.2、echart按需引用
这里直接创建一个echart.js文件,然后在需要使用的地方引用即可,
引用方式从引用官方组件换成引用自定义的echarts.js
// import * as echarts from 'echarts' // 全量引入
import echarts from '@/utils/echarts' // 按需引入
echart.js文件
// echarts 按需引入
// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
import * as echarts from 'echarts/core'
// 引入图表类型,图表后缀都为 Chart
import { BarChart, LineChart, PieChart } from 'echarts/charts'
// 自定义类型的图表
import { CustomChart } from 'echarts/charts'
// 引入提示框,标题,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
import {
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent,
DatasetComponent,
TransformComponent,
DataZoomComponent
} from 'echarts/components'
// 标签自动布局、全局过渡动画等特性
import { LabelLayout, UniversalTransition } from 'echarts/features'
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
// import { CanvasRenderer } from 'echarts/renderers'
import { SVGRenderer } from 'echarts/renderers'
// 将以上引入的组件使用use()方法注册
echarts.use([
BarChart,
LineChart,
PieChart,
CustomChart,
// 提示框,标题,直角坐标系,数据集 等组件
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent,
DatasetComponent,
TransformComponent,
DataZoomComponent,
// 标签自动布局、全局过渡动画等特性
LabelLayout,
UniversalTransition,
// Canvas 渲染器
// CanvasRenderer
SVGRenderer
])
// 导出
export default echarts
1.3、lodash按需引用
方法一:使用’lodash-es’代替’lodash’
import { cloneDeep } from 'lodash-es' // 按需引入
import { throttle, debounce } from 'lodash-es' // 按需引入
// import lodash from 'lodash-es' // 全量引入
方法二:使用’lodash’时注意按需引用
// import cloneDeep from 'lodash/cloneDeep' // 按需引入
// import lodash from 'lodash' // 全量引入
2、生产环境去除console.log
开发环境需要调试加的控制台日志,在生产环境也打印的话,一方面容易造成数据泄漏,一方面也会占用体积
2.1、 babel-plugin-transform-remove-console
这是一个Babel插件,可以在构建期间删除所有console.*语句。
npm install --save-dev babel-plugin-transform-remove-console
然后,在babel.config.js
或.babelrc
文件中添加以下代码:
const plugins = []
// remove console.* in production
if (process.env.NODE_ENV === 'production') {
plugins.push('transform-remove-console')
// plugins.push(['transform-remove-console', { 'exclude': ['error', 'warn'] }])
}
module.exports = {
//...
plugins: plugins
}
2.2、terser-webpack-plugin
这个插件来压缩js代码的,它自带一个删除 console.log 的功能
webpack4
上需要下载安装 terser-webpack-plugin
插件,并且需要以下配置:
npm install terser-webpack-plugin --save-dev
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
// ...other config
optimization: {
minimize: !isDev,
minimizer: [
new TerserPlugin({
extractComments: false,
terserOptions: {
compress: {
pure_funcs: ['console.log']
}
}
}) ]
}
webpack5
内部本身就自带 js 压缩功能,他内置了terser-webpack-plugin
插件,我们不用再下载安装。而且在 mode=“production” 的时候会自动开启 js 压缩功能。
如果你要在开发环境使用,就用下面:
// webpack.config.js中
module.exports = {
optimization: {
usedExports: true, //只导出被使用的模块
minimize : true // 启动压缩
}
}
2.3、 uglifyjs-webpack-plugin
uglifyjs-webpack-plugin 是一个用于压缩 JavaScript 代码的 Webpack 插件,它是以 UglifyJS 为基础,用于对 JavaScript 进行代码压缩和混淆。
npm install --save-dev uglifyjs-webpack-plugin
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
// ...
optimization: {
minimize: true,
minimizer: [
new UglifyJsPlugin({
// 开启并发
parallel: true,
// 多进程压缩
uglifyOptions: {
compress: {},
output: {
comments: false,
},
},
}),
],
}
}
注:2和3插件相关的代码没有经过实际使用测试效果!!!
3、合理使用source-map
productionSourceMap的作用在于定位问题,打包时会生成.map文件,在生产环境就可以在浏览器查看到输出的信息具体是在哪一行,但相应的包的体积也会变大,也会影响构建速度,将其设置为false则不生成.map文件
把productionSourceMap 置为false,既可以减少包大小,也可以加密源码
module.exports = {
productionSourceMap: false
}
4、Code Splitting(代码拆分)
Code Splitting 是一种优化 Web 应用程序性能的技术,它将应用程序的 JavaScript 代码拆分成多个较小的部分,并根据需要按需加载,减少首页的加载时间,加快响应速度。
代码在没分包之前,整个工程的代码全部被打包到一个main.js里面,那么在访问某一个页面时,必然就会造成资源的浪费,和延长页面的加载时间。
代码分包之后,各个模块分开进行打包,main.js被拆分之后变的体积很小,页面在进行加载的时候,也会按需进行加载。
作用:
-
加速页面加载
当应用程序的 JavaScript 代码被拆分为多个较小的部分并按需加载时,可以减少页面加载时间并加快网站的响应速度。 -
减少资源浪费
对于大型的 Web 应用程序,往往包含了大量的 JavaScript 代码,这些代码不会在每个页面中都被用到。通过 Code Splitting 技术,可以将不必要的代码延迟加载,从而减少资源浪费。 -
提高用户体验
在使用 Code Splitting 技术时,用户只需要加载实际需要的代码,而不是整个应用程序的所有代码。这样可以提高页面响应速度和用户的体验,同时降低应用程序的加载时间和资源利用率。
我这里把
elementUI
,echarts
,xlsx
,和项目中使用的components单独拆分成文件,如chunk-echarts.js,chunk-elementUI.js等,其他的按配置的规则分配到不同的chunk js文件
vue.config.js
module.exports = {
// ...
chainWebpack(config) {
config
.when(process.env.NODE_ENV !== 'development',
config => {
config
.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial' // only package third parties that are initially dependent
},
elementUI: {
name: 'chunk-elementUI', // split elementUI into a single package
priority: 25, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
},
echarts: {
name: 'chunk-echarts', // split echarts into a single package
priority: 30, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?echarts(.*)/ // in order to adapt to cnpm
},
xlsx: {
name: 'chunk-xlsx', // split xlsx into a single package
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/](xlsx|file-saver)[\\/]/ // in order to adapt to cnpm
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true
}
}
})
}
)
}
}
5、Tree shaking技术
webpack 中文网 Tree Shaking
前端体验优化tree-shaking
1分钟了解 Tree-shaking
浅析 Tree Shaking
使用Tree shaking技术,剔除无用的代码,减小打包后文件大小,提高页面加载速度。
Tree Shaking 是一种用于清除 JavaScript 中未被使用代码(dead-code)的技术。它依赖于 ES6 模块语法中的静态结构,并通过识别和移除项目中没有使用到的部分来减少 bundle 的体积,从而提高应用程序的性能。
在 Webpack 中启用 Tree Shaking 需要做如下配置:
使用 ES6 模块语法:
Tree Shaking 依赖于 ES6 模块语法的静态结构,因此需要确保在项目中使用 ES6 模块语法,并避免使用 CommonJS / AMD 等非静态的模块语法。
在 Webpack 的配置文件中设置 mode:
odule.exports = {
// ...
mode: 'production',
optimization: {
usedExports: true, // 开启 Tree shaking
},
};
在 package.json 文件中设置 sideEffects 属性:
{
"name": "example",
"sideEffects": [
"*.css",
"*.scss",
"*.jpg",
]
}
在 package.json 文件中添加 sideEffects 属性,告诉 Webpack 哪些模块不应该被 Tree Shaking,如 CSS / SCSS 文件等有副作用的文件。
确保使用了 UglifyJSPlugin 插件:
UglifyJSPlugin 是 Webpack 内置的一个插件,用于对 JavaScript 代码进行压缩和混淆,同时也可以用于移除未被使用的代码。在 Webpack 4 中,由于 mode 为 production 时,会自动使用 TerserWebpackPlugin 进行 JavaScript 代码压缩和优化,因此不需要单独添加 UglifyJSPlugin。
启用 Tree Shaking 后,Webpack 会分析项目代码,将所有没有被引用到的模块和代码块删除掉。这样做不仅可以减少打包后的体积,还可以提高应用程序的性能,因为没有被使用的无效代码不再需要解析和执行。
在设置好上述配置后,Webapck 就会自动进行 Tree shaking,删除未使用的代码。
下面给出一个简单的示例:
// index.js
import { add } from './math.js';
console.log(add(1, 2));
// math.js
export function add(x, y) {
return x + y;
}
export function sub(x, y) {
return x - y;
}
以上代码中,sub 函数没有被使用,因此 Tree shaking 后会被删除。最终输出的代码中,只包含 add 函数的代码,sub 函数的代码被成功删除。
一般情况下,Tree Shaking 需要结合其他优化技术一起使用,例如代码分割(Code Splitting)、懒加载(Lazy Loading)、预加载(Preloading)等,以进一步提高应用程序的性能和用户体验。
6、大模块cdn引入
CDN 提供商
https://unpkg.com/
https://www.jsdelivr.com/
https://www.bootcdn.cn/
http://www.staticfile.org/
unpkg 使用,比如搜xlsx
在浏览器录入
unpkg.com/xlsx
会跳到最新版本的源码
退几格,在浏览器录入
unpkg.com/xlsx/dist/
会跳到最新版本的dist列表
文章
正确使用externals,vue工程构建性能提升67%
Vue项目CDN引入ElementUI
缺点:线上使用 cdn 可能会因为网络问题不稳定,除非公司有自己的 cdn 库。
不过这确实也是一种优化方案,效果也还不错。它的配置也很简单,在 externals 中配置如下:
module.exports = {
// 设置通过cdn引入的库,不打包到vendor中(需要去掉导入的相关代码),需要在index.html中引入cdn资源
externals: {
vue: 'Vue', // 使用cdn引用element-ui时,必须设置vue和element-ui为外部依赖。
'element-ui': 'ELEMENT', // 不去 node_modules 中找,而是去找 全局变量 ELEMENT
// 'vue-router': 'VueRouter',
// axios: 'axios',
echarts: 'echarts'
// XLSX: 'XLSX'
}
}
如何得知第三方包对应的
key
,value
是啥,key
是package.json
中安装的包名,value
时包真实注册或者说暴露的全局变量的值,比如element-ui
的value
是ELEMENT
,打开elememt-ui
的源码,格式化可以看到如下,注册的值是ELEMENT
,且依赖了vue
;其他包同样思路
在public/index.html 引入 CDN
<!-- vue必须在element-ui前面 -->
<script src="https://unpkg.com/vue@2.6.14/dist/vue.min.js"></script>
<!-- unpkg element-ui 源 -->
<link href="https://unpkg.com/element-ui@2.15.13/lib/theme-chalk/index.css" rel="stylesheet">
<script src="https://unpkg.com/element-ui@2.15.13/lib/index.js"></script>
<!-- bootcdn element-ui 源 -->
<!-- <link href="https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.13/theme-chalk/index.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.13/index.min.js"></script> -->
<script src="https://unpkg.com/echarts@5.4.2/dist/echarts.min.js"></script>
<!-- <script src="https://unpkg.com/xlsx@0.18.5/dist/xlsx.full.min.js"></script> -->
如果在main.js 使用了下面的代码可以去掉
import ElementUI from 'element-ui'
Vue.use(ElementUI)
关于echarts 使用还是通过下面的代码
import * as echarts from 'echarts'
7、压缩css、js、图片资源,删除无用代码
8、gzip压缩
Gzip 是一种常用的文件压缩算法,可以将文本文件压缩成更小的体积,提高网站的加载速度和用户体验。在 Web 开发中,常用的压缩工具有 Gzip 和 Brotli 等。
要开启 Gzip 压缩,你可以使用 webpack 内置的
compression-webpack-plugin
插件。该插件会自动将打包后的 js、css 文件进行 Gzip 压缩,并生成对应的 .gz 文件。
以下是如何在 项目中开启 Gzip 压缩的步骤:
1、安装 compression-webpack-plugin 插件。
npm install compression-webpack-plugin --save-dev
在 vue.config.js 中添加配置,启用 compression-webpack-plugin
。
const CompressionWebpackPlugin = require('compression-webpack-plugin');
module.exports = {
configureWebpack: {
plugins: [
// 启用 Gzip 压缩
new CompressionWebpackPlugin({
filename: '[path][base].gz',
algorithm: 'gzip',
test: /\.(js|css)$/,
threshold: 10240,
minRatio: 0.8,
deleteOriginalAssets: false,
}),
],
},
}
以上配置中,CompressionWebpackPlugin 的参数含义如下:
filename:压缩后的文件名,[path] 表示文件路径,[base] 表示文件名。
algorithm:压缩算法,这里选用 Gzip。
test:匹配需要压缩的文件类型。
threshold:文件大小超过这个值才会被压缩,单位是字节。
minRatio:压缩后的文件大小和原文件大小的比率,小于这个值才会生成压缩文件。
deleteOriginalAssets:是否删除原文件。若为 true,则原文件将会被删除,只保留压缩后的文件。
执行 npm run build 构建生产环境代码,可以看到在 dist 目录下生成了 .gz 的压缩文件。
在服务器上启用 Gzip 压缩。如果你是使用 Nginx 作为 Web 服务器,可以在 nginx.conf 文件中设置:
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_vary on;
gzip_proxied any;
这样就可以在服务器端启用 Gzip 压缩,提高网站的加载速度和用户体验。
三、性能、用户体验优化
3.1、路由懒加载
路由懒加载是指在需要的时候才加载对应的路由模块,而不是在项目初始化时就加载所有的路由模块。这样可以减少初始加载时间和资源消耗,提高页面加载速度和用户体验。在 Vue 项目中,可以使用动态导入的方式来实现路由懒加载。
下面是如何使用路由懒加载的步骤:
安装 @babel/plugin-syntax-dynamic-import 插件
npm install @babel/plugin-syntax-dynamic-import --save-dev
在 .babelrc 文件中添加插件
{
"plugins": ["@babel/plugin-syntax-dynamic-import"]
}
在路由配置文件(如 router.js)中定义路由时,使用 import 函数懒加载路由组件
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
const Home = () => import('./views/Home.vue')
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: Home
},
]
})
以上代码中,通过使用 import 函数来动态加载组件,在访问对应路由时,才会加载该组件。这样就实现了路由懒加载。
3.2、vue-lazyload插件,图片懒加载
vue-lazyload
是一个 Vue.js 的插件,用于实现图片的懒加载。它可以自动监听页面的滚动事件,在图片出现在用户可见区域时再进行加载,从而减轻页面加载压力,优化用户体验和加快页面渲染速度。
以下是使用 vue-lazyload
实现图片懒加载的基本步骤:
1). 安装 vue-lazyload
插件
npm install vue-lazyload --save
2). 编写 Vue 组件,并在模板中引入待懒加载的图片
<template>
<div class="container">
<img v-lazy="imgSrc" alt="图片描述">
</div>
</template>
<script>
export default {
data() {
return {
imgSrc: 'path/to/image.jpg'
}
}
}
</script>
3). 在 Vue 中注册 vue-lazyload
插件
import Vue from 'vue'
import VueLazyLoad from 'vue-lazyload'
Vue.use(VueLazyLoad)
4). 配置 vue-lazyload
的全局参数
import Vue from 'vue'
import VueLazyLoad from 'vue-lazyload'
Vue.use(VueLazyLoad, {
loading: 'loading.gif', // 加载中的占位图
error: 'error.gif' // 加载失败的占位图
})
通过以上步骤,就可以实现对 Vue 应用程序中图片的简单懒加载,提高页面性能表现和用户体验。
需要注意的是,在移动端浏览器上,为了提高页面的滚动流畅度和用户体验,建议在
vue-lazyload
中启用listenEvents
选项,使用Touch
事件代替scroll
事件来监听页面滚动。同时,考虑到用户可能会在滚动过程中快速滑动界面,所以建议开启adapter
选项,其作用是在页面滚动时自动调整懒加载图片的加载时机和顺序,从而避免某些图片由于滚动过快而无法加载。
3.3、使用缓存
使用缓存和资源重用技术,减少不必要的重复请求和渲染,如使用keep-alive组件缓存组件状态。
缓存技术是一种优化 Web 应用程序性能和用户体验的有效方式,Vue.js 项目也可以通过缓存技术进一步提高应用程序的响应速度、减轻服务器负担和增强用户体验。下面是几种常见的 Vue.js 项目中使用缓存技术提高用户体验的方法:
1). 浏览器缓存
浏览器缓存是一种常见且易于实现的缓存技术,通过将资源文件(例如图片、CSS、JS 等)缓存到客户端浏览器中,可以减少服务器请求次数,加快页面加载速度并减轻服务器负担。在 Vue.js 中,你可以使用 Vue Router 提供的keep-alive
组件来实现组件缓存,当组件被缓存后,每次访问该组件时,都会从浏览器缓存中加载,从而减少服务器的请求次数,提高用户体验。
2). 服务端缓存
在 Vue.js 项目中,我们通常会使用 AJAX 或者 Axios 发送 HTTP 请求以获取数据,如果每次请求都要重新向服务器请求数据,会消耗大量的时间和带宽资源,影响用户体验。因此,可以使用服务端缓存技术来缓存数据,例如使用 Redis 来缓存常用数据或者使用 CDN 存储静态资源文件,从而减轻服务器的请求负担,提高数据访问速度和响应时间。
3). 懒加载
如果一个页面中包含大量的图片或者其他资源文件,会极大地影响页面的加载速度和用户体验。为了避免这种情况,可以采用懒加载技术,即在用户需要浏览某个区域时才加载该区域的资源文件。在 Vue.js 中,你可以使用vue-lazyload
插件实现图片的懒加载。
4). 数据缓存
在 Vue.js 项目中,可以使用 Vuex 来管理应用程序的状态,在应用程序中经常需要获取相同的数据,因此可以使用 Vuex 的缓存技术来缓存请求过的数据,在下次请求时直接从缓存中获取数据,从而减少服务器的请求次数,提高用户体验。
3.4、watch & computed
watch
和computed
是 Vue 中非常常用的两种响应式数据处理方式,它们都能够监听数据变化并执行相应的操作。在提高 Vue 项目的性能和用户体验方面,watch
和computed
也有着不同的应用场景和优化方法。
1). 优化 watch 的性能
- 尽量避免在 watch 中进行异步操作,因为异步操作通常会阻塞 JS 线程,影响页面性能。如果必须进行异步操作,可以使用
vm.$nextTick
或Promise.then
方法,将异步操作推迟到下一次 DOM 更新时执行。- 在监听数组或对象等复杂数据类型时,尽量使用
deep
选项来监听其子属性的变化,而不是使用immediate
选项来立即执行回调函数。因为immediate
会立即执行回调函数,导致监听的数据被多次计算和渲染,影响页面性能。- 避免在 watch 中执行过多的计算和渲染操作,尽量将这些操作放在
computed
属性中进行处理。
- 优化 computed 的性能
- 避免在
computed
中引用其他computed
,因为这样会导致多余的计算和渲染,降低页面性能。- 对于数据量较大或计算量较大的
computed
,可以使用lazy
选项,将其设置为惰性计算,只有在需要计算时才会重新计算。- 对于一些频繁变化的响应式数据,可以考虑使用
watch
来监听其变化,而不是使用computed
来计算。
3.5、防抖 & 节流
函数节流(throttle)与 函数防抖(debounce)都是为了限制函数的执行频次,以优化函数触发频率过高导致的响应速度跟不上触发频率,出现延迟,假死或卡顿的现象
是应对频繁触发事件的优化方案。
Vue - 使用Lodash实现防抖和节流
3.6、v-if与v-show的使用
v-if
和v-show
是 Vue 中两种常用的条件渲染指令。它们都可以根据表达式的值来控制元素的显示和隐藏,但是在具体使用上存在一些差异。
1). v-if
v-if
指令的作用是,根据绑定的表达式的真假值来决定是否渲染 DOM 元素。当绑定的表达式为真值时,该元素会被渲染到页面中;当绑定的表达式为假值时,该元素不会被渲染到页面中,相当于从 DOM 树中移除该元素及其子元素。
优点:在条件不成立时,可以减少不必要的 DOM 元素的渲染和加载,从而提高页面的加载速度和性能表现。
缺点:每次切换条件,都会重新创建和销毁对应的组件或元素,因此在程序运行时可能会比v-show
慢。
2). v-show
v-show
指令的作用是,根据绑定的表达式的真假值来控制元素的显示和隐藏。与v-if
不同,v-show
并不会从 DOM 树中移除元素,而是通过修改其 CSS 样式来控制元素的显示和隐藏。
优点:在条件不成立时,并不会从 DOM 树中移除该元素及其子元素,因此在切换条件时,可以保留该元素的状态和属性,提高页面切换的平滑度和体验。
缺点:在条件不成立时,该元素仍会被加载到页面中,因此可能会增加页面的渲染和加载时间。所以,对于需要频繁切换的元素或组件,建议使用v-show
。