将vue-cli搭建的Vue前端工程更新构建工具为pnpm+vite经验分享
Vue

将vue-cli搭建的Vue前端工程更新构建工具为pnpm+vite经验分享

朱治龙
2023-06-24 / 0 评论 / 21 阅读 / 正在检测是否收录...

缘起

我们团队负责的超算云服务控制台前端工程(console-ui)从提交第一行代码(2021-03-18)至今已 2 年多时间,两年多的时间内随着需求的增加,前端工程也越来越庞大,陈旧的基础设施暴露出了如下问题:

1、Node.js 版本仍然使用 2021 年发布的 14.x,而最新的 Node 大版本已更新到20.x,仍然使用旧版本导致一些构建工具版本不能随意更新,也同时留下一定的安全隐患。

2、项目目前仍使用两年前发行的 Webpack4.46.0 版本作为项目构建工具,随着项目逐渐庞大,Webpack 在开发时编译项目时间通常达到 2 分钟以上,严重影响开发效率。

3、前端生态日新月异,守着老旧的生态,不便于团队成长。

借本次升级 console-ui 核心 vue版本的契机,考虑同时升级到新的构建工具。Vite 从 2021 年发布以来,在构建速度及开发体验上一直口碑不错,经过两年多的迭代,也趋于稳定,完全可以应用于产线了。且根据官方的介绍 Vite 4.3 开始的版本比之前的版本在开发及构建速度上又有了成倍的提升:
vitejs版本更新截图
发行日志见链接:https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md

开始折腾

1、升级 Node.js

目前 console-ui 构建还是用的Node.js 14.x, 而现在 Node.js 的 LTS 版本是18.x,按照 Node.js 的版本发行规律,预计目前最新的 20.x 版本在今年的 10 月份将成为下一个 LTS 版本,且 Node.js 的向下兼容性较强,所以目前咱们可以拥抱 Node.js 20.x 相关生态。所以本次升级就直接选用 Node.js 20.x 了。

开发机是使用nvm管理 Node.js 多版本的。使用如下命令安装并使用Node.js 20:

nvm install 20.3.0
nvm use 20.3.0

2、升级构建工具

在升级过程如下:

创建初始工程

使用官方开始页面提供的命令初始化一个初始工程:

pnpm create vite console-ui --template vue

3、console-ui 已有功能最简验证

3.1、支持多主题

console-ui的设定是可以打包不同主题的页面,构建时根据 env 变量打包不同主题的版本,然后采用不同主题文件。经验证vite也支持,vite.config.js 核心配置代码如下:

css: {
  preprocessorOptions: {
    scss: {
      additionalData: `@import "@/styles/themes/${env.VITE_APP_DIST_MODE}.scss";`
    }
  }
}

由于不同的主题有些不同的资源文件(如favicon.ico、主题相关的图片等)需要引用,在这里我们在public 目录下新建2个目录用于管理不同主题资源文件的管理,然后配置不同的publicDir:

publicDir: `./public/${distMode}`,

3.2、验证多页面支持

console-ui提供针对普通用户的 UI 界面,同时也提供管理员使用的维护界面,这两套界面在原工程是使用多页面机制实现的。经验证,vite 也能很好的支持,核心配置代码如下:

plugins: [
  createHtmlPlugin({
    minify: true,
    pages: [
      {
        entry: '/src/main.js',
        filename: 'index.html',
        template: 'index.html',
        injectOptions: {
          data: {
            title: '超算云服务控制台'
          }
        }
      },
      {
        entry: 'src/pages/admin/index.js',
        filename: 'admin.html',
        template: 'admin.html',
        injectOptions: {
          data: {
            title: '超算运服务控制台-系统维护'
          }
        }
      }
    ]
  })
]

4、开始整合

整合时会涉及一些资源文件引用的改造,一般改动会比较多,为不影响主分支开发,建议单独开个分支处理。整合过程比较繁琐,就不一一罗列了,主要涉及如下事项:

  • 替换 ENV 变量:vue-cli 工程的环境变量都是以Vue_APP 开头的变量,可以全局替换为VITE_APP开头,而且 Webpack 工程一般使用 process.env.XXX 访问ENV变量,而 Vite 是使用 import.meta.env.XXX 访问 ENV 变量,为了少改动,可以使用如下方式继续使用process.env.XXX 方式访问:
define: {
  'process.env': { ...env }
}
  • 调整资源引用问题:Webpack 项目多数使用require('xxx')的方式在 JS 里引入资源,确实特别的方便,但是这一套在 Vite支持不是很好,即便有插件支持(如vite-plugin-commonjs)但依然没有 Webpack 的 require 机制强大。一些不需要参与打包的资源可以放在 public 目录,然后使用URL路径访问,参与打包的文件,可改为 import XXX from 'xxxx.png'的方式引入

整体改造过程提交记录截图如下:
改造过程提交记录截图

优化

整合后发现打包的文件比原 Webpack 打包的 dist 目录更大,前端工程层面的优化,主要以从以下几点着手处理:

  • 压缩:可采用vite-plugin-compression插件对打包后的文件进行了压缩处理
  • 静态资源上CDN:优化过程主要使用rollup-plugin-visualizer 对打包后的文件进行分析,对一些占用打包空间较大的文件使用CDN引入的方式进行优化
  • 按需引入:如lodashmoment.js等,尽量按需引入,减少非必须的语言包等,能很大程度的减小打包体积。

附上本次改造核心的完整 vite.config.js 文件内容:

import path from 'path'
import vitePluginCommonjs from 'vite-plugin-commonjs'
import { createHtmlPlugin } from 'vite-plugin-html'
import { visualizer } from 'rollup-plugin-visualizer'
import viteCompression from 'vite-plugin-compression'
import externalGlobals from 'rollup-plugin-external-globals'
// import eslintPlugin from 'vite-plugin-eslint'

import { defineConfig, loadEnv } from 'vite'
import legacy from '@vitejs/plugin-legacy'
import vue2 from '@vitejs/plugin-vue2'
export default ({ mode }) => {
  const env = loadEnv(mode, process.cwd())
  const distMode = env.VITE_APP_DIST_MODE || 'paratera'
  const cdn = {
    scripts: mode.includes('development') ? [] : [
      'https://statics.paratera.com/console/libs/vue.2.7.14.min.js',
      // 'https://cdn.jsdelivr.net/npm/vue-router@3.6.5/dist/vue-router.min.js',
      // 'https://cdn.jsdelivr.net/npm/vuex@3.6.2/dist/vuex.min.js',
      'https://statics.paratera.com/console/libs/element-ui.2.15.13.min.js',
      'https://statics.paratera.com/console/libs/exceljs.4.3.0.min.js'
    ]
  }
  // https://vitejs.dev/config/
  const viteConfig = {
    base: '/',
    publicDir: `./public/${distMode}`,
    define: {
      'process.env': { ...env }
    },
    plugins: [
      vue2(),
      // eslintPlugin(),
      createHtmlPlugin({
        minify: true,
        pages: [
          {
            entry: '/src/main.js',
            filename: 'index.html',
            template: 'index.html',
            injectOptions: {
              data: {
                title: '超算云服务控制台',
                cdn
              },
              tags: [
                {
                  injectTo: 'body-prepend',
                  tag: 'div',
                  attrs: {
                    id: 'tag1'
                  }
                }
              ]
            }
          },
          {
            entry: 'src/pages/admin/index.js',
            filename: 'admin.html',
            template: 'admin.html',
            injectOptions: {
              data: {
                title: '超算运服务控制台-系统维护',
                cdn
                // injectScript: `<script src="./inject.js"></script>`,
              }
            }
          }
        ]
      }),
      vitePluginCommonjs(),
      legacy({
        targets: ['ie >= 11'],
        additionalLegacyPolyfills: ['regenerator-runtime/runtime']
      }),
      viteCompression({
        verbose: true, // 是否在控制台输出压缩结果
        disable: false, // 是否禁用,相当于开关在这里
        threshold: 10240, // 体积大于 threshold 才会被压缩,单位 b,1b=8B, 1B=1024KB  那我们这里相当于 9kb多吧,就会压缩
        algorithm: 'gzip', // 压缩算法,可选 [ 'gzip' , 'brotliCompress' ,'deflate' , 'deflateRaw']
        ext: '.gz' // 文件后缀
      })
    ],
    resolve: {
      extensions: ['.js', '.vue', '.json'],
      alias: [
        {
          find: /@\/.+/,
          replacement: (val) => {
            return val.replace(/^@/, path.resolve(__dirname, './src/'))
          }
        },
        {
          // this is required for the SCSS modules
          find: /^~(.*)$/,
          replacement: '$1'
        }
      ]
    },
    css: {
      preprocessorOptions: {
        scss: {
          additionalData: `@import "@/styles/themes/${distMode}.scss";`
        }
      }
    },
    server: {
      port: 8686
    },
    build: {
      // target: 'es2015',
      // cssTarget: 'chrome80',
      // brotliSize: false,
      // chunkSizeWarningLimit: 2000,
      // commonjsOptions: {
      //   //  改为 ture 后就会转化 require 语法
      //   transformMixedEsModules: true
      // },
      rollupOptions: {
        input: {
          index: path.resolve(process.cwd(), 'index.html'),
          admin: path.resolve(process.cwd(), 'admin.html')
        },
        external: [
          // 'vue',
          'element-ui',
          'exceljs'
        ],
        plugins: [
          externalGlobals({
            vue: 'Vue',
            // 'vue-router': 'VueRouter',
            // vuex: 'Vuex',
            'element-ui': 'ELEMENT',
            'exceljs': 'ExcelJS'
          })
        ],
        output: {
          chunkFileNames: 'assets/js/chunks/[name].[hash].js',
          assetFileNames: (chunkInfo) => {
            // 用后缀名称进行区别处理
            // 处理其他资源文件名 e.g. css png 等
            let subDir = 'images'
            const extName = path.extname(chunkInfo.name)
            if (['.css'].includes(extName)) {
              subDir = 'css'
            }
            if (['.woff', '.ttf'].includes(extName)) {
              subDir = 'fonts'
            }
            return `assets/${subDir}/[name].[hash].[ext]`
          },
          // 入口文件名
          entryFileNames: 'assets/js/[name].[hash].js'
        }
      }
    }
  }
  const lifecycle = process.env.npm_lifecycle_event
  if (lifecycle.includes(':report')) {
    viteConfig.plugins.push(visualizer({ open: true, brotliSize: true, gzipSize: true, filename: './dist/report.html' }))
  }
  return defineConfig(viteConfig)
}

升级前后 package.json 比对:

升级前后 package.json 比对

成效

对比项优化前(Webpack)优化后(Vite)
安装依赖66.60s17.2
开发启动110228ms2536ms
打包部署123.77s1m 57s

对比截图

安装依赖:

  • Webpack ↓
    webpack 安装依赖耗时截图
  • Vite ↓
    Vite 安装依赖耗时截图

开发启动:

  • Webpack ↓
    Webpack 开发启动耗时截图
  • Vite ↓
    Vite 开发启动耗时截图

打包截图:

  • Webpack ↓
    Webpack 打包耗时截图
  • Vite ↓
    Vite 打包耗时截图
0

评论 (0)

取消