Vue2 升级 Vue3 - 过程记录

2,227 阅读5分钟

环境更新

package.json

// common
    +  "@vue/compat":                          "^3.2.47",
       "vue":                   "2.6.11"   ->   "3.2.47",
       "vue-router":            "3.1.6"    ->   "4.0.6",
       "vuex":                  "3.6.2"    ->   "4.1.0"
    +  "@vue/compiler-sfc":                    "^3.2.47",
       "vue-loader":            "15.9.8"   ->  "17.0.1",
    -  "vue-template-compiler": "2.6.11"
       "webpack-cli":           "4.9.0"    ->  "4.10.0"
       "webpack-dev-server":    "4.3.0"    ->  "4.11.1"
       "vue-loader":            "15.9.8"   ->  "17.0.1"
      
// modules
    -  "element-ui":            "2.15.9"
    +  "element-plus":                          "2.2.0"
    +  "@element-plus/icons-vue":               "2.1.0"
    -  "vue-code-diff":         "^1.2.0"
    +  "v-code-diff":                           "1.3.0"
       "vue-form-json-schema": "2.9.2"     ->   "3.0.0-alpha.0"
       "@fortawesome/vue-fontawesome": "2.0.8"->"3.0.3"
    
// test
       "vue/test-utils":        "1.3.4"    ->  "2.3.2"
    +  "@vue/vue3-jest":                       "27.0.0"
    +  "babel-jest":                           "27.5.1"
       "jest":                  "26.4.2"   ->  "27.5.1"
    +  "jest-environment-jsdom":               "27.5.1"
    -  "vue-jest":              "3.0.7"
    +  "vue3-jest":                            "27.0.0-alpha.1"            
    

Error & Warning

Module not found: Error: Can't resolve 'vue' in ...

vue2更新为vue3的初始,需要引入@vue/compat作为桥梁。 在webpack.conf.js中,加入:

    alias: {
      'vue': '@vue/compat'
    }
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          compilerOptions: {
            compatConfig: {
              MODE: 2
            }
          }
        }
      },
   .....

注:全部替换结束后,最好移除@vue/compat,结束兼容模式。

Vue packages version mismatch: vue & vue-template-compiler

vue 3已经不再使用vue-template-compiler,而是改为使用@vue/compiler-sfc

但是,移除之后会造成新的error: [vue-loader] vue-template-compiler must be installed as a peer dependency, or a compatible compiler implementation must be passed via options.

解决方法是升级vue-loader到^v17

sass-loader > this.getOptions is not a function

那就先不要升级sass loader啊!!!

高级别的sass-loader:minimum supported webpack version is 5!

vue-rouder > Can't import the named export 'computed' from non EcmaScript module (only default export is available)

为mjs文件加入新规则:

  {
    test: /\.mjs$/,
    include: /node_modules/,
    type: "javascript/auto"
  }

vue-router > Cannot read properties of null (reading 'parent')

Uncaught TypeError: Cannot read properties of null (reading 'parent')
    at warnDeprecatedUsage (vue-router.mjs?6605:2457:1)

这个bug有毒吧!!!!!

只要先mount vueApp,再use(router)就好了!!!

搞了半天!!!无能咆哮!!!

但是顺序一搞,又来一个新的warningFailed to resolve component: [router-view]

所以这个solution是错的啊啊啊!!!还是要先use再mount啊!!!

最后的solution居然是!!!把vue-router的版本从4.1降到4.0.6!!!

4.0.6!!!

业务代码重构

gogocode

 npm install gogocode-cli
 sudo gogocode -s ./src -t gogocode-plugin-vue -o ./src-out // v2 -> v3
 gogocode -s ./src-out -t gogocode-plugin-element -o ./src-out1 // element-UI -> element-Plus

断开所有router和UI引用,逐个文件进行修改替换。

最后记得移除package.json里的gogocode

重要改动

在vuex中引用vue实例

vue2: this._vm

vue3: 需要在引入组件的时候,传入vueApp

如:需要在vuex中使用全局的$api

main.js:

import { createStore } from './store/store.js'
import ApiPlugin from './request/api/api.js'

const store = createStore(vueApp)
vueApp.use(store)

store.js:

import { createStore as createVuexStore } from 'vuex'

export const createStore = (vueApp) => {
  return createVuexStore({
    modules: {
      moduleA: ModuleA(vueApp),
    },
  })
}

moduleA.js:

export default (vueApp) => {
    
    const _vm = vueApp.config.globalProperties
    
    return {
    ...
    actions: {
        func () {
            _vm.$api.funcName()...
        }
    }
}

Error & Warning

升级素材库

element-UI

升级为element-Plus

可以通过gogocode来转换代码。但必须人工校验!!!(实际上我对比了前后的code,似乎好像可能没有什么改动……)

参考# Element Plus Breaking Changes

其中,icon的引用是较大的改动,需要逐个修改。其他的组件,对照link中的表格一一对应替换调整即可。

icon

v2: <el-button icon="search"> & <el-button icon="fa fa-save">

v3:

对于el-icon:<el-button :icon="Search">其中Search为引入的icon component.

引入其他icon:<el-button><i class="fa fa-save"></i></el-button>

引用单组建

当单个组建引用时,注意组建名的改变。如

v2:import { Message } from 'element-ui'

v3:import { ElMessage } from 'element-plus'

样式微调

配色: 采用use的方式引入,适用于较多组件样式都需要修改的情形。对于只需要调整主题色的情形,只修改var比较方便。

//element-variables.scss:root {
  --el-color-primary: green;
  --el-color-primary-light-3: #7cb718;
  --el-color-primary-light-5: #97c941;
  --el-color-primary-light-7: #a8ce66;
  --el-color-primary-light-8: #d0e4ad;
  --el-color-primary-light-9: #f8feed;
  --el-color-primary-dark-2: darkGreen;
}

在main.js中引入:

import ElementPlus from 'element-plus'
import 'element-plus/theme-chalk/src/index.scss'
import './styles/element-variables.scss'

可能需要单独调整的样式:

.el-submenu中的hover的背景色
.el-menu--popup-right-star的margin,即子目录的显示位置
.el-dialog__headerbtn的close btn位置

vue-form-json-schema

使用vue-form-json-schema@3.0.0-alpha.0

vue3-form-json-schema根本不能用!

升级版本之后,需要有以下改动:

  • el-row和el-col不再自动转为div,需要手动处理span和offset
  • el-input不再自动转为div。需要手动设置type=number的形式的class。
  • el-icon设置class不再生效,需要重新引入。
  • el-button和el-icon的click事件不再生效,需要额外定义。
  • el-checkbox
  • el-select / el-option全部平铺显示,组件引用方法改变。改使用HTML中的select和options。
    • multiple需要另外设计 -> 哭了啊html的multiple select行为是什么鬼!
    • remote需要另外设计

因为对element-plus不支持,且alpha版本不稳定,所以迟早要替换它。

el-select

  1. 创建option children,包括绑定onClick事件。使用<ul><li>
  2. 创建selection component
  •       第一层div,绑定onClick事件,触发点击时展开dropdown-list
    
  •       第二层divinput wrapper,注入input外层的class
    
  •       第三层,包括input,dropdownComponent,suffix icon
  1. 处理isMultiple,包括鼠标事件冒泡和值回传,还有高度自适应。
  2. 处理remote,提前fetch取值,完成后eventBus触发刷新回流。
  3. 处理label,不显示原始value,显示可读的label。

很!麻!烦!而且不!好!用!

所以!!!前面的都是错的!!!

最后,真正的解决方法:

   <vue-form-json-schema 
     :components="components"
   />

import { shallowRef } from 'vue'

import {
    ElButton, ElSelect, ElOption, ElInput, ElCheckbox, ElTooltip, ElTag,
    ElSelectTag, ElIcon, ElFormItem, ElOptionGroup
} from 'element-plus'

data () {
    return {
         components: {
            'el-button': shallowRef(ElButton),
            'el-icon': shallowRef(ElIcon),
            'el-select': shallowRef(ElSelect),
            'el-option': shallowRef(ElOption),
            'el-input': shallowRef(ElInput),
            'el-checkbox': shallowRef(ElCheckbox),
            'el-tooltip': shallowRef(ElTooltip),
            'el-tag': shallowRef(ElTag),
            'el-select-tag': shallowRef(ElSelectTag),
            'el-form-item': shallowRef(ElFormItem),
            'el-option-group': shallowRef(ElOptionGroup)
      },

vue-code-diff

直接转为v-code-diff,丝滑~ # v-code-diff,一个 vue2/3 可用、更多特性支持的代码对比插件

vue-js-modal

vue3不支持了!!!考虑换组件!!!

Error & Warning

Extraneous non-props attributes (class) were passed to component

对于el-dialog,不应该el-dialog层传入id和class。

unit test

升级module

JEST 27.x

vuex传入vueApp参数

由于需要在store中使用vm,所以需要在store引入vueApp。(详见 ### 在vuex中引用vue实例)

在unit test中,也需要进行引用。

import { createStore } from 'vuex'

...
const store = (vueApp) => {
  vueApp.config.globalProperties.$store = createStore({
    modules: {
      MODULEA: cloneDeep(MODULEA(vueApp)),
    }
  })
}
...
wrapper = mount(App, {
    global: {
        plugins: [store]
    }
}

wrapper.text拿不到东西

打出wrapper.html()发现,由于Element-Plus的dropdown是默认插入到Body层的,导致HTML导出为:

...
<!--teleport start-->
<!--teleport end-->
...

需要把el-dropdown设置:teleported="false"

wrapper.vm为空{}

mount之后,获取wrapper.vm,console出来是空的。

检查发现是因为vue文件中使用了setup。

# Bug: wrapper.vm does not contain props when using script setup syntax with Vue 3.2.45 and VTU 2.2.2 #1863

setMethod不再支持

直接在mount之后赋值。

  wrapper.vm.initJsonEditor = () => {
    console.log('debug ==> mock init')
    jest.fn()
  }

test router

before:expect(router.currentRoute._value.query.tab).toBe('components')

afterexpect(router.history.current.query.tab).toBe('components')

Error & Warning

Cannot find module 'vue-template-compiler

babelJest.getCacheKey is not a function

Cannot read properties of undefined (reading 'testEnvironmentOptions')

以上都是jest相关版本问题!!!

vue3-jest,jest,vue/unit-test,babel-jest都需要安装到互相适配的版本上!!!巨坑!!!

feat: support jest v27 in vue3-jest #343

Support jest 27 #344

还有一些break changes: # Migrating from Vue Test Utils v1

一个巨坑

如果webpack在打包过程中,不打包vuex的话,会导致vuex中的值无法更新!!!可能跟reactive机制相关!!!

耗时记录

project 1: vue 2.6.12 & vuex 3.6.2 & webpack 4 & element-UI

2023/3/13 开始知识调研 & 环境替换

2023/3/16 开始代码调整

2023/3/20 element-UI -> element-plus 1d

2023/3/21 vue-form-json-schema 修改component形式

2023/3/29 升级unit test环境,修改test代码

2023/4/4 上传到staging server并测试

2023/4 部署到production server

2023/4 处理非block的一系列改动。

  • 彻底替换vue-form-json-schema
  • 替换butterfly
  • 替换eventBus
  • 使用composition API优化代码
  • 移除构建版本

project 2: vue 2.6.14 & vuex 3.6.2 @ webpack 4 & electron 13.6.9

2023/4/18 开始环境替换