본문 바로가기

웹/TypeScript

typescript, swc, express, alias를 활용해서 esm 모듈 시스템 만들기

상황

기존의 commonjs로 작성된 서버를 esmodule로 바꾸는 상황이에요. 왜 바꾸냐하면 esm 모듈이 표준으로 많이 바뀐다고 해서 바꿨어요.

준비

🟦 package.json의 설정

package.json 파일에 "type": "module"을 설정하면, Node.js가 해당 프로젝트의 JavaScript 파일을 ES 모듈(ESM) 방식으로 해석해요. 기본적으로 Node.js는 CommonJS 모듈 시스템을 사용하지만, 이 설정을 추가하면 .js 파일에서도 importexport 구문을 사용할 수 있게 돼요.

 

🟦 tsconfig.json 설정

typescript 컴파일러가 ts 파일을 js로 변환할 때 어떤 모듈 방식으로 변환해야할지 설정시켜요.

target은 output되는 js코드의 ecmascript 버전을 결정해요.

module은 컴파일된 JavaScript 코드가 어떤 모듈 시스템을 사용할지를 결정해요

 

🟩 그러면 pacakage.json과 tsconfig의 module은 뭔차이?

package.json에 설정하는 "type": "module"은 런타임에서 Node.js가 해당 프로젝트의 JavaScript 파일을 어떻게 해석할지 결정하는 옵션이에요.

 

tsconfig.json"module" 옵션은 TypeScript 컴파일러가 코드를 어떤 모듈 시스템 형식으로 변환할지를 지정해요

 

 

🟩 tsconfig.json 파일

{
  "compilerOptions": {
    "target": "ESNext", // 생성되는 JavaScript의 버전을 최신 ECMAScript로 설정해요.
    "module": "NodeNext", // Node.js의 ESM 방식에 맞춰 모듈 시스템을 설정해요.
    "moduleResolution": "nodeNext", // Node.js의 ESM 방식에 맞는 모듈 해석 방법을 사용해요.
    "rootDir": "./src", // 소스 코드의 루트 디렉토리를 지정해요.
    "baseUrl": "./", // 절대 경로 기준 디렉토리를 설정해요.
    "paths": { // 경로 별칭 설정으로 모듈 임포트를 간소화할 수 있어요.
      "@lib/*": ["src/lib/*"],
      "@config/*": ["src/config/*"],
      "@models/*": ["src/models/*"],
      "@dao/*": ["src/dao/*"],
      "@service/*": ["src/service/*"],
      "@routes/*": ["src/routes/*"],
      "@migrations/*": ["src/migrations/*"],
      "@utils/*": ["src/utils/*"],
      "@middlewares/*": ["src/middlewares/*"],
      "@dummyData/*": ["src/dummyData/*"]
    },
    "resolveJsonModule": true, // JSON 파일도 모듈처럼 import 할 수 있도록 해요.
    "noImplicitAny": false, // 암시적인 any 타입 사용을 허용해요.
    "declaration": true, // 타입 정의 파일(.d.ts)을 생성해요.
    "declarationMap": true, // 선언 파일에 대한 소스맵을 생성해요.
    "outDir": "./build", // 컴파일된 파일이 저장될 디렉토리를 지정해요.
    "esModuleInterop": true, // CommonJS 모듈과의 호환성을 높여줘요.
    "preserveSymlinks": false, // 심볼릭 링크를 원본 경로로 해석하도록 설정해요.
    "forceConsistentCasingInFileNames": true, // 파일명 대소문자의 일관성을 강제해요.
    "strict": true, // 엄격한 타입 검사를 활성화해요.
    "skipLibCheck": true // 외부 라이브러리의 타입 검사를 건너뛰어 컴파일 속도를 높여줘요.
  },
  "include": [
    "src/**/*" // src 폴더 내의 모든 파일을 컴파일 대상으로 포함해요.
  ],
  "exclude": [
    "node_modules", // 외부 라이브러리는 컴파일 대상에서 제외해요.
    "build" // 컴파일된 결과물은 제외해요.
  ]
}

여러가지 옵션이 있어요.

import에서 alias를 사용하기 위해서 paths 옵션을 사용해요.

 

 

🟦 pacakage.json의 script 설정

"scripts": {
    "type-check": "tsc --noEmit",
    "build:tsc": "tsc && tsc-alias",
    "build": "cross-env swc ./src -d build --strip-leading-paths",
    "dev": "cross-env nodemon --watch \\"src/**/*.ts\\" --ext ts --exec \\"node --loader @swc-node/register/esm src/index.ts\\"",
    "start": "node build/index.js",
    "test": "vitest",
  },

🟩 types-check

안타깝게도 swc를 사용하면 type check가 안되요. 그래서 tsc로 타입을 체크하는 것을 만들었어요.

 

 

🟩 build

cross-env가 있는데요. 이건 왜쓰냐면 운영체제에 상관없이 환경 변수를 설정할 수 있도록 하기 위해서 에요.

윈도우와 리눅스, 맥 등의 환경에서 환경 변수를 설정하는 방식이 다르기 때문에, cross-env를 사용하면 스크립트 내에서 동일한 명령어로 환경 변수를 설정할 수 있어요.

swc로 실행하는데요. —strip-leading-paths 옵션은 컴파일 결과물에서 파일 경로의 앞부분에 붙은 불필요한 경로 정보를 제거하는 역할을 해요. 즉, build 파일에 컴파일된 결과값들이 절대경로 형태로 들어가게 되요. ex) build/src/…

저는 build/… 형태로 빌드 결과를 얻고싶어서 옵션을 사용했어요.

 

 

🟩 build:tsc

기존 tsc로 빌드하는 거에요. alias를 적용해야하니 tsc-alias 페키지가 필수에요. npm i -D tsc-alais

 

 

 

🟩 dev

nodemon 패키지를 사용했어요. 기존에는 tsc-watch 패키지를 사용했는데 esm 모듈을 실행하는데 어려움이 있어서 nodemon으로 바꿨어요.

nodemon은 기본적으로 .js 파일만 감시할 수 있어요. ts파일의 변경을 감지하도록 --ext ts 옵션을 추가해야해요.

src 밑 ts 파일이 변경되면 자동으로 node --loader @swc-node/register/esm src/index.ts 명령어를 실행시켜줘요.

--loader 옵션은 Node.js의 모듈 로딩 과정에 커스텀 로더를 삽입해요.

@swc-node/register/esm 로더는 typescript나 최신 ecmascript 문법을 일반적인 js코드로 변환해요. swc를 활용해서 소스 코드를 nodejs가 이해할 수 있는 js로 실행되도록 만들어주는 거에요.

 

 

🟦 .swrrc

{
  "$schema": "<https://json.schemastore.org/swcrc>", // SWC 설정에 대한 JSON 스키마 URL. 올바른 구성 옵션 검증에 도움을 줘요.
  "jsc": {    // SWC의 JavaScript/TypeScript 컴파일러 설정 영역입니다.
    "target": "es2020", // 출력될 JavaScript 코드의 ECMAScript 버전을 es2020으로 설정해요.
    "parser": {      // 소스 코드 파싱 옵션을 지정합니다.
      "syntax": "typescript", // 소스 코드가 TypeScript 문법을 사용함을 지정해요.
      "tsx": false, // TSX (JSX in TypeScript) 사용 여부. 여기서는 사용하지 않아요.
      "decorators": true // 데코레이터 문법을 사용하도록 활성화해요.
    },
    "baseUrl": "./", // 모듈 해석 시 기준 디렉터리를 현재 프로젝트 루트로 설정해요.
    "paths": {      // 모듈 경로 별칭을 설정하여 import 구문을 간소화할 수 있어요.
      "@lib/*": ["src/lib/*"],
      "@config/*": ["src/config/*"],
      "@models/*": ["src/models/*"],
      "@dao/*": ["src/dao/*"],
      "@service/*": ["src/service/*"],
      "@routes/*": ["src/routes/*"],
      "@migrations/*": ["src/migrations/*"],
      "@utils/*": ["src/utils/*"],
      "@middlewares/*": ["src/middlewares/*"],
      "@dummyData/*": ["src/dummyData/*"]
    },
    "keepClassNames": true // 출력 코드에서 클래스 이름을 보존하여 디버깅이나 런타임 리플렉션에 유용하게 사용돼요.
  },
  "module": {
    "type": "es6" // 출력 모듈 시스템을 ES6 모듈(ESM)로 설정해요.
  },
  "sourceMaps": true, // 디버깅을 위해 소스맵 생성을 활성화해요.
  "exclude": [
    "node_modules", // 컴파일 대상에서 node_modules 폴더를 제외해요.
    "build" // 이미 생성된 빌드 결과물 폴더도 제외해요.
  ]
}

swc가 컴파일할 때 사용되는 규칙들이에요. root 폴더에 존재해요.

build 될 때 alias도 이 .swcrc 설정 파일에서 설정할 수 있어요.

728x90