/타입스크립트-활용/Conditional-Exports로-타입스크립트-지원하는-라이브러리-만들기/thumbnail.png

Conditional Exports로 타입스크립트 지원하는 라이브러리 만들기

소개

최근 @devcomfort/text-transcoder v0.3 업데이트를 진행했습니다.

v0.3 패치 노트 확인하기

v0.3 패치는 v0.2의 타입스크립트 앰비언트 파일(d.ts) 인식 불가 이슈로 발생하는 불완전한 타입스크립트 지원을 보완하는 패치였습니다.

이번 포스트에서는 제가 겪은 이슈를 해결하는 과정과 함께 조건부 내보내기 conditional exports의 사용법타입스크립트 라이브러리를 배포하기 위한 설정법을 공유하려고 합니다.

주의사항

이 문서는 package.jsontsconfig.json에 대한 이해도가 부족하다면 이해하기 힘들 수 있습니다.

사전지식: Conditional Exports가 뭔가요?

Node.js v12부터 추가된 기능입니다.

ECMAScript(ESM)과 CommonJS(CJS) 등 라이브러리 사용자의 프로젝트 상황에 다른 코드를 내보내야 하는 경우, 요청 경로에 따라 다른 스크립트 코드를 제공해야 하는 경우

프로젝트 환경에 따라 다양한 코드를 상황에 맞게 제공해야 하는 경우에 사용됩니다.

Conditional Exports는 package.json 내부에 exports 필드를 설정하여 사용할 수 있으며, 사용자가 접근 하는 경로를 기준으로 다양한 내보내기 설정을 입력할 수 있습니다.
자세한 내용은 해결 과정에서 소개하고 있습니다.

v0.2 이슈 소개

@devcomfort/text-transcoder v0.2의 이슈는 package.json에 이미 types 필드를 설정했음에도 사용 시 타입 힌트가 인식되지 않았던 문제였습니다.

해결 과정

이슈 해결을 위해 다음 내용을 추가하였습니다.

  1. tsconfig.jsondeclaration 필드를 설정하여 빌드 시 자동으로 타입 힌트 파일(d.ts)이 빌드되도록 설정하였습니다.
  2. package.json exports 필드를 추가하여 조건부 내보내기를 추가하였습니다.

1. tsconfig.json 설정하기: declaration 필드 설정

tsconfig.json에서 declaration 필드를 true로 설정하면 빌드 시 자동으로 앰비언트 파일이 빌드되도록 설정할 수 있습니다.
반대로 false로 설정하면 앰비언트 파일이 생성되지 않습니다.

아래와 같이 설정하고 빌드하며 결과물에 앰비언트 파일을 추가할 수 있습니다.

json
{
// ...
"compilerOptions": {
// ...
"declaration": true
// ...
}
// ...
}

2. package.json 설정하기: exports 필드 설정: Conditional Exports 추가

package.json 내에 exports 필드 설정을 추가하면 ECMAScript(ESM), CommonJS(CJS) 상황에 맞게 다른 파일을 제공하도록 라이브러리를 개발할 수 있습니다.

Conditional Exports 설정상의 주의점: 완벽한 상대경로의 필요

exports 필드 설정을 위해서는 진입 경로를 기본으로 설정해야 합니다.
진입 경로상대 경로로 지정해야 하며, .으로 시작해야 합니다.

예를 들어 package.json 파일을 기준으로 동일한 경로에 위치한 index.js를 가리키려면 ./index.js로 설정해야 하고 src 디렉토리 내의 index.js를 가리키려면 ./src/index.js 설정해야 합니다.

exports 필드는 위와 같이 .으로 시작하는 완벽한 상대경로만 인식합니다.

Conditional Exports 내용 설정하기

진입 경로를 설정한 후에는 node, default, types, require, import와 같은 5가지 조건 설정을 원하는대로 적용할 수 있습니다.
필요하지 않은 설정은 생략할 수 있습니다.

  • node: Node.js를 통해 라이브러리에 접근할 때, node 필드 값에 해당하는 경로의 JavaScript 코드가 로드됩니다.
  • default: 환경과 무관하게 기본적으로 default 필드 값에 해당하는 경로의 JavaScript 코드가 로드됩니다.
  • types: 타입스크립트와 호환되는 선언 파일을 사용하려면 types 필드 값에 해당 파일의 경로를 입력해야 합니다. 이렇게 선언하면 진입 경로에 맞춰 해당 타입 힌트가 적용됩니다.
  • require: CommonJS(CJS) 환경에서 사용할 JavaScript 코드의 경로를 지정하면, 해당 경로의 JavaScript 코드가 로드됩니다.
  • import: ECMAScript(ESM) 환경에서 사용할 JavaScript 코드의 경로를 지정하면, 해당 경로의 JavaScript 코드가 로드됩니다.

다음은 라이브러리의 . 경로를 호출하는 상황을 가정하여 ESM/CJS 지원, 타입스크립트 지원이 가능한 package.json 코드를 작성하였습니다.

@devcomfort/text-transcoder v0.3 패치 노트의 예시를 그대로 사용하였으며, 일반적으로 라이브러리를 배포하기 위해 빌드된 파일은 dist 폴더에 저장하기 때문에 아래와 같이 작성하였습니다.

json
{
// ...
"exports": {
// "기본 경로를 호출하는 경우"
".": {
"import": "./dist/esm/index.js", // ECMAScript(ESM) 기반 프로젝트에서는 ./dist/esm/index.js 경로의 코드를 제공합니다.
"require": "./dist/cjs/index.js", // CommonJS(CJS) 기반 프로젝트에서는 ./dist/cjs/index.js 경로의 코드를 제공합니다.
"types": "./dist/index.d.ts" // 타입 힌트는 ./dist/index.d.ts 경로의 파일을 기반으로 제공합니다.
}
}
// ...
}

Conditional Exports의 동작 이해하기: 설정한 상대 경로가 실제로 어떻게 동작할까요?

json
{
"exports": {
".": {
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js",
"types": "./dist/index.d.ts"
}
}
}

위와 같이 @devcomfort/text-transcoder v0.3 프로젝트에서는 . 경로를 기준으로 하였지만, ./dist 경로 설정했다고 가정할 수도 있습니다.

., ./dist 2가지 설정에 따라 사용자의 호출 코드가 어떻게 동작하는지 보여드리겠습니다.

라이브러리 이름이 @devcomfort/text-transcoder인 경우:

  • exports 경로를 .로 설정하면 (위에서 제시된 설정):
    javascript
    // ECMAScript(ESM)
    import { reEncode } from '@devcomfort/text-transcoder'
    // CommonJS(CJS)
    const { reEncode } = require('@devcomfort/text-transcoder')
  • exports 경로를 ./dist로 설정하면 (../dist로 경로 변경):
    javascript
    // ECMAScript(ESM)
    import { reEncode } from '@devcomfort/text-transcoder/dist'
    // CommonJS(CJS)
    const { reEncode } = require('@devcomfort/text-transcoder/dist')

경로를 변경하면 위와 같이 호출할 때의 경로가 변경되게 됩니다.

위의 예시를 참고하면 더욱 올바른 조건부 내보내기(conditional exports) 설정을 할 수 있을 것이라 기대합니다.

참조