将别名@导入重构为相对路径

2023-12-29

在使用 Webpack、TypeScript 或其他转换 ES 模块导入的工具的模块化环境中,使用路径别名,一个常见的约定是@ for src https://stackoverflow.com/questions/42711175/what-does-the-symbol-do-in-javascript-imports.

使用别名绝对路径转换项目是我经常遇到的问题:

src/foo/bar/index.js

import baz from '@/baz';

相对路径:

src/foo/bar/index.js

import baz from '../../baz';

例如,使用别名的项目需要与另一个不使用别名的项目合并,由于样式指南或其他原因,不能选择将后者配置为使用别名。

这无法通过简单的搜索和替换来解决,并且手动修复导入路径非常繁琐且容易出错。我希望原始 JavaScript/TypeScript 代码库在其他方面保持完整,因此使用转译器对其进行转换可能不是一个选择。

我想使用我选择的 IDE (Jetbrains IDEA/Webstorm/Phpstorm) 来实现这种重构,但也接受使用任何其他 IDE (VS Code) 或普通 Node.js 的解决方案。

如何才能实现这一目标?


将别名导入重新连接到相对路径的三种可能的解决方案:

1. babel-插件-模块-解析器

Use babel-plugin-module-resolver https://github.com/tleunen/babel-plugin-module-resolver,同时省略其他 babel 插件/预设。

.babelrc:
"plugins": [
  [
    "module-resolver",
    {
      "alias": {
        "^@/(.+)": "./src/\\1"
      }
    }
  ]
]

构建步骤:babel src --out-dir dist(输出在dist,不会就地修改)

Processed example file:
// 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 https://babeljs.io/docs/en/babel-preset-typescript并激活.ts扩展通过babel src --out-dir dist --extensions ".ts".

2. Codemod jscodeshift 与正则表达式

所有相关的导入/导出变体来自MDN https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import docs https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export应该支持。该算法的实现如下:

1. 输入:表单中的路径别名映射alias -> resolved path类似于 TypeScripttsconfig.json paths或者 Webpack 的resolve.alias:

const pathMapping = {
  "@": "./custom/app/path",
  ...
};

2. 迭代所有源文件,例如遍历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 https://astexplorer.net/#/gist/5d148d18ce5ccd86cd6aae8b65167111/76588f532a6c1d8e3c698879790ebffd476e0e6a, whose source.value of 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})(.*)(["|'])$

, where ${aliasesKeys}包含路径别名"@"。新的导入路径可以通过修改第二个和第三个捕获组(路径映射+解析为相对路径)来处理。

该变体无法处理 AST,因此可能被认为不如 jscodeshift 稳定。

目前,正则表达式仅支持导入。副作用导入形式import "module-name"被排除在外,这样做的好处是搜索/替换更安全。

Sample:

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
  )
);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

将别名@导入重构为相对路径 的相关文章

  • 如何将选定的元素从 devTools 页面发送到 chrome 侧边栏页面

    我正在开发 chrome devTools 扩展 基本上 我在 元素 面板中添加了一个侧边栏窗格 开发工具 js chrome devtools panels elements createSidebarPane ChromeTrast f
  • Javascript 闭包 - 变量范围问题

    我正在阅读 Mozilla 开发者网站上有关闭包的内容 我注意到在他们的常见错误示例中 他们有以下代码 p Helpful notes will appear here p p E mail p
  • 如何使用标准 JavaScript 在 CSS 转换结束后立即重新启动它?

    我构建了一种密码生成器 只要倒计时到期 它就会显示新密码 不幸的是 我只设法弄清楚如何运行我的代码一次 倒计时由一个简单的 CSS 过渡组成 我想保留它 因为它比我的其他尝试平滑得多 其中我尝试使用 JavaScript 重复更新宽度 va
  • D3更新circle-pack数据新节点与现有节点重叠

    我正在关注一般更新模式 http bl ocks org mbostock 3808234但在分层方面存在问题 使用圆形包装布局 我pack新数据 update enter and exit圆形元素 然而 当新元素enter 它们重叠upd
  • Visual Studio Code - .net core - 生成xml文档

    我们正在使用Swagger UI描述我们项目 API 的文档 Swagger 必须从中读取 XMLprojectname xml显示所有 C R U D 我们在项目中拥有的功能 问题是当我从 Visual Studio 切换到 Visual
  • 每n秒执行一次函数

    我制作了这个在 10 秒后点击链接的代码片段 function timeout window setTimeout function img left click 1000 setTimeout timeout 1000 timeout 我
  • jslint 配置 |传递全局变量

    我如何提醒 jshint 我有全局变量 即命名它们 我知道你可以做到这一点 但我不记得语法了 我在这里定义了一个全局的 function window glob1 local var 稍后像这样使用 不同的 IIFE function gl
  • setTimeout范围问题

    我在控制玩家重生的函数内部定义了一个 setTimeout 我正在创建一个游戏 var player death function this alive false Console log death var timer3 setTimeo
  • 如何将本地文本文件上传到文本区域(网页内)

    我是一名新手程序员 需要一些帮助来弄清楚如何将本地文本文件上传到我正在构建的网站内的文本区域 我非常精通 HTML CSS 对 Javascript JQuery 有相当的了解 而且我刚刚学习 PHP 您能提供的任何帮助我将不胜感激 我有一
  • 对使用“new”创建的数组上“map”的行为感到困惑[重复]

    这个问题在这里已经有答案了 我对结果感到困惑mapping 使用创建的数组new function returnsFourteen return 14 var a new Array 4 gt undefined x 4 in Chrome
  • IE localStorage 事件失火

    在 Internet Explorer 9 和 10 中 localStorage 实现意外地触发事件 这里有很棒的线索 Chrome 的 localStorage 实现存在错误 https stackoverflow com questi
  • 如何绕过Access-Control-Allow-Origin?

    我正在一个平台上对我自己的服务器进行ajax调用 他们设置了阻止这些ajax调用的平台 但我需要它从我的服务器获取数据以显示从我的服务器数据库检索到的数据 我的 ajax 脚本正在运行 它可以将数据发送到我的服务器的 php 脚本以允许其处
  • Angular 2 将字符串转换为 md5 哈希

    我找到了ts md5 https www npmjs com package ts md5包 但在示例中它有一个hashStr方法 但现在不行了 类型上不存在属性 hashStr Md5 使用该错误后 该错误会记录在我的控制台中 我怎样才能
  • 如何在打字稿中使用外部js

    我通过 Typescript 代码生成 Angular JS 代码 在一种情况下 我需要将外部 JS 文件添加到我的打字稿文件中 并且需要访问 js 文件中的类 我像这样添加js文件
  • Javascript location.href 到 mailto 触发 GET HTTP,该 HTTP 在 Chrome 中被取消

    我有一个按钮可以触发以下 javascript 函数 function sendEmail var mail mailto email protected cdn cgi l email protection location href m
  • 在 Chrome 开发者工具中禁用调试器语句

    我正在尝试对恶意 JavaScript 进行逆向工程 当我最初加载侧面时 会注入 JS 代码 其中包括 debugger 语句并将断点注入我的 chrome 开发人员控制台 通过stackoverflow阅读 禁用所有断点does not帮
  • 从请求url获取hash参数

    我有这样的网址 http www coolsite com daily plan id 1 http www coolsite com daily plan id 1解析该字符串并读取哈希值 id 之后的值 的最简单方法是什么 谢谢 在客户
  • 如何使用 Javascript OAuth 库不暴露您的密钥?

    看着Twitter OAuth 库 https dev twitter com docs twitter libraries 我看到了这个注释 将 JavaScript 与 OAuth 结合使用时要小心 不要暴露你的钥匙 然后 看着jsOA
  • 如果 jquery 验证激活,如何在单选按钮中放置红色边框[重复]

    这个问题在这里已经有答案了 我的问题是 如果 jquery 验证像示例图片中那样激活 我无法使单选按钮具有红色边框 任何人都可以帮我解决这个问题吗 http i38 photobucket com albums e149 eloginko
  • ‘state’未定义 no-undef

    我使用教程来学习 React 但我很快就陷入困境 在教程中 他们使用以下代码 import React Component from react class Counter extends Component state count 0 r

随机推荐