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

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

📄 既然做了那就彻底点,原来的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,我直接移除,都这么多年了你还用不支持这些语法的浏览器是不是该升级一下了😊😊😊
  • 其他的配置视情况而定,在插件列表中找到对应的插件就好

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

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变量

less 复制代码
// 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文件用于存放这些变量

arduino 复制代码
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'.

大概就是这个意思

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中的配置如下

arduino 复制代码
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中能自动解决这个问题😒

kotlin 复制代码
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可能和原来预想的不同,我就是因为这个原因直接白屏了,因为后面模块名不对导致加载逻辑全错,下面贴出代码对比

javascript 复制代码
 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写的就不贴了

javascript 复制代码
 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功能就好,非常方便

javascript 复制代码
//   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版本

kotlin 复制代码
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) 系统调用

javascript 复制代码
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才知道入口

xml 复制代码
<!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也是

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

相关推荐
喜欢踢足球的老罗13 分钟前
一张跨域图的“四次换乘“:blob URL 与 Chrome 扩展架构里的工程艺术
前端·chrome·架构
程序员黑豆13 分钟前
AI全栈开发 - Java:基本数据类型 vs 引用数据类型的内存存储
java·前端·ai编程
FserSuN15 分钟前
Chrome CORS / PNA / LNA 问题排查与解决方案
前端·chrome
小小小小宇23 分钟前
Claude Code 自动运行方法大全
前端
道友可好24 分钟前
AI 测试全绿,代码却是错的
前端·人工智能·后端
国科安芯44 分钟前
商业航天通信载荷数字处理单元供电架构研究——基于ASP7A84AS的高精度低压差线性稳压器技术分析
前端·单片机·嵌入式硬件·fpga开发·架构·安全性测试
TangentDomain1 小时前
AI 写代码时代,游戏 UI 架构为什么停在 MVP?
前端·游戏·架构
英勇无比的消炎药1 小时前
前端提效神器全新AI组件库TinyRobot改写日常开发模式
前端·vue.js
GuWenyue1 小时前
10分钟搞定TodoList实战!从0搭建Bun+TS的RESTful接口服务
前端·typescript·bun