>웹 프론트엔드 >JS 튜토리얼 >node.js의 모듈 별칭에 대해 자세히 알아보세요(일부 함정 공유)

node.js의 모듈 별칭에 대해 자세히 알아보세요(일부 함정 공유)

青灯夜游
青灯夜游앞으로
2021-12-20 11:05:495486검색

이 글은 node.js의 모듈 별칭을 이해하고 모듈 별칭의 원리와 모듈 별칭의 일반적인 문제(피트)를 소개하는 내용이 모든 사람에게 도움이 되기를 바랍니다.

node.js의 모듈 별칭에 대해 자세히 알아보세요(일부 함정 공유)

먼저 module-alias가 무엇인지 소개할 필요가 있습니다. 공식 홈페이지 링크(공식 홈페이지 주소 https://github.com/ilearnio/module-alias)입니다. ). module-alias是什么,这里有其官网链接(官网地址 https://github.com/ilearnio/module-alias)。

简单点说,module-alias提供了在node环境下的路径别名功能。一般前端开发可能会比较熟悉webpackalias配置、typescriptpaths配置等,这些都是提供了陆军别名的功能。路径别名在代码开发过程中是yyds,不然你看到这种../../../../xx路径时,是肯定会抓狂的。

使用webpack打包的项目webpack本身会处理源代码中路径别名的配置到打包后代码的转换过程,但是如果单纯使用typescript进行编译的项目,虽然typescript在编译过程中可以正常处理paths中路径别名的配置,但是并不会改变打包后的代码,造成在打包后的代码中仍然存在路径别名配置,看一个经过typescript编译后的代码:

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
require("./module-alias-register");
var commands_1 = require("@/commands");
var package_json_1 = require("../package.json");
(0, commands_1.run)(package_json_1.version);

这里是tsconfig.json的配置内容

"paths": {
  "@/*": [
    "src/*"
  ]
}

可以看到在经过typescript编译后的代码中,仍然存在@符号,然而当代码运行的过程中,比如允许在node中,require并不能正常识别路径里的这个符号,导致找不到相应模块而抛出异常。

这也是module-alias这个库存在的目的。

module-alias介绍

从官网上看,这个库使用方法只需要两步,真的已经是极简状态了。

1、路径别名配置:module-alias支持两种路径别名配置方式

  • package.json中增加_moduleAliases属性进行配置

    "_moduleAliases": {
        "@": "./src"
    }
  • 通过提供的API接口addAliasaddAliasesaddPath,增加配置

    moduleAlias.addAliases({
      '@'  : __dirname + './src',
    });

2、在项目启动时首先导入该库:require(module-alias/register)即可,当然选择使用API方式的需要导入对应的函数进行处理

一般我们都是使用package.json中配置路径别名 + 项目入口处require(module-alias/register)来使用这个库。

module-alias原理介绍

module-alias通过覆写了全局对象Module上的方法_resolveFilename来实现路径别名的转换,简单来说就是通过拦截原生的_resolveFilename方法调用,进行路径别名的转换,当获取到文件的真实路径后,再调用原声的_resolveFilename方法。

下面是其源代码,基本上分为两部分:路径别名转换+原生_resolveFilename调用

var oldResolveFilename = Module._resolveFilename
Module._resolveFilename = function (request, parentModule, isMain, options) {
  for (var i = moduleAliasNames.length; i-- > 0;) {
    var alias = moduleAliasNames[i]
    if (isPathMatchesAlias(request, alias)) {
      var aliasTarget = moduleAliases[alias]
      // Custom function handler
      if (typeof moduleAliases[alias] === 'function') {
        var fromPath = parentModule.filename
        aliasTarget = moduleAliases[alias](fromPath, request, alias)
        if (!aliasTarget || typeof aliasTarget !== 'string') {
          throw new Error('[module-alias] Expecting custom handler function to return path.')
        }
      }
      request = nodePath.join(aliasTarget, request.substr(alias.length))
      // Only use the first match
      break
    }
  }

  return oldResolveFilename.call(this, request, parentModule, isMain, options)
}

看似简单的背后,往往也会踩坑

module-alias踩坑

一般我们会在node项目中使用module-alias库,因为node项目一般会从typescript转换成js代码,但是往往并不会进行打包处理,因为node项目中一般也确实不需要打包,显得有些冗余。这时候就需要module-alias上场了。

但是这个项目有点不一般,我们在项目中使用了多层代码组织方式,最外层有全局的package.json, 内层包有自己的package.json, 简单说是使用了monorepo的代码组织方式,问题也就是由此而来

module-alias无法正常解析在package.json中配置的路径别名

刚开始确实没想到是多层项目组织方式的问题,官网对module-alias/register

간단히 말하면 module-alias

nodenode.js의 모듈 별칭에 대해 자세히 알아보세요(일부 함정 공유) 환경에서 경로 별칭 기능을 제공합니다. 일반 프런트엔드 개발자는 webpackalias 구성, typescriptpaths 구성 등에 익숙할 수 있습니다. . 군 별칭 기능이 추가되었습니다. 코드 개발 프로세스에서 경로 별칭은 yyds입니다. 그렇지 않으면 이 ../../../../xx 경로를 보면 확실히 미쳐버릴 것입니다.

webpack webpack 자체를 사용하여 패키징된 프로젝트는 소스 코드의 경로 별칭 구성에서 패키징된 코드로의 변환 프로세스를 처리하지만, 단순히 를 사용하는 경우 >typescript 코드로 컴파일된 프로젝트의 경우 <code>typescript는 일반적으로 컴파일 프로세스 중에 경로의 경로 별칭 구성을 처리할 수 있지만 변경하지는 않습니다. 결과적으로 패키지된 코드에 경로 별칭 구성이 여전히 존재합니다. typescript로 컴파일된 코드를 살펴보세요.

function init (options) {
  // 省略了部分内容
  var candidatePackagePaths
  if (options.base) {
    candidatePackagePaths = [nodePath.resolve(options.base.replace(/\/package\.json$/, &#39;&#39;))]
  } else {
    // There is probably 99% chance that the project root directory in located
    // above the node_modules directory,
    // Or that package.json is in the node process&#39; current working directory (when
    // running a package manager script, e.g. `yarn start` / `npm run start`)
    // 重点看这里!!!
    candidatePackagePaths = [nodePath.join(__dirname, &#39;../..&#39;), process.cwd()]
  }
  var npmPackage, base
  for (var i in candidatePackagePaths) {
    try {
      base = candidatePackagePaths[i]
      npmPackage = require(nodePath.join(base, &#39;package.json&#39;))
      break
    } catch (e) { // noop }
  }
  // 省略了部分内容
  var aliases = npmPackage._moduleAliases || {}
  for (var alias in aliases) {
    if (aliases[alias][0] !== &#39;/&#39;) {
      aliases[alias] = nodePath.join(base, aliases[alias])
    }
  }
  // 省略了部分内容
}
🎜다음은 tsconfig.json의 구성입니다. > 내용🎜rrreee🎜 typescript로 컴파일한 코드에는 @ 기호가 여전히 존재하는 것을 볼 수 있습니다. 그러나 예를 들어 node에서 코드가 실행되면 require는 경로에서 이 기호를 제대로 인식할 수 없으므로 해당 모듈을 찾고 예외가 발생합니다. 🎜🎜이것은 module-alias 라이브러리의 목적이기도 합니다. 🎜

모듈 별칭 소개

🎜공식 홈페이지에 따르면 이 라이브러리를 사용하는 방법은 두 단계만 필요하며 정말 미니멀합니다. 🎜🎜1. 경로 별칭 구성: module-alias는 두 가지 경로 별칭 구성 방법을 지원합니다🎜
  • 🎜package.json_moduleAliases 추가 code>속성 구성🎜rrreee
  • 🎜제공된 API 인터페이스 addAlias, addAliases, addPath를 통해 추가 🎜rrreee
🎜2. 프로젝트가 시작되면 먼저 라이브러리를 가져옵니다. require(module-alias/register) 물론 API 메서드를 사용하려면 다음을 수행해야 합니다. 처리를 위해 해당 함수를 가져옵니다🎜🎜일반적으로 프로젝트 입구에서 package.json + require(module-alias/register)에 경로 별칭을 구성하여 이 라이브러리를 사용합니다. . 🎜

모듈 별칭 원칙 소개

🎜module-alias는 전역 개체 를 덮어씁니다. 모듈 _resolveFilename 메서드는 경로 별칭을 변환하는 데 사용됩니다. 간단히 말하면 기본 _resolveFilename 메서드 호출을 가로채서 경로 별칭을 변환하는 것입니다. 파일의 실제 경로를 지정한 후 원래 _resolveFilename 메서드를 호출합니다. 🎜🎜다음은 소스 코드이며 기본적으로 두 부분으로 나뉩니다: 경로 별칭 변환 + 기본 _resolveFilename 호출🎜rrreee
🎜간단해 보이는 것 뒤에는 종종 함정이 있습니다🎜

모듈 별칭 함정

🎜일반적으로 우리는 노드 프로젝트 별칭에서 module-을 사용합니다. 라이브러리, node 프로젝트는 일반적으로 typescript에서 js 코드로 변환되지만 node 때문에 패키징되지 않는 경우가 많습니다. 프로젝트는 일반적으로 패키징할 필요가 없으며 이는 약간 중복되는 것처럼 보입니다. 이것이 module-alias가 작동하는 때입니다. 🎜🎜하지만 이 프로젝트는 좀 특이한데요. 프로젝트에서 가장 바깥쪽 레이어에는 전역 package.json이 있고, 내부 패키지에는 자체 가 있습니다. package.json , 간단히 말하면 monorepo의 코드 구성 방법이 사용되는데 여기서 문제가 발생합니다. 🎜
🎜module-alias는 package.json에 구성된 경로 별칭을 제대로 확인할 수 없습니다🎜
🎜처음에는 멀티 레이어 프로젝트 구성 방식에 문제가 있을 것이라고 예상하지 못했습니다. module-alias/register 를 지원하지 않습니다. 사용 지침이 있습니다: 🎜🎜🎜🎜🎜하지만 저는 당시 이 지침에 정말 주의를 기울이지 않았습니다. 그렇지 않았다면 밟지 않았을 것입니다. 이 함정. 다음번에는 지침을 주의 깊게 읽어야 하지만 지침이 너무 길어서 주의 깊게 보지 않을 가능성이 높습니다. . . 결국 이렇게 간단한 사용법으로는 별 문제 없을 것 같죠?

module-alias/register流程

既然踩坑了,就有必要了解一下踩坑的原因,避免反复踩坑才好。可以详细了解下module-aliasinit方法的实现。为了节省篇幅,省略了部分细节

function init (options) {
  // 省略了部分内容
  var candidatePackagePaths
  if (options.base) {
    candidatePackagePaths = [nodePath.resolve(options.base.replace(/\/package\.json$/, &#39;&#39;))]
  } else {
    // There is probably 99% chance that the project root directory in located
    // above the node_modules directory,
    // Or that package.json is in the node process&#39; current working directory (when
    // running a package manager script, e.g. `yarn start` / `npm run start`)
    // 重点看这里!!!
    candidatePackagePaths = [nodePath.join(__dirname, &#39;../..&#39;), process.cwd()]
  }
  var npmPackage, base
  for (var i in candidatePackagePaths) {
    try {
      base = candidatePackagePaths[i]
      npmPackage = require(nodePath.join(base, &#39;package.json&#39;))
      break
    } catch (e) { // noop }
  }
  // 省略了部分内容
  var aliases = npmPackage._moduleAliases || {}
  for (var alias in aliases) {
    if (aliases[alias][0] !== &#39;/&#39;) {
      aliases[alias] = nodePath.join(base, aliases[alias])
    }
  }
  // 省略了部分内容
}

可以看重点部分,如果我们没有给base参数,module-alias默认会从../../目录和当前目录下找寻package.json文件,而且../..目录下的package.json文件的优先级比当前目录下的优先级还要高,这里的优先级设置似乎和正常的优先级逻辑有点差别,一般都会让当前目录的优先级比较高才比较符合正常逻辑,所以会导致加载的不是当前目录下的package.json文件,而导致找不到路径别名配置而出错。

关于这点似乎有不少人踩坑了,还有人提了issues,但是似乎暂时并没有人回应。

解决办法

通过API方式注册路径别名,或者手动调用init方法,传入base参数,指定package.json文件.

似乎只有踩坑了,才会更深入的了解

更多node相关知识,请访问:nodejs 教程!!

위 내용은 node.js의 모듈 별칭에 대해 자세히 알아보세요(일부 함정 공유)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제