ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Next.js + TS] 초기세팅 - (4) Babel 설정
    Front-End(Web)/React - 프레임워크(React, Next) 2021. 11. 2. 02:39
    반응형

    Next.js + TS 초기세팅에 대한 글을 쓰면서, 마지막으로 Babel 세팅에 대한 내용을 정리하려고 하였으나 이전에 개념만으로도 글이 길어졌다.

    * [Next.js + TS] 초기세팅 - (3) Babel 개념 : https://abangpa1ace.tistory.com/196

     

    이번 포스팅에서는, Babel을 실제로 세팅하는 방법, 그리고 Next.js 및 TS환경을 좀 더 낫게 사용하기 위해 내가 적용했던 몇 가지 항목들을 설명해보고자 한다.


     💛 Babel 과 Typescript

    바벨을 설정하기 이전에, Typescript 환경에서 바벨을 쓰는 것의 목적? 의의? 를 이해하기 위한 선행학습이 필요했다.

     

    사실, Typescript tsc(tsconfig.json) 역시 TS를 JS로 컴파일하는데 관한 설정을 담고 있다.

    그런 반면, Babel 역시 JS버전 컴파일뿐만 아니라 TS의 트랜스파일링까지 관여할 수 있다는 관련글들을 보아왔다.

     

    둘의 역할이 일부 중첩되는 것을 느꼈으며, 그렇다면 불필요한 이중작업이거나 혹은 설정충돌 등의 위험성이 없을까 생각하게 되었다.

     

     

    - babel.config.js vs tsc

    우선 놀라운 점은, babel과 tsc는 완벽하게 상관이 없다는 것이다!

     

    Webpack에서 .ts파일을 처리하기 위해, 기본적으로 ts-loader가 요구된다.

    하지만, 우리는 Babel을 사용하면서 babel-loader를 사용, 여기서 TS처리를 위한 @babel/preset-typescript 프리셋을 적용한다.

    그렇기에, babel은 독자적으로 TS를 해석 및 처리하고, 여기에 tsconfig.json이 전혀 관여되지 않는 것이다.

    • babel-loader : 타입체킹을 하지 않아 컴파일 속도가 빠르다. @babel/preset-typescript가 필요하며, 타입체크를 위한 플러그인이 필요함
    • ts-loader : 타입체킹을 해서 컴파일 속도가 느리다. transpileOnly 옵션으로 속도를 높이거나, 타입체킹을 별도로 실행하는 등 방법을 사용하곤 한다.

     

    - Using Babel with Typescript

    그렇기에, Babel의 TS처리방식 babel.config.json에서 별도로 설정하게 된다.

    Typescript 공식문서 역시 사용조건에 따라 각각의 loader를 권장하나, 일정 규모이상의 프로젝트는 Babel과 tsc의 혼용을 추천한다.

    • 소스 입력 파일과 빌드 출력 결과가 거의 비슷한가요? tsc
    • 여러 잠재적인 결과물을 내는 빌드 파이프라인이 필요하신가요? babel로 트랜스파일링, tsc로 타입검사

    위에서 두 번째 방법은, Babel의 preset-typescript로 JS파일을 생성한 후, tsc를 사용한 타입검사 및 .d.ts 파일을 생성하는 복합적인 접근방식이다.

     

    Babel은 Typescript를 지원하기 때문에 기존 빌드 파이프라인으로 작업할 수 있을뿐만 아니라,

    Babel이 코드타입을 검사하지 않기 때문에 JS 컴파일 시간이 빨라지며, tsc에서 별도로 타입체킹을 가져가는 방법인 것이다.

     

     

    1) 파일이름 변경 : .js -> .ts

     

    파일이 /src 디렉토리에 있다는 가정하에, 아래 커맨드 구문을 통해서도 바꿀 수 있다.

    find src -name "*.js" -exec sh -c 'mv "$0" "${0%.js}.ts"' {} ;

     

    2) Babel에 Typescript 설정 추가

     

    * 의존성 모듈

    npm install --save-dev 
    	@babel/preset-typescript 
        	@babel/plugin-proposal-class-properties 
        	@babel/plugin-proposal-object-rest-spread

     

    * 설정파일 반영(babel.config.js)

    {
      "presets": [
    	"@babel/typescript"
      ],
      "plugins": [
        "@babel/proposal-class-properties",
        "@babel/proposal-object-rest-spread"
      ]
    }

     

    3) check-types 옵션 추가

     

    * package.json 설정

    "scripts": {
      "check-types": "tsc"
    }

    이 명령은 단순히 Typescript 컴파일러(tsc)를 호출한다. 이를 Typescript 셋팅 시 설치해줘야 한다.

     

    * tsconfig.json 설정

    {
    	"compilerOptions": {
    		// Target latest version of ECMAScript.
    		"target": "esnext",
    		// Search under node_modules for non-relative imports.
    		"moduleResolution": "node",
    		// Process & infer types from .js files.
    		"allowJs": true,
    		// Don't emit; allow Babel to transform files.
    		"strict": true,
    		// Disallow features that require cross-file information for emit.
    		"isolatedModules": true,
    		// Import non-ES modules as default imports.
    		"esModuleInterop": true,
            "declaration": true,
            "emitDeclarationOnly": true,
    	},
    	"include": [
    		"src"
    	]
    }

    여기서 중요한 "compilerOptions"의 몇 가지 옵션들을 설명해보겠다.

    • isolatedModules : 각각 소스코드 파일을 모듈화하며, Babel이 TS파일을 안전하게 트랜스파일 하는지 확인한다. Babel과 같은 외부도구를 사용할 때 true로 설정하는 것이 좋다.
    • declaration : .ts 파일의 .d.ts 파일 또한 출력물에 포함된다.
    • emitDeclarationOnly : declaration 파일만 출력물에 포함된다. noEmit 옵션과 같이 사용 불가하다.

    💛 Babel 설정하기

    * .bablerc 설정

    {
      "presets": ["next/babel"],
      "plugins": [
        [
          "styled-components",
          {
            "ssr": true,
            "displayName": true,
            "preprocess": false
          }
        ],
        [
          "module-resolver",
          {
            "root": ["."],
            "alias": {
              "@": "./src"
            },
            "extensions": [".js", ".jsx", ".tsx"]
          }
        ]
      ]
    }

     

    - presets

    필요한 모든 플러그인을 일일히 설정하는 것은 번거롭다. 그렇기에, 목적에 맞는 여러 플러그인을 번들링한 preset을 기본적으로 활용한다.

    • preset-env : ES6 변환을 위해 주로 사용, 가장 많이 사용된다.
    • preset-react : React 변환을 위해 주로 사용
    • preset-typescript : Typescript 트랜스파일을 위해 주로 사용

    * preset 목록 : https://babeljs.io/docs/en/presets

     

    내 프로젝트에서는 Next.js 환경을 사용했으므로, 이에 적합한 ["next/babel"] 을 프리셋으로 설정했다.

     

    - Plugins

    코드를 변환해주는 각각의 feature들을 플러그인이라고 한다.

     

    1) babel-plugin-styled-components

     

    Next.js 환경에서 Styled Components를 사용할 때 발생하는 이슈때문에 적용하게 된 플러그인이다.

    Styled Components로 각각 네이밍된 컴포넌트들에 hash된 class명이 할당되므로, 스타일이 완벽히 적용하기 전에 렌더가 되버리는 것이다.

     

    그렇기에, Styled Components 스타일링을 SSR로 실행하는 플러그인 옵션을 설정해야 한다.

    // 설치
    yarn add -D babel-plugin-styled-components
    // 적용
    {
      "plugins": [
        ...,
        [
          "styled-components",
          {
            "ssr": true,		// 서버 사이드 렌더링 옵션
            "displayName": true,	// tag 클래스명에 컴포넌트명이 접두사로 붙어 디버깅에 유리
            "preprocess": false,	// experimental feature라고 하며, 끄는 것이 일반적
          }
        ],
        
        // 혹은 플러그인 자체로 설정하기도 한다.
        ["babel-plugin-styled-components"],
      ],
    }

     

    * _document.tsx 설정

     

    나도 프로젝트에서 빠트린 부분이다. (왠지, 리렌더링 시에 적용되지 않던 이슈가 있었는데, 이 부분이 원인이었던 듯 하다.)

    _app.tsx 다음으로 실행되며, 통상 <Head> 웹 접근성이나 <body> 커스텀에 쓰이나, Styled-components 관련 설정도 필요하다.

    import Document, { Html, Head, Main, NextScript, DocumentContext } from "next/document";
    import { ServerStyleSheet } from "styled-components";
    
    class MyDocument extends Document {
      static async getInitialProps(ctx: DocumentContext) {
        const sheet = new ServerStyleSheet();
        const originalRenderPage = ctx.renderPage;
        try {
          ctx.renderPage = () =>
            originalRenderPage({
              enhanceApp: (App) => (props) =>
                sheet.collectStyles(<App {...props} />),
            });
    
          const initialProps = await Document.getInitialProps(ctx);
          return {
            ...initialProps,
            styles: (
              <>
                {initialProps.styles}
                {sheet.getStyleElement()}
              </>
            ),
          };
        } finally {
          sheet.seal();
        }
      }
    
      render() {
        return (
          <Html>
            <Head>
              // 생략
            </Head>
            <body>
              <Main />
              <NextScript />
            </body>
          </Html>
        );
      }
    }
    
    export default MyDocument;
    • sheetServerStyleSheet 사용을 위한 모듈 인스턴스, renderPageSSR 시 css-in-js 커스텀을 위한 모듈이다.
    • enhanceApp 에서 react tree를 수집하면서, App 내 컴포넌트들의 모든 스타일을 collectStyles() 로 수집한다.
    • 마지막에, initialProps와 함께 styles 프로퍼티에 getStyleElement() 를 커스텀해줘서 스타일 태그가 형성되게끔 한다.

     

    2) babel-plugin-module-resolver

    // Use this:
    import MyUtilFn from 'utils/MyUtilFn'
    // Instead of that:
    import MyUtilFn from '../../../../utils/MyUtilFn'

    Module Resolver 를 사용하는 이유는 매우 간단하다. 위 예시처럼 절대경로를 사용해서, depth에서 올 수 있는 혼선가능성과 가독성을 개선하기 위해서이다.

     

    // 설치
    yarn add -D babel-plugin-module-resolver
    // 적용
    {
      "plugins": [
        ...,
        [
          "module-resolver",
          {
            "root": ["."],
            "alias": {
              "@": "./src"
            },
          }
        ],
      ],
    }
    • root : 모듈경로를 분석하는 기준(루트) 경로를 설정한다.(String or Array) 통상, "./" 혹은 "./src" 로 설정.
    • alias : 특정 경로에 대해 별칭을 설정한다. assets 등 경로단축에도 많이 사용하나, 통상 루트나 src를 "@", "~" 등 특수문자로 축약하는 용도로도 사용됨.
    • 이외에도, extensions(확장자), transformFunctions(자동완성 메서드) 등의 옵션들이 있다.

     

    * tsconfig.json 추가 수정

     

    Typescript 컴파일 시 절대경로 참고를 위해, 루트의 tsconfig.json에 baseUrl, paths를 설정해줘야 한다.

    "@" alias가 루트(baseUrl, "./")의 src 폴더인 것을 알려줘야 한다. 그리고, include에 .ts, .tsx를 추가한다.

    {
      "compilerOptions": {
        // ...
        "baseUrl": ".",
        "paths": {
          "@": [
            "src/*"
          ]
        },
        "include": [
          "next-env.d.ts",
          "**/*.ts",
          "**/*.tsx"
        ],
      },
    }

     

    * 기타 조치

    • next.config.js : webpack(config) 옵션 추가 => config.resolve.modules.push(__dirname);
    • Cache 초기화 : babel은 hot loader가 적용되지 않아 캐쉬설정을 초기화해야함 => yarn start --reset-cache
    • vscode 에디터 재시작

    Babel 설정파일 작성법을 끝으로 포스팅이 마무리되었다.

    단순히, .babelrc 파일 작성법을 기록하고자 시작했던 포스팅이었지만, 생각보다 이전에 선행으로 고민되어야 할 부분이 많았다.

    • Babel은 기본적으로 JS 컴파일을 위한 도구이다. 여기서 TS, SSR 등을 설정하는 방법뿐만 아니라 그 원리(의의)도 중요!
    • Babel 설정법은 한가지가 아니다. 설정파일도 babel.config.js 와 .babelrc 가 존재하며, 이 차이에 대해 알아봐야했다.
    • Babel 설정을 설명하기 위해 용어설명도 필수였다.(preset, plugin) 또한, tsc, _document.tsx 등 다른 컴포넌트와 설정파일의 추가설정도 이루어져야 했다.
    • 절대경로의 경우, 내가 회사 프로젝트에서 경험해보고 매우 용이함을 느낀 부분이다. 필수적이진 않으나, 빠르고 효율적인 개발을 위해 복잡하더라도 루트지정과 alias(@) 설정은 매우 유용하다!

     

    Next.js, Typescript 들은 더 나은 코드와 환경을 위해 현대 개발환경에서는 필수적으로 적용되는 부분들이다.

    이들의 문법과 사용법을 익히는 것 못지 않게, 환경을 구성하는 설정을 이해하고 더 나은 추가설정을 진행할 수 있는 부분이 향후 리드 개발자가 되었을 때, 개발자들이 일하기 좋은 레포지토리를 만드는 시발점이라 생각한다.

     

    이번 프로젝트를 (끝까지 완주하진 못했으나) 준비 및 진행하면서,

    Next.js 및 TS 사용법을 익힌 것보다, 동기들에게 더 나은 환경을 구성해주기 위해 직접 설정을 해본 경험이 더 소중함을 느꼈다.

     

    [참고]

    - [Next.js + TS + Styled] danmin20 님의 블로그 : https://velog.io/@danmin20/Next.js-Typescript-Styled-component-%EC%89%BD%EA%B2%8C-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0

    - [babel-loder & ts-loader] 우아한 테크캠프 Github 블로그 : https://github-wiki-see.page/m/woowa-techcamp-2021/store-4/wiki/babel-loader-%26-%40babel-preset-typescript-vs-ts-loader-%EA%B0%84%EB%8B%A8%ED%95%9C-%EC%A0%95%EB%A6%AC  

    - [Babel with Typescript] TOAST UI : https://ui.toast.com/weekly-pick/ko_20181220  

    - [ServerStyleSheet] swlh 님의 블로그 : https://medium.com/swlh/server-side-rendering-styled-components-with-nextjs-1db1353e915e

    - [절대경로 설정] react-native-seoul 님의 블로그 : https://medium.com/react-native-seoul/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90-module-resolver-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0-b28f607fd0bb

    반응형
Designed by Tistory.