TypeScript와 webpack 통합하기
TypeScript와 webpack 통합하기
TypeScript는 타입이 있는 JavaScript의 상위 집합(superset)이라서, 그대로 두면 일반 JavaScript로 컴파일되어요. 이 가이드에서는 webpack에 TypeScript를 통합하는 방법을 함께 하나씩 살펴볼게요.
본문
기본 설정 (Basic Setup)
먼저 TypeScript 컴파일러와 로더(loader)를 설치해요.
npm install --save-dev typescript ts-loader
이제 디렉터리 구조와 설정 파일을 이렇게 바꿔볼게요.
project
webpack-demo
├── package.json
├── package-lock.json
+ ├── tsconfig.json
- ├── webpack.config.js
+ ├── webpack.config.ts
├── /dist
│ ├── bundle.js
│ └── index.html
├── /src
- │ ├── index.js
+ │ └── index.ts
└── /node_modules
tsconfig.json
JSX를 지원하고 TypeScript를 ES5까지 내려 컴파일하도록 설정해 볼게요.
{
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": true,
"module": "esnext",
"moduleResolution": "bundler",
"target": "esnext",
"jsx": "react-jsx",
"allowJs": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
tsconfig.json의 각 옵션을 더 자세히 알고 싶다면 TypeScript 문서를 확인해 보세요. webpack 설정 자체가 궁금하다면 configuration concepts 문서를 참고하면 돼요.
이제 webpack이 TypeScript를 처리하도록 설정해 볼게요. 먼저 필요한 의존성을 설치해요.
npm install --save-dev ts-node @types/node
webpack.config.ts
import path from "node:path";
import { fileURLToPath } from "url";
import webpack from "webpack";
// in case you run into any TypeScript error when configuring `devServer`
import "webpack-dev-server";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const config: webpack.Configuration = {
entry: "./src/index.ts",
module: {
rules: [
{
test: /\.tsx?$/,
use: "ts-loader",
exclude: /node_modules/,
},
],
},
resolve: {
extensions: [".tsx", ".ts", ".js"],
},
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist"),
},
};
export default config;
Typescript file로 설정을 작성하는 방법에 대한 더 자세한 내용은 해당 문서를 참고하세요.
이렇게 하면 webpack이 ./index.ts를 *진입점(entry)*으로 삼고, .ts와 .tsx 파일을 전부 ts-loader로 로드해서, 현재 디렉터리에 bundle.js 파일을 내보내게 돼요.
이제 ./index.ts에서 lodash를 가져오는 방식을 조정해 볼게요. lodash 정의에는 기본 내보내기(default export)가 없어서, import 문을 다음과 같이 바꿔줘야 해요. 먼저 TypeScript 정의를 설치해요.
npm install --save-dev @types/lodash
그리고 파일 맨 위에 있는 import 문을 이렇게 수정해요.
./index.ts
- import _ from 'lodash';
+ import * as _ from 'lodash';
function component() {
const element = document.createElement('div');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
return element;
}
document.body.appendChild(component());
webpack.config.ts에서 TypeScript를 쓰는 세 가지 방법 (Ways to Use TypeScript)
webpack.config.ts에서 TypeScript를 사용하는 방법은 세 가지가 있어요.
- Node.js 내장 타입 제거(type stripping) 기능 이용 (권장):
webpack -c ./webpack.config.ts
Node.js의 내장 type-stripping으로 설정을 불러오려 시도하고, 그다음 interpret와 rechoir를 이용해 설정 파일을 불러오려 시도해요. 이 경우 tsx나 ts-node 같은 도구가 필요할 수 있어요.
- Node.js용 커스텀
--import/--require사용:
NODE_OPTIONS='--import=tsx --no-experimental-strip-types' webpack -c ./webpack.config.ts
NODE_OPTIONS='--require=ts-node/register --no-experimental-strip-types' webpack -c ./webpack.config.ts
--no-experimental-strip-types플래그는 Node.js 버전 22.7.0부터 필요해요.
- Node.js ≥ v22.7.0의 내장 transform types 기능 사용:
enum 선언이나 매개변수 속성(parameter properties)처럼 JavaScript 코드 생성이 필요한, 지울 수 없는 TypeScript 문법을 변환할 때는 이렇게 해요.
NODE_OPTIONS='--experimental-transform-types' webpack --disable-interpret -c ./webpack.config.ts
TypeScript 경로 별칭 (TypeScript Path Aliases)
5.105.0+
tsconfig.json의 compilerOptions.paths나 compilerOptions.baseUrl로 import 별칭을 만든다면, webpack 5.105부터는 webpack이 resolve.tsconfig를 통해 그 별칭을 바로 읽어 올 수 있어요. 이 기능이 tsconfig-paths-webpack-plugin을 대체하게 되었으니, 더 이상 그 플러그인은 쓰지 않는 게 좋아요.
resolve.tsconfig는 boolean | string | object를 받아요.
webpack.config.ts
export default {
resolve: {
tsconfig: true, // automatically find tsconfig.json
},
};
문자열을 넘기면 특정 파일을 가리킬 수 있어요. 모노레포에서 유용하죠.
export default {
resolve: {
tsconfig: "./tsconfig.app.json",
},
};
객체를 넘기면 TypeScript project references도 함께 처리할 수 있어요.
export default {
resolve: {
tsconfig: {
configFile: "./tsconfig.json",
references: "auto", // inherit references from tsconfig, or pass an array of paths
},
},
};
tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
이렇게 해 두면 @/components/Button이 별도 플러그인 없이, resolve.alias에 별칭을 중복해서 적어 넣지 않아도 src/components/Button으로 해석되어요.
tsconfig-paths-webpack-plugin에서 마이그레이션하기
지금 tsconfig-paths-webpack-plugin을 쓰고 있다면, 내장된 resolve.tsconfig 옵션으로 바꿀 수 있어요.
- import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin';
export default {
resolve: {
- plugins: [new TsconfigPathsPlugin()],
+ // Auto-find tsconfig.json in the project root
+ tsconfig: true,
+
+ // Or explicitly point to one
+ // tsconfig: './tsconfig.app.json'
},
};
그리고 프로젝트에서 패키지를 제거해요.
npm uninstall tsconfig-paths-webpack-plugin
로더 (Loader)
이 가이드에서는 ts-loader를 사용해요. 다른 web 어셋을 import하는 것 같은 webpack 기능을 활성화하기가 조금 더 쉽거든요.
참고로 이미 babel-loader로 코드를 트랜스파일하고 있다면, 추가 로더 대신 @babel/preset-typescript를 써서 JavaScript와 TypeScript 파일을 모두 Babel이 처리하게 할 수도 있어요. 다만 ts-loader와 달리 내부의 @babel/plugin-transform-typescript 플러그인은 타입 검사를 수행하지 않는다는 점을 기억해 두세요.
소스 맵 (Source Maps)
소스 맵 자체를 더 알고 싶다면 development guide 문서를 확인해 보세요.
소스 맵을 활성화하려면, TypeScript가 컴파일된 JavaScript 파일에 인라인 소스 맵을 출력하도록 설정해 줘야 해요. TypeScript 설정에 다음 줄을 추가해 볼게요.
tsconfig.json
{
"compilerOptions": {
"outDir": "./dist/",
+ "sourceMap": true,
"noImplicitAny": true,
"module": "esnext",
"moduleResolution": "bundler",
"target": "esnext",
"jsx": "react-jsx",
"allowJs": true,
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
이제 webpack에 이 소스 맵을 추출해서 최종 bundle에 포함시키라고 알려줘요.
webpack.config.ts
import path from "node:path";
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.ts',
+ devtool: 'inline-source-map',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: [ '.tsx', '.ts', '.js' ],
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};
devtool 옵션에 대한 더 자세한 내용은 devtool 문서를 참고하세요.
클라이언트 타입 (Client types)
TypeScript 코드 안에서 webpack 특유의 기능, 예를 들어 import.meta.webpack 같은 것을 쓸 수 있어요. webpack은 이런 기능들을 위한 타입도 제공하므로, TypeScript reference 지시어를 추가해 선언해 주세요.
/// <reference types="webpack/module" />
console.log(import.meta.webpack); // without reference declared above, TypeScript will throw an error
프로젝트 전체에서 이 타입을 사용하려면, tsconfig.json의 compilerOptions.types에 webpack/module을 추가해요.
{
"compilerOptions": {
"types": [
+ "webpack/module"
]
}
}
서드파티 라이브러리 사용하기 (Using Third Party Libraries)
npm에서 서드파티 라이브러리를 설치할 때는 반드시 그 라이브러리의 타입 정의(typing definition)도 함께 설치하는 게 중요해요.
예를 들어 lodash를 쓰고 싶다면, 타입 정의를 설치하기 위해 다음 명령을 실행하면 돼요.
npm install --save-dev @types/lodash
npm 패키지가 이미 선언 타입을 번들에 포함하고 있다면, 그에 해당하는 @types 패키지를 받을 필요는 없어요. 자세한 내용은 TypeScript changelog blog를 참고하세요.
다른 어셋 가져오기 (Importing Other Assets)
TypeScript와 함께 코드가 아닌 어셋을 사용하려면, 그 import에 대한 타입을 지연(defer)시켜 줘야 해요. 이를 위해 프로젝트 안에서 TypeScript의 커스텀 정의를 의미하는 custom.d.ts 파일이 필요해요. .svg 파일에 대한 선언을 이렇게 만들어 볼게요.
custom.d.ts
declare module "*.svg" {
const content: any;
export default content;
}
여기서는 .svg로 끝나는 모든 import를 지정해서 SVG용 새 모듈을 선언하고, 그 모듈의 content를 any로 정의했어요. URL임을 더 명확히 하고 싶다면 타입을 string으로 정의할 수도 있어요. CSS, SCSS, JSON 등 다른 어셋에도 같은 개념이 적용돼요.
빌드 성능 (Build Performance)
빌드 도구에 대한 자세한 내용은 Build Performance 가이드를 참고하세요.