您现在的位置是:首页 >学无止境 >打包优化之vite构建(视图分析、CDN引入、依赖分包、gzip压缩)网站首页学无止境

打包优化之vite构建(视图分析、CDN引入、依赖分包、gzip压缩)

失眠时间 2024-06-17 10:48:29
简介vite 分包


前言

之前开发项目后,每次构建的时候都会经历较长时间的等待,且打包后文件加载速度慢的情况,结合最近vite+vue3项目构建优化,总结下构建优化方案,如有更好方案,欢迎各位大佬分享。

一、视图分析

工欲善其事必先利其器,构建优化也是,需要先知道项目代码的整体情况
Rollup 插件可视化工具rollup-plugin-visualizer,构建后可视化并分析你的 Rollup 包,看看哪些模块占用了空间。使用方法如下:

  • 依赖安装
npm i rollup-plugin-visualizer -D
  • 依赖引入并使用
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
  plugins: [vue(), visualizer({
     emitFile: false,
     filename: 'analysis-chart.html', // 分析图生成的文件名
     open:true // 如果存在本地服务端口,将在打包后自动展示
   })],
})
  • 构建后会显示具体项目资源情况
    当前项目构建后可看到element-plusEcharts最大,可使用CDN引入方式优化
    分析视图

二、CDN引入(减少构建时间,加快加载速度)

CDN 是构建在数据网络上的一种分布式的内容分发网。
CDN的作用是采用流媒体服务器集群技术,克服单机系统输出带宽及并发能力不足的缺点,可极大提升系统支持的并发流数目,减少或避免单点失效带来的不良影响。

Tips :有以下两种配置方式,可根据个人习惯使用对应配置,不过需要注意如果CDN的资源出现错误,那么所引入的项目也会出现错误,建议把资源都Down到本地,然后在引入本地的链接,保证不会被所引入资源报错影响
开发环境不走CDN,只有构建后走

常见cdn网站

  • UNPKG:https://unpkg.com
  • jsDelivr :https://www.jsdelivr.com

具体怎样查找库cdn包的路径呢,以 为例element-plus: @2.2.32

直接按照此格式访问 https://unpkg.com/element-plus@2.2.32链接得到 https://unpkg.com/element-plus@2.3.4/dist/index.full.js即是对应的cdn包路径了

jsDelivr 的格式为https://cdn.jsdelivr.net/npm/package@version
https://cdn.jsdelivr.net/npm/element-plus@2.2.32

①通过vite-plugin-cdn-import

  • 依赖安装
npm i vite-plugin-cdn-import -D
  • 使用方法及引入
    如果需要引入自动配置 需引入autoComplete使用,具体可自动配置可看源码(ctrl+左键点击查看)了解

以最近项目为例,具体配置如下
vite.config.ts文件

import { fileURLToPath, URL } from 'node:url'
import { resolve } from 'path'
import { defineConfig, loadEnv, splitVendorChunkPlugin, type PluginOption } from 'vite'

// import importToCDN from 'vite-plugin-cdn-import'
import { autoComplete, Plugin as importToCDN } from 'vite-plugin-cdn-import';
export default defineConfig(({ command, mode }) => {
  const env = loadEnv(mode, `${process.cwd()}/env`, '')
  return {
    base: '/',
    plugins: [
      vue(),
      ...
      // Tips:如果出现 CDN 配置的资源无法访问需修改配置或者可先不使用 importToCDN 
      importToCDN({
		// prodUrl:可选,默认指向 https://cdn.jsdelivr.net/npm/{name}@{version}/{path}
        // 可使用这种格式 https://cdn.jsdelivr.net/npm/element-plus@2.2.32 查看是否存在 例如打开浏览器访问得到  https://cdn.jsdelivr.net/npm/element-plus@2.2.32/dist/index.full.js
        // 也可指向 'https://unpkg.com/{name}@{version}/{path}'、本地根目录、获取自己的服务器 等
        // prodUrl: '/{path}', // 根目录 需要格外注意配置路径是否正确,且需要把资源先down下来
        // prodUrl: 'https://xxx.com/{name}@{version}/{path}', // 自己的服务器上
        prodUrl: 'https://unpkg.com/{name}@{version}/{path}', // https://unpkg.com/
        modules: [
          autoComplete('vue'),
          autoComplete('axios'),
          autoComplete('lodash'),
          {
            name: 'element-plus',
            // ElementPlus 为什么不是同下面第二种配置的elementPlus是因为这个配置同CDN资源一致,而下面的配置同需同main.ts的引入名称一致
            var: 'ElementPlus', // 外部化的依赖提供一个全局变量 同rollupOptions配置中的globals的值
            // https://unpkg.com/element-plus@2.2.32/dist/index.full.js 或者 dist/index.full.js
            path: 'dist/index.full.js',
            // 可选
            css: 'dist/index.css'
          },
          {
            name: 'vue-i18n',
            var: 'VueI18n',
            path: 'dist/vue-i18n.global.prod.js',
          },
          {
            name: 'vue-router',
            var: 'VueRouter',
            path: 'dist/vue-router.global.js'
          },
          // VueDemi这个是pinia用来判断是vue2还是vue3所需要的,要额外引入一下
          {
            name: 'vue-demi',
            var: 'VueDemi',
            path: 'https://unpkg.com/vue-demi@0.13.1/lib/index.iife.js'
          },
          {
            name: 'pinia',
            var: 'Pinia',
            path: 'dist/pinia.iife.js'
          },
          // echarts,只有配置全局的时候有效,不然构建的时候还是会打包执行。也可以把echarts处理成按需引入
          {
            name: 'echarts',
            var: 'echarts',
            path: 'dist/echarts.js'
          },
          // echarts 内使用了
          {
            name: 'zrender',
            var: 'zrender ',
            path: 'dist/zrender.js'
          },
        ],
      }),
    ],
    ...
  }
})

Tips:可能在构建后有时候会报以下错误,具体需要看上面代码的var是否为CDN全局定义字段,下面截图是本人在首次配置期间遇到的
需要考虑依赖之间支付存在关联,如果有,需要同时配置
且需要考虑配置的Var是否合理
在这里插入图片描述
在这里插入图片描述

②通过rollup-plugin-external-globals插件

这种方式需要单独在index.html中引入cdn资源

配置期间遇到的问题

  • 先说下本人配置期间遇到的问题,找了很久终于发现的问题所在,下面是问题描述

为啥老是提示这个错误???
多次构建验证发现externalGlobals得配置的值需要和CDN引入的全局定义一致,不然就会报错(例如Vue、VueRouter等)
caught TypeError: Failed to resolve module specifier "vue". Relative references must start with either "/", "./", or "../".
在这里插入图片描述

caught TypeError: Failed to resolve module specifier "vue". Relative references must start with either "/", "./", or "../".

例如配置Vue CDN: https://unpkg.com/vue@3.2.45/dist/vue.global.prod.js,则需要配置为"Vue",如图

在这里插入图片描述

具体配置

需要安装个 rollup-plugin-external-globals插件

npm i rollup-plugin-external-globals -D

废话不多说直接上代码,即rollupOptions部分

`vite.config.ts`文件配置如下
```ts
import externalGlobals from 'rollup-plugin-external-globals';
...
build: {
	minify: "terser", // 必须开启:使用 terserOptions 才有效果
	terserOptions: {
	  compress: {
	    drop_console: process.env.NODE_ENV === 'production' ? true : false,
	    drop_debugger: process.env.NODE_ENV === 'production' ? true : false,
	  },
	},
	rollupOptions: {
	  // 确保外部化处理那些你不想打包进库的依赖
	  external: [
	    'vue',
        'vue-i18n',
        'vue-router',
        'element-plus',
        // VueDemi这个是pinia用来判断是vue2还是vue3所需要的,要额外引入一下
        'vue-demi',
        'pinia',
	  ],
	  plugins: [
	    // vue: 'Vue' --- vue为依赖包名  Vue为项目构建后的全局变量
	    // 同上,给外部化的依赖提供一个全局变量,即配置在external得值
	    externalGlobals({
	      vue: 'Vue',
	      'vue-i18n': 'VueI18n',
	      'vue-router': 'VueRouter',
	      elementPlus: 'element-plus',
	      // VueDemi这个是pinia用来判断是vue2还是vue3所需要的,要额外引入一下
	      'vue-demi': 'VueDemi',
	      pinia: 'createPinia',
	    })
	  ],
  },
},

这种配置需要再在inde.html文件手动引入对应CDN文件资源

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <script src="https://unpkg.com/vue@3.2.45/dist/vue.global.prod.js"></script>
    <script src="https://unpkg.com/axios@1.3.3/dist/axios.min.js"></script>
    <script src="https://unpkg.com/vue-i18n@9.3.0-beta.16/dist/vue-i18n.global.prod.js"></script>
    <script src="https://unpkg.com/vue-router@4.1.6/dist/vue-router.global.prod.js"></script>
    <script src="https://unpkg.com/element-plus@2.2.32/dist/index.full.js"></script>
    <script src="https://unpkg.com/vue-demi@0.13.1/lib/index.iife.js"></script>
    <script src="https://unpkg.com/pinia@2.0.28/dist/pinia.iife.js"></script>
    <!-- <script src="https://unpkg.com/echarts@5.4.1/dist/echarts.js"></script> -->
    <link rel="icon" href="/src/assets/favicon.ico">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SIS</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

构建后的资源如图
在这里插入图片描述

三、依赖分包

当未配置构建工具的分包功能,构建的后的资源将会较大且是独立的一个js and css 文件,这样就会存在本地加载文件的压力
vite底层集成了rollup的功能,我们可以直接配置即可,具体可去官网查看更详细配置

build: {
      minify: "terser", // 必须开启:使用 terserOptions 才有效果
      terserOptions: {
        compress: {
          drop_console: process.env.NODE_ENV === 'production' ? true : false,
          drop_debugger: process.env.NODE_ENV === 'production' ? true : false,
        },
      },
      rollupOptions: {
        // 静态资源分类打包
        output: {
          // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
          globals: {},
          chunkFileNames: 'static/js/[name]-[hash].js',
          entryFileNames: 'static/js/[name]-[hash].js',
          assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
          manualChunks(id) { // 静态资源分拆打包
            if (id.includes('node_modules')) {
              return id.toString().split('node_modules/')[1].split('/')[0].toString();
            }
          }
        }
      },
    },

配置拆包后构建文件如下:
在这里插入图片描述

四、开启gzip压缩

gzip是现今Internet 上使用非常普遍的一种数据压缩格式(一种文件格式)。

HTTP协议上的GZIP编码是一种用来改进WEB应用程序性能的技术。大流量的WEB站点常常使用GZIP压缩技术来让用户感受更快的速度。这一般是指www服务器中安装的一个功能,当有人来访问这个服务器中的网站时,服务器中的这个功能就将网页内容压缩后传输到来访的电脑浏览器中显示出来.一般对纯文本内容可压缩到原大小的40%.这样传输就快了,效果就是你点击网址后会很快的显示出来.当然这也会增加服务器的负载.
一般服务器中都安装有这个功能模块的。

如上描述,gzip不只是我们需要在打包的时候生成,还得在服务器开

那么我们先处理打包的时候进入gzip压缩

  • 安装插件
npm i vite-plugin-compression -D
  • 引用方式

Tips:配置过程中有个小插曲插件源码上备注说明如下,由于英文水平有限,Whether to enable compression,使用翻译软件为是否启用压缩,可实际配置之后发现应该是是否禁用压缩的意思

	...
    /**
     * Whether to enable compression
     * @default: false
     */
    disable?: boolean;
    ...

vite.config.ts文件引入如下:

...
import viteCompression from 'vite-plugin-compression';
...
export default defineConfig(({ command, mode }) => {
  const env = loadEnv(mode, `${process.cwd()}/env`, '')
  return {
    mode,
    // env配置文件变量前缀
    envPrefix: 'SIS_',
    // env配置文件夹位置
    envDir: 'env',
    // 构建后文件引用 相对路径
    // base: env.SIS_PUBLIC_PATH, // 同webpack assetsPublicPath
    base: '/',
    plugins: [
      vue(),
      ...
      // 构建压缩文件
      viteCompression({
        // 记录压缩文件及其压缩率。默认true
        verbose: true,
        // 是否启用压缩,默认false
        disable: false,
        // 需要使用压缩前的最小文件大小,单位字节(byte) b,1b(字节)=8bit(比特), 1KB=1024B
        threshold: 10240, // 即10kb以上即会压缩
        // 压缩算法 可选 'gzip' | 'brotliCompress' | 'deflate' | 'deflateRaw'
        algorithm: 'gzip',
        // 压缩后的文件格式
        ext: '.gz',
      }),
      ...
	]
  }
})
  • 服务器开启gzip

这里使用nginx来做服务器
具体需要在location /位置配置gzip_static on; ,配置如下

		location / {
            try_files $uri $uri/ @router;#需要指向下面的@router否则会出现vue的路由在nginx中刷新出现404
            index index.html index.htm;

            gzip_static on;
        }
        #对应上面的@router,主要原因是路由的路径资源并不是一个真实的路径,所以无法找到具体的文件
        #因此需要rewrite到index.html中,然后交给路由在处理请求资源
        location @router {
            rewrite ^.*$ /index.html last;
        }
  • 配置后访问页面显示
    如果页面资源请求头有Content-Encoding: gzip和响应头有Accept-Encoding: gzip, deflate, br,且可以看到文件大小使用压缩后小了很多即配置成功

在这里插入图片描述
压缩前为 99.2kb
在这里插入图片描述
压缩前为 14.4kb
在这里插入图片描述

五、最终配置

根据上述四个步骤最终配置如下,话不多说,直接上完整vite.config.ts配置

import { fileURLToPath, URL } from 'node:url'
import { resolve } from 'path'
import { defineConfig, loadEnv, splitVendorChunkPlugin, type PluginOption } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
const getProxyConfig = require('./build/proxy');
import viteCompression from 'vite-plugin-compression';
import htmlPlugin from "vite-plugin-html-config";
import { visualizer } from 'rollup-plugin-visualizer';
// import importToCDN from 'vite-plugin-cdn-import'
import { autoComplete, Plugin as importToCDN } from 'vite-plugin-cdn-import';
import externalGlobals from 'rollup-plugin-external-globals';
// https://vitejs.dev/config/
export default defineConfig(({ command, mode }) => {
  const env = loadEnv(mode, `${process.cwd()}/env`, '')
  Object.assign(process.env, env, { NODE_ENV: mode })
  return {
    mode,
    // env配置文件变量前缀
    envPrefix: 'SIS_',
    // env配置文件夹位置
    envDir: 'env',
    // 构建后文件引用 相对路径
    // base: env.SIS_PUBLIC_PATH, // 同webpack assetsPublicPath
    base: '/',
    plugins: [
      vue(),
      vueJsx(),
      // splitVendorChunkPlugin(),
      // 构建压缩文件
      viteCompression({
        // 记录压缩文件及其压缩率。默认true
        verbose: true,
        // 是否启用压缩,默认false
        disable: false,
        // 需要使用压缩前的最小文件大小,单位字节(byte) b,1b(字节)=8bit(比特), 1KB=1024B
        threshold: 10240, // 即10kb以上即会压缩
        // 压缩算法 可选 'gzip' | 'brotliCompress' | 'deflate' | 'deflateRaw'
        algorithm: 'gzip',
        // 压缩后的文件格式
        ext: '.gz',
      }),
      // 构建时间
      htmlPlugin({
        metas:[
          {
            name: "builtTime",
            content: new Date().toLocaleString(),
          }
        ]
      }),
      visualizer({
        emitFile: false,
        filename: 'analysis-chart.html', // 分析图生成的文件名
        open:true // 如果存在本地服务端口,将在打包后自动展示
      }) as PluginOption,
      // Tips:如果出现 CDN 配置的资源无法访问需修改配置或者可先不使用 importToCDN 
      importToCDN({
        // 可选 配置期间可用来查看需要处理的依赖是否存在 例如  https://unpkg.com/element-plus@2.2.32
        prodUrl: 'https://unpkg.com/{name}@{version}/{path}',
        modules: [
          autoComplete('vue'),
          autoComplete('axios'),
          autoComplete('lodash'),
          {
            name: 'element-plus',
            var: 'ElementPlus', // 外部化的依赖提供一个全局变量 同rollupOptions配置中的external的值
            // https://unpkg.com/element-plus@2.2.32/dist/index.full.js 或者 dist/index.full.js
            path: 'dist/index.full.js',
            // 可选
            css: 'dist/index.css'
          },
          {
            name: 'vue-i18n',
            var: 'VueI18n',
            path: 'dist/vue-i18n.global.prod.js',
          },
          {
            name: 'vue-router',
            var: 'VueRouter',
            path: 'dist/vue-router.global.js'
          },
          // VueDemi这个是pinia用来判断是vue2还是vue3所需要的,要额外引入一下
          {
            name: 'vue-demi',
            var: 'VueDemi',
            path: 'https://unpkg.com/vue-demi@0.13.1/lib/index.iife.js'
          },
          {
            name: 'pinia',
            var: 'Pinia',
            path: 'dist/pinia.iife.js'
          },
          // {
          //   name: 'echarts',
          //   var: 'Echarts',
          //   path: 'dist/echarts.js'
          // },
          // {
          //   name: 'zrender',
          //   var: 'zrender ',
          //   path: 'dist/zrender.js'
          // },
        ],
      }),
    ],
    resolve: {
      alias: {
        '@': fileURLToPath(new URL('./src', import.meta.url))
      }
    },
    server: {
      // host设置为true才可以使用network的形式,以ip访问项目
      host: true,
      port: 8082,
      open: false,
      hmr: {
        overlay: false
      },
      // 跨域设置允许
      cors: false,
      // 如果端口已占用直接退出
      strictPort: false,
      proxy: getProxyConfig()
    },
    // css全局配置
    css: {
      preprocessorOptions: {
        // less: {
        //     javascriptEnabled: true,
        //     additionalData:  `@import "${resolve(__dirname, 'src/assets/styles/base.less')}";`
        // }
        less: {
          modifyVars: {
            hack: `true; @import (reference) "${resolve('src/assets/styles/variables.less')}";`,
          },
          javascriptEnabled: true
        }
      }
    },
    build: {
      minify: "terser", // 必须开启:使用 terserOptions 才有效果
      terserOptions: {
        compress: {
          drop_console: process.env.NODE_ENV === 'production' ? true : false,
          drop_debugger: process.env.NODE_ENV === 'production' ? true : false,
        },
      },
      chunkSizeWarningLimit: 1024, // 文件最大报警阈值设置
      rollupOptions: {
        // 静态资源分类打包
        output: {
          // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
          globals: {},
          chunkFileNames: 'assets/js/[name]-[hash].js',
          entryFileNames: 'assets/js/[name]-[hash].js',
          assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
          manualChunks(id) { // 静态资源分拆打包
            if (id.includes('node_modules')) {
              return id.toString().split('node_modules/')[1].split('/')[0].toString();
            }
          }
        }
      },
    },
  }
})

总结

1、视图分析可帮助我们在构建后清晰的了解项目资源情况(rollup-plugin-visualizer插件的引入)
2、CDN引入资源,上述配置实际有三种配置方法,其中两种需要引入插件实现

  • 直接使用vite-plugin-cdn-import插件引入,根据配置自动引入所需资源
  • 使用rollup-plugin-external-globals插件,结合rollupOptionsexternal配置资源变量,还得手动在 Index.html引入所需资源
  • 结合rollupOptionsexternaloutput直接配置所需资源变量,无需手动引入资源及安装其他依赖

3、依赖分包直接在rollupOptions根据官网说明配置即可
4、使用vite-plugin-compression插件开启gzip压缩,且服务器配置开始压缩格式(本文使用了nginx的配置)

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。