-
ESM에서 alias path 사용하기 (module-alias)Nodejs 2023. 8. 13. 12:31
module-alias
최근 한 프로젝트의 모듈 시스템을 ESM으로 업데이트하다가 ESM에서는 기존에 alias path를 사용하던 방식이 먹히지 않는 것을 확인했다. CJS 프로젝트에서는
module-alias
라는 라이브러리를 이용해서, 사용중인 alias path들을 빌드 경로와 직접 맵핑해주는 방식을 사용해왔다.예를들어,
// alias.ts import { join } from 'node:path'; import moduleAlias from 'module-alias'; const DIST_PATH = join(__dirname, '..', 'types'); moduleAlias.addAliases({ types: DIST_PATH, });
// index.ts import ./alias.ts; ...
하지만 이 코드는 CJS와 ESM이 모듈을 읽는 방식이 다르기 때문에 ESM으로 설정하는 순간 무용지물이 되며, 실행을 해보면 아래와 같은 에러가 발생한다.
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/Users/me/gitove/src/types/commit' imported from /Users/me/gitove/dist/commands/commit/questions.js at new NodeError (node:internal/errors:400:5) at finalizeResolution (node:internal/modules/esm/resolve:308:15) at moduleResolve (node:internal/modules/esm/resolve:945:10) at defaultResolve (node:internal/modules/esm/resolve:1153:11) at nextResolve (node:internal/modules/esm/loader:163:28) at file:///Users/me/gitove/node_modules/.pnpm/esm-module-alias@2.0.3/node_modules/esm-module-alias/index.js:34:12 at nextResolve (node:internal/modules/esm/loader:163:28) at ESMLoader.resolve (node:internal/modules/esm/loader:842:30) at ESMLoader.getModuleJob (node:internal/modules/esm/loader:424:18) at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:77:40) { code: 'ERR_MODULE_NOT_FOUND' } Node.js v18.13.0 ELIFECYCLE Command failed with exit code 1.
모듈을 언제 읽어오는가?
CJS에서는 모듈을 런타임에 읽는다. 그래서 다음과 같은 상황이 발생할 수 있다.
// a.js console.log('a1'); console.log(require('./b').b); console.log('a2'); exports.a = 1; // b.js console.log('b1'); console.log(require('./a').a); console.log('b2'); exports.b = 2;
$ node a.js a1 b1 undefined b2 2 a2
아직
a
가 export되지 않았기 때문에b.js
에서require('./a').a
을 찍어보면undefined
라고 나온다.반면에, ESM에서는 런타임이 아닌 실행전에 코드를 분석하면서 모듈의 경로들을 먼저 읽어낸다. 즉,
module-alias
처럼 런타임 중 가장 먼저 실행이 되도록 배치하여 이후 다른 모듈의 경로를 읽을 때 alias path를 인지하도록 하는 방법은 애초에 불가능해진다."-r" 플래그를 사용할 수는 없을까?
보통 ESM에서 이런 유사한 문제가 발생하는 경우 흔하게 사용되는 해법 중 하나는 node를 실행할 때
-r
옵션을 주고 필요로하는 모듈을 먼저 실행하는 것이다.-r, --require=... module to preload (option can be repeated) $ node --experimental-specifier-resolution=node -r module-alias/register --no-warnings dist/index.js",
하지만, 이 방법에도 한가지 문제가 있다.
module-alias
가 내부적으로require
문을 사용하고 있기 때문에 다음과 같은 에러가 발생한다.[Error [ERR_REQUIRE_ESM]: require() of ES Module /Users/me/gitove/dist/config/alias.js not supported. Instead change the require of alias.js in null to a dynamic import() which is available in all CommonJS modules.] { code: 'ERR_REQUIRE_ESM' }
이에 대해
esm
이라는 라이브러리를 추가로 설치하거나,ts-node
에 내장된esm
모듈을 또 추가로-r
옵션과 함께 전달해주는 방식들도 소개되기는 하는데, 내 경우에는 잘 되지 않았다.esm-module-alias
그래서 찾아보니
esm-module-alias
라는, 같은 문제를 먼저 겪으셨던 고마운 개발자분께서 만든 라이브러리가 있었다.사용방법은
module-alias
와 유사하다.import generateAliasResolver from 'esm-module-alias'; const aliases = { types: 'dist/types', }; export const resolve = generateAliasResolver(aliases);
하지만 마찬가지로 ESM에서는 프로그램 실행 이전에 설정을 적용하기 위해 코드상에서 import해 오는 것이 아니라, 이 경우에는
-l
플래그를 이용해 기본 파일로더를 교체한다.--loader, --experimental-loader=... use the specified module as a custom loader $ node --loader=./dist/config/loader.js --experimental-specifier-resolution=node --no-warnings dist/index.js
참고자료
ESM 삽질기
Stackoverflow - ESM does not resolve module-alias
npm - esm-module-alias'Nodejs' 카테고리의 다른 글
카카오톡채널 AI챗봇 콜백API를 사용방법 간단 정리 (Express) (0) 2023.08.31