vue3组件库添加脚本的实现示例 |
添加脚本在操作组件库的时候有一些操作比较繁琐,因此添加脚本,通过脚本执行这些繁琐的事情 在项目根目录创建script?目录 添加组件创建组件目录及固定结构每次新建组件都需要徐建如下操作:
通过编写一些脚本帮我自动创建对应的文件夹和文件,在script?目录创建add.js?和tools.js?文件以及创建add?文件夹,在add?文件夹下创建directoryFile.js?文件 :::demo
${componentName}/index
:::
`;
// 文档目录
const directory = path.join(docPath, componentName);
// 判断是否有工具路径
const isExists = fs.existsSync(directory);
if (isExists) {
exit(`${directory}目录已经存在`);
}
// 文档路径
const documentPath = path.join(directory, "/base.md");
fs.mkdirSync(directory);
// 写入文件
fs.writeFileSync(documentPath, documentTemplate); // 工具
myLog("创建组件文档", documentPath);
// ------- 创建组件文档 end ------
// ---------创建组件demo start -----
// demo路径
const demoPath = path.join(__dirname, "../../docs/demos");
// demo目录
const demoDirectory = path.join(demoPath, componentName);
// 创建文件夹
fs.mkdirSync(demoDirectory);
// 文件路径
const demoFilePath = path.join(demoDirectory, "/index.vue");
// demo 模板
const demoTemplate = `<template>
<div>
<${capitalizeFirstLetter(componentName)} />
</div>
</template>
<script>
export default {
name: "${componentName}-demo",
};
</script>
<style lang="scss" scoped>
</style>`;
// 写入文件
fs.writeFileSync(demoFilePath, demoTemplate); // 工具
myLog("创建demo文件", demoFilePath);
// ---------创建组件demo end -----
// ---------创建组件 start -----
// 组件路径
const componentPath = path.join(__dirname, "../../packages");
// 组件目录
const componentDirectory = path.join(componentPath, componentName);
// 创建文件夹
fs.mkdirSync(componentDirectory);
// 组件主目录
const componentMainDirectory = path.join(componentDirectory, "src");
// 创建文件夹
fs.mkdirSync(componentMainDirectory);
// 组件主文件
const componentMainFilePath = path.join(componentMainDirectory, "/index.vue");
// 组件内容
const componentTemplate = `<template>
<div>
<div>${componentName}</div>
</div>
</template>
<script>
export default {
name: "${componentName}",
};
</script>
<style lang="scss" scoped>
</style>`;
fs.writeFileSync(componentMainFilePath, componentTemplate);
// 组件安装文件
const componentInstallPath = path.join(componentDirectory, "index.ts");
// 判断导入组件组件组件使用大写还是小写
let subassembly =
capitalizeFirstLetter(componentName) === componentName
? lowerFirstLetter(componentName)
: capitalizeFirstLetter(componentName);
// 组件安装
const componentInstall = `import ${subassembly} from "./src/index.vue";
import { withInstall } from "../withInstall";
const ${capitalizeFirstLetter(componentName)} = withInstall(${subassembly});
export default ${capitalizeFirstLetter(componentName)};`;
// 写入文件
fs.writeFileSync(componentInstallPath, componentInstall);
myLog("创建组件目录", componentDirectory);
// ---------创建组件 end -----
};组件介绍 :::demo
${componentName}/index
:::
`;
// 文档目录
const directory = path.join(docPath, componentName);
// 判断是否有工具路径
const isExists = fs.existsSync(directory);
if (isExists) {
exit(`${directory}目录已经存在`);
}
// 文档路径
const documentPath = path.join(directory, "/base.md");
fs.mkdirSync(directory);
// 写入文件
fs.writeFileSync(documentPath, documentTemplate); // 工具
myLog("创建组件文档", documentPath);
// ------- 创建组件文档 end ------
// ---------创建组件demo start -----
// demo路径
const demoPath = path.join(__dirname, "../../docs/demos");
// demo目录
const demoDirectory = path.join(demoPath, componentName);
// 创建文件夹
fs.mkdirSync(demoDirectory);
// 文件路径
const demoFilePath = path.join(demoDirectory, "/index.vue");
// demo 模板
const demoTemplate = `<template>
<div>
<${capitalizeFirstLetter(componentName)} />
</div>
</template>
<script>
export default {
name: "${componentName}-demo",
};
</script>
<style lang="scss" scoped>
</style>`;
// 写入文件
fs.writeFileSync(demoFilePath, demoTemplate); // 工具
myLog("创建demo文件", demoFilePath);
// ---------创建组件demo end -----
// ---------创建组件 start -----
// 组件路径
const componentPath = path.join(__dirname, "../../packages");
// 组件目录
const componentDirectory = path.join(componentPath, componentName);
// 创建文件夹
fs.mkdirSync(componentDirectory);
// 组件主目录
const componentMainDirectory = path.join(componentDirectory, "src");
// 创建文件夹
fs.mkdirSync(componentMainDirectory);
// 组件主文件
const componentMainFilePath = path.join(componentMainDirectory, "/index.vue");
// 组件内容
const componentTemplate = `<template>
<div>
<div>${componentName}</div>
</div>
</template>
<script>
export default {
name: "${componentName}",
};
</script>
<style lang="scss" scoped>
</style>`;
fs.writeFileSync(componentMainFilePath, componentTemplate);
// 组件安装文件
const componentInstallPath = path.join(componentDirectory, "index.ts");
// 判断导入组件组件组件使用大写还是小写
let subassembly =
capitalizeFirstLetter(componentName) === componentName
? lowerFirstLetter(componentName)
: capitalizeFirstLetter(componentName);
// 组件安装
const componentInstall = `import ${subassembly} from "./src/index.vue";
import { withInstall } from "../withInstall";
const ${capitalizeFirstLetter(componentName)} = withInstall(${subassembly});
export default ${capitalizeFirstLetter(componentName)};`;
// 写入文件
fs.writeFileSync(componentInstallPath, componentInstall);
myLog("创建组件目录", componentDirectory);
// ---------创建组件 end -----
};在add.js?导入 /* eslint-disable no-undef */
import { warn } from "./tools.js";
import { directoryFile } from "./add/directoryFile.js";
// 组件名称
let componentName = process.argv[2];
if (!componentName) {
warn("Usage: npm run add <component-name>");
process.exit(1);
}
// 创建目录
directoryFile();然后在package.json?中添加命令就可以了 "add": "node ./script/add.js" 这样需要创建组件的时候只需要在命令行中输入 npm run add 组件名称 修改components.d.ts上面的操作创建了组件的目录及基本结构,在创建组件的时候还需要再components.d.ts?引入组件设置类型 /* eslint-disable no-undef */
// 添加 packagescomponents.d.ts 文件中的类型
import fs from "fs-extra";
import { myLog, capitalizeFirstLetter } from "../tools.js";
const componentName = process.argv[2]; // 从命令行参数获取组件名
// 需要处理的文件路径
const filePath = "./packages/components.d.ts"; // 文件路径,请替换为实际路径
// 需要导入的组件路径
const importPath = `./${componentName.toLowerCase()}/src/index.vue`; // 假设组件的导入路径与组件名相关联
// 导入组件
const importStatement = `import ${capitalizeFirstLetter(componentName)} from "${importPath}";
`;
// 组件类型
const componentDeclaration = ` ${capitalizeFirstLetter(componentName)}: typeof ${capitalizeFirstLetter(componentName)};
`;
async function addComponent() {
try {
let fileContent = await fs.readFile(filePath, "utf8");
// 检查是否已存在该组件的导入和声明,避免重复添加
if (!fileContent.includes(importStatement) && !fileContent.includes(componentDeclaration)) {
// 在导入部分添加新组件导入
// eslint-disable-next-line quotes
const importSectionEndIndex = fileContent.indexOf('declare module "vue"');
fileContent =
fileContent.slice(0, importSectionEndIndex) +
importStatement +
fileContent.slice(importSectionEndIndex);
// 在GlobalComponents接口中添加新组件声明
const globalComponentsStartIndex = fileContent.indexOf("{", importSectionEndIndex);
const globalComponentsEndIndex = fileContent.indexOf("}", importSectionEndIndex);
// 提取出 导出的类型
const globalComponentsSection = fileContent.slice(
globalComponentsStartIndex,
globalComponentsEndIndex,
);
fileContent = fileContent.replace(
globalComponentsSection,
`${globalComponentsSection}${componentDeclaration}`,
);
await fs.writeFile(filePath, fileContent, "utf8");
// console.log(`Component ${componentName} has been added successfully.`);
myLog(`Component ${componentName} has been added successfully.`);
} else {
// console.log(`Component ${componentName} is already present in the file.`);
myLog(`Component ${componentName} is already present in the file.`);
}
} catch (error) {
console.error("An error occurred:", error);
}
}
export default addComponent;修改index.ts在add?文件夹下面创建add-indexTs.js?文件 /* eslint-disable no-undef */
import fs from "fs";
import { myLog, capitalizeFirstLetter } from "../tools.js";
// 指定要修改的文件路径
const filePath = "./packages/index.ts"; // 要添加的组件名称
const componentName = process.argv[process.argv.length - 1]; // 从命令行参数获取,如 'abc'
// 确保组件名符合导入语句的格式
const formattedComponentName = componentName.replace(/.vue$/, "").replace(/^//, "");
export const addIndexTs = () => {
// 读取文件内容
fs.readFile(filePath, "utf8", (err, data) => {
if (err) {
console.error(`读取文件失败: ${err}`);
return;
}
// 添加导入语句在现有导入语句之后(如果尚未存在)
const importRegex = /imports+.+?froms+["'].*?["'];/g;
let lastImportMatch;
while ((lastImportMatch = importRegex.exec(data)) !== null) {
// 找到最后一个匹配的导入语句
}
const importLine = `
import ${capitalizeFirstLetter(formattedComponentName)} from "./${formattedComponentName}";
`;
if (!lastImportMatch || !data.includes(importLine)) {
const insertPosition = lastImportMatch
? lastImportMatch.index + lastImportMatch[0].length
: data.indexOf("const components:");
data = data.slice(0, insertPosition) + importLine + data.slice(insertPosition);
}
// 更新组件列表(如果尚未存在)
const componentsStart = "const components: { [propName: string]: Component } = {";
const componentsEnd = "};";
const componentsIndexStart = data.indexOf(componentsStart);
const componentsIndexEnd = data.indexOf(componentsEnd, componentsIndexStart);
if (componentsIndexStart !== -1 && componentsIndexEnd !== -1) {
let componentsBlock = data.substring(
componentsIndexStart,
componentsIndexEnd + componentsEnd.length,
);
if (!componentsBlock.includes(`${formattedComponentName}:`)) {
componentsBlock = componentsBlock.replace(
componentsEnd,
` ${capitalizeFirstLetter(formattedComponentName)},
${componentsEnd}`,
);
data =
data.substring(0, componentsIndexStart) +
componentsBlock +
data.substring(componentsIndexEnd + componentsEnd.length);
}
}
// 更新导出语句
const exportStart = "export {";
const exportEnd = "};";
const exportIndexStart = data.lastIndexOf(exportStart);
const exportIndexEnd = data.indexOf(exportEnd, exportIndexStart);
if (exportIndexStart !== -1 && exportIndexEnd !== -1) {
let exportsBlock = data.substring(exportIndexStart, exportIndexEnd + exportEnd.length);
if (!exportsBlock.includes(`${formattedComponentName},`)) {
const currentExports = exportsBlock
.replace(exportStart, "")
.replace(exportEnd, "")
.trim()
.split(",")
.map(s => s.trim());
if (currentExports[currentExports.length - 1] !== "") {
currentExports.push(capitalizeFirstLetter(formattedComponentName));
exportsBlock = `${exportStart} ${currentExports.join(", ")} ${exportEnd}`;
} else {
exportsBlock = exportsBlock.replace(
exportEnd,
`, ${formattedComponentName}
${exportEnd}`,
);
}
data =
data.substring(0, exportIndexStart) +
exportsBlock +
data.substring(exportIndexEnd + exportEnd.length);
}
}
// 写回文件
fs.writeFile(filePath, data, "utf8", err => {
if (err) {
console.error(`写入文件失败: ${err}`);
} else {
myLog(`${formattedComponentName} 已成功添加到文件`, "packages/index.ts");
}
});
});
};添加vitePress菜单经过以上操作添加组件基本完成,还剩余添加说明文档的侧边栏 /* eslint-disable no-undef */
import fs from "fs";
import { myLog } from "../tools.js";
const vitePressConfig = "./docs/.vitepress/config.ts";
const componentName = process.argv[2]; // 从命令行参数获取,如 'abc'
const componentNameCn = process.argv[3]; // 从命令行参数获取,如 'abc'
const addMenu = `{ text: "${componentNameCn || "组件名称"}", link: "/components/${componentName}/base.md" },`;
export const addVitePressConfig = () => {
// 读取文件
fs.readFile(vitePressConfig, "utf8", (err, data) => {
if (err) {
console.error(`读取文件失败: ${err}`);
return;
}
let componentsIndexStart = data.indexOf("items: [");
let componentsEnd = "],";
let componentsIndexEnd = data.indexOf(componentsEnd, componentsIndexStart);
let componentsBlock = data.substring(componentsIndexStart, componentsIndexEnd);
componentsBlock = `
${componentsBlock}
${addMenu}
`;
data =
data.substring(0, componentsIndexStart) +
componentsBlock +
data.substring(componentsIndexEnd);
fs.writeFile(vitePressConfig, data, "utf8", err => {
if (err) {
console.error(`写入文件失败: ${err}`);
} else {
myLog(
`${componentNameCn || "组件"}${componentName} 已成功添加到文档库菜单`,
"docs/.vitepress/config.ts",
);
}
});
});
};使用经过以上完成了组件创建脚本,这样就不需要我们自己在修改一些文件了,直接编写组件内容、demo、组件文档就可以了 在命令行中使用 npm run add table 表格
组件名称首字母无论是大写还是小写字母,在使用的时候都需要使用的时候都需要变成大写字母 提交git每次需要提交代码的时候都需要执行git add .? 、git commit -m ""? 、git push? 安装依赖 npm i child_process -D 在script?文件夹下面创建push.js? /* eslint-disable no-undef */
// 导入Node.js的child_process模块中的exec函数,用于在子进程中执行Shell命令
import { exec } from "child_process";
import { finish, warn } from "./tools.js";
// 这个异步函数接收一个命令字符串作为参数,使用exec执行该命令,并将其包装成一个Promise 。如果命令执行成功,它会解析Promise并返回包含stdout和stderr的对象;如果执行失败,则拒绝Promise并返回错误 。
const runCommand = command =>
new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
reject(error);
} else {
resolve({ stdout, stderr });
}
});
});
// 这是一个异步函数,负责执行一系列Git操作:添加所有改动、根据提供的或默认的提交信息进行提交、然后推送更改 。它接受一个可选的commitMessage参数,默认值为"新增组件" 。
const main = async (commitMessage = "新增组件") => {
try {
await runCommand("git add .");
const messageOption = commitMessage ? `-m "${commitMessage}"` : "";
await runCommand(`git commit ${messageOption}`);
await runCommand("git push");
finish("代码提交成功");
} catch (error) {
warn("Error during Git operations:", error);
}
};
// 从命令行参数读取提交信息
// 从Node.js进程的命令行参数中读取信息 。process.argv是一个数组,包含了启动脚本的Node.js可执行文件的路径、脚本文件的路径,以及之后的所有参数 。slice(2)用来去掉前两个元素,只保留实际传入的参数 。如果提供了参数,它们会被连接成一个字符串作为提交信息,否则使用默认的"新增组件" 。
const args = process.argv.slice(2);
const commitMessage = args.length > 0 ? args.join(" ") : "新增组件";
// 调用main函数,并使用.catch处理任何在执行过程中抛出的错误,错误信息会被打印到控制台 。
main(commitMessage).catch(console.error);打包部署对项目进行打包部署分为如下几步:
在script?文件夹下卖弄创建npmPush.js?和npmPush?文件夹 更改版本号在npmPush?文件夹下面创建bumpVersion.js? // 修改版本号
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
import { finish, warn } from "../tools.js";
export const bumpVersion = () => {
// 当前文件路径
const __filename = fileURLToPath(import.meta.url);
// 当前文件的目录
const __dirname = path.dirname(__filename);
// 读取package.json文件
const packagePath = path.resolve(__dirname, "../../package.json");
const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8"));
// 原来的版本
const originally = packageJson.version;
// 分解版本号为数组,便于操作
const versionParts = packageJson.version.split(".").map(Number);
// 示例:递增补丁版本号
versionParts[2]++; // 假设是主版本.次版本.补丁版本的形式
// 重新组合版本号
packageJson.version = versionParts.join(".");
// 将修改后的内容写回package.json
fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2), "utf8");
finish(`版本更新成功: ${originally} --> ${packageJson.version}`, packagePath);
};在script pmPush.js?文件中引入 import { bumpVersion } from "./npmPush/bumpVersion.js";
bumpVersion();打包组件库在npmPush?文件夹下面创建packNpm.js? /* eslint-disable no-undef */
// 导入Node.js的child_process模块中的exec函数,用于在子进程中执行Shell命令
import { finish, warn, runCommand } from "../tools.js";
export const packNpm = async () => {
// 这是一个异步函数,负责执行一系列Git操作:添加所有改动、根据提供的或默认的提交信息进行提交、然后推送更改 。它接受一个可选的commitMessage参数,默认值为"新增组件" 。
try {
await runCommand("npm run lib");
finish("打包成功");
} catch (error) {
warn("打包发生错误:", error);
}
};在script pmPush.js?文件中引入 import { bumpVersion } from "./npmPush/bumpVersion.js";
import { packNpm } from "./npmPush/packNpm.js";
const npmPush = async () => {
await bumpVersion();
await packNpm();
};
npmPush();提交npm私库在npmPush?文件夹下面创建submitNpm.js? /* eslint-disable no-undef */
import { finish, warn, runCommand } from "../tools.js";
export const submitNpm = async () => {
try {
await runCommand("npm publish");
finish("推送成功");
await runCommand("npm run push '部署' ");
finish("代码提交成功");
} catch (error) {
warn("打包发生错误:", error);
}
};在script pmPush.js?文件中引入 import { bumpVersion } from "./npmPush/bumpVersion.js";
import { packNpm } from "./npmPush/packNpm.js";
import { submitNpm } from "./npmPush/submitNpm.js";
const npmPush = async () => {
await bumpVersion();
await packNpm();
await submitNpm();
};
npmPush();总结暂时先添加这三个脚本吧,需要注意的是:在打包部署之前需要登录npm库 在代码中用到的tools.js?文件 /* eslint-disable no-undef */
// 导入Node.js的child_process模块中的exec函数,用于在子进程中执行Shell命令
import { exec } from "child_process";
import ora from "ora";
// 首字母大写
export const capitalizeFirstLetter = str => str.charAt(0).toUpperCase() + str.slice(1);
// 首字母转小写
export const lowerFirstLetter = str => str.charAt(0).toLowerCase() + str.slice(1);
// 打印
// 打印方法
export const myLog = function (text, path) {
/* var black="
|