问题:window下文件的路径的大小写是不敏感的,而linux下文件的路径名称需要区分大小写,在用linux打包文vue项目的时候会出现路径错误。要在linux 下完成打包,需修改文件中路径引用严格区分大小写。
自动校验和修复效果:
文章目录
-
- 一、问题
- 二、检查大小写及修复脚本
-
- 1、创建路径大小写检查修复脚本
- 脚本特点
- 2、使用说明
-
- [2.1、 基本使用](#2.1、 基本使用)
- 2.2、自动修复
- [2.3、 添加执行脚本到 package.json](#2.3、 添加执行脚本到 package.json)
- 2.3、预防性配置
-
- [在 vite.config.js 中添加路径别名检查](#在 vite.config.js 中添加路径别名检查)
- [3、 其他配置(暂未验证,可以不配置)](#3、 其他配置(暂未验证,可以不配置))
- 三、配置git大小写敏感
一、问题
1、问题截图:

问题解释:在src/plugins/components.js文件内找不到xxxx/src/components/MyUpload/index.vue
因为实际的文件是xxxx/src/components/Myupload/index.vue
2、问题原因:
windows下路径大小写不敏感 ,如/page/index.js== Page/index.js,但是在Linux环境下大小写敏感
·/page!=/Page 它们表示两个不同的文件夹,所以在我们开发时应该遵守规范,严格按照大小写引入。
还有个一原因,写路由的时候,会省略index.vue文件,比如/page/login/index.vue 写成page/login,打包的时候也会报错.
二、检查大小写及修复脚本
知道路径大小写问题后,如果一个个文件去查找是非常麻烦且会有遗漏;此时可通过以下脚本自动查找和修复:这个脚本会扫描项目文件,找出不匹配的路径引用并提供修复选项。
1、创建路径大小写检查修复脚本
在项目的根目录下,创建 fix-path-case.js 文件:
脚本特点
- 全面扫描:支持 Vue、JS、TS 等文件类型
- 智能修复:自动找到正确的大小写路径
- 安全可靠 :默认只报告不修改,需明确指定
--fix - 详细报告:显示文件名、行号、原路径和建议路径
- 忽略无关目录:自动跳过 node_modules 等目录
csharp
#!/usr/bin/env node
// 检查文件大小写引入问题:
// node fix-path-case.js
// 自动修复:
// node fix-path-case.js --fix
import fs from "fs" // 文件系统模块,用于读写文件
import path from "path" // 路径处理模块
import { promisify } from "util"
const readdir = promisify(fs.readdir)
const stat = promisify(fs.stat)
const readFile = promisify(fs.readFile)
const writeFile = promisify(fs.writeFile)
// const exists = promisify(fs.exists);
class PathCaseChecker {
constructor(options = {}) {
this.rootDir = options.rootDir || process.cwd()
this.srcDir = path.join(this.rootDir, "src")
this.extensions = options.extensions || [".vue", ".js", ".ts", ".jsx", ".tsx"]
this.ignoreDirs = options.ignoreDirs || ["node_modules", ".git", "dist", "build"]
this.autoFix = options.autoFix || false
this.issues = []
// 存储所有文件的真实路径映射(小写 -> 实际大小写)
this.filePathMap = new Map()
this.aliasMap = {
"@": this.srcDir,
"~": this.srcDir
}
}
// 递归收集所有文件的实际路径
async collectAllFiles(dir, relativePath = "") {
try {
const files = await readdir(dir)
for (const file of files) {
const fullPath = path.join(dir, file)
const fileRelativePath = path.join(relativePath, file)
const fileStat = await stat(fullPath)
if (fileStat.isDirectory()) {
if (!this.ignoreDirs.includes(file)) {
await this.collectAllFiles(fullPath, fileRelativePath)
}
} else {
const ext = path.extname(file)
if (this.extensions.includes(ext)) {
// 存储小写路径到实际路径的映射
const lowerPath = fileRelativePath.toLowerCase().replace(/\\/g, "/")
this.filePathMap.set(lowerPath, fileRelativePath.replace(/\\/g, "/"))
}
}
}
} catch (error) {
console.error(`收集文件失败: ${dir}`, error.message)
}
}
// 解析别名路径为实际路径
resolveAliasPath(importPath) {
if (importPath.startsWith("@/") || importPath.startsWith("~/")) {
const alias = importPath.startsWith("@/") ? "@" : "~"
const relativePath = importPath.slice(2) // 去掉 '@/' 或 '~/'
return path.join(this.aliasMap[alias], relativePath)
}
return importPath
}
// 检查导入路径是否存在大小写问题
async checkImportPath(importPath, currentFile) {
// 跳过非项目路径
if (!importPath.startsWith("@/") && !importPath.startsWith("~/") && !importPath.startsWith(".")) {
return null
}
let actualFilePath = importPath
// 处理别名路径
if (importPath.startsWith("@/") || importPath.startsWith("~/")) {
actualFilePath = this.resolveAliasPath(importPath)
} else if (importPath.startsWith(".")) {
// 处理相对路径
const currentDir = path.dirname(currentFile)
actualFilePath = path.resolve(currentDir, importPath)
}
// 转换为相对于 src 目录的路径
let relativeToSrc = path.relative(this.srcDir, actualFilePath)
if (relativeToSrc.startsWith("..")) {
// 如果不在 src 目录内,使用相对于项目根目录的路径
relativeToSrc = path.relative(this.rootDir, actualFilePath)
}
const normalizedRelativePath = relativeToSrc.replace(/\\/g, "/")
const lowerPath = normalizedRelativePath.toLowerCase()
// 检查是否存在大小写不匹配
const actualCasePath = this.filePathMap.get(lowerPath)
if (actualCasePath && actualCasePath !== normalizedRelativePath) {
// 计算正确的导入路径
let correctImportPath
if (importPath.startsWith("@/") || importPath.startsWith("~/")) {
const alias = importPath.startsWith("@/") ? "@" : "~"
correctImportPath = `${alias}/${actualCasePath}`
} else {
// 相对路径需要重新计算
const currentDir = path.dirname(currentFile)
const absoluteCorrectPath = path.join(this.srcDir, actualCasePath)
correctImportPath = path.relative(currentDir, absoluteCorrectPath).replace(/\\/g, "/")
if (!correctImportPath.startsWith(".")) {
correctImportPath = "./" + correctImportPath
}
}
return {
original: importPath,
correct: correctImportPath,
actualFile: path.join(this.srcDir, actualCasePath)
}
}
return null
}
// 提取文件中的所有导入语句(改进版)
extractImports(content, filePath) {
const imports = []
// 匹配各种导入模式
const patterns = [
// import ... from 'path'
/from\s+['"]([^'"]+)['"]/g,
// import 'path'
/import\s+['"]([^'"]+)['"]/g,
// require('path')
/require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
// 动态 import()
/import\s*\(\s*['"]([^'"]+)['"]\s*\)/g
]
patterns.forEach(pattern => {
let match
while ((match = pattern.exec(content)) !== null) {
const importPath = match[1]
// 过滤掉明显不是文件导入的路径
if (this.isFileImport(importPath, filePath)) {
imports.push({
path: importPath,
start: match.index,
end: match.index + match[0].length,
original: match[0]
})
}
}
})
return imports
}
// 判断是否为文件导入路径
isFileImport(importPath, currentFile) {
// 跳过明显的包导入
if (!importPath.startsWith(".") && !importPath.startsWith("@/") && !importPath.startsWith("~/")) {
return false
}
// 检查是否有文件扩展名或看起来像文件路径
const ext = path.extname(importPath)
if (ext && this.extensions.includes(ext)) {
return true
}
// 对于目录导入,检查是否存在对应的文件
return this.looksLikeFilePath(importPath, currentFile)
}
// 判断路径是否像文件路径
looksLikeFilePath(importPath, currentFile) {
// 包含目录分隔符或常见目录名
if (importPath.includes("/") && !importPath.includes("node_modules")) {
// 尝试解析路径并检查是否存在对应文件
let resolvedPath
if (importPath.startsWith("@/") || importPath.startsWith("~/")) {
resolvedPath = this.resolveAliasPath(importPath)
} else {
resolvedPath = path.resolve(path.dirname(currentFile), importPath)
}
// 检查是否存在对应的文件(任何扩展名)
return this.extensions.some(ext => {
const filePath = resolvedPath + ext
return (
fs.existsSync(filePath) || fs.existsSync(path.join(resolvedPath, "index" + ext)) || fs.existsSync(path.join(resolvedPath, "index.js")) || fs.existsSync(path.join(resolvedPath, "index.vue"))
)
})
}
return false
}
// 检查单个文件
async checkFile(filePath) {
try {
const content = await readFile(filePath, "utf8")
const imports = this.extractImports(content, filePath)
let newContent = content
let hasChanges = false
const replacements = []
// 收集所有需要替换的位置
for (const imp of imports) {
const result = await this.checkImportPath(imp.path, filePath)
if (result) {
replacements.push({
original: imp.original,
replacement: imp.original.replace(imp.path, result.correct),
start: imp.start,
end: imp.end,
issue: {
file: filePath,
originalPath: imp.path,
correctPath: result.correct,
actualFile: result.actualFile
}
})
}
}
// 从后往前替换,避免位置偏移
replacements.sort((a, b) => b.start - a.start)
for (const replacement of replacements) {
const before = newContent.substring(0, replacement.start)
const after = newContent.substring(replacement.end)
newContent = before + replacement.replacement + after
this.issues.push(replacement.issue)
hasChanges = true
}
if (hasChanges && this.autoFix) {
await writeFile(filePath, newContent, "utf8")
console.log(`✅ 已修复: ${filePath}`)
}
return hasChanges
} catch (error) {
console.error(`❌ 检查文件失败: ${filePath}`, error.message)
return false
}
}
// 递归扫描目录
async scanDirectory(dirPath) {
try {
const files = await readdir(dirPath)
for (const file of files) {
const fullPath = path.join(dirPath, file)
const fileStat = await stat(fullPath)
if (fileStat.isDirectory()) {
if (!this.ignoreDirs.includes(file)) {
await this.scanDirectory(fullPath)
}
} else {
const ext = path.extname(file)
if (this.extensions.includes(ext)) {
await this.checkFile(fullPath)
}
}
}
} catch (error) {
console.error(`❌ 扫描目录失败: ${dirPath}`, error.message)
}
}
// 生成详细报告
generateReport() {
if (this.issues.length === 0) {
console.log("\n🎉 没有发现路径大小写问题!")
return
}
console.log(`\n📊 发现 ${this.issues.length} 个路径大小写问题,统计如下:\n`)
const groupedIssues = this.issues.reduce((groups, issue) => {
const key = issue.file
if (!groups[key]) groups[key] = []
groups[key].push(issue)
return groups
}, {})
Object.entries(groupedIssues).forEach(([file, issues]) => {
console.log(`📁 ${file}:`)
issues.forEach((issue, index) => {
console.log(` ${index + 1}. 引入路径: ${issue.originalPath}`)
console.log(` 正确路径: ${issue.correctPath}`)
console.log(` 实际文件: ${issue.actualFile}`)
})
console.log("")
})
if (!this.autoFix) {
console.log(`\n ❌存在 ${this.issues.length} 个路径大小写问题:`)
console.log(' 💡建议运行 "node fix-path-case.js --fix" 自动修复这些问题')
}
if (this.autoFix && this.issues.length > 0) {
console.log(`\n ✅已修复路径大小写问题`)
}
}
// 主执行函数
async run() {
console.log("🔍 开始收集项目文件信息...")
await this.collectAllFiles(this.srcDir)
console.log("📁 扫描路径大小写问题...")
console.log(`项目根目录: ${this.rootDir}`)
console.log(`源码目录: ${this.srcDir}`)
await this.scanDirectory(this.srcDir)
this.generateReport()
if (this.issues.length > 0 && !this.autoFix) {
// console.log('\n❌ 发现路径大小写问题,请运行修复命令');
// process.exit(1) // 如果有问题且未修复,退出码为1
}
}
}
// CLI 参数解析
const args = process.argv.slice(2)
const options = {
autoFix: args.includes("--fix") || args.includes("-f"),
rootDir: process.cwd()
}
// 处理自定义目录
const dirIndex = args.findIndex(arg => arg === "--dir" || arg === "-d")
if (dirIndex !== -1 && args[dirIndex + 1]) {
options.rootDir = path.resolve(args[dirIndex + 1])
}
// 运行检查器
const checker = new PathCaseChecker(options)
checker.run().catch(error => {
console.error("❌ 执行失败:", error)
process.exit(1)
})
2、使用说明
2.1、 基本使用
bash
# 将脚本放在项目根目录,然后运行:
node fix-path-case.js
这会扫描项目并报告所有路径大小写问题,但不会自动修改。
2.2、自动修复
bash
# 自动修复所有发现的问题
node fix-path-case.js --fix
2.3、 添加执行脚本到 package.json
在 package.json 中添加脚本:
json
{
"scripts": {
"dev": "pnpm fix:paths && vite --mode beta --host",
"lint:paths": "node fix-path-case.js",
"fix:paths": "node fix-path-case.js --fix"
}
}
然后运行:
bash
pnpm lint:paths # 检查问题
pnpm fix:paths # 自动修复
pnpm dev #启动项目时候自动校验和修复
pnpm lint:paths检查问题效果截图:

pnpm fix:paths自动修复效果截图:

pnpm dev启动项目时候自动校验和修复:

2.3、预防性配置
在 vite.config.js 中添加路径别名检查
javascript
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
// 确保所有别名都使用正确的大小写
},
},
});
3、 其他配置(暂未验证,可以不配置)
3.1 配套的 ESLint 配置
创建 .eslintrc-path-case.js 文件来增强路径检查:
javascript
module.exports = {
rules: {
'import/no-unresolved': ['error', { caseSensitive: true }],
},
overrides: [
{
files: ['*.vue'],
rules: {
'vue/component-name-in-template-casing': ['error', 'PascalCase'],
}
}
]
};
3.2 创建路径检查的 Git Hook
在 .husky/pre-commit 中添加:
bash
#!/bin/bash
node fix-path-case.js --dry-run
if [ $? -ne 0 ]; then
echo "发现路径大小写问题,请运行 'npm run fix:paths' 修复"
exit 1
fi
三、配置git大小写敏感
3.1、检查git是否校验大小写敏感
git在提交代码时,会忽略文件名称大小写,导致本地代码与远程代码不一致,此时可利用终端指令来检查下
如果返回值为true,则表示Git会将文件名大小写视为不敏感 ;如果返回值为false ,则表示Git会将文件名大小写视为敏感
csharp
# 查看全局设置
git config --get core.ignorecase
# 查看特定项目设置(需要进入项目根目录)
git config --global --get core.ignorecase
3.2配置大小写敏感
csharp
# 设置全局设置
git config --global core.ignorecase false
# 设置特定项目设置(需要进入项目根目录)
git config core.ignorecase false