注意:升级前,node版本需要在8.9的版本以上!
本文目录:
- 为什么要升级框架?
- 主要模块升级对应版本
- vue2.x与vue3.x的区别
- 升级过程中遇到的问题
- 项目发布
一、为什么要升级框架?
- 放弃
Object.defineProperty
,使用更快的原生Proxy
- diff算法优化使性能比vue2.x快1.2~2倍
- 组合/CompositionAPI(类似于
React Hooks
) - 模板可以有多个根元素
- 按需编译,体积比vue2.x更小
二、主要模块升级对应版本
模块 | 原始版本 | 升级版本 |
---|---|---|
node.js | v8.x | v14.17.6 |
脚手架 | @vue-cli 2.x | @vue/cli 4.x |
vue | ^2.5.2 | ^3.0.0 |
vue-router | ^3.1.3 | ^4.0.12 |
vuex | ^3.0.1 | ^4.0.0 |
ant-design-vue | ^1.5.3 | ^2.7.8 |
三、vue2.x与vue3.x的区别
3.1 vue-cli2 VS vue/cli4
3.1.1 基本文件目录的区别
下列表格中括号里为举例说明
配置项 | vue2.x | vue3.x |
---|---|---|
环境变量 | 根目录config文件夹下创建xx.env.js(test.env.js)文件 | 根目录创建以env(.env.test) 开头的文件 |
webpack | 根目录build文件夹下创建webpack(webpack.base.config.js)配置文件 | 根目录vue.config.js文件中配置 |
index.html | 初始化后在根目录下 | 初始化后在public文件夹下 |
babel | 根目录下.babelrc文件 | 根目录下.babel.config.js文件 |
3.1.2 配置代理
vue-cli2:项目根目录config文件夹下的index.js文件
module.exports = {
dev: {
proxyTable: {
"/tsapi/": { //代理地址
target: 'http://keynote-test.zhonganonline.com/tsapi/', //需要代理的地址
changeOrigin: true, //是否跨域
secure: false,
baseURL: 'http://keynote-test.zhonganonline.com/tsapi/',
pathRewrite: {
'^/tsapi/': '/'
}
}
}
}
}
vue/cli4:项目根目录vue.config.js文件
module.exports = {
devServer: {
port: 8080,
host: 'za.zhonganonline.com',
https: true,
open: true,
proxy: {
'/tsapi/': {
target: 'http://keynote-test.zhonganonline.com/tsapi/',
changeOrigin: true,
pathRewrite: {
'^/tsapi/': '/'
}
}
}
}
};
注意服务配置位置
3.1.3 配置别名
vue-cli2:项目根目录下build文件夹下webpack.base.conf.js文件
module.exports = {
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
'@static': resolve('static')
}
},
}
vue/cli4: 项目根目录vue.config.js文件
module.exports = {
chainWebpack: (config) => {
config.resolve.alias
// 将项目根目录中,src的路径配置为别名@
.set('@', resolve('src'))
.set('common', resolve('src/common'))
.set('utils', resolve('src/utils'));
}
};
通过链式操作进行更细颗粒度的webpack配置
3.1.4 启动命令
vue-cli2:项目根目录vue.config.js文件
"scripts": {
"dev": "cross-env VUE_APP_DOMAIN='chinese' webpack-dev-server --inline --progress --config build/webpack.dev.conf.js"
"build": "cross-env VUE_APP_DOMAIN='chinese' node build/build.js"
},
vue/cli4: 项目根目录vue.config.js文件
"scripts": {
"serve": "cross-env VUE_APP_DOMAIN='chinese' vue-cli-service serve",
"build": "cross-env VUE_APP_DOMAIN='chinese' vue-cli-service build"
},
vue2到vue3将 webpack-dev-server
换成了vue-cli-service
,vue-cli-service
其实执行的是 node_modules/.bin/vue-cli-service
,也是基于 webpack-dev-server
实现的。而 webpack-dev-server
又是基于node的express框架实现。
3.2 语法区别
3.2.1main.js模块
vue2.x
import Vue from 'vue';
import Antd from 'ant-design-vue';
Vue.use(Antd);
new Vue({
el: '#app',
components: { App },
template: '<App/>'
})
vue3.0
import { createApp } from 'vue';
import Antd from 'ant-design-vue';
import App from './App.vue';
createApp(App).use(Antd).mount('#app');
- vue3.0无需引入整个
vue
模块,只需按需引入createApp
注册组件即可 - vue3.0中
createApp
方法会返回一个新的应用实例,该实例不会被其他实例全局配置属性污染 - vue3.0应用模块可以使用链式调用的方式
3.2.2 router模块
vue2.x
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
const routes = [
{
path: '/',
component: () => import('../components/template/indexQuery/index.vue'),
name: 'indexQuery'
}
]
export default new Router({ routes })
vue3.0
import { createRouter, createWebHashHistory } from 'vue-router';
const routes = [
{
path: '/',
component: () => import('../components/template/indexQuery/index.vue'),
name: 'indexQuery'
}
]
export const router = createRouter({
history: createWebHashHistory(),
routes // 路由对象
});
- 同
createApp
,createRouter
不再是一个类而是一组函数 - mode: 'history' 配置已经
history
配置所取代,根据使用模式,使用不同的函数代替history: createWebHistory() hash: createWebHashHistory() abstract: createMemoryHistory() // 与浏览器分离的路由模式
3.2.3 响应式API与组合式API对比
项目中还未应用,待后期补充
3.3 Antv组件中Api升级
3.3.1 Form表单
antvue1.x 中提供了 Form、FormModel 两个表单组件, 2.0 版本中将 Form、FormModel 进行合并,保留了 FormModel 的使用方式,改名成 Form。
ant1.x
dom节点:<a-form :form="form"></a-form>
data() {
return {
form: this.$form.createForm(this, { name: "coordinated" })
}
}
validateFields((err, value) => { if (!err) {}}); // 使用form对象的属性方法
ant2.x
dom节点:<a-form :model="postData" ref="formRef"></a-form>
methods:validateFields().then(values => {});// 使用form对象的属性方法
3.3.2 图标升级
ant1.x版本全量引入图标icon增加了打包产物的尺寸,ant2.x版本中实现按需引入的方式,减少了打包后的文件体积
ant1.x
<a-icon type="smile" />
ant2.x
<template>
<smile-outlined />
</template>
<script>
import SmileOutlined from '@ant-design/icons-vue/SmileOutlined';
export default {
components: {
SmileOutlined,
},
};
</script>
3.3.3 常用组件调整API
- v-model 更改成 v-model:value
涉及组件:Radio、Mentions、CheckboxGroup、Rate、DatePicker、Select
ant1.x
<a-input v-model="postData.dicChinese"/>
ant2.x
<a-input v-model:value="postData.dicChinese"/>
v-model 更改成 v-model:checked
涉及组件: CheckableTag、Checkbox、Switchv-model 更改成 v-model:visible
涉及组件: Tag、Popconfirm、Popove、Tooltip、Moda、Dropdown
3.3.4 其他一些变化
在开发过程中经常碰到请求到的数据直接赋值后数据类型变成了Proxy,这导致我们用不了这种数据。有两种办法改变数据类型:
- 通过vue中的响应式对象可使用toRaw()方法获取原始对象
import { toRaw } from 'vue'
const trueUserList=toRaw(data)
- 通过json序列化
JSON.parse(JSON.stringify(data))
四、升级过程中遇到的问题
4.1 代码规范
由于各个项目代码格式都不一致,每个人的代码写法也不一样,导致项目代码看起来十分混乱且不美观,故而在新版本项目里结合vscode插件Prettier - Code formatter
实现代码保存时自动格式化。
4.1.1 为什么选择Prettier?Prettier和Eslint有什么区别?
Eslint主要负责质量检查、风格检查,但Prettier只负责风格检查。两者的作用都是为了让项目中的代码规范能够统一,只不过方向不一致。
4.1.2 conventional commit
conventional commit为约定式提交。为什么使用约定式提交呢?
- 自动化生成CHANGELOG日志
- 基础提交类型,自动决定语义化的版本变更
- 通知相关合作人员变更
- 触发构建和部署流程
- 后续开发人员可以看到一个更加结构化的提交历史,降低对项目做贡献的难度
代码提交规范具体流程
4.1.3 Prettier配置实现ctrl+s保存自动格式化代码
-
安装Prettier - Code formatter插件
-
选择格式化工具
-
设置vscode Ctrl+s保存代码时自动格式化代码
- 配置团队中规定好的格式(项目根目录中新建.prettierrc.js文件,文件内部写入格式)
{
"semi": true, // 需要分号
"singleQuote": true, // 引号为单引号
"trailingComma": "none" // 代码结尾不加逗号
}
tips:prettier格式化与eslint规范冲突的问题
按照我们的习惯可以更改eslint配置,也可以修改prettier配置,比如我的项目里习惯代码块结束加分号,但是eslint会产生代码报错。这种情况我们可以在eslint里rules配置项中加上 semi: 0
,意思是不校验分号。同理我们也可以在prettier配置里修改 "semi": false
项。
其他配置项均可参考以上配置
4.2 项目启动报错
yarn serve启动项目的时候报错TypeError: this.setDynamic is not a function
或者Plugin/Preset files are not allowed to export ob*jects, only functions
原因是vue里使用的是最新的babel7的版本,以@babel开头的包,但是我们升级前的项目里写的babel配置都是用的旧版本,所以在babel.config.js
里需要把babel配置项都改成最新的即可,如下代码:
module.exports = {
presets: [['@babel/preset-env', { modules: false }]],
plugins: ['@babel/plugin-transform-runtime'],
comments: false,
env: {
test: {
presets: ['@babel/preset-env'],
plugins: ['istanbul']
}
}
};
遇到类似babel的问题应该在babel配置项或者版本兼容性入手。
4.3 接入前端规范扫描
在我们的代码上传打包发版之前,需要接入前端静态扫描,以确保我们的项目在ship平台发布成功。入口:wiki前端静态扫描接入手册
五、项目发布
到这里,项目框架代码升级已经成功了,并且启动正常。现在要将最新的代码上传到git,再去ship平台进行部署。在ship平台构建过程中,遇到一些问题。
第一次打包时遇到报错
Cannot read properties of undefined (reading 'NormalModule')
推断是因为package.json文件中的一些包版本过高,而项目中使用的公司镜像版本低,所以当前的公司镜像中没有此类高版本的包。故而在Dockerfile文件中将镜像版本升级至base-registry.zhonganinfo.com/env/node-nginx:v16.13.0-centos7-ngx1.20
第二次打包遇到报错,是因为包安装不成功。将Dockerfile文件中的
run npm install
换成run npm install -legacy-peer-deps
。此步骤是因为npm7现在默认安装peerDependencies,升级过程中可能有一些模块安装时产生了版本冲突进而中断安装过程。这里使用-legacy-peer-deps参数绕过peerDependencies自动安装,这个参数会告诉NPM忽略项目中引入的相同模块不同版本的问题并继续安装,保证各个依赖间对自身所用不同版本模块共存第三次打包再次遇到报错
Cannot read properties of undefined (reading 'NormalModule')
,推测是因为vue.config.js文件中webpack配置有些问题,尝试删除less、less-loader等依赖中间省略无数次打包,调整webpack配置项写法,均有报错
打包遇到报错
Cannot read properties of undefined (reading 'preProcessor')
,直接把vue.config.js文件中webpack配置项中的pluginOptions项删除第n次打包遇到报错
Cannot read properties of undefined (reading 'style-resources-loader'
,到这里已经快崩溃了,不知道到底哪里出了问题,看了下package.json文件中各个依赖版本,尝试将webpack降级再次打包满心激动以为胜利就在眼前,结果又又又又报错了~,但是仔细一看是eslint报错,说是ant2中的v-model:value写法eslint校验不通过,所以在eslint配置中rules对象里加上一个取消eslint校验
'vue/no-v-model-argument': 'off'
。至此项目上线构建打包流程走通并部署成功
注:在部署构建阶段,本地开启和打包项目均无任何问题,一到上线打包就有报错。构建阶段数次崩溃,还好最后解决了所有问题,又进了一小步