[ 살펴보기 ] Typescript - compilerOptions ( modules )
![[ 살펴보기 ] Typescript - compilerOptions ( modules )](https://cdn.hashnode.com/res/hashnode/image/upload/v1731249197751/ece7c9d3-5198-4c21-9e94-f8fa70b41a22.jpeg)
tsconfig.json 파일을 통해 추가할 수 있는 설정 중 module과 관련된 options 살펴보자. module과 관련하여 설정할 수 있는 option은 다양하며 그 중 일부를 살펴본다. module과 관련해서 설정할 수 있는 option의 모든 list는 documentation을 통해 확인할 수 있다. ( Reference - Modules )
Module관련 설정option을 살펴보기 전에 Javascript의 module system과 Typescript가 compile하여 생성하는 output이 실행되는 runtime에 대한 대략적인 관계를 살펴보는 것이 좋다.
현재 Javascript에서 주로 사용되는 module system은 ECMAScript official module system인 ESM과 ESM가 나오기 전 NodeJS에서 사용했던 module system인 CommonJS 두 가지다. ( NodeJS도 12 versionk 이후 ESM을 지원한다 )
Typescript compiler인 tsc는 typescript file을 compile해서 output code를 만들 때 output code가 실행될 최종 runtime에 따라 output code에 적용될 module system 역시 고려해야 한다. Code가 실행될 최종 runtime이 12버전 이상의 NodeJS라면 ESM, CommonJS 모두 지원하므로 상관이 없지만 최종 runtime이 modern browser라면 browser가 지원하는 ESM module system을 사용하는 output을 생성해야 한다.
현재 작업 중인 프로젝트에서 파일이 어떤 type의 module로 취급되어야 하는지 설정하는 방법은 file extension을 통한 방법과 package.json의 type property를 설정하는 두 가지 방법이 사용된다.
먼저 아래 package.json의 type property를 통해 module type을 설정하면 javascript의 file extension을 고칠 필요 없이 project 전체에 적용될 module system을 지정할 수 있다. 아래는 작업 중인 프로젝트에서 ESM module system을 사용하기 위한 설정이다.
{
...
"type": "module", // ecm module system을 사용
// commonjs 사용시 "commonjs"로 설정
...
}
위와 같이 설정하면 javascript의 extension을 변경할 필요없이 ESM module syntax를 사용할 수 있다.
반면 위와 같이 package.json type을 설정하지 않고 ESM module syntax를 사용하고자 한다면 javascript의 extension을 .mjs ( 또는 .mts )로 변경하여 해당 파일이 ESM module file로 취급되어야 함을 알려주어야 해야 한다.
즉, package.json의 type property를 설정하지 않거나 .cjs ( 또는 .cts ) extension을 가진 javascript file은 CommonJS module로 취급되고 package.json의 type property를 module로 설정하거나 .mjs ( 또는 .mts ) extension을 가진 javascript file은 ESM module로 취급된다.
이제 tsconfig.json에서 module 관련 설정할 수 있는 options들을 살펴보자.
Base URL
다른 모듈을 import할 때 모듈을 import하는 경로의 기준이 되는 위치를 정한다.
Project directory가 다음과 같다고 가정해보자.
- tsconfig.json
/src
- index.ts
- info.ts
Project directory가 위와 같은 상황에서 baseUrl을 아래와 같이 설정하면 모듈을 import할 때 기준이 되는 경로는 tsconfig.json 파일이 위치한 경로가 된다.
// tsconfig.json
{
"compilerOptions": {
"baseUrl": '.',
...
}
}
설정이 위와 같고 index.ts에서 about.ts의 함수를 import한다면 import 경로는 다음과 같다.
// index.ts
import { getAddress } from "src/address";
...
Paths
source code에서 import를 할 때 import하는 경로의 alias를 설정할 수 있다. 예를 들어 project structure 다음과 같다고 가정해보자.
...
tsconfig.json
/src
main.ts
/utils
calculate.ts
그리고 tsconfig.json을 다음과 같이 설정한다.
// tsconfig.json
{
"compilerOptions": {
...
"baseUrl": ".",
"paths": {
"@/utils/*": ["./src/utils/*"]
}
}
}
이제 다른 file에서 utils folder에 위치하는 file을 import할 때 /src/utils이 아닌 @/utils alias를 통해 import할 수 있다.
main.ts
import { sum } from "@/utils/calculate.js";
const testSum = sum(1, 2);
paths option에서 alias의 경로를 설정할 때 baseUrl option에서 설정한 경로를 기준으로 설정한다. 예를 들어 baseUrl을 다음과 같이 ./src로 설정되어 있다면 @/utils/* alias의 경로는 ./src/utils/*이 아닌 ./utils/*로 설정해준다.
tsconfig.json
{
"compilerOptions": {
...
"baseUrl": "./src",
"paths": {
"@/utils/*": ["./utils/*"]
}
}
}
Module
Tsc를 통해 compile되는 js파일이 사용할 module system을 설정한다. 명시적으로 설정되어 있지 않으면 default 값은 CommonJS다.
다음 ts 파일을 tsc를 통해 compile한다고 가정해보자. package.json의 type은 module로 설정하고 테스트 한다.
// package.json
{
...
"type": "module",
...
}
// index.js
import { getAddress } from "src/address";
const test = getAddress("test address");
console.log(test);
위의 코드를 compile할 때 module에 설정된 값에 따라 compile되는 형식은 다음과 같다.
- ESNext : 포스트를 작성하는 일자 기준으로 module property에 설정할 수 있는 가장 최근의 ES version이 ES2022이므로 기본적으로 적용되는 내용은 ES2022과 동일하지만 ES2022 이후에 추가되는 module 관련 feature 역시 포괄하는 설정이다.
// tsconfig.json
{
"compilerOptions": {
"module": 'ESNext',
...
}
}
// index.js
import { getAddress } from "src/address";
const test = getAddress("test address");
console.log(test);
- NodeNext : 포스트를 작성하는 일자 기준으로 module property에 설정할 수 있는 가장 최근의 Node version이 Node16이므로 기본적으로 적용되는 내용은 Node16과 동일하지만 Node16 이후에 추가되는 module 관련 feature 역시 포괄하는 설정이다.
// tsconfig.json
{
"compilerOptions": {
"module": 'NodeNext',
...
}
}
// index.js
import { getAddress } from "src/address";
const test = getAddress("test address");
console.log(test);
만약 module option이 NodeNext로 설정되어 있으면 compiled된 javasciprt에 적용되는 module system은 compile하는 파일의 extension이나 ( mts 또는 cts ) pacakge.json type property에 설정된 값에 따라 CommonJS 혹은 ESM이 적용된다.
예를 들어 package.json의 type을 생략하거나 commonjs로 설정하고 위의 코드를 다시 compile하면 결과는 다음과 같다.
// package.json
{
...
"type": "commonjs",
...
}
// tsconfig.json
{
"compilerOptions": {
"module": 'NodeNext',
...
}
}
// index.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const address_js_1 = require("src/address.js");
const test = (0, address_js_1.getAddress)("test address");
console.log(test);
ESNext의 결과와 NodeNext의 결과가 같아서 둘 다 같아 보일 수도 있지만 둘은 엄연히 다르다. 예를 들어 module의 meta information을 조회할 수 있는 import.meta는 module 속성이 ESNext 또는 ES2020 이상으로 설정되었을 때는 type 오류가 발생하지 않지만 NodeNext로 설정 시 오류가 발생한다.
// tsconfig.json
{
"compilerOptions": {
"module": 'ESNext',
...
}
}
// index.js
import { getAddress } from "src/address.js";
const test = getAddress("test address");
console.log(test);
console.log(import.meta.url);
주의할 점은 파일이 ESM module system 기반으로 compile 되었는데 package.json의 type이 commonjs인 상태로 nodeJS를 통해 compile된 파일을 실행하려고 하면 module 관련 에러가 발생한다. 마찬가지로 commonjs module system 기반으로 compile된 파일을 package.json type이 module인 상태로 nodeJS를 통해 실행하려고 해도 module 관련 에러가 발생한다.
위에서 설명한 옵션 외에도 commonjs, umd와 같은 module 옵션이 존재하지만 새로운 프로젝트에선 추천하지 않는다.
// tsconfig.json
{
"compilerOptions": {
"module": 'CommonJS',
...
}
}
// index.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const address_1 = require("src/address");
const test = (0, address_1.getAddress)("test address");
console.log(test);
// tsconfig.json
{
"compilerOptions": {
"module": 'UMD',
...
}
}
// index.js
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "src/address"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const address_1 = require("src/address");
const test = (0, address_1.getAddress)("test address");
console.log(test);
});
ModuleResolution
Typescirpt는 source code에서 import한 module이 compile 후에 output code가 runtime에서 실행될 때도 import한 module을 정상적으로 찾을 수 있도록 작업을 수행해야 한다. moduleResolution은 이러한 module resolution strategy를 설정하며. 명시적으로 설정하지 않으면 default 값은 module property에 설정한 값에 따라 달라진다.
예를 들어 moduleResolution이 node16 또는 nodeNext로 설정되어 있다면 typescript가 import module을 찾을 때 import { getAddress } from "./address.js";와 같이 relatve path로 import된 module은 relative 경로에서 module을 찾고 import { format } from "date-fns"와 같은 경우는 node_modules에서 module을 찾는다.
또한 NodeJS에서 import keyword를 통해 module을 import할 때 file extension을 생략할 수 없게 되어 있다. 그렇기에 moduleResolution을 node16 혹은 nodeNext로 설정할 경우 이러한 제한 역시 함께 적용된다.
// tsconfig.json
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
}
}
// main.ts
import { getAddress } from "./address";
// extension이 없으므로 type 에러 발생
만약 moduleResolution을 Bundler로 설정을 한다면 module을 import할 때 extension을 생략할 수 있다. Transpiler나 bundler를 통해 project를 build하면 최종 결과물에 대한 file extension 작업은 대부분 transpiler & bundler에서 수행이 되기 때문이다.
// tsconfig.json
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "Bundler",
}
}
// main.ts
import { getAddress } from "./address";
// type 에러가 발생하지 않는다.
이외에도 설정할 수 있는 option은 classic, node10이 존재하지만 현재는 거의 사용되지 않는다.
allowImportingTsExtensions
source code에서 .ts, mts, .tsx와 같은 typesciprt extension file의 import를 허용한다. tsc를 통해 typescript code를 compile하면 tsc는 input code에서 import keyword로 import하는 module의 extension을 변경하지 않고 그대로 사용한다. 즉, 다음 코드는 compile을 하더라도 output file에서 .ts extension을 그대로 import한다.
index.ts ( compile 전 )
import { sum } from "./utils/calculate.ts";
const test2 = sum(1, 2);
dist/inex.js ( compile 후 )
import { sum } from "./utils/calculate.ts";
// output file도 import는 그대로 .ts extension을 import
const test2 = sum(1, 2);
Browser와 NodeJS runtime은 기본적으로 javascript를 이해하고 실행할 수 있으므로 위의 code는 runtime에서 실행될 수 없는 code다. 그러므로 typescript는 input file ( compile 전 )에서 typescript로 작성된 다른 module을 import할 때도 .js extension을 통해 import한다.
project structure
...
tsconfig.json
/src
index.ts
/utils
calculate.ts
index.ts ( compile 전 )
import { sum } from "./utils/calculate.js";
// 실제는 typescript지만 .js extension으로 import된다.
const testResult = sum(1, 2);
만약 input code에서 사용된 .ts extension을 .js extension으로 변경해주는 작업을 transpiler나 bundler에서 해주고 있거나 Deno나 Bun과 같이 runtime level에서 typescript를 지원하는 runtime을 사용한다면 allowImportingTsExtensions property를 true로 설정하여 .ts extension을 import할 수 있게 설정할 수 있다. 다만 해당 option을 사용하기 위해선 noEmit option 역시 true로 설정되어 있어야 한다.
// tsconfig.json
{
"compilerOptions": {
...
"allowImportingTsExtensions": true,
"noEmit": true
},
}
// main.ts
import { getAddress } from "./address.ts"; // import .ts extension
const test = getAddress("test address");
noUncheckedSideEffectImports
import "./styles/main.css"; 형식과 같은 side effect import를 할 때 js file이 아닌 파일을 side effect import의 허용 여부를 설정한다. default로는 허용을 하며 해당 option을 true로 설정하면 js file이 아닌 다른 파일의 side effect import를 허용하지 않는다.
// tsconfig.json
{
"compilerOptions": {
...
"noUncheckedSideEffectImports": true,
},
}
// main.ts
import "./styles/main.css"; // 오류 발생
만약 css 파일만 side effect import를 허용하고 싶다면 다음과 같이 declaration을 생성한다.
// global.d.ts
declare module "*.css" {}
resolveJsonModule
.json extension file의 import 허용 여부를 설정한다.
// tsconfig.json
{
"compilerOptions": {
...
"resolveJsonModule": true,
},
}
// main.ts
import settings from "./settings.json"; // json import 허용
![[ 살펴보기 ] RDB - Relationships](https://cdn.hashnode.com/res/hashnode/image/upload/v1739711556668/48dc9e84-a621-42aa-9c9f-5fc5c436f0ec.jpeg)
![[ 살펴보기 ] MySQL - Data types](https://cdn.hashnode.com/res/hashnode/image/upload/v1739593589113/530f8704-4d27-42c9-a451-bb5c63150b99.jpeg)
![[ 살펴보기 ] TypeORM - Transactions, Migration](https://cdn.hashnode.com/res/hashnode/image/upload/v1739106042581/980b8133-61d4-406a-a026-65be9c28eace.jpeg)
![[ 살펴보기 ] TypeORM - Relations](https://cdn.hashnode.com/res/hashnode/image/upload/v1738666874402/b688bd0b-b6bb-4f43-87d8-c1b46b59f1b7.jpeg)
![[ 살펴보기 ] TypeORM - Basics](https://cdn.hashnode.com/res/hashnode/image/upload/v1738666803591/bef5df17-7dc7-4123-ae55-004d5042df39.jpeg)