3 回答
TA贡献1900条经验 获得超5个赞
将别名导入重新连接到相对路径的三种可能的解决方案:
1. babel-plugin-module-resolver
使用babel-plugin-module-resolver,同时忽略其他 babel 插件/预设。
.babelrc:
"plugins": [
[
"module-resolver",
{
"alias": {
"^@/(.+)": "./src/\\1"
}
}
]
]
构建步骤:(babel src --out-dir dist在 中输出dist,不会就地修改)
处理的示例文件:
// input // output
import { helloWorld } from "@/sub/b" // import { helloWorld } from "./sub/b";
import "@/sub/b" // import "./sub/b";
export { helloWorld } from "@/sub/b" // export { helloWorld } from "./sub/b";
export * from "@/sub/b" // export * from "./sub/b";
对于 TS,您还需要@babel/preset-typescript并.ts通过babel src --out-dir dist --extensions ".ts".
2. Codemod jscodeshift 与正则表达式
应支持MDN 文档中所有相关的导入/导出变体。该算法是这样实现的:
1. Input: path aliases 以alias -> resolved path类似于 TypeScripttsconfig.json paths或 Webpack 的形式映射resolve.alias:
const pathMapping = {
"@": "./custom/app/path",
...
};
2. 遍历所有源文件,例如 traverse src:
jscodeshift -t scripts/jscodeshift.js src # use -d -p options for dry-run + stdout
# or for TS
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:
/**
* Corresponds to tsconfig.json paths or webpack aliases
* E.g. "@/app/store/AppStore" -> "./src/app/store/AppStore"
*/
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);
/**
* Filter out normal module exports, like export function foo(){ ...}
* Include export {a} from "mymodule" etc.
*/
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,其source.value的ImportDeclaration节点可以被修饰:
4. 对于每个路径声明,测试包含路径别名之一的正则表达式模式。
5.获取别名的解析路径并转换为相对于当前文件位置的路径(归功于@Reijo)
replace-path-alias.js
(4. + 5.):
const path = require("path");
function replacePathAlias(currentFilePath, importPath, pathMap) {
// if windows env, convert backslashes to "/" first
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;
// use path.posix to also create foward slashes on windows environment
let mappedImportPathRelative = path.posix.relative(
path.dirname(currentFilePath),
mappedImportPath
);
// append "./" to make it a relative import path
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}
包含路径别名"@"
。可以通过修改第2和第3个捕获组(路径映射+解析为相对路径)来处理新的导入路径。
此变体无法处理 AST,因此可能被认为不如 jscodeshift 稳定。
目前,正则表达式仅支持导入。import "module-name"
排除了表单中的副作用导入,这样可以使搜索/替换更安全。
样品:
const path = require("path");
// here sample file content of one file as hardcoded string for simplicity.
// For your project, read all files (e.g. "fs.readFile" in node.js)
// and foreach file replace content by the return string of replaceImportPathAliases function.
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 }
`;
// corresponds to tsconfig.json paths or webpack aliases
// e.g. "@/app/store/AppStoreImpl" -> "./custom/app/path/app/store/AppStoreImpl"
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
);
// append "./" to make it a relative import path
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
)
);
TA贡献1865条经验 获得超7个赞
我创建了一个脚本来执行此操作。
它基本上遍历项目树,搜索所有文件,/"@(\/\w+[\w\/.]+)"/gi使用正则表达式查找看起来像“@/my/import”的导入,然后使用nodejs的path模块创建相对路径。
我希望你没有任何我没有在这个简单脚本中涵盖的边缘情况,所以最好备份你的文件。我只在一个简单的场景中测试过它。
这是代码:
const path = require("path");
const args = process.argv;
const rootName = args[2];
const rootPath = path.resolve(process.cwd(), rootName);
const alias = "@";
if (!rootPath || !alias) return;
const { promisify } = require("util");
const fs = require("fs");
const readFileAsync = promisify(fs.readFile);
const readDirAsync = promisify(fs.readdir);
const writeFileAsync = promisify(fs.writeFile);
const statsAsync = promisify(fs.stat);
function testForAliasImport(file) {
if (!file.content) return file;
const regex = /"@(\/\w+[\w\/.]+)"/gi;
let match,
search = file.content;
while ((match = regex.exec(search))) {
const matchString = match[0];
console.log(`found alias import ${matchString} in ${file.filepath}`);
file.content = file.content.replace(
matchString,
aliasToRelative(file, matchString)
);
search = search.substring(match.index + matchString.length);
}
return file;
}
function aliasToRelative(file, importString) {
let importPath = importString
.replace(alias, "")
.split('"')
.join("");
const hasExtension = !!path.parse(importString).ext;
if (!hasExtension) {
importPath += ".ext";
}
const filepath = file.filepath
.replace(rootPath, "")
.split("\\")
.join("/");
let relativeImport = path.posix.relative(path.dirname(filepath), importPath);
if (!hasExtension) {
relativeImport = relativeImport.replace(".ext", "");
}
if (!relativeImport.startsWith("../")) {
relativeImport = "./" + relativeImport;
}
relativeImport = `"${relativeImport}"`;
console.log(`replaced alias import ${importString} with ${relativeImport}`);
return relativeImport;
}
async function writeFile(file) {
if (!file || !file.content || !file.filepath) return file;
try {
console.log(`writing new contents to file ${file.filepath}...`);
await writeFileAsync(file.filepath, file.content);
} catch (e) {
console.error(e);
}
}
async function prepareFile(filepath) {
const stat = await statsAsync(filepath);
return { stat, filepath };
}
async function processFile(file) {
if (file.stat.isFile()) {
console.log(`reading file ${file.filepath}...`);
file.content = await readFileAsync(file.filepath);
file.content = file.content.toString();
} else if (file.stat.isDirectory()) {
console.log(`traversing dir ${file.filepath}...`);
await traverseDir(file.filepath);
}
return file;
}
async function traverseDir(dirPath) {
try {
const filenames = await readDirAsync(dirPath);
const filepaths = filenames.map(name => path.join(dirPath, name));
const fileStats = await Promise.all(filepaths.map(prepareFile));
const files = await Promise.all(fileStats.map(processFile));
await Promise.all(files.map(testForAliasImport).map(writeFile));
} catch (e) {
console.error(e);
}
}
traverseDir(rootPath)
.then(() => console.log("done"))
.catch(console.error);
请务必提供目录名称作为参数。喜欢src的实例。
对于 IDE 部分,我知道 Jetbrains Webstorm 让你定义 npm 任务。
创建一个scripts目录来保存脚本。
定义一个脚本package.json
"scripts": {
...
"replaceimports": "node scripts/script.js \"src\""
}
在 npm 工具窗口中注册 npm 任务以供使用。
添加回答
举报