最近接手了一个新项目,发现这个项目中有10多个前端工程项目,每个工程都是用的vue-cli,作为一个vite深度使用患者。现在再用webpack感觉好难受,配置繁琐,启动缓慢这点真的无法接受。但是去vite官网,但是却没有发现有提供快速从vue-cli迁移到vite的脚手架,所以想那就自己搞个吧!
梳理下vue-cli到vite需要做些什么?
对于初始化一个vite项目,首先需要初始化一个vite.config.ts,并且定义项目的html文件和对应的入口文件main.ts。但是在接入vite之前,我们需要先清除下旧打包工具的相关依赖和代码,所以我们的工具要做的事情也清楚了
- 清除旧打包工具相关依赖
- 清除旧打包工具相关代码
- 新增新打包工具依赖
- 新增新打包工具配置文件
- 修改package.json中的相关脚本
- 重新安装依赖
准备工作
本来想直接搞一个vue-cli迁移vite的插件,但是想了想,如果以后又想迁移到respack等新的构建工具,是不是又得创建一个新工程, 一定会有很多能复用的地方,所以打算搞个多合一,先占坑!!!
初始化项目
因为是要搞一个cli工具,并且多合一,我们先规划下目录。
- 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;
}
选择包管理工具
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;
}
提前准备好一些文件操作相关工具类
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;
在templates中定义新打包工具的一些配置文件
在这里提前定义好新打包工具需要的配置文件,到时直接复制过去。
添加新打包工具
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;
修改构建命令
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;
总结
一个简易版的构建迁移工具已经完事了,完整代码贴在下面了,但是这个不一定满足所有每个人需求。比如需要添加一下项目相关的统一配置文件,打包命令等...,大家都可以可以参考这个项目结构去添加自己想做的事情!!!
git地址:github.com/waltiu/vue-…