记录一次vue-codemod的使用

9 阅读3分钟

背景

vue2升级vue3,后管系统,但是规模比较大,没法直接升级,只能是新老项目并行。按照菜单迁移页面,直接迁移过来,会有些api不支持,需要转成vue3,所以使用了vue-codemod。因为只能单规则执行,所以写了个shell脚本

批量执行shell脚本

体现了工程化思维

#!/bin/bash

# Vue Codemod 批量执行脚本
# 用法: ./vue-codemod-batch.sh <路径>
# 示例: ./vue-codemod-batch.sh src/views/delivery

# 检查是否传入路径参数
if [ -z "$1" ]; then
  echo "❌ 错误: 请提供要转换的路径"
  echo "用法: $0 <路径>"
  echo "示例: $0 src/views/delivery"
  exit 1
fi

TARGET_PATH=$1

# 检查路径是否存在
if [ ! -e "$TARGET_PATH" ]; then
  echo "❌ 错误: 路径 '$TARGET_PATH' 不存在"
  exit 1
fi

echo "🚀 开始对路径 '$TARGET_PATH' 执行 Vue Codemod 转换..."
echo ""

# ============================================================
# 已实现的转换规则列表(基于 vue-comdemod 文档)
# 文档来源: https://github.com/vuejs/vue-codemod
# ============================================================

# ✅ 已实现的规则 (文档中明确标记为 implemented)
IMPLEMENTED_RULES=(
  # RFC04 & RFC09: Global API 改造
  "new-global-api"                    # Vue 全局 API 改造 (import Vue from 'vue' 等)
  "vue-as-namespace-import"           # import Vue from 'vue' -> import * as Vue from 'vue'
  "define-component"                  # Vue.extend -> defineComponent
  "new-vue-to-create-app"             # new Vue() -> createApp()
  "remove-contextual-h-from-render"   # render(h) -> render() + import { h }
  "remove-production-tip"             # 移除 Vue.config.productionTip

  # RFC01 & RFC06: Slots 改造
  "scoped-slots-to-slots"             # $scopedSlots -> $slots

  # Vuex & Vue Router
  "vuex-v4"                           # Vuex 3 -> 4
  "vue-router-v4"                     # Vue Router 3 -> 4

  # 其他
  "remove-vue-set-and-delete"         # 移除 Vue.set / Vue.delete
)

# ⚠️  通用转换规则
GENERIC_RULES=(
  "remove-trivial-root"               # 移除简单的根组件
)

# 🔴 未实现的规则(文档中标记为 🔴)
# 这些规则在文档中有描述但尚未实现,运行会报错
# "global-to-per-app-api"             # Vue.config -> app.config (🔴 未实现)
# "emits-option"                      # 自动生成 emits 选项 (🔴 未实现)
# "async-component-api"               # 异步组件新 API (🔴 未实现)
# "transition-class-change"           # Transition 类名调整 (🔴 未实现)
# "events-api-change"                 # 事件 API 迁移 (🔴 未实现)

# 合并所有可执行的规则
RULES=(
  "${IMPLEMENTED_RULES[@]}"
  "${GENERIC_RULES[@]}"
)

# ============================================================
# 执行转换
# ============================================================

# 记录成功和失败的规则
SUCCESS_COUNT=0
FAILED_COUNT=0
FAILED_RULES=()
SKIPPED_COUNT=0

echo "📋 将执行 ${#RULES[@]} 个转换规则:"
echo "   已实现规则: ${#IMPLEMENTED_RULES[@]} 个"
echo "   通用规则: ${#GENERIC_RULES[@]} 个"
echo ""

# 遍历执行每个规则
for RULE in "${RULES[@]}"; do
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  echo "📦 执行转换规则: $RULE"
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

  # 先检查是否是已实现的规则
  IS_IMPLEMENTED=false
  for IMPL_RULE in "${IMPLEMENTED_RULES[@]}"; do
    if [ "$IMPL_RULE" = "$RULE" ]; then
      IS_IMPLEMENTED=true
      break
    fi
  done

  # 执行转换
  npx vue-codemod "$TARGET_PATH" -t "$RULE" 2>&1
  EXIT_CODE=$?

  if [ $EXIT_CODE -eq 0 ]; then
    echo "✅ 规则 '$RULE' 执行成功"
    ((SUCCESS_COUNT++))
  else
    # 检查输出是否包含 "No files transformed"(没有匹配文件,不算失败)
    if echo "$OUTPUT" | grep -q "No files transformed"; then
      echo "⏭️  规则 '$RULE' 跳过(没有匹配的文件)"
      ((SKIPPED_COUNT++))
    else
      echo "⚠️  规则 '$RULE' 执行失败 (exit code: $EXIT_CODE)"
      ((FAILED_COUNT++))
      FAILED_RULES+=("$RULE")
    fi
  fi

  echo ""
done

# ============================================================
# 统计输出
# ============================================================

echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📊 转换完成统计"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ 成功: $SUCCESS_COUNT 个规则"
echo "⏭️  跳过: $SKIPPED_COUNT 个规则 (无匹配文件)"
echo "⚠️  失败: $FAILED_COUNT 个规则"

if [ ${#FAILED_RULES[@]} -gt 0 ]; then
  echo ""
  echo "失败的规则列表:"
  for RULE in "${FAILED_RULES[@]}"; do
    echo "  - $RULE"
  done
fi

echo ""
echo "🎉 所有转换规则执行完毕!"
echo ""
echo "💡 后续建议:"
echo "   1. 运行 'git diff' 查看所有变更"
echo "   2. 运行 'npm run lint' 或 'npx prettier --write' 格式化代码"
echo "   3. 运行 'npm run dev' 测试应用是否正常"
echo ""
echo "📚 参考文档:"
echo "   ESLint 可修复的规则:"
echo "     - vue/no-deprecated-v-bind-sync (.sync → v-model:)"
echo "     - vue/no-deprecated-v-on-number-modifiers (移除 keyCode)"
echo "     - vue/no-deprecated-slot-attribute (slot → v-slot)"
echo "     - vue/no-deprecated-slot-scope-attribute (slot-scope → v-slot)"

Vue 3 迁移 Codemods 使用指南

已实现的 Codemods(可直接使用)

转换名称功能描述
new-global-apiVue 3 全局 API 迁移
vue-as-namespace-importimport Vue from 'vue'import * as Vue from 'vue'
define-componentVue.extenddefineComponent
new-vue-to-create-appnew Vue()Vue.createApp()
remove-contextual-h-from-renderrender(h)render() + 引入 h
remove-production-tip移除 Vue.config.productionTip
scoped-slots-to-slots.$scopedSlots.$slots
vuex-v4Vuex 3.x → 4.x 迁移
vue-router-v4Vue Router 3.x → 4.x 迁移
remove-vue-set-and-delete移除 Vue.set / Vue.delete
remove-trivial-root移除 trivial 根组件

ESLint 可自动修复的规则

规则变更内容
v-bind.sync转换为 v-model 参数
keyCode 支持移除 keyCode 支持
data 对象声明规范化 data 对象声明
废弃的 slot/slot-scope 属性检测并修复
inline-template检测并修复
transition 根节点行为处理过渡组件作为根节点的行为变更
废弃的 events API检测并修复
废弃的 filter 语法检测并修复

尚未实现的迁移项(标记为 🔴)

迁移项说明
global-to-per-app-api全局 API 按应用实例迁移
函数式组件/异步组件 API 变更相关 API 变更需要手动处理
渲染函数 VNode 数据格式转换VNode 数据格式需要手动转换
自定义指令 API 变更钩子重命名需要手动处理
组件名冲突处理需要手动解决组件名冲突
异步组件语法转换需要手动转换语法
emits 选项自动生成需要手动生成 emits 选项
过渡类名调整enter-classenter-from-class
is 属性的新语法需要手动更新 is 属性语法

使用方法

npx vue-codemod <路径> -t <转换名称> --params [参数]

使用示例

场景命令示例
迁移全局 APInpx vue-codemod src/ -t new-global-api
迁移 Vue.extendnpx vue-codemod src/components/ -t define-component
迁移 Vuexnpx vue-codemod src/ -t vuex-v4
迁移 Vue Routernpx vue-codemod src/ -t vue-router-v4

迁移注意事项

  1. 自动化工具的局限性:已实现的 codemods 可处理大部分常见迁移场景,但仍有部分边缘情况需要人工检查
  2. 🔴 标记项:尚未实现的迁移项需要手动处理,建议优先评估这些项对项目的影响
  3. 双重检查:在部署到生产环境前,务必对所有迁移后的代码进行全面的测试验证
  4. 兼容层配置:对于标记为 🔵 的项目,确保正确配置 Vue 3 兼容运行时

建议的迁移流程

  1. 评估阶段:识别项目中需要迁移的代码范围
  2. 自动化迁移:使用已实现的 codemods 批量处理可自动化的变更
  3. 手动处理:针对 🔴 标记项进行手动代码修改
  4. 测试验证:进行全面测试,特别是边缘情况
  5. 生产部署:确认无误后部署到生产环境

重要提醒:根据 Vue 3 迁移指南,自动化工具无法覆盖所有场景,人工审查是确保生产环境稳定性的关键步骤。建议在迁移过程中保持与团队成员的协作,确保所有变更都被正确理解和实施。