如何将vue-cli快速迁移到vite?

507 阅读5分钟

最近接手了一个新项目,发现这个项目中有10多个前端工程项目,每个工程都是用的vue-cli,作为一个vite深度使用患者。现在再用webpack感觉好难受,配置繁琐,启动缓慢这点真的无法接受。但是去vite官网,但是却没有发现有提供快速从vue-cli迁移到vite的脚手架,所以想那就自己搞个吧!

1735115423628.png

梳理下vue-cli到vite需要做些什么?

对于初始化一个vite项目,首先需要初始化一个vite.config.ts,并且定义项目的html文件和对应的入口文件main.ts。但是在接入vite之前,我们需要先清除下旧打包工具的相关依赖和代码,所以我们的工具要做的事情也清楚了

  • 清除旧打包工具相关依赖
  • 清除旧打包工具相关代码
  • 新增新打包工具依赖
  • 新增新打包工具配置文件
  • 修改package.json中的相关脚本
  • 重新安装依赖

准备工作

本来想直接搞一个vue-cli迁移vite的插件,但是想了想,如果以后又想迁移到respack等新的构建工具,是不是又得创建一个新工程, 一定会有很多能复用的地方,所以打算搞个多合一,先占坑!!!

初始化项目

因为是要搞一个cli工具,并且多合一,我们先规划下目录。

1735112889942.png

  • constant 定义可复用的一些常量
  • lib 包含多个迁移工具核心代码
  • template 包含每个新的构建工具内置代码模板
  • utils 一些可以复用的工具类
  • package.json 在bin中配置多个迁移工具的入口

命令行交互,选择新迁移工具的参数

这里使用@clack/prompts进行一些参数的选择和输入。

import * as p from "@clack/prompts";
// 提前定义好一个变量保存选择的参数
const answers={}
const start = async () => {
  await p.group(
    {
      welcome: () => {
        console.clear();
        p.intro("欢迎使用构建迁移工具");
      },
      // 选择项目使用的框架
      selectFramework: async () => {
 
      },
      // 选择对应包管理工具
      selectPackageManager: async () => {

      },
      // 删除旧的打包工具相关依赖包
      deleteOldTool: async () => {

      },
      // 添加新的打包工具相关依赖包
      addNewTool: async () => {

      },
      // 修改项目运行,构建,预览命令
      changeScript: async () => {

      },
    },
    {
      onCancel: () => {
        p.cancel("打包工具迁移已取消!");
        process.exit(0);
      },
    }
  );
};
start();

框架选择

const FRAMEWORK_VUE2 = "vue2";
const FRAMEWORK_VUE3 = "vue3";
const FRAMEWORK_REACT = "react";

const frameworkList = [
  {
    value: FRAMEWORK_VUE2,
  },
  {
    value: FRAMEWORK_VUE3,
  },
  {
    value: FRAMEWORK_REACT,
  },
];

const selectFramework =async () => {
    const response = await p.select({
      message: "您当前正在使用的项目框架?",
      options: frameworkList,
    });

    answers.framework = response;
 }

1735113622116.png

选择包管理工具

export const PACKAGE_MANAGER_NPM = "npm";
export const PACKAGE_MANAGER_YARN = "yarn";
export const PACKAGE_MANAGER_PNPM = "pnpm";

const packageManagerList = [
  {
    value: PACKAGE_MANAGER_NPM,
  },
  {
    value: PACKAGE_MANAGER_YARN,
  },
  {
    value: PACKAGE_MANAGER_PNPM,
  },
];
const selectPackageManager = async () => {
    const response = await p.select({
      message: "您当前正在使用的包管理工具?",
      options: packageManagerList,
    });
    answers.packageManager = response;
  }

1735113797623.jpg

提前准备好一些文件操作相关工具类

import * as fs from "fs";
import path, { basename } from "path";
import { fileURLToPath } from "url";

export const BASE_PATH = process.cwd() + "\\";
export const DIR_NAME = path.dirname(fileURLToPath(import.meta.url));

// package.json
export const DEPENDENCIES_CONFIG_FILE_PATH = "package.json";

// node_modules
export const NODE_MODULES_CACHE_FILE_PATH = "node_modules";

// 获取文件内容
export async function getFile(fileName, basePath = BASE_PATH) {
  return await new Promise((resolve, reject) => {
    try {
      const file = fs.readFileSync(basePath + fileName, "utf8");
      resolve(file);
    } catch (e) {
      reject(new Error("File not found"));
    }
  });
}

// 保存文件
export async function saveFile(fileName, fileContents) {
  return await new Promise((resolve, reject) => {
    try {
      const dirPath = BASE_PATH + fileName;
      fs.mkdirSync(path.dirname(dirPath), { recursive: true });
      fs.writeFileSync(dirPath, fileContents);
      resolve();
    } catch (e) {
      reject(e);
    }
  });
}

// 删除文件
export async function removeFile(fileName) {
  return await new Promise((resolve, reject) => {
    try {
      if (fs.existsSync(BASE_PATH + fileName)) {
        fs.unlinkSync(BASE_PATH + fileName);
        resolve();
      }else{
        reject()
      }
    } catch (e) {
      reject(e);
    }
  });
}

// 复制文件
export async function copyFile(source, destination) {
  return await new Promise((resolve, reject) => {
    try {
      fs.copyFileSync(BASE_PATH + source, BASE_PATH + destination);
      resolve();
    } catch (e) {
      reject(e);
    }
  });
}

// 获取某个目录下的全部文件
function getDirFilesContent(dir, filePaths = []) {
  return new Promise((resolve, reject) => {
    try {
      const files = fs.readdirSync(dir, { withFileTypes: true });
      files.forEach(async (item) => {
        const filePath = `${dir}\\${item.name}`;
        const status = fs.statSync(filePath);
        if (status.isDirectory()) {
          try {
            const paths = await getDirFilesContent(filePath, filePaths);
            filePaths = filePaths.concat(paths);
          } catch (error) {
            console.log(error, "error");
          }
        } else {
          filePaths.push(filePath);
        }
      });
      resolve(filePaths);
    } catch (error) {
      console.log(error, "error");
    }
  });
}

// 获取全部文件地址
export async function getDirFiles(dirName, folderName) {
  return new Promise(async (resolve, reject) => {
    try {
      const directoryPath = path.join(dirName || DIR_NAME, folderName); // 替换为你的目录名
      const filePaths = await getDirFilesContent(directoryPath);
      resolve({
        filePaths,
        directoryPath,
      });
    } catch (error) {
      resolve(error);
    }
  });
}


// 安装依赖
const installDependence = (dependenceName) => {
  return new Promise((resolve, reject) => {
    const packageManager = answers.packageManager;
    let command = "";
    if (packageManager === PACKAGE_MANAGER_NPM) {
      command = "npm add -D ";
    } else if (packageManager === PACKAGE_MANAGER_PNPM) {
      command = "pnpm add -D ";
    } else if (packageManager === PACKAGE_MANAGER_YARN) {
      command = "yarn add -D ";
    }
    if (!dependenceName || !command) {
      reject("");
    }
    try {
      console.log(`${command}${dependenceName}`)
      exec(`${command}${dependenceName}`, (error,stdout) => {
        console.log(stdout,'stdout')
        console.log(error)
        if (!error) {
          resolve();
        }
      });
    } catch (error) {
      reject(error);
    }
  });
};

删除旧打包工具相关内容

import { DEPENDENCIES_CONFIG_FILE_PATH } from "../constant/filePath.js";
import { getFile, removeFile, saveFile } from "./file.js";

// 待删除的依赖, 在这里定义好可能涉及到的全部依赖
const DEPENDENCIES_KEYS = ["vite", "webpack", "@vue/cli"];
// 待删除的文件,在这里定义好可能涉及到的全部文件
const FILES_PATH = ["vue.config.js", "test/c.ts"];

// 删除依赖
const removeDependencies = (dependencies) => {
  const newDependencies = {};
  Object.entries(dependencies).forEach(([key, version]) => {
    if (!DEPENDENCIES_KEYS.some((item) => key.includes(item))) {
      newDependencies[key] = version;
    }
  });
  return newDependencies;
};

// 删除文件
const removeFiles = async () => {
  return Promise.all(FILES_PATH.map((item) => removeFile(item)));
};

const deleteOldTool = async () => {
  try {
    const file = await getFile(DEPENDENCIES_CONFIG_FILE_PATH);
    const fileJson = JSON.parse(file);
    fileJson.devDependencies = {
      ...removeDependencies(
        fileJson.devDependencies || {}
      ),
      ...removeDependencies(fileJson.dependencies || {})
    }
    fileJson.dependencies = {}
    fileJson.version="5.0.0-beta.0"
    fileJson.main="lib/index.umd.js"
    fileJson.module="lib/index.mjs"
    await saveFile(
      DEPENDENCIES_CONFIG_FILE_PATH,
      JSON.stringify(fileJson, null, 2)
    );
    await removeFiles();
  } catch (error) {
  }
};

export default deleteOldTool;

image.png

在templates中定义新打包工具的一些配置文件

在这里提前定义好新打包工具需要的配置文件,到时直接复制过去。

1735115305599.png

添加新打包工具

import { PACKAGING_TOOL_VITE } from "../constant/packagingTool.js";
import { getDirFiles, getFile, saveFile } from "./file.js";
import installDependence from "./installDependence.js";

const getFileContent = async (file, directoryPath) => {
  const content = await getFile(file, "");
  return {
    filePath: file.replace(directoryPath + "\\", ""),
    fileContent: content,
  };
};

// 添加新打包工具配置文件
const addTemplateFile = async (packagingTool) => {
  const { filePaths, directoryPath } = await getDirFiles(
    "",
    `../template/${packagingTool}/`
  );
  const fileContentList = await Promise.all(
    filePaths.map((item) => getFileContent(item, directoryPath))
  );
  return Promise.all(
    fileContentList.map((item) => saveFile(item.filePath, item.fileContent))
  );
};

const addNewTool = async () => {
  try {
    const packagingTool = answers.packagingTool;
    let dependenceNames = [];
    if (packagingTool === PACKAGING_TOOL_VITE) {
       // 定义需要添加的依赖
      dependenceNames = ["vite", "@vitejs/plugin-vue", "vite-plugin-dts"];
    }
    // 添加依赖
    await installDependence(dependenceNames.join(" "));
    // 添加配置
    await addTemplateFile(packagingTool);
  } catch (error) {
    console.log("addNewTool", error);
  }
};

export default addNewTool;

image.png

修改构建命令

import answers from "../constant/answers.js";
import { DEPENDENCIES_CONFIG_FILE_PATH } from "../constant/filePath.js";
import { PACKAGING_TOOL_VITE } from "../constant/packagingTool.js";
import { getFile, saveFile } from "./file.js";

const scriptsMap = {
  [PACKAGING_TOOL_VITE]: {
    instal:"npx pnpm install",
    serve: "vite",
    dev:"vite",
    package: "vite build",
  },
};

const changeScript = async () => {
  try {
    const packagingTool = answers.packagingTool;
    const file = await getFile(DEPENDENCIES_CONFIG_FILE_PATH);
    const fileJson = JSON.parse(file);
    fileJson.scripts = {
      ...(fileJson.scripts || {}),
      ...(scriptsMap[packagingTool] || {}),
    };
    await saveFile(
      DEPENDENCIES_CONFIG_FILE_PATH,
      JSON.stringify(fileJson, null, 2)
    );
  } catch (error) {
    console.log("changeScript", error);
  }
};

export default changeScript;

1735114757477.jpg

总结

一个简易版的构建迁移工具已经完事了,完整代码贴在下面了,但是这个不一定满足所有每个人需求。比如需要添加一下项目相关的统一配置文件,打包命令等...,大家都可以可以参考这个项目结构去添加自己想做的事情!!!

git地址:github.com/waltiu/vue-…

npm地址:www.npmjs.com/package/pl-…