babel은 ts를 어떻게 해석할까?
babel이 typescript를 어떻게 해석하는지 찾아보기 위해 babeljs.io 에서 @babel/plugin-transform-typescript
를 검색해봤다.
@babel/plugin-transform-typescript
@babel/plugin-transform-typescript · Babel
이 페이지에서 얻은 주요 힌트가 몇 가지 있다.
- 이 플러그인은
@babel/preset-typescript
프리셋에 포함 되어 있다. - 이 플러그인은 타입 유형을 해석하기 위한 것이다. 타입스크립트가 제공하는
?.
,??
,this.#x
등의 문법까지 지원하려면 @babel/preset-env 를 함께 사용해야 한다.
📖 ?., ??, this.#x 이러한 문법은 각각 optional chainning, nullish coalescing, private fields (class properties) 의 문법으로 불린다.
첫 번째 힌트를 따라 @babel/preset-typescript 에 가서 무슨 차이점이 있나 살펴봤다.
@babel/preset-typescript
@babel/preset-typescript · Babel
여기서는 이 프리셋이 @babel/plugin-transform-typescript 플러그인을 포함하고 있다는 내용 말곤 별다른 힌트를 얻지 못했다.
크게 중요한 것 같진 않지만 다음 인용문 하나를 확인할 수 있었다.
You will need to specify --extensions ".ts" for @babel/cli & @babel/node cli's to handle .ts files.
번역기: @babel/cli및@babel/node cli가.ts파일을 처리하려면--extensions ".ts"를 지정해야 합니다.
의식의 흐름을 따라가보니 CRA(create react app)에서는 어떻게 typescript를 지원할까? 에 도달했다.
CRA 분석
CRA를 통해서 타입스크립트를 지원하는 리액트 프로젝트를 만들어봤다.
# ts로 시작하는 react 프로젝트 생성
yarn create react-app cra-ts --template typescript
# 다음 명령으로도 프로젝트 생성 가능
# npx create-react-app cra-ts --template typescript
# 방금 생성한 프로젝트로 이동
cd cra-ts
CRA프로젝트는 react-scripts의 버전에 따라서 빌드 관련 설정이 감춰져 있는데, eject 명령을 통해 감춰진 설정을 추출할 수 있다.
config/ 디렉터리에서 각종 설정파일들을 확인할 수 있다.
yarn eject
CRA는 webpack 번들러를 이용하여 소스코드를 빌드하거나 개발용 서버를 실행하기 때문에 config/webpack.config.js
파일을 열어 typescript를 키워드로 검색해봤고, babel-loader
설정에 달려있는 주석문을 통해 하나의 힌트를 얻었다.
The preset includes JSX, Flow, TypeScript, and some ESnext features.
번역기: preset에는 JSX, Flow, TypeScript 및 일부 ESnext 기능이 포함됩니다.
찾았다! preset에 typescript를 포함한다는데 어떤 preset인가 보니 babel-preset-react-app 이었다.
아직 의문점이 남긴 하지만 CRA는 babel-preset-react-app을 통해 typescript를 지원한다!는 것을 알 수 있었다.
config/webpack.config.js
// Process application JS with Babel.
// The preset includes JSX, Flow, TypeScript, and some ESnext features.
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
customize: require.resolve(
'babel-preset-react-app/webpack-overrides'
),
presets: [
[
require.resolve('babel-preset-react-app'),
{
runtime: hasJsxRuntime ? 'automatic' : 'classic',
},
],
],
// ...other options
},
},
📖 webpack은 각종 .js, .ts, .svg, .css, .scss 등의 자원들을 -loader 라는 도구를 이용하여 웹브라우저가 해석 가능한 html, css, js 파일로 번들링 해주는 도구이다.
내가 처음에 생각했던 @babel/plugin-transform-typescript
플러그인은 보이지 않았기에 내 의식은 babel-preset-react-app 을 찾아가기 시작했다.
babel-preset-react-app
create-react-app/packages/babel-preset-react-app at main · facebook/create-react-app
여기서 내가 원하는 답에 매우 근접한 문구를 찾았다.
Make sure you have a tsconfig.json file at the root directory. You can also use the typescript option on .babelrc
번역기: 루트 디렉토리에 tsconfig.json 파일이 있는지 확인하십시오. .babelrc에서 typescript 옵션을 사용할 수도 있습니다.
루트 디렉터리에 tsconfig.json이 있으면 typescript를 지원하는구나!
또는 babelrc 설정을 아래처럼 구성하면 .ts를 .js로 transpile 할 수 있구나!
{
"presets": [["react-app", { "flow": false, "typescript": true }]]
}
여기까지 와보니 예전에 ts 기반의 리액트 컴포넌트를 패키지로 배포하기 위해서 @babel/preset-react-app과 @babel/preset-typescript를 함께 사용한 적이 있는데. 이건 프리셋을 중복해서 등록한 것과 같다는 것을 깨달았다... 나중에 꼭 고쳐야지.
- babel 설정 개선!
이제 거의 다 왔기 때문에 babel-preset-react-app의 소스코드를 좀 찾아봤다.
이 preset을 구성하는 presets 설정을 보니 @babel/preset-env 와 @babel/preset-react 그리고, typescript 여부에 따라 @babel/preset-typescript 프리셋이 추가되는 것을 알 수 있었다.
module.exports = function (api, opts, env) {
// ...
return {
presets: [
// ...
(isEnvProduction || isEnvDevelopment) && [
// Latest stable ECMAScript features
require('@babel/preset-env').default,
{
// Allow importing core-js in entrypoint and use browserlist to select polyfills
useBuiltIns: 'entry',
// Set the corejs version we are using to avoid warnings in console
corejs: 3,
// Exclude transforms that make all code slower
exclude: ['transform-typeof-symbol'],
},
],
[
require('@babel/preset-react').default,
{
// Adds component stack to warning messages
// Adds __self attribute to JSX which React will use for some warnings
development: isEnvDevelopment || isEnvTest,
// Will use the native built-in instead of trying to polyfill
// behavior for any plugins that require one.
...(opts.runtime !== 'automatic' ? { useBuiltIns: true } : {}),
runtime: opts.runtime || 'classic',
},
],
isTypeScriptEnabled && [require('@babel/preset-typescript').default],
].filter(Boolean),
plugins: [
// ...
],
}
}