梵蒂冈之花
将别名导入重新连接到相对路径的三种可能的解决方案:1. babel-plugin-module-resolver使用babel-plugin-module-resolver,同时忽略其他 babel 插件/预设。.babelrc:"plugins": [ [ "module-resolver", { "alias": { "^@/(.+)": "./src/\\1" } } ]]构建步骤:(babel src --out-dir dist在 中输出dist,不会就地修改)处理的示例文件:// input // outputimport { 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 TSjscodeshift --extensions=ts --parser=ts -t scripts/jscodeshift.js src3.对于每个源文件,找到所有的进出口申报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 environmentlet mappedImportPathRelative = path.posix.relative( path.dirname(currentFilePath), mappedImportPath);// append "./" to make it a relative import pathif (!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 ));
鸿蒙传说
我创建了一个脚本来执行此操作。它基本上遍历项目树,搜索所有文件,/"@(\/\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 任务以供使用。