文章目录
本文一共有三个脚本
初始脚本:不成熟,不推荐
优化版脚本: 直接将所有的vue页面使用对应路径命名,推荐
定制版脚本: 将vue路由对应的vue页面,使用路由的name;对于非vue路由的页面,直接使用对应地址路径命名,推荐
1.思路
通过遍历src/views下的文件,找到.vue文件,截取文件路径的最后两位或全部路径,使用驼峰命名
2.初始脚本
可以通过脚本批量修改 .vue 文件
创建脚本 auto-set-component-name.mjs
针对没有添加defineOptions的.vue文件添加defineOptions和name(但是未处理有defineOptions的情况)
import fs from "fs"
import path from "path"
import { fileURLToPath } from "url"
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
// 🔧 配置区 ============================================
const targetDir = path.join(__dirname, "src/views")
const PATH_DEPTH = Infinity // 自由修改数字:2→最后两级,3→最后三级,Infinity→全部路径
// =====================================================
const toPascalCase = (str) => {
return str
.replace(/[-_](.)/g, (_, c) => c.toUpperCase())
.replace(/(^\w)/, m => m.toUpperCase())
.replace(/\.vue$/, '')
}
const processDirectory = (dir) => {
const files = fs.readdirSync(dir, { withFileTypes: true })
files.forEach(file => {
const fullPath = path.join(dir, file.name)
file.isDirectory() ? processDirectory(fullPath) : processVueFile(fullPath)
})
}
const processVueFile = (filePath) => {
if (path.extname(filePath) !== '.vue') return
const relativePath = path.relative(targetDir, filePath)
const pathSegments = relativePath
.split(path.sep)
.slice(-PATH_DEPTH) // 🔥 核心修改点:根据配置截取路径段
.map(segment => toPascalCase(segment))
const componentName = pathSegments.join('')
let content = fs.readFileSync(filePath, 'utf8')
// 修复正则表达式:支持任意顺序的 setup 属性
const scriptSetupRegex = /<script\s+((?:.(?!\/script>))*?\bsetup\b[^>]*)>/gmi
if (!content.includes('defineOptions')) {
content = content.replace(scriptSetupRegex, (match, attrs) => {
return `<script ${attrs}>
defineOptions({
name: '${componentName}'
})`
})
fs.writeFileSync(filePath, content)
console.log(`✅ 成功注入 name: ${componentName} → ${filePath}`)
}
}
processDirectory(targetDir)
console.log('🎉 所有 Vue 组件 name 注入完成!')
3.运行脚本
node auto-set-component-name.mjs
4.优化版本
思路:
4.1通过遍历sr/view下的.vue文件
找到每个.vue的路径,使用路径去生成name
4.2判断是否有defineOptions
有defineOptions,在判断是否有name:
- 有name:替换name(可注释此段逻辑)
- 无name:添加name
无defineOptions:
- 直接添加defineOptions和name
4.3优化版脚本
直接将所有的vue页面使用对应路径命名
import fs from "fs"; // 文件系统模块,用于读写文件
import path from "path"; // 路径处理模块
import { fileURLToPath } from "url"; // 用于转换URL路径
const __filename = fileURLToPath(import.meta.url); // 当前文件绝对路径
const __dirname = path.dirname(__filename); // 当前文件所在目录
// 🔧 配置区 ============================================
const targetDir = path.join(__dirname, "src/views"); // 目标目录:当前目录下的src/views
const PATH_DEPTH = Infinity; // 路径深度设置 自由修改数字:2→最后两级,3→最后三级,Infinity→全部路径
// =====================================================
const toPascalCase = (str) => {
return str
.replace(/[-_](.)/g, (_, c) => c.toUpperCase()) // 转换连字符/下划线后的字母为大写
.replace(/(^\w)/, (m) => m.toUpperCase()) // 首字母大写
.replace(/\.vue$/, ""); // 移除.vue后缀
};
const processDirectory = (dir) => {
const files = fs.readdirSync(dir, { withFileTypes: true }); // 读取目录内容
files.forEach((file) => {
const fullPath = path.join(dir, file.name); // 获取完整路径
file.isDirectory() ? processDirectory(fullPath) : processVueFile(fullPath); // 递归处理目录,直接处理文件
});
};
const processVueFile = (filePath) => {
if (path.extname(filePath) !== ".vue") return; // 过滤非Vue文件
// 生成组件名逻辑
const relativePath = path.relative(targetDir, filePath);
const pathSegments = relativePath
.split(path.sep) // 按路径分隔符拆分
.slice(-PATH_DEPTH) // 根据配置截取路径段
.map((segment) => toPascalCase(segment)); // 转换为PascalCase
const componentName = pathSegments.join(""); // 拼接成最终组件名
let content = fs.readFileSync(filePath, "utf8"); // 文件内容处理
const oldContent = content; // 保存原始内容用于后续对比
const scriptSetupRegex = /<script\s+((?:.(?!\/script>))*?\bsetup\b[^>]*)>/gim; // 灵活匹配找到script
let hasDefineOptions = false; // 标识是否找到defineOptions
// 处理已存在的 defineOptions
const defineOptionsRegex = /defineOptions\(\s*{([\s\S]*?)}\s*\)/g;
content = content.replace(defineOptionsRegex, (match, inner) => {
hasDefineOptions = true; // 标记已存在defineOptions
// 替换或添加 name 属性
const nameRegex = /(name\s*:\s*['"])([^'"]*)(['"])/;
let newInner = inner;
if (nameRegex.test(newInner)) { // 存在name属性时替换
newInner = newInner.replace(nameRegex, `$1${componentName}$3`);
console.log(`✅ 成功替换【name】: ${componentName} → ${filePath}`);
} else { // 不存在时添加
newInner = newInner.trim() === ""
? `name: '${componentName}'`
: `name: '${componentName}',\n${newInner}`;
console.log(`✅ 成功添加【name】: ${componentName} → ${filePath}`);
}
return `defineOptions({${newInner}})`; // 重组defineOptions
});
// 新增 defineOptions(如果不存在)
if (!hasDefineOptions) {
content = content.replace(scriptSetupRegex, (match, attrs) => {
return `<script ${attrs}>
defineOptions({
name: '${componentName}'
})`;
});
console.log(`✅ 成功添加【defineOptions和name】: ${componentName} → ${filePath}`);
}
// 仅在内容变化时写入文件
if (content !== oldContent) {
fs.writeFileSync(filePath, content);
// console.log(`✅ 成功更新 name: ${componentName} → ${filePath}`);
}
};
processDirectory(targetDir);
console.log("🎉 所有 Vue 组件 name 处理完成!");
4.4定制版脚本
该定制版:将vue路由对应的vue页面,使用路由的name;对于非vue路由的页面,直接使用对应地址路径命名
import constantRoutes from "./src/router/constant_routes.js"
// 动态添加路由添加
const dynamicRoutes = constantRoutes||[] // 获取vue路由配置
// console.log('%c【' + 'dynamicRoutes' + '】打印', 'color:#fff;background:#0f0', dynamicRoutes)
// 递归找对象
const findItem = (pathUrl, array) => {
for (const item of array) {
let componentPath
// 使用示例
componentPath = getComponentPath(item.component) ? getComponentPath(item.component).replace(/^@|\.vue$/g, '') : undefined
// console.log('%c【' + 'componentPath' + '】打印', 'color:#fff;background:#0f0', pathUrl, componentPath)
// 检查当前项的id是否匹配
if (componentPath === pathUrl) return item;
// 如果有子节点则递归查找
if (item.children?.length) {
const result = findItem(pathUrl, item.children);
if (result) return result; // 找到则立即返回
}
}
return undefined; // 未找到返回undefined
}
// 提取组件路径的正则表达式
const IMPORT_PATH_REGEX = /import\(["'](.*?)["']\)/;
// 获取路径字符串
const getComponentPath = (component) => {
if (!component?.toString) return null;
const funcString = component.toString();
const match = funcString.match(IMPORT_PATH_REGEX);
return match ? match[1] : null;
};
// console.log('%c【' + 'componentPath' + '】打印', 'color:#fff;background:red', componentPath)
import fs from "fs"; // 文件系统模块,用于读写文件
import path from "path"; // 路径处理模块
import { fileURLToPath } from "url"; // 用于转换URL路径
const __filename = fileURLToPath(import.meta.url); // 当前文件绝对路径
const __dirname = path.dirname(__filename); // 当前文件所在目录
// 🔧 配置区 ============================================
const targetDir = path.join(__dirname, "src/views"); // 目标目录:当前目录下的src/views
const PATH_DEPTH = Infinity; // 路径深度设置 自由修改数字:2→最后两级,3→最后三级,Infinity→全部路径
// =====================================================
const toPascalCase = (str) => {
return str
// .replace(/[-_](.)/g, (_, c) => c.toUpperCase()) // 转换连字符/下划线后的字母为大写
// .replace(/(^\w)/, (m) => m.toUpperCase()) // 首字母大写
.replace(/\.vue$/, ""); // 移除.vue后缀
};
const processDirectory = (dir) => {
const files = fs.readdirSync(dir, { withFileTypes: true }); // 读取目录内容
console.log('%c【' + 'dir' + '】打印', 'color:#fff;background:#0f0', dir)
files.forEach((file) => {
const fullPath = path.join(dir, file.name); // 获取完整路径
file.isDirectory() ? processDirectory(fullPath) : processVueFile(fullPath); // 递归处理目录,直接处理文件
});
};
const processVueFile = (filePath) => {
if (path.extname(filePath) !== ".vue") return; // 过滤非Vue文件
// 生成组件名逻辑
const relativePath = path.relative(targetDir, filePath);
console.log('%c【' + 'targetDir' + '】打印', 'color:#fff;background:#0f0', targetDir)
console.log('%c【' + 'filePath' + '】打印', 'color:#fff;background:#0f0', filePath)
console.log('%c【' + 'relativePath' + '】打印', 'color:#fff;background:#0f0', relativePath)
const pathSegments = relativePath
.split(path.sep) // 按路径分隔符拆分
.slice(-PATH_DEPTH) // 根据配置截取路径段
.map((segment) => toPascalCase(segment)); // 转换为PascalCase
const vuePath = '/views/' + pathSegments.join("/"); // 拼接成最终组件名
let componentName = findItem(vuePath, dynamicRoutes)?.name ? findItem(vuePath, dynamicRoutes)?.name : vuePath
console.log(filePath, componentName);
let content = fs.readFileSync(filePath, "utf8"); // 文件内容处理
const oldContent = content; // 保存原始内容用于后续对比
const scriptSetupRegex = /<script\s+((?:.(?!\/script>))*?\bsetup\b[^>]*)>/gim; // 灵活匹配找到script
let hasDefineOptions = false; // 标识是否找到defineOptions
// 处理已存在的 defineOptions
const defineOptionsRegex = /defineOptions\(\s*{([\s\S]*?)}\s*\)/g;
content = content.replace(defineOptionsRegex, (match, inner) => {
hasDefineOptions = true; // 标记已存在defineOptions
// 替换或添加 name 属性
const nameRegex = /(name\s*:\s*['"])([^'"]*)(['"])/;
let newInner = inner;
if (nameRegex.test(newInner)) { // 存在name属性时替换
newInner = newInner.replace(nameRegex, `$1${componentName}$3`);
console.log(`✅ 成功替换【name】: ${componentName} → ${filePath}`);
} else { // 不存在时添加
newInner = newInner.trim() === ""
? `name: '${componentName}'`
: `name: '${componentName}',\n${newInner}`;
console.log(`✅ 成功添加【name】: ${componentName} → ${filePath}`);
}
return `defineOptions({${newInner}})`; // 重组defineOptions
});
// 新增 defineOptions(如果不存在)
if (!hasDefineOptions) {
content = content.replace(scriptSetupRegex, (match, attrs) => {
return `<script ${attrs}>
defineOptions({
name: '${componentName}'
})`;
});
console.log(`✅ 成功添加【defineOptions和name】: ${componentName} → ${filePath}`);
}
// 仅在内容变化时写入文件
if (content !== oldContent) {
fs.writeFileSync(filePath, content);
// console.log(`✅ 成功更新 name: ${componentName} → ${filePath}`);
}
};
processDirectory(targetDir);
console.log("🎉 所有 Vue 组件 name 处理完成!");
5.package.json添加命令行
上述的脚本虽然可以通过node auto-set-component-name.mjs
执行,但是考虑到每次新增页面时候都要执行下。
可以考虑将在package.json里加入命令行
"dev": "pnpm setName && vite --mode beta --host",
"setName": "node auto-set-component-name.mjs",
使用pnpm setName
即使运行node auto-set-component-name.mjs
,或者在启动项目时候,pnpm dev
也会触发pnpm setName
的执行,以此确保所有.vue的name都有