Vue项目构建优化

本文作者为 360 奇舞团前端开发工程师 宁航

在开发大型前端项目时,往往是一个需求对应一个分支,当完成需求后,就需要将代码打包、部署。代码通常需要部署到多个环境中,这些环境包括:日常环境、测试环境、回归环境和生产环境。回归环境用于在发布前进行测试,生产环境是用户访问的版本。随着时间的推移,项目中会不断引入许多新的依赖(如第三方库、插件等)和图片资源,代码数量也会逐渐增多,从而导致构建项目更加耗时,这也意味着部署项目需要消耗更长的时间。

我负责的项目构建需要 66 秒,有时将代码部署到日常环境后,还需要临时修改重新部署;随后再依次部署到测试环境、回归环境,验证无误后,才能部署到线上环境。如果碰到要紧急上线的任务,这一过程无疑是十分费时的。因此,我决定针对项目构建时间过长的问题进行优化,以此来提高工作效率。本文将对如何优化项目构建速度进行详细介绍,具体过程如下:

一、前期准备

当前的构建工具有很多种,例如Rollup、Webpack、Vite等,在进行优化工作前,首先要明确项目使用的是哪个构建工具,我的项目是使用Vue CLI 3创建的,Vue CLI 在创建项目时会默认使用 Webpack 来构建项目,但在 Vue CLI中,默认情况下是不直接暴露 webpack 配置的,只能通过 vue.config.js 文件来修改配置。

其次,由于webpack在不断更新,新版本会增加许多优化策略,因此,还要明确项目使用的webpack版本,再基于这个版本,采用更有针对性的优化方法。经查询,发现Vue CLI 3对应webpack4,后续的优化方法将围绕该版本展开。

最后,我们还需要对构建过程进行详细分析,以便制定合理的优化策略。当我们运行如下命令,就会开始构建:

yarn build

yarn build会执行package.json中定义的构建脚本,在我的项目中,实际上运行了vue-cli-service build,该命令会进行如下操作:

(1)检查配置文件: Vue CLI首先会查找并解析项目中的配置文件vue.config.js,以获取构建配置和其他相关的配置信息。

(2)代码转译和打包: Vue CLI会使用webpack和相关的加载器(例如babel-loader)对项目的源代码进行转译和打包。这包括将Vue单文件组件转换为JavaScript、处理CSS预处理器(如SassLess)等。

(3)静态资源处理: Vue CLI会处理项目中的静态资源,如图片、字体等文件。这可能包括复制这些文件到输出目录,并在构建过程中引入适当的路径。

(4)压缩和优化: 构建过程还涉及到对输出的JavaScript、CSS和其他资源文件进行压缩和优化,以减小文件大小并提高应用性能。

核心步骤如下图所示:

f88066e794dc15a57756a9f1b659a462.png

现在,我们可以思考下可以在哪个阶段进行优化了。“源代码打包”是最先开始的操作,我首先想到的是这一阶段消耗的时间必然与代码体积呈正相关,即代码体积越大,需要编译的时间就越长,大致如下图所示。因此,如果能减少需要打包的代码体积,就可以节省一部分时间了。

d52f92b1419d1c7a839e8ae78268b62c.png

此外,“源代码打包”阶段还会使用配置的loader对代码进行处理,在一个项目中可以配置多个loader,例如用vue-svg-inline-loader将 SVG 文件转换为 Vue 组件中的内联 SVG ,用babel-loader来转译js文件。但webpack为单线程模式,只能依次使用每个loader处理代码,如果遇到耗时较长的loader,后续loader就只能等待。因此,如果能找到耗时较长的loader,让它们同时运行,也能节省一些时间。

构建时还会引入项目的图片,如果大尺寸的图片过多,也会影响构建性能。所以,我们还需要将大尺寸的图片进行替换,以提升构建速度。

针对以上3个优化点,我寻找了多个方案进行尝试,最终生效的方案如下图所示,后续将会对前两个方案进行详细介绍。

9d05c6784bd278b7d43be1bc36e60edb.png

二、提前编译第三方库

引入webpack-bundle-analyzer插件分析项目体积

Webpack Bundle Analyzer 插件是一个用于分析 Webpack 打包结果的工具,它提供了一个直观的可视化界面,展示了项目打包后的文件结构,各个模块的大小、占比、依赖关系等信息。我们可以根据分析结果,针对性地优化文件大小,减少不必要的资源占用。

  1. 安装

yarn add -D webpack-bundle-analyzer
  1. 使用

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
    configureWebpack: {
        plugins: [
            new BundleAnalyzerPlugin()
        ]
    }
}

该插件将以树状图的形式展示项目打包后的内容, 从中可以看出每个文件的体积大小。

8965893876ef615016f655eee2dac112.gif

文件的体积参数有以下3种:

  • stat表示文件未压缩的原始体积

  • parsed表示文件经过插件处理后的体积,假如项目中使用了Uglify插件,那么parsed为文件压缩后的体积

  • gzip表示文件经过gzip压缩后的体积

  1. 项目体积分析

我在项目中引入webpack-bundle-analyzer插件后,得到了如下的分析图:

cc9cab524c59380baf761bd310387fcf.png

通过观察该树状图,可知项目的总体积为 28 MB,其中3个最大的文件分别为index.js(8.71 MB), preview.js(7.5 MB), 和survey.js(7.45 MB)。这三个文件都引用了element-ui、moment等第三方库。第三方库的代码往往比较稳定,不会频繁变化。如果将这些第三方库打包成一个动态链接库,并在相关页面引入,那么在每次构建主项目时就不需要重新构建这些库了,从而使得源代码体积减少,打包速度加快。

使用动态链接库技术

动态链接库(Dynamic Link Library,DLL)是一种在Windows操作系统中常见的技术,可以用来在程序运行时加载共享的代码和资源。动态链接库中的函数、变量和资源可以被多个程序共享使用。这意味着不同的程序可以同时使用同一个动态链接库,从而减少磁盘空间和内存占用。

Webpack提供了两个插件DllPluginDllReferencePlugin来配置动态链接库,DllPlugin用于将第三方库(例如 VueReact 等)打包到一个或多个独立的动态链接库(DLL)中。DllReferencePlugin用于在主项目中引用预先打包好的动态链接库。以下是使用这 2 个插件的基本步骤:

  1. 创建一个用于打包第三方库的配置文件

首先,在项目根目录下创建 webpack.dll.config.js 文件,用于配置 DllPlugin

// 导入 path 和 webpack 模块
const path = require('path');
const webpack = require('webpack');

// 导出配置对象
module.exports = {
  // 指定 webpack 模式为生产模式
  mode: 'production', 

  // 入口配置,将需要打包的第三方库列出
  entry: {
    vendor: ['vue', 'vue-router', 'vuex', /* 此处可以继续添加其他第三方库 */ ]
  },

  // 输出配置,指定生成的动态链接库文件名称和路径
  output: {
    filename: '[name].dll.js', // 动态链接库文件名,[name] 表示入口名称
    path: path.resolve(__dirname, 'public/dll'), // 动态链接库文件输出目录
    library: '[name]' // 将动态链接库导出的内容赋值给变量名 [name]
  },

  // 插件配置,使用 webpack.DllPlugin 插件
  plugins: [
    new webpack.DllPlugin({
      name: '[name]', // 全局变量名称,保持与 output.library 一致
      path: path.resolve(__dirname, 'public/dll/[name].manifest.json') // 动态链接库清单文件路径
    })
  ]
};
  1. 创建npm脚本

在 package.json 文件中,添加一个新的 npm 脚本,用于运行上述 webpack 配置文件:

"scripts": {
  // ...其他脚本
  "dll": "webpack --config webpack.dll.config.js"
}

运行yarn dll,会在public/dll文件夹下生产vendor.dll.jsvendor.manifest.json2个文件。

  • vendor.dll.js文件文件包含了指定的第三方库(例如 VueVue RouterVuex 等)的代码和资源,以及这些库的导出信息。

  • vendor.manifest.json 文件是一个清单文件,记录了vendor.dll.js文件包含了哪些模块,以及每个模块的路径、ID 等信息。主项目在构建时会通过这个清单文件来确定如何引用动态链接库中的模块,以确保正确地加载和使用这些模块。

  1. 在 vue.config.js 文件引入 DllReferencePlugin插件

在 configureWebpack 配置中引入 DllReferencePlugin

// 导入 webpack 模块
const webpack = require('webpack');

// 导出配置对象
module.exports = {
  // 其他配置...

  // 配置 webpack
  configureWebpack: {
    // 插件配置
    plugins: [
      // 使用 webpack.DllReferencePlugin 插件
      new webpack.DllReferencePlugin({
        // 指定上下文路径为当前工作目录
        context: process.cwd(),
        // 指定动态链接库清单文件的路径
        manifest: require('./public/dll/vendor.manifest.json')
      })
    ]
  }
};
  1. html文件中引入DLL文件

配置完成后,运行yarn build命令,得到如下结果。

20dd7387737e7356dad8dead4c689970.png

从图中可以看出,项目的总体积减少到了 7.35 MB,其中3个最大的文件分别减少到 2.74 MB(index.js), 1.74 MB( preview.js), 和 1.68 MB (survey.js)。构建时间由 66 秒缩短到了 43 秒,显著加快。

如果项目中引入了新的第三方库,则需要将该库添加到 webpack.dll.config.js 的 entry 中,并重新运行yarn dll即可。

三、为耗时loader开启多线程

引入speed-measure-webpack-plugin插件分析loader耗时

speed-measure-webpack-plugin 是一个用于测量 Webpack 打包速度的插件,包括各个阶段的耗时情况,例如初始化、加载、编译、优化、打包等;也可以分析每个 loader 和插件在打包过程中的耗时情况。这有助于我们找到影响性能的具体原因,进行针对性的优化。

  1. 安装

yarn add -D speed-measure-webpack-plugin
  1. 使用

我们需要创建一个 SpeedMeasurePlugin 的实例,并使用它来包装配置对象。

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();

module.exports = smp.wrap({
    configureWebpack: {
        plugins: [
            new BundleAnalyzerPlugin()
        ]
    }
});

如下图所示,运行yarn build后,就可以看到每个pluginloader分别花费了多少时间。

774efb3d2b2b0906778ec5123ae5fadb.png

使用thread-loader开启多线程

thread-loader可以将指定的 loader 放在 worker 池的子线程中运行,这样能充分利用多核 CPU 的性能,从而实现并行处理。thread-loader 适用于任何耗时的 loader,特别是那些需要大量计算的 loader,例如 Babel、TypeScript 等。每个 worker 是一个单独的 Node.js 进程。

在我的项目中,babel-loadervue-svg-inline-loader耗时较多,因此我把thread-loader 放置到这些loader之前,为这些loader开辟了单独的线程池。具体配置如下:

module.exports = {
  // 配置 webpack
  chainWebpack: config => {
    // 针对 .js 文件的规则配置
    config.module
      .rule('js') // 添加一个规则命名为 'js'
      .test(/\.js$/) // 匹配文件后缀为 .js 的文件
      .exclude // 排除特定目录
      .add(/node_modules/) // 添加排除目录为 node_modules
      .end() 
      .use('thread-loader') // 使用 thread-loader 处理 .js 文件
      .loader('thread-loader') // 指定 thread-loader 作为 loader
      .end() 
      .use('babel-loader') // 使用 babel-loader 处理 .js 文件
      .loader('babel-loader') // 指定 babel-loader 作为 loader
      .end() 

    // 针对 .vue 文件的规则配置
    config.module
      .rule('vue') // 添加一个规则命名为 'vue'
      .use('thread-loader') // 使用 thread-loader 处理 .vue 文件
      .loader('thread-loader') // 指定 thread-loader 作为 loader
      .options({ 
        workers: 2 // 指定 worker 数量为 2
      })
      .end() 
      .use('vue-loader') // 使用 vue-loader 处理 .vue 文件
      .loader('vue-loader') // 指定 vue-loader 作为 loader
      .end() 
      .use('vue-svg-inline-loader') // 使用 vue-svg-inline-loader 处理 .vue 文件
      .loader('vue-svg-inline-loader') // 指定 vue-svg-inline-loader 作为 loader
      .end() 
  }
}

如果不配置thread-loader,各个loader的加载过程如图:

25f08ba7a159da084cd3449678d175ae.png

为耗时loader开启单独线程后,加载过程如图:

7758a0d857fe7d898dca9503c0c335df.png

通过为耗时的loader开启多线程,使得项目构建时间从 43 秒减少到了 34 秒。

四、小结

通过使用DllPluginDllReferencePlugin插件将第三方库打包成动态链接库,引入thread-loader将耗时较长的loader放入单独的线程池中加载,替换项目中的大图片,使得项目构建时间从 66 秒减少到了 34 秒,总共减少 32 秒,约 48% 。

项目构建优化是需要不断尝试的,许多方案都不通用,以上3个优化点在本项目中起到了作用,下面我还将记录没有生效的方案,希望能为读者提供一些不同的思路,或许这些方案对你的项目有帮助。

  1. HardSourceWebpackPlugin插件进行缓存

在优化工作开始后,我最先想到的方案是:在第一次构建时,将没有变化的模块(如第三方库)的打包结果缓存下来,后续构建时直接读取缓存,就可以节省很多时间了。经过一番查找,发现HardSourceWebpackPlugin插件很适合用来执行这项工作。

Webpack提供了HardSourceWebpackPlugin插件来为模块提供中间缓存。如前文所述,Webpack在构建时,会解析项目中的每个模块,并根据需要对其进行转换和编译。在这一过程中,该插件会把编译结果保存下来。在下一次构建时,HardSourceWebpackPlugin插件会比较当前的模块和缓存中的模块是否一致,如果没有变化,就直接使用缓存结果。

引入该插件后,在本地的测试时发现:第一次构建花费的时间与之前相同,后续的构建速度却显著提升。但是,由于我的项目是使用部门统一的工作台部署,每次都需要重新执行yarn install安装依赖,所以该插件并不能产生作用。如果你的项目不是这种工作模式,那我推荐你使用该插件。

  1. 压缩代码

Webpack4默认情况下会对输出的JavaScriptCSS和其他资源文件进行压缩,但是我们还可以通过一些插件自定义压缩行为。

  • 压缩JS代码

我们可以使用terser-webpack-plugin插件来删除空格、注释及未使用的代码,使得压缩后的代码体积更小;此外,它还支持并行压缩。

  • 样式文件压缩

mini-css-extract-plugin插件可以将打包生成的css代码从JavaScript bundle 中提取出来,在多页面应用中,如果多个页面共享一些css样式,使用该插件可以避免重复打包这些共享的样式。此外,分离出的css文件也可以与JavaScript文件同时加载,从而提高页面加载速度。

需要注意的是插件本身也需一定的时间来加载,因此,我们还应比较引入插件的时间是否高于压缩文件后节省的时间,合理使用。

五、参考资料

  • webpack-bundle-analyzer插件:https://github.com/webpack-contrib/webpack-bundle-analyzer

  • speed-measure-webpack-plugin插件:https://github.com/stephencookdev/speed-measure-webpack-plugin

  • **DllPluginDllReferencePlugin:**https://webpack.js.org/plugins/dll-plugin/

  • **thread-loader: **https://webpack.js.org/loaders/thread-loader/

  • **terser-webpack-plugin:**https://www.npmjs.com/package/terser-webpack-plugin

  • **mini-css-extract-plugin:**https://www.npmjs.com/package/mini-css-extract-plugin


- END -

关于奇舞团

奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

55cf6e317a268e74052f2c5e3e0a7171.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/413610.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux Centos7配置SSH免密登录

Linux Centos7配置SSH免密登录 配置SSH免密登录说明: 分两步 第一步、给Server A生成密钥对 第二步、给Server B授权 生成密钥对之后,我们可以看看它保存的目录下的文件。 接下来我们就要把Server A(10.1.1.74)的公钥拷贝到Se…

Linux运维-Web服务器的配置与管理(Apache+tomcat)(没成功,最后有失败经验)

Web服务器的配置与管理(Apachetomcat) 项目场景 公司业务经过长期发展,有了很大突破,已经实现盈利,现公司要求加强技术架构应用功能和安全性以及开始向企业应用、移动APP等领域延伸,此时原来开发web服务的php语言已经不适应新的…

Rust使用calamine读取excel文件,Rust使用rust_xlsxwriter写入excel文件

Rust使用calamine读取已存在的test.xlsx文件全部数据,还读取指定单元格数据;Rust使用rust_xlsxwriter创建新的output.xlsx文件,并写入数据到指定单元格,然后再保存工作簿。 Cargo.toml main.rs /*rust读取excel文件*/ use cala…

13.云原生之常用研发中间件部署

云原生专栏大纲 文章目录 mysql主从集群部署mysql高可用集群高可用互为主从架构互为主从架构如何实现主主复制中若是两台master上同时出现写操作可能会出现的问题该架构是否存在问题? heml部署mysql高可用集群 nacos集群部署官网文档部署nacoshelm部署nacos redis集…

像用Excel一样用Python:pandasGUI

文章目录 启动数据导入绘图 启动 众所周知,pandas是Python中著名的数据挖掘模块,以处理表格数据著称,并且具备一定的可视化能力。而pandasGUI则为pandas打造了一个友好的交互窗口,有了这个,就可以像使用Excel一样使用…

EtherCAT主站转Ethernet/IP网关

产品功能 1 YC-ECTM-EIP工业级Profinet 网关 2 EtherCAT转 EtherNet/IP 3 支持EtherNet/IP从站 4 即插即用 无需编程 轻松组态 ,即实现数据交互 5 导轨安装 支持提供EDS文件 6 EtherNET/IP与EtherCAT互转数据透明传输可接入PLC组态 支持CodeSys/欧姆龙PLC,西门…

Python 实现Excel自动化办公(上)

在Python 中你要针对某个对象进行操作,是需要安装与其对应的第三方库的,这里对于Excel 也不例外,它也有对应的第三方库,即xlrd 库。 什么是xlrd库 Python 操作Excel 主要用到xlrd和xlwt这两个库,即xlrd是读Excel &am…

《opencv实用探索·二十二》支持向量机SVM用法

1、概述 在了解支持向量机SVM用法之前先了解一些概念: (1)线性可分和线性不可分 如果在一个二维空间有一堆样本,如下图所示,如果能找到一条线把这两类样本分开至线的两侧,那么这个样本集就是线性可分&#…

Day03:Web架构OSS存储负载均衡CDN加速反向代理WAF防护

目录 WAF CDN OSS 反向代理 负载均衡 思维导图 章节知识点: 应用架构:Web/APP/云应用/三方服务/负载均衡等 安全产品:CDN/WAF/IDS/IPS/蜜罐/防火墙/杀毒等 渗透命令:文件上传下载/端口服务/Shell反弹等 抓包技术&#xff1a…

腾讯云4核8G服务器收费贵不贵?

腾讯云4核8G服务器多少钱?轻量应用服务器4核8G12M带宽一年446元、646元15个月,云服务器CVM标准型S5实例4核8G配置价格15个月1437.3元,5年6490.44元,标准型SA2服务器1444.8元一年,在txy.wiki可以查询详细配置和精准报价…

ky10-server docker 离线安装包、离线安装

离线安装脚本 # ---------------离线安装docker------------------- rpm -Uvh --force --nodeps *.rpm# 修改docker拉取源为国内 rm -rf /etc/docker mkdir -p /etc/docker touch /etc/docker/daemon.json cat >/etc/docker/daemon.json<<EOF{"registry-mirro…

蓝桥杯刷题2

1. 修建灌木 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scan new Scanner(System.in);int n scan.nextInt();for (int i 1;i < n1;i){int distance Math.max(i-1,n-i);System.out.println(distance*2);}scan.close…

Android13 Audio框架

一、Android 13音频代码结构 1、framework: android/frameworks/base 1.AudioManager.java &#xff1a;音频管理器&#xff0c;音量调节、音量UI、设置和获取参数等控制流的对外API 2.AudioService.java &#xff1a;音频系统服务&#xff08;java层&#xff09;&#xff0c…

Eslint在Vscode中使用技巧的相关技巧

ps :该文章会详细结论构建一个脚手架遇到的问题&#xff0c;会持续更新&#xff0c;请定时查看 Eslint相关​ 在vscode中使用eslint插件 在vscode中用户配置没有开启eslint.enable 在vscode中工作区配置开启eslint.enable settings.json中没有做eslint相关配置 在编写的vue…

了解docker与k8s

随着 k8s 作为容器编排解决方案变得越来越流行&#xff0c;有些人开始拿 Docker 和 k8s 进行对比&#xff0c;不禁问道&#xff1a;Docker 不香吗&#xff1f; k8s 是 kubernetes 的缩写&#xff0c;8 代表中间的八个字符。 其实 Docker 和 k8s 并非直接的竞争对手两者相互依存…

java Springboot vue 健身房系统,简单练手项目

该项目主要分为管理员和会员模块 管理员具有&#xff1a;会员管理&#xff0c;器材管理,员工管理&#xff0c;健身课程管理 会员模块&#xff0c;可以在线报名健身课程&#xff0c;查看自己课程 采用VUE前端开发和springboot后端开发&#xff0c;极简代码编写&#xff0c;没…

java使用itex生成PDF

Text是著名的开放源码的站点sourceforge一个项目&#xff0c;是用于生成PDF文档的一个java类库。通过iText不仅可以生成PDF或rtf的文档&#xff0c;而且可以将XML、Html文件转化为PDF文件。 项目要使用iText&#xff0c;必须引入jar包。才能使用&#xff0c;maven依赖如下&…

浅析ARMv8体系结构:原子操作

文章目录 概述LL/SC机制独占内存访问指令多字节独占内存访问指令 独占监视器经典自旋锁实现 LSE机制原子内存操作指令CAS指令交换指令 相关参考 概述 在编程中&#xff0c;当多个处理器或线程访问共享数据&#xff0c;并且至少有一个正在写入时&#xff0c;操作必须是原子的&a…

idea 更新maven java版本变化

今天遇到个问题就是&#xff0c;点击maven的reload&#xff0c;会导致setting 里的java compiler 版本变化 这里的话&#xff0c;应该是settings.xml文件里面的这个限定死了&#xff0c;修改一下或者去掉就可以了 <profile><id>JDK-1.8</id><activatio…

MFC 打印图片 dc.EndDoc(); 并没有释放怎么办? 原因:打印到pdf才会出现,打印到真实打印机就正常

最后找到原因&#xff1a;打印到pdf才会出现&#xff0c;打印到真实打印机就正常 MFC释放资源 BOOL bPrintingOK dc.StartDoc(&di); 在MFC (Microsoft Foundation Classes) 应用程序中&#xff0c;当你使用 CDC::StartDoc 方法开始一个打印任务时&#xff0c;如果该任务…