三种将别名导入重写为相对路径的可能解决方案:
1. babel-plugin-module-resolver
使用babel-plugin-module-resolver
,同时省略其他babel插件/预设。
.babelrc
"plugins": [
[
"module-resolver",
{
"alias": {
"^@/(.+)": "./src/\\1"
}
}
]
]
构建步骤: babel src --out-dir dist
(输出到dist
文件夹,不会修改原文件)
import { helloWorld } from "@/sub/b"
import "@/sub/b"
export { helloWorld } from "@/sub/b"
export * from "@/sub/b"
对于TS,您还需要
@babel/preset-typescript
,并通过
babel src --out-dir dist --extensions ".ts"
激活
.ts
扩展名。
2. 使用正则表达式的jscodeshift Codemod
应支持来自
MDN文档和
相关的所有导入/导出变体。该算法的实现如下:
1. 输入:路径别名映射,形式为
alias -> resolved path
,类似于TypeScript的
tsconfig.json
paths
或Webpack的
resolve.alias
:
const pathMapping = {
"@": "./custom/app/path",
...
};
2. 迭代所有源文件,例如遍历 src
目录:
jscodeshift -t scripts/jscodeshift.js src
jscodeshift --extensions=ts --parser=ts -t scripts/jscodeshift.js src
3. 对于每个源文件,查找所有的导入和导出声明。
function transform(file, api) {
const j = api.jscodeshift;
const root = j(file.source);
root.find(j.ImportDeclaration).forEach(replaceNodepathAliases);
root.find(j.ExportAllDeclaration).forEach(replaceNodepathAliases);
root
.find(j.ExportNamedDeclaration, node => node.source !== null)
.forEach(replaceNodepathAliases);
return root.toSource();
...
};
jscodeshift.js
:
const pathMapping = {
"@": "./src",
foo: "bar",
};
const replacePathAlias = require("./replace-path-alias");
module.exports = function transform(file, api) {
const j = api.jscodeshift;
const root = j(file.source);
root.find(j.ImportDeclaration).forEach(replaceNodepathAliases);
root.find(j.ExportAllDeclaration).forEach(replaceNodepathAliases);
root
.find(j.ExportNamedDeclaration, (node) => node.source !== null)
.forEach(replaceNodepathAliases);
return root.toSource();
function replaceNodepathAliases(impExpDeclNodePath) {
impExpDeclNodePath.value.source.value = replacePathAlias(
file.path,
impExpDeclNodePath.value.source.value,
pathMapping
);
}
};
进一步说明:
import { AppStore } from "@/app/store/appStore-types"
创建以下AST,其ImportDeclaration
节点的source.value
可以被修改:
4. 对于每个路径声明,测试包含路径别名之一的正则表达式模式。
5. 获取别名的解析路径,并将其转换为相对于当前文件位置的路径(感谢@Reijo)。
replace-path-alias.js
(4. + 5.):
const path = require("path");
function replacePathAlias(currentFilePath, importPath, pathMap) {
currentFilePath = path.posix.join(...currentFilePath.split(path.sep));
const regex = createRegex(pathMap);
return importPath.replace(regex, replacer);
function replacer(_, alias, rest) {
const mappedImportPath = pathMap[alias] + rest;
let mappedImportPathRelative = path.posix.relative(
path.dirname(currentFilePath),
mappedImportPath
);
if (!mappedImportPathRelative.startsWith("../")) {
mappedImportPathRelative = `./${mappedImportPathRelative}`;
}
logReplace(currentFilePath, mappedImportPathRelative);
return mappedImportPathRelative;
}
}
function createRegex(pathMap) {
const mapKeysStr = Object.keys(pathMap).reduce((acc, cur) => `${acc}|${cur}`);
const regexStr = `^(${mapKeysStr})(.*)$`;
return new RegExp(regexStr, "g");
}
const log = true;
function logReplace(currentFilePath, mappedImportPathRelative) {
if (log)
console.log(
"current processed file:",
currentFilePath,
"; Mapped import path relative to current file:",
mappedImportPathRelative
);
}
module.exports = replacePathAlias;
3. 仅正则表达式搜索和替换
遍历所有源并应用正则表达式(未经充分测试):
^(import.*from\\s+["|'])(${aliasesKeys})(.*)(["|'])$
其中${aliasesKeys}
包含路径别名"@"
。可以通过修改第二个和第三个捕获组(路径映射+解析为相对路径)来处理新的导入路径。
该变体不能处理AST,因此可能不如jscodeshift稳定。
目前,正则表达式仅支持导入。以import "module-name"
形式的副作用导入被排除在外,这样可以更安全地进行搜索/替换。
Sample:
const path = require("path");
const fileContentSample = `
import { AppStore } from "@/app/store/appStore-types"
import { WidgetService } from "@/app/WidgetService"
import { AppStoreImpl } from "@/app/store/AppStoreImpl"
import { rootReducer } from "@/app/store/root-reducer"
export { appStoreFactory }
`;
const pathMappingSample = {
"@": "./src",
foo: "bar"
};
const currentFilePathSample = "./src/sub/a.js";
function replaceImportPathAliases(currentFilePath, fileContent, pathMap) {
const regex = createRegex(pathMap);
return fileContent.replace(regex, replacer);
function replacer(_, g1, aliasGrp, restPathGrp, g4) {
const mappedImportPath = pathMap[aliasGrp] + restPathGrp;
let mappedImportPathRelative = path.posix.relative(
path.dirname(currentFilePath),
mappedImportPath
);
if (!mappedImportPathRelative.startsWith("../")) {
mappedImportPathRelative = `./${mappedImportPathRelative}`;
}
return g1 + mappedImportPathRelative + g4;
}
}
function createRegex(pathMap) {
const mapKeysStr = Object.keys(pathMap).reduce((acc, cur) => `${acc}|${cur}`);
const regexStr = `^(import.*from\\s+["|'])(${mapKeysStr})(.*)(["|'])$`;
return new RegExp(regexStr, "gm");
}
console.log(
replaceImportPathAliases(
currentFilePathSample,
fileContentSample,
pathMappingSample
)
);
@
,使得代码库中不再出现@/
。将一个项目与另一个项目合并意味着之前作为单独项目的源文件被复制到不假定使用别名的项目中。 - Estus Flaskgit status
干净(或者只是提到源代码将在磁盘上被修改)。我可能误解了那个答案... - Devin Rhode