普通视图

发现新文章,点击刷新页面。
今天 — 2026年1月1日首页

详解把老旧的vue2+vue-cli+node-sass项目升级为vite

作者 mikan
2026年1月1日 16:21

🎯 实在是太慢了,影响开发效率,

📄 既然做了那就彻底点,原来的yarn 也换为了 pnpm , node-sass 换为了 dart-sass, vue-cli 换为 vite 7+

首先要做好思想准备,你的耐心是有限的,但是面对你的是无穷无尽的报错和各种奇怪的原因跑不起来的深渊,不过我觉得报错才是我们的朋友,因为白屏才是最可怕的,什么信息也没有,要自己去找

下面就开始了,我们想一想,如果升级打包工具的话,哪些写法要变呢,聪明的你应该已经想到了,原来依赖vue-cli(webpack)环境的部分肯定是用不了了的,所以我们要找到这些替代方案,比如垫片和适配器和改为新的api方法.还有就是原来解析.vue文件的loader没了,现在要换人了那就是 vite-plugin-vue2 ,还有其他的插件和loader,根据你的项目而定,都需要被替换.

大部分插件都可以在这里面找到 vite插件列表

1 更换环境

首先vite和 vite-plugin-vue2 装上,有两个版本,2.7以上和非2.7以上版本,我这边项目很老,而且还有些原来外包魔改后的库依赖2.5,我就不升级了,老实用vue2.5x的版本

Vue2 plugin for Vite

Vite plugin for Vue 2.7

之后就是需要告诉vite如何打包我们的项目,我们只需要写vite.config.js就好了

  • 其中 ClosePlugin 是解决因为打包的时候会挂在built in 那个地方不正确退出
  • vueDevTools 在vue2中用不了,看了下issue,只能用浏览器插件了,所以只能 codeInspectorPlugin 将就下了
  • css预处理器,一般情况下都不需要配置,只需要安装sass等依赖就好了,但是我这边不一样,原来外包的人用了很多的骚操作
  • 至于babel和core-js,我直接移除,都这么多年了你还用不支持这些语法的浏览器是不是该升级一下了😊😊😊
  • 其他的配置视情况而定,在插件列表中找到对应的插件就好

下面是我的配置,给大家用来参考

import { defineConfig, loadEnv } from 'vite'
import { createVuePlugin } from 'vite-plugin-vue2'
import { codeInspectorPlugin } from 'code-inspector-plugin'
import { resolve } from 'path'
import ClosePlugin from './vite-plugin-close.js'

// 使用 defineConfig 定义 Vite 配置
export default defineConfig(({ mode, command }) => {
  const env = loadEnv(mode, process.cwd())
  console.log(env)

  return {
    base: './',
    server: {
      port: 8090,
      host: true,
      open: true,
      proxy: {
        '/api-test': {
          target: 'http://172.20.203.30:18000',
          changeOrigin: true,
          rewrite: (p) => {
            const s = p.replace('/api-test', '')
            // console.log(`proxy to test =>  ${p}`);
            return s
          }
        }
      }
    },

    // 配置插件
    plugins: [
      ClosePlugin(),
      codeInspectorPlugin({
        bundler: 'vite'
      }),

      //  oops vue2 不支持 😭😭😭
      // vueDevTools({
      //   launchEditor: env.VITE_LAUNCH_EDITOR ?? 'code'
      // }),
    ],
    css: {
      preprocessorOptions: {
        scss: {
          additionalData: (content, filename) => {
            return getAdditionalData(filename) + content
          },
          // 或者指定禁用特定警告
          silenceDeprecations: [
            'legacy-js-api',      // 旧版 JS API
            'global',             // 全局变量
            'import',             // @import 语法
            'color',              // 旧的颜色函数
            'division',           // 除法运算
            'mixin',              // 混合器警告
            'selector'           // 选择器警告
          ]
        }
      }
    },

    // 配置模块解析规则
    resolve: {
      // 配置路径别名
      alias: {
        '@': resolve('src'),
        'element-ui-theme': resolve('node_modules/element-ui/packages/theme-chalk/src')
      },
      // https://cn.vitejs.dev/config/#resolve-extensions
      extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
    }
  }
})

function getAdditionalData(filePath) {
  // 根据文件路径决定是否注入
  if (filePath.includes('xr-theme.scss')) {
    return '' // 不向 xr-theme.scss 中的文件注入
  }

  return `
    @import "@/styles/xr-theme.scss";
    @import "element-ui/packages/theme-chalk/src/common/var.scss";
  `
}
css 相关
  • 首先如果你换了dart-sass 的话,会有很多的报错 比如 /deep/ 需要用 ::v-deep 来替换

  • 原来scss中还用了这种特殊的语法,在js中使用css变量

// the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export {
  colorPrimary: $--color-primary; //0052CC
  colorBlack: $--color-black; //  172B4D
  colorSuccess: $--color-success; //  36B37E
  colorWarning: $--color-warning; //  FFAB00
  colorDanger: $--color-danger; //  FF5630
  colorN40: $--color-n40; //  DFE1E6
}

在vite中暂时没有找到很好的支持,所以建立了一个js文件用于存放这些变量

export const XRTheme = {
  colorPrimary: '#0052CC',
  colorBlack: '#172B4D',
  colorSuccess: '#36B37E',
  colorWarning: '#FFAB00',
  colorDanger: '#FF5630',
  colorN40: '#DFE1E6',

  //   ......
}

之后用regex全局替换,要把全部的import xrTheme from '@/styles/xr-theme.scss' 这种替换为 import { XRTheme as xrTheme } from '@/common/cssTheme.js'.

大概就是这个意思

"import xrTheme from '@/styles/xr-theme.scss'".replace(/import +(xrTheme) +from +'@\/styles\/xr-theme.scss'/,"import { XRTheme as $1} from '@/common/cssTheme.js'")

原来人还有一个骚操作,全局注入导入

在vue-cli中的配置如下

const oneOfsMap = config.module.rule('scss').oneOfs.store
oneOfsMap.forEach((item) => {
  item
    .use('sass-resources-loader')
    .loader('sass-resources-loader')
    .options({
      resources: [
        resolve('src/styles/xr-theme.scss'),
        resolve(
          'node_modules/element-ui/packages/theme-chalk/src/common/var.scss'
        )
      ]
    })
    .end()
})

vite中scss有支持,我写为函数形式是因为他们要注入 @import "@/styles/xr-theme.scss"; 但是向 xr-theme.scss 文件注入会无限递归,所以需要限制,但是不知道为什么vue-cli中能自动解决这个问题😒

css: {
  preprocessorOptions: {
    scss: {
      additionalData: (content, filename) => {
        return getAdditionalData(filename) + content
      },
    }
  }
}


function getAdditionalData(filePath) {
  // 根据文件路径决定是否注入
  if (filePath.includes('xr-theme.scss')) {
    return '' // 不向 xr-theme.scss 中的文件注入
  }

  return `
    @import "@/styles/xr-theme.scss";
    @import "element-ui/packages/theme-chalk/src/common/var.scss";
  `
}

当然还会有些警告,比如scss某些语法已经过时了,但是感觉暂时也没有很好的方法来换他们,因为感觉不是一行regex能搞定的,最好有语法分析,至于官方的迁移工具是不支持.vue文件的.所以暂时没时间搞了

这一通操作下来改了180+个文件

js和环境相关

原来导入了一些函数,但是不存在,cli中不会报错,但是vite中会找不到,需要对这些前人留下的坑全部给填掉

process.env.VUE_APP_CONTACT_URL 这种变量需要被替换为 import.meta.env.VITE_CONTACT_URL 记得VUE开头要换位VITE开头,不然找不到

process.env.VUE_APP_([^ ]+) 替换为 import.meta.env.VITE_($1)

require api替换 require('@/assets/callCenter/ring.png') 替换为 new URL('@/assets/img/head.png', import.meta.url).href

regex 如下 "require('@/assets/callCenter/ring.png')".replace(/require\('([^']+)'\)/,"new URL('$1', import.meta.url).href")

其中store 和 router 使用了动态导入,但是require.context是webpack的语法,看vite官方文档,有一个api比较像,注意eager要为true,不然返回的是一个Map<string,Promise<Module>>的类型

importAll(require.context('./modules', false, /.js$/)) 替换为 importAll(import.meta.glob('./modules/*.js', { eager: true }))

importAll中是modules.keys().forEach()写法,需要替换为Object.keys(modules),而且key可能和原来预想的不同,我就是因为这个原因直接白屏了,因为后面模块名不对导致加载逻辑全错,下面贴出代码对比

 const syncRouters = []
 const asyncRouterMap = []
 
-function importAll(r) {
+function importAll(modules) {
   let manageIndex = -1
-  r.keys().forEach(key => {
-    const fileName = key.slice(2, -3)
-    const itemRouter = r(key).default
+
+  Object.keys(modules).forEach(moduleKey => {
+    const fileName = moduleKey.slice(2, -3)
+    const itemRouter = modules[moduleKey].default
+    console.log(fileName,moduleKey,itemRouter)
+
     if (fileName.includes('sync')) {
       syncRouters.push(...itemRouter)
     } else {
       asyncRouterMap.unshift(asyncRouterMap.splice(oaIndex, 1)[0])
     }
   }
 }
 
-importAll(require.context('./modules', false, /\.js$/))
+importAll(import.meta.glob('./modules/*.js', { eager: true }))
 
 export {
   syncRouters,

原来还在vue代码中用过path.resolve 这种nodejs中的api,直接写一个垫片resolve之后导入,全局替换导入模块,当然你也可以写vite插件替换或者模拟虚拟模块解析之后映射,至于实现我是找ai写的就不贴了

 import { mapGetters } from 'vuex'
 import { getNavMenus } from './components/utils'
-import path from 'path'
+import { path } from '@/common/path'
 
 export default {
   name: 'CrmLayout',
           auth = this.$auth(item.permissions.join('.'))
         }
         if (!item.hidden && auth) {
           sideMenus.push({
             ...item,
             path: path.resolve(mainPath, item.path)

这一波又是60+个文件的修改

第三方依赖兼容

外包使用的vue2-org-tree 的库中找不到某个文件,其实是路径解析的问题,我们需要明确后缀,注意:vite会查看pkg.json,优先使用module

这个操作需要改源码,直接用pnpm patch功能就好,非常方便

//   pkg.json
{
    "main": "dist/index.js",
    "module": "src/main.js",
}

//   index.js
- import Vue2OrgTree from './components/org-tree'
+ import Vue2OrgTree from './components/org-tree.vue'

项目中外包使用了自己魔改的el-ui库,并且基于魔改库重写了插件,比如 el-bigdata-table 中的 render.js 用到了jsx语法,感觉没必要为了一个依赖引入jsx,所以建了一个子工程,写一套打包配置来打包为h函数版本,最后拷贝到项目,聪明的你可能要问为什么要自己写打包配置,因为他的包中只有源码,没有上传打包配置😂,下面是jsx版本

export default function render(h, oldRender) {
  return (
    <div
      style={[{height: `${this.table.virtualBodyHeight}px`}]}
      class={['el-table__virtual-wrapper', {'el-table--fixed__virtual-wrapper': this.fixed}]}
      v-mousewheel={this.table.handleFixedMousewheel}
    >
      <div style={[{transform: `translateY(${this.table.innerTop}px)`}]}>
      {
        oldRender.call(this, h)
      }
      </div>
    </div>
  );
}
打包部分

打包还有点小插曲,就是卡built in xxx.xxxs这里,看国外stack overflow中和一些文章中说要写个插件,其实就是在结束的钩子中调用 process.exit(0) 系统调用

export default function ClosePlugin() {
  return {
    name: 'ClosePlugin', // required, will show up in warnings and errors

    // use this to catch errors when building
    buildEnd(error) {
      if (error) {
        console.error('Error bundling')
        console.error(error)
        process.exit(1)
      } else {
        console.log('Build ended')
      }
    },

    // use this to catch the end of a build without errors
    closeBundle(id) {
      console.log('Bundle closed')
      process.exit(0)
    }
  }
}

模板文件修改,主要是加入 <script type="module" src="/src/main.js"></script>,这样vite才知道入口

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <link rel="icon" href="favicon.ico">
    <title>CRM</title>
  </head>
  <body>
    <noscript>
    </noscript>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

记得发布的vite的base参数也要调整,nginx也是

可能还有遗漏,但是大概就是这些了,感谢观看😊😊😊!

❌
❌