webpack3 + vue2迁移Vite5踩坑分享

355 阅读3分钟

背景

同一天新旧项目一起维护,出现反复使用nvm切换node版本,启动服务,打包等操作,该过程消耗过多的时间,因此决定升级该项目构建工具改用Vite对齐新项目,同时将Vue2.6版本升级2.7,支持Vue3语法编码

  • 旧项目(vue2+webpack3),依赖node14版本。

  • 新项目Vite+vue3,依赖node18+(或20+)版本。

项目情况

  • 项目阶段:测试修复bug阶段
  • 前端投入:1人(无多人协同合并代码冲突风险)

投入资源和风险可控,具备升级条件。

升级前后对比

  • 启动项目极快,大幅度提升开发模式体验
  • 打包时间低于预期。

备注:打包2种模式,terser模式,打包最慢,压缩率最高,支持移除日志

构建工具启动时间打包时间压缩zip
webpack330秒64秒2.21M
vite41秒49秒(terser)1.69M(terser)
vite51秒22秒(esbuild) 38秒(terser)1.66M(esbuild)1.59M(terser)

参考资料

参考链接:

vuecli+ vue2.6 升级vite4案例:

www.cnblogs.com/coderwhytop…

zhuanlan.zhihu.com/p/471966642…

Webpack + vue2.7 升级Vite4案例:

juejin.cn/post/724993…

(备注:以上案例均为升级Vite4经验,升级Vite5存在差异,例如Vite5已不支持vite-plugin-vue2插件【vue2.6】)

改造事项

Vite版本

选择Vite5(2023-11-16发布), 官方特别说明使用Rollup 4,构建性能的大幅提升。

tdesign-vue

tdesign-vue组件在Vue2.7下使用,版本后需要加上-naruto。

sass替代node-sass

使用sass插件,改动代码最多的点。需要全局替换,深度穿透样式代码写法/deep/改成::v-deep

//旧
/deep/ .xxx{}

//新
::v-deep ...{}

图片资源引入【重点】

分为img和background-image2种方式。

自动替换方法

安装vite-plugin-require-transform,自动替换require成 import。

该方案不能兼容所有场景

1.例如旧代码引入图片不采用require写法

//无法识别
<img src="~@/assets/images/common/folder.svg" />

//能识别
<img :src="require(`@/assets/images/home/star.svg`)" />

2.background-image,需替换成@符号【推荐】或改成相对路径引入

//旧
background: url('~@/assets/images/common/count-card.png');

//新
background: url('@/assets/images/common/count-card.png');
// 或
background: url('../../../../../assets/images/common/count-card.png');

3.最麻烦的场景,数据遍历动态加载图片

<img :src="require(`@/assets/images/home/star-${type}.svg`)" />

不想额外实现功能,就用原始方法,v-if把全部动态的图片都写出来

<img v-if="type === 'air'" :src="require(`@/assets/images/home/star-air.svg`)" />
<img v-if="type === 'water'" :src="require(`@/assets/images/home/star-water.svg`)" />

嫌弃太笨,就推荐官方动态URL方案,封装获取图片地址的参数

人工逐个修改

png格式

js文件内引入,只需要require 改成import。

//旧
require('../assets/images/common/upload.png')

//新 png格式
import uploadImg  from '@/assets/images/common/upload.png'
data() {
    return {
      uploadImg: uploadImg
    }
 },
svg格式

需要借助插件vite-plugin-svg-icons,单独封装组件

npm i vite-plugin-svg-icons -D

参考文档:www.cnblogs.com/wang7151000…

样式资源

引入方式

旧:

公用样式资源改成在main.js中引入,不再使用@import

import '@/assets/css/common.css';
import '@/assets/fonts/font.css';

scss变量资源(例如组件库主题色配置 全局样式变量)在vite.config.js 中引入

css: {
            preprocessorOptions: {
                scss: {
                    //   additionalData: `@use "@/styles/element/index.scss" as *;`,
                    additionalData: "@import 'src/assets/css/variable.scss';"
                },
            },
        },

样式覆盖

Vite打包插入样式资源顺序,把组件库(第三方插件)放到最后,因此在覆盖组件库样式,务必加!important

postcssrc.js

移除旧postcss插件的使用

module.exports = {
  "plugins": {
    // "postcss-import": {},
    // "postcss-url": {},
    // "autoprefixer": {}
  }
}

首先安装 autoprefixer@vitejs/plugin-legacy 这两个依赖包

npm install autoprefixer @vitejs/plugin-legacy --save-dev

创建一个名为 postcss.config.js 的文件并添加以下内容:

module.exports = {
  plugins: [require('autoprefixer')]
}

修改项目vite.config.js 文件,将其中的 plugins 字段更新为以下内容:

import legacy from '@vitejs/plugin-legacy';
export default defineConfig({
  // ...
  plugins: [
    // ...
    legacy(),
  ],
});

jsx语法

尽管已经通过createVuePlugin插件避免常规的jsx语法报错,但还是有特殊场景需要前端修改组件,例如代码中有使用jsx语法自定义内容(最常见就是Tdesign Table 单元格自定义)

{ colKey: 'sort', width: '50px', cell: () => <t-icon name='move' /> },

需要单独在组件的script标签加上lang (可以全局替换)

// 旧
<script>

// 新
<script lang="jsx">

环境配置

webpack使用process.env读取环境配置变量,如果还想保留使用process.env(不建议)

import { defineConfig, loadEnv } from 'vite'
const CWD = process.cwd();

export default defineConfig(({ mode }) => {
    const env = loadEnv(mode, CWD);
    return {
        define: {
            'process.env': env, 
        },
    }
})

Vite改用import.meta.env读取,.env配置文件记得变量名需要改用VITE前缀开头

VITE_APP_AUTH_CLIENT_ID = 'dgtools'
VITE_APP_AUTH_SERVE = 'http://192.168.0.666' 
VITE_APP_STATE = 'dg/brain'
VITE_APP_CONTEXT_PATH = '/dg/brain/api'
VITE_APP_AUTH_PATH = '/oauth/oauth'

vite.config.js示例

import { defineConfig, loadEnv } from 'vite';
import vue2 from '@vitejs/plugin-vue2';
import { createHtmlPlugin } from 'vite-plugin-html';
import { fileURLToPath, URL } from 'node:url';
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import path from 'path'
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig(({mode}) => {
    const env = loadEnv(mode, process.cwd(), '');
    let date = new Date()
    const version = `1.0.0`;
    const { VITE_APP_STATE } = env
    return {
        plugins: [
            vue2(
            ),
            createHtmlPlugin({
                minify: false, // 是否压缩 HTML
                entry: 'src/main.js', // 入口文件路径
                template: 'index.html', // 自定义模板路径,‌如果不设置则使用默认的 index.html
                inject: {
                  data: {
                    title: '系统标题',
                    // injectScript: `<link rel="icon" href="/${VITE_APP_STATE}/favicon.ico" />`,
                  },
                },
            }),
            createSvgIconsPlugin({
                // 指定要缓存的文件夹
                iconDirs: [path.resolve(process.cwd(), 'src/assets/images/svg')],
                // 指定symbolId格式
                symbolId: '[name]'
            }),
            visualizer({
                open: false,
            })
        ],
        build: {
            //https://vitejs.dev/config/build-options#build-minify
            // minify: true,
            reportCompressedSize: true,
            cssCodeSplit: true,
            // assetsDir: 'assets', 
            outDir: 'dist', // 

            rollupOptions: {
                output: {
                    // 引入文件名的名称  output.manualChunks 否则,它将会根据 chunk 的内容确定。
                    chunkFileNames: `js/[name]-[hash]-${version}.js`,
                    // 包的入口文件名称
                    entryFileNames: `js/[name]-[hash]-${version}.js`,
                    // 资源文件像 字体,图片等
                    // assetFileNames: '[ext]/[name]-[hash].[ext]',
            
                    manualChunks(id) {
                      if (id.includes('/src/')) {
                        return 'components-index'
                      }
                      // 把库全部单独分拆出来
                      if (id.includes('node_modules')) {
                        // console.log('id----', id)
                        return id
                          .toString()
                          .split('node_modules/')[1]
                          .split('/')[0]
                          .toString()
                      }
                    }
                },

            },
            minify: 'terser', // 'terser' 相对较慢,但大多数情况下构建后的文件体积更小。'esbuild' 最小化混淆更快但构建后的文件相对更大。
            terserOptions: {
                compress: {
                    drop_console: true, // 生产环境去除console
                    drop_debugger: true, // 生产环境去除debugger
                },
            }
        },
        base: VITE_APP_STATE,
        server: {
            host: '0.0.0.0',
            port: '8083',
            open: false,
            cors: true, // 允许跨域
            proxy: {
                '/dg/brain/api': {
                    target: 'http://192.168.0.666',
                    changeOrigin: true,
                    rewrite: (path) => path.replace('/dg/brain/api', '/')
                },
            }
        },
        resolve: {
            alias: {
                '@': fileURLToPath(new URL('./src', import.meta.url)),
            },
            extensions: [
                '.js',
                '.json',
                '.jsx',
                '.mjs',
                '.ts',
                '.tsx',
                '.vue',
            ],
        },
        css: {
            preprocessorOptions: {
                scss: {
                    additionalData: "@import 'src/assets/css/variable.scss';"
                },
            },
        },
    }



})

package.json插件示例

{
  "name": "yoursystemname",
  "version": "1.0.0",
  "description": "",
  "author": "",
  "private": true,
  "scripts": {
    "dev": "vite --mode development",
    "build:test": "vite build --mode test",
    "build": "vite build --mode production",
    "preview": "vite preview",
    "unit": "jest --config test/unit/jest.conf.js --coverage",
    "lint": "eslint --ext .js,.vue src test/unit"
  },
  "dependencies": {
    "@bundled-es-modules/axios": "^0.27.2",
    "@rollup/plugin-replace": "^5.0.5",
    "@vitejs/plugin-vue2": "^2.3.1",
    "animate.css": "^4.1.1",
    "axios": "^1.6.2",
    "echarts": "^5.4.1",
    "echarts-stat": "^1.2.0",
    "element-ui": "^2.15.6",
    "mapbox-gl": "^2.15.0",
    "markdown-it": "^13.0.1",
    "qs": "^6.11.0",
    "swiper": "^8.4.7",
    "tdesign-vue": "1.8.3-naruto",
    "terser": "^5.26.0",
    "vite": "5.3.3",
    "vue": "^2.7.0",
    "vue-bus": "^1.2.1",
    "vue-router": "^3.5.2",
    "vue-toastification": "^1.7.14",
    "vuedraggable": "^2.24.3",
    "vuex": "3.6.2"
  },
  "devDependencies": {
    "eslint": "^8.55.0",
    "eslint-plugin-vue": "^9.19.2",
    "rollup-plugin-visualizer": "^5.12.0",
    "sass": "~1.32.13",
    "unplugin-vue-components": "^0.26.0",
    "vite-plugin-html": "^3.2.2",
    "vite-plugin-svg-icons": "^2.0.1"
  },
  "engines": {
    "node": ">= 6.0.0",
    "npm": ">= 3.0.0"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]
}

同名组件覆盖

不同目录下的同名组件,存在覆盖的情况,目前通过import时候起别名来解决。

import BaseTagCustom from "@/pages/dataWorkbench/components/BaseTag.vue"


components: {
  BaseTagCustom,
},

结语

负责项目是vue2的前端,如果有兴趣升级Vite,希望该文档可以帮助到你。