跟随通义灵码一步步升级vue2(ts)项目到vue3版本

简介: 本文介绍了如何将基于 TypeScript 的 Vue 2 项目升级到 Vue 3。主要内容包括:依赖升级、配置文件调整、组件代码迁移、状态管理迁移以及优化策略迁移。文章提供了详细的步骤和示例代码,帮助开发者顺利完成升级。特别推荐使用通义灵码工具来加速组件和状态的转写过程。


由于typescript飞速发展,某些vue2项目也在vue3出现之前集成了typescript开发,例如我的个人网站,当时花费了不少时间。而vue3我使用一段时间后,在2022年左右开始投入生产,但是这个个站就没怎么维护了。若是想继续,升级是无法避开,毕竟vue2也不怎么熟悉了

如果vue2项目是js编写的,升级参考:跟随通义灵码一步步升级vue2(js)项目到vue3版本,本文主要讲如何将vue2的typescript项目升级成vue3,并提供一个prompt提示词,教你如何利用通义灵码快速完成组件转写

1 依赖升级

1.1 老项目依赖 - vuex-module-decorators+vue-property-decorator

老项目使用装饰器包vue-property-decorator实现vue组件的ts支持,使用vuex-module-decorators实现vuex状态管理的支持。

"scripts": {
    "dev": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "lz-string": "~1.4.4",
    "tslib": "~2.1.0",
    "vue": "2.6.10",
    "vue-router": "~3.5.1",
    "vuex": "~3.6.2"
  },
  "devDependencies": {
    "@types/js-cookie": "3.0.6",
    "@types/lz-string": "^1.3.33",
    "@vue/cli-plugin-typescript": "~3.11.0",
    "@vue/cli-service": "~3.11.0",
    "babylonjs": "~4.1.0",
    "js-cookie": "~2.2.1",
    "sass": "~1.18.0",
    "sass-loader": "~7.1.0",
    "ts-loader": "~6.2.1",
    "typescript": "~3.7.2",
    "vue-meta": "~2.3.3",
    "vue-property-decorator": "8.5.1",
    "vue-template-compiler": "2.6.10",
    "vuex-module-decorators": "^0.11.0",
    "webpack": "4.47.0"
  },

image.gif

1.2 新项目ts依赖

"scripts": {
    "dev": "vite --port 5174",
    "build": "vue-tsc && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "babylonjs": "~4.1.0",
    "dayjs": "^1.11.11",
    "lz-string": "^1.5.0",
    "vue": "^3.3.4",
    "vue-router": "^4.2.5"
  },
  "devDependencies": {
    "@types/node": "^20.14.9",
    "@vitejs/plugin-vue": "^4.2.3",
    "@vitest/ui": "^1.6.0",
    "@vue/test-utils": "^2.4.6",
    "autoprefixer": "^10.4.19",
    "jsdom": "^24.1.0",
    "postcss": "^8.4.39",
    "sass": "^1.69.5",
    "sass-loader": "^14.2.1",
    "tailwindcss": "^3.4.4",
    "typescript": "^5.0.2",
    "vite": "^4.4.5",
    "vitest": "^1.6.0",
    "vue-tsc": "^1.8.5"
  }

image.gif

1.3 升级思路

1.3.1 vue3直接支持typescript,所以去掉了装饰器式的组件定义

1.3.2 去掉vuex依赖

实际上,因为状态比较简单,直接使用vue3的reactive定义响应式状态属性state,其它的方法从actions里面提取出来,这样改动很小,每个compute属性返回一个vue3 computed的计算属性

如果是比较复杂的项目,可以考虑前期这样,后面替换成pinia,实际上我个人是不推荐使用pinia的,除非有kpi需求等

1.3.3 修改配置

这个是必须的,主要是webpack和vite配置的升级

1.3.4 修改组件代码

这个是工作量最大的,下面会讲一些注意点

1.3.5 创建vue3项目将老代码和依赖移过去(推荐)

这是我实践且推荐的方法,注意目录结构不要变,一点点的移过去会更稳

2 webpack升级到vite的配置

比较详细的官方会有,这里只是讲一些关键点

2.1 新增vite配置文件

新增vite.config.ts,配置如下(如果是采用1.3.5的推荐方法可跳过)

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  build: {
    rollupOptions: {
      //
    },
  },
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
  server: {
    proxy: {
     
    },
  },
});

image.gif

2.2 更新外部依赖配置externals

很多时候,我们会把比较大的包或常用的包通过url引入,这时候就需要修改配置,例如vue.config.js的配置如下

const path = require('path');
module.exports = {
  chainWebpack: (config) => {
    config.externals({
      vue: 'Vue',
    });
  },
};

image.gif

vue3则需要安装依赖

yarn add vite-plugin-external -D

image.gif

vite.config.ts中需要使用插件:

import { defineConfig } from 'vite';
import createExternal from 'vite-plugin-external';
export default defineConfig({
  plugins: [vue(),
            createExternal({
              externals: {
                 vue: 'Vue'
              }
            })
 ],
});

image.gif

2.3 更新代理服务器配置

vue.config.js的配置如下:

module.exports = {
  
  devServer: {
    port: 8080,
    proxy: {
      '^/api': {
        target: 'https://api.xxx.fun',
        // ws: true,
        changeOrigin: true,
        pathRewrite: {
          '^/api': 'aaa',
        },
        onProxyReq: function (request) {
          request.setHeader('origin', 'https://www.xxx.fun');
        },
      },
    },
  },
};

image.gif

在vite.config.ts中则对应:

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig({
  server: {
    proxy: {
      "^/api": {
        target: "https://api.xxx.fun",
        // ws: true,
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, "/xxx"),
        headers: {
          Origin: "https://www.xxx.fun",
        },
      },
    },
  },
});

image.gif

可以较大的变化就是头的修改和pathrewrite了,更多详细信息见:开发服务器选项 | Vite 官方中文文档

2.3 入口html文件调整

2.3.1 将index.html从public移出至更目录

2.3.2 在body最下面新增es入口

即   <script type="module" src="/src/main.ts"></script>

2.3.3 去掉以前的baseurl配置

vue2项目支持的html模板语法<%= BASE_URL %>vite下不再默认支持,去掉即可,然后修改index.html文件即可

3 常用优化策略迁移

3.1 摇树优化treeshake默认支持

由于rollup默认支持treeshake,所以可以去掉vue2的相关配置,也就是package.json中的sideEffects字段

3.2 分包优化的调整-指定分包

在vue2中分包方式和vue3没变化,都是使用import函数,但是有一点区别:

  • vue2中未命名分包会进入chunk,vue3会是一个单独的文件
  • vue2命名分包可使用下面的方式,vue3不生效需要删除注释
import(/* webpackChunkName: "aaa" */ './AAA.vue')

image.gif

  • vue3中在vite.config.ts中配置:
export default defineConfig({
  plugins: [vue({})],
  build: {
    rollupOptions: {
      output:{
        manualChunks(id) {
          if (id.includes("AAA.vue")) {
            return 'aaa'
          }
        }
      }
    },
  },
});

image.gif

3.3 小文件引入

vue2支持用require引入文件,vue3也支持使用file-loader,所以变动不大。

4 组件和状态迁移

4.1 组件代码迁移

关键步骤就是:

  • script 新增 setup
  • 去掉class和decorator
  • Prop定义使用defineProps
  • state定义使用reactive
  • compute使用computed
  • slot使用v-slot,子组件使用时用<template></template>包括实现slot的插入使用

例如一个多语言组件

<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';
import { ClientModule } from '@/store/client';
import { parseLangText } from '@/utils/basic';
@Component({ name: 'lang' })
export default class extends Vue {
  @Prop({
    required: true
  })
  private val: any;
  protected render(h: any) {
    const lang = ClientModule.lang;
    const [txt, fl] = parseLangText(this.val, lang);
    return h(
      'span',
      { class: fl === 'en' ? 'english' : 'chinese', ...this.$props },
      txt
    );
  }
}
</script>

image.gif

可以转化为

<script lang="ts" setup>
import { computed } from "vue";
import { langModule } from "../../modules";
const mod = langModule();
const props = defineProps<{
  val: any;
}>();
const text = computed(() => {
  return mod.parseText(props.val);
});
const classList = computed(() => {
  return mod.lang === "en" ? "english" : "chinese";
});
</script>
<template>
  <span :class="classList"> {{ text }} </span>
</template>

image.gif

推荐使用通义灵码来转写,提示词:将次vue2组件,转化成 vue3 SFC + typescript + setup 组件


4.2 vuex store迁移

关键点就是:

  • 用类或者一个js对象代替store,state用reactive定义实现响应式
  • 计算属性使用computed替代
  • action/mutation都定义为一个对象方法

例如 一个简单的store

import {
  VuexModule,
  Module,
  Mutation,
  Action,
  getModule
} from 'vuex-module-decorators';
import store from '../';
import { IMedia } from '@/types/media';
import Vue from 'vue';
export interface IPlayerState {
  audioList: IMedia[];
}
@Module({ dynamic: true, store, namespaced: true, name: 'player' })
class Player extends VuexModule implements IPlayerState {
  public audioList: IMedia[] = [];
  public target: IMedia | null = null;
  public get audioPlay() {
    return this.audioList.find(ele => ele.playing);
  }
  @Mutation
  public play() {
    if (this.target) {
      Vue.set(this.target, 'playing', true);
    }
  }
  @Mutation
  public setTarget(e: IMedia | null) {
    if (this.target && e && this.target.src === e.src) {
      return;
    }
    if (e !== null) {
      e.playing = false;
    }
    this.target = e;
  }
  @Mutation
  public stop() {
    if (this.target) {
      Vue.set(this.target, 'playing', false);
    }
  }
}
export const PlayerModule = getModule(Player);

image.gif

可以转化成

import { IMedia } from "../../types";
import { reactive,computed} from "vue";
type MediaPlayerInfo = {
  audioList: IMedia[];
  target: IMedia | null;
};
export class PlayerManager {
  static initialState() {
    return {
      audioList: [],
      target: null,
    };
  }
  static build(
    stateBuilder: (s: MediaPlayerInfo) => ObjectState<MediaPlayerInfo>
  ) {
    return new PlayerManager(this.initialState());
  }
public readonly state:MediaPlayerInfo
  constructor(state: MediaPlayerInfo) {
this.state=reactive(state)
}
  audioPlaying() {
    return computed(()=>this.state["audioList"].find((ele) => ele.playing));
  }
  target() {
    return computed(()=>this.state.target);
  }
  public play() {
    const target = this.state.target;
    if (target) {
      target.playing = true;
      target.playing = true;
    }
  }
  public setTarget(e: IMedia | null) {
    const target = this.state.target;
    if (target && e && target.src === e.src) {
      return;
    }
    if (e !== null) {
      e.playing = false;
    }
    this.state.target= e;
  }
  public stop() {
    const target = this.state.target;
    if (target) {
      target.playing = false;
    }
  }
}

image.gif

推荐使用通义灵码来辅助完成,提示词:将此vue2-vuex store,转化成typescript服务,状态部分使用vue3的reactive进行管理

4.3 迁移策略

4.3.1 从页面组件或store模块作为一次任务

避免漏掉,一个个的完成

4.3.2 从最细粒度的开始迁移

也就是页面所用到的最小组件开始,这样可以避免过多的报错导致代码工具或者提示不可以

4.3.3 多commit代码

哪怕没完成,也不要在未commit的时候撤销等待,避免浪费工作量

5 版本管理

5.1 可在新分支新目录下存放全部的代码

这样的好处是merge等不会出现冲突

5.2 老版本核心依赖版本,用~而不是^

例如vue2/vue-router/vuex,锁定小版本,写固定版本最好。这样的好处就是不用担心老项目出现大的变化,vue2有些版本还是会出现breaking change的,这也是我对vue比较揪心的。例如vue2.7就让一些slot不可以了。

5.3 用好通义灵码,节省90%转写时间

如果不适应代码辅助,手动一行行的还是很累的事情,尤其是类型定义等。使用工具,绝对提速10倍,除了部分配置需要手动,以及对生成代码的微调。

相关文章
|
4天前
|
SQL 人工智能 安全
【灵码助力安全1】——利用通义灵码辅助快速代码审计的最佳实践
本文介绍了作者在数据安全比赛中遇到的一个开源框架的代码审计过程。作者使用了多种工具,特别是“通义灵码”,帮助发现了多个高危漏洞,包括路径遍历、文件上传、目录删除、SQL注入和XSS漏洞。文章详细描述了如何利用这些工具进行漏洞定位和验证,并分享了使用“通义灵码”的心得和体验。最后,作者总结了AI在代码审计中的优势和不足,并展望了未来的发展方向。
|
13天前
|
存储 弹性计算 人工智能
阿里云Alex Chen:普惠计算服务,助力企业创新
本文整理自阿里云弹性计算产品线、存储产品线产品负责人陈起鲲(Alex Chen)在2024云栖大会「弹性计算专场-普惠计算服务,助力企业创新」中的分享。在演讲中,他分享了阿里云弹性计算,如何帮助千行百业的客户在多样化的业务环境和不同的计算能力需求下,实现了成本降低和效率提升的实际案例。同时,基于全面升级的CIPU2.0技术,弹性计算全线产品的性能、稳定性等关键指标得到了全面升级。此外,他还宣布了弹性计算包括:通用计算、加速计算和容器计算的全新产品家族,旨在加速AI与云计算的融合,推动客户的业务创新。
|
11天前
|
编解码 Java 程序员
写代码还有专业的编程显示器?
写代码已经十个年头了, 一直都是习惯直接用一台Mac电脑写代码 偶尔接一个显示器, 但是可能因为公司配的显示器不怎么样, 还要接转接头 搞得桌面杂乱无章,分辨率也低,感觉屏幕还是Mac自带的看着舒服
|
18天前
|
存储 人工智能 缓存
AI助理直击要害,从繁复中提炼精华——使用CDN加速访问OSS存储的图片
本案例介绍如何利用AI助理快速实现OSS存储的图片接入CDN,以加速图片访问。通过AI助理提炼关键操作步骤,避免在复杂文档中寻找解决方案。主要步骤包括开通CDN、添加加速域名、配置CNAME等。实测显示,接入CDN后图片加载时间显著缩短,验证了加速效果。此方法大幅提高了操作效率,降低了学习成本。
3097 10
|
13天前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1578 12
|
5天前
|
人工智能 关系型数据库 Serverless
1024,致开发者们——希望和你一起用技术人独有的方式,庆祝你的主场
阿里云开发者社区推出“1024·云上见”程序员节专题活动,包括云上实操、开发者测评和征文三个分会场,提供14个实操活动、3个解决方案、3 个产品方案的测评及征文比赛,旨在帮助开发者提升技能、分享经验,共筑技术梦想。
772 102
|
1月前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
18天前
|
人工智能 Serverless API
AI助理精准匹配,为您推荐方案——如何快速在网站上增加一个AI助手
通过向AI助理提问的方式,生成一个技术方案:在网站上增加一个AI助手,提供7*24的全天候服务,即时回答用户的问题和解决他们可能遇到的问题,无需等待人工客服上班,显著提升用户体验。
1519 9
|
6天前
|
SQL 存储 人工智能
【产品升级】Dataphin V4.3重大升级:AI“弄潮儿”,数据资产智能化
DataAgent如何助理业务和研发成为业务参谋?如何快速低成本的创建行业数据分类标准?如何管控数据源表的访问权限?如何满足企业安全审计需求?
388 0
【产品升级】Dataphin V4.3重大升级:AI“弄潮儿”,数据资产智能化
|
3天前
|
人工智能 自然语言处理 程序员
提交通义灵码创新实践文章,重磅好礼只等你来!
通义灵码创新实践征集赛正式开启,发布征文有机会获得重磅好礼+流量福利,快来参加吧!
212 7