[JS] Dual Package 사용하기

이 글에서는 듀얼 패키지를 사용해야 하는 이유, 그로 인해 발생할 수 있는 위험성, 그리고 그 위험을 관리하는 방법에 대해 살펴보겠습니다.

1️⃣ 듀얼 패키지를 사용해야 하는 이유

Node.js에서 패키지를 관리하고 모듈화하는 방법은 지속적으로 발전해 왔습니다. 이전에는 CommonJS 모듈 시스템이 주로 사용되었지만, 최근에는 ES 모듈의 지원이 추가되면서 개발자들에게 더 많은 유연성과 선택지를 제공하고 있습니다.

ES6의 지원 초창기에는 Node.js가 CommonJS 진입점을 실행하는 동안 빌드 도구(예: 번들러)가 ES 모듈 진입점을 사용할 수 있도록 했습니다. 즉 Babel과 같은 트랜스파일러를 사용하여거나나 다른 도구들이 ES 모듈 파일을 사용할 수 있었습니다.

현재 Node.js는 ES 모듈 진입점을 직접 실행할 수 있으며, 패키지는 CommonJS와 ES 모듈 진입점을 모두 포함할 수 있습니다. 이는 조건부 내보내기(Conditional exports)를 통해 같은 지정자에서도 가능합니다. 이제는 "module"이 단순히 번들러에 의해서만 사용되는 것이 아니라, ES 모듈 진입점에 의해 참조된 파일들이 ES 모듈로 평가됩니다.

package.json의 "main"은 CommonJS 진입점을, "module"은 ES 모듈 진입점을 지정했습니다.

{
	"main": "./lib/index.cjs",
	"module": "./lib/index.mjs"
}

"main"은 기본적으로 해당 패키지를 사용할 때의 경로를 나타내며, "module"은 ES6 호환 환경에서의 기본 경로를 제공합니다.

2️⃣ 듀얼 패키지를 사용했을 때의 위험성

듀얼 패키지 환경에서는 CommonJS를 사용하는 모듈과 ES 모듈을 사용하는 모듈이 함께 존재할 때, 각각이 서로 다른 인스턴스를 참조할 수 있는 위험이 있습니다. 이는 두 모듈 시스템이 각각의 캐시 메커니즘을 사용하기 때문에 발생합니다.

CommonJS 모듈은 require() 호출 시 모듈 코드를 실행하고, 그 결과로 반환된 객체를 캐시합니다. 이 캐싱은 파일 경로를 기준으로 이루어집니다. 즉, 같은 파일 경로로 require()가 호출되면, Node.js는 파일을 다시 읽고 평가하지 않고 캐시된 객체를 반환합니다.

ES 모듈은 import 문을 통해 로드되며, 모듈의 URL을 캐싱의 키로 사용합니다. 이는 웹 환경에서의 자원 로딩 방식과 유사합니다. 모듈은 최초 로드 시 정적으로 분석되고 평가되며, 그 결과가 캐시됩니다.

따라서 두 모듈의 방식을 함께 사용한다면, 하나의 인스턴스를 사용하지 않는 불상사가 발생하게 됩니다.

3️⃣ 위험성의 예시

예를 들어, auth-library라는 사용자 인증을 관리하는 패키지를 사용할 경우를 생각해 봅시다. 애플리케이션의 한 부분은 ES 모듈을 사용하여 이 라이브러리를 로드하고, 다른 부분은 CommonJS를 사용하여 로드합니다. 사용자가 한 시스템에서 로그인한 상태로 인식되지만, 다른 시스템에서는 로그인하지 않은 상태로 인식되는 상황이 발생할 수 있습니다.

4️⃣ 위험성을 해결하기 위한 방법

Node.js에서 제안하는 한 가지 해결 방법은 다음과 같은 구조를 사용하는 것입니다.

// package.json
{
  "type": "module",
  "exports": {
    "import": "./wrapper.mjs",
    "require": "./index.cjs"
  }
}
// index.cjs
exports.name = 'value';
// wrapper.mjs
import cjsModule from './index.cjs';
export const name = cjsModule.name;

위 코드 예시에서 보듯, exports를 이용한 조건부 내보내기를 사용하여, wrapper.mjs는 CommonJS 모듈 index.cjs에서 내보낸 값을 가져와 ES 모듈 사용자에게 제공합니다. 이렇게 하면 ES 모듈과 CommonJS 모듈 간에 일관된 인터페이스와 상태가 유지되어, 두 환경 간의 충돌을 피할 수 있습니다.

 

Modules: Packages | Node.js v22.3.0 Documentation

Modules: Packages# Introduction# A package is a folder tree described by a package.json file. The package consists of the folder containing the package.json file and all subfolders until the next folder containing another package.json file, or a folder nam

nodejs.org