Webpack과 같이 TypeScript와 React

불꽃남자·2021년 1월 5일
3

저번 포스팅에서 babel과 webpack을 사용하며 기본적인 것들을 배웠다. 이제 여기에 TypeScript와 React를 얹을 일만 남았다.

1. React

이제 yarn add react react-dom 명령어로 React를 설치하고, 작동하는 걸 확인하기 위해 index.jsx에 App 컴포넌트를 불러와서 div#root에다가 렌더링한다. 그리고 React에서도 css-loader가 잘 작동되는지 확인하고 싶어서 App.css도 적용했다.

// index.jsx

import React from "react";
import ReactDOM from "react-dom";
import App from "./components/App";

ReactDOM.render(<App />, document.getElementById("root"));


// App.jsx

import React from "react";
import './App.css';

const App = () => {
  return (
    <div>
      <h1>SYNTHWAVE</h1>
    </div>
  );
};

export default App;
/* App.css */

h1 {
  color: hotpink;
}

하지만 이대로 webpack을 작동시키면 webpack은 오류를 반환한다.

  1. 첫째는 import할 때 확장자를 명시하지 않으면 ['.wasm', '.mjs', '.js', '.json'] 순으로 확장자를 대입하고, 없으면 module not found 오류를 반환한다.
  2. 둘째는 webpack이 jsx 확장자가 뭔지 모른다.

물론 JSX없이 React를 사용할 수도 있다. ES6 없이 React를 사용할 수도 있다. 그럼 상황에따라 babel을 사용하지 않아도 된다. React Docs의 Babel, JSX, 그리고 빌드 과정에 그 방법에 대한 설명이 나와있다.
하지만 JSX와 상위 ES문법이 제공하는 편의성은 무시할 수 없다. 그래서 굳이 babel을 써가며 사용하는 것이다.

1.1. resolve.extensions

첫 번째부터 살펴보자. webpack의 option중에는 resolve.extensions 라는 배열 형태의 option이 있다. 위의 import App from "./component/App" 처럼 확장자를 명시하지 않으면 resolve.extensions 배열 내의 확장자명을 대입해서 찾아본다. resolve.extensions의 기본값은 ['.wasm', '.mjs', '.js', '.json']이다.
하지만 component 디렉토리에는 App.jsx 가 있을 뿐이다. webpack은 component 디렉토리에서 App.wasm, App.mjs, App.js, App.json 순으로 찾아보고, 없으면 App이라는 module이 없다고 판단하고 module not found 에러를 반환하게 된다.

그런 모듈 없는데요?

그래서 resolve.extensions 배열에 .jsx를 추가해야 한다.

// webpack.config.js

module.exports = {
  ...
  resolve: {
    extensions: [".jsx", ".js"],
  },
  devtool: "source-map",
  mode: "development",
};

이제 webpack은 App.jsx라는 모듈을 찾을 수 있게 되었다.

1.2. @babel/preset-react

두 번째는 webpack은 .jsx가 뭔지 모른다는 것이다. 위의 resolve.extensions 설정을 했다면 webpack을 실행해보자.


jsx가 뭐에요?

syntax 에러를 반환하면서 @babel/preset-react를 추가하라는 이야기도 같이 해준다.
@babel/preset-react는 간단히 말해 .jsx를 .js로 변환시켜준다. yarn add -D @babel/preset-react 명령어로 설치하고 적용시킨다.

// webpack.config.js

module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/, //여기에 jsx도 추가해야 한다.
        use: [
          {
            loader: "babel-loader",
            options: {
              presets: [
                [
                  "@babel/preset-env",
                  {
                    useBuiltIns: "usage",
                    corejs: "3.6.4",
                    targets: {
                      chrome: "87",
                    },
                  },
                ],
                "@babel/preset-react",
              ],
            },
          },
        ],
        exclude: /node_modules/,
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
  ...
};

babel preset도 등록 순서에 따라 작동 순서가 있나 싶었는데, 딱히 상관 없었다.

이제 webpack은 jsx도 읽을 줄 알게 되었다. webpack을 실행한다.

잘 작동한다. 훌륭하다.

2. TypeScript

이제 여기에 TypeScript를 얹어야 한다.
우선 yarn add -D typescript 명령어로 typescript를 설치한다. 그리고 yarn add @types/react @types/react-dom 명령어로 react와 react-dom에 대한 타입정의도 설치한다. react와 react-dom은 -D 옵션을 사용하지 않는다.

이제 jsx였던 확장자를 tsx로 바꾸고, Title 컴포넌트를 만들어 props에 대한 type을 정의해서 typescript를 사용한다.

// index.tsx

import React from "react";
import ReactDOM from "react-dom";
import App from "./components/App";

ReactDOM.render(<App />, document.getElementById("root"));


// App.tsx

import React from "react";

import Title from "./Title";
import "./App.css";

const App = () => {
  return (
    <div>
      <Title title="SYNTHWAVE" />
    </div>
  );
};

export default App;


// Title.tsx

import React from 'react';

interface titleProps {
  title: String
}

const Title = ({ title }: titleProps) => {
  return <h1>{title} from Title component</h1>
}

export default Title;

2.1. ts-loader

이제 webpack에게 tsx가 뭔지 가르쳐줘야할 차례다. typescript에 webpack을 사용하기 위해서는 ts-loader가 필요하다. yarn add -D ts-loader 명령어로 ts-loader를 설치하고 적용하자. 물론 resolve.extensions에 .tsx와 .ts도 추가시켜야 한다.

// webpack.config.js

module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.(ts|tsx)$/,
        loader: "ts-loader",
        exclude: /node_modules/,
      },
      {
        test: /\.(js|jsx)$/,
        use: [
          {
            loader: "babel-loader",
            options: {
              presets: [
                [
                  "@babel/preset-env",
                  {
                    useBuiltIns: "usage",
                    corejs: "3.6.4",
                    targets: {
                      chrome: "87",
                    },
                  },
                ],
                "@babel/preset-react",
              ],
            },
          },
        ],
        exclude: /node_modules/,
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
  ...
  resolve: {
    extensions: [".tsx", ".ts", ".jsx", ".js"],
  },
  ...
};

2.2. tsconfig.json

아 참. TypeScript를 사용하려면 tsconfig.json 파일이 꼭 필요하다. npx typescript --init 명령어로 tsconfig.json의 default가 생성된다.
TSConfig Docs에서 tsconfig 옵션에 대해 자세하게 볼 수 있다.
TypeScript는 JS의 느슨한 타입 제약에 때문에 런타임에서 발생하는 오류들을 강력한 타입 제약으로 컴파일 단계에서 잡아내기 위한 도구라고 볼 수 있다. 그렇기 때문에 처음 tsconfig을 생성하면 디렉토리 여기저기서 에러가 터지는 광경을 볼 수 있다.

하지만 이 모습이 의외로 정상이다. tsconfig의 default는 React가 아니라 바닐라 JS에 맞춰 설정되어 있기 때문이다. 그래서 나의 개발환경에 맞춰 tsconfig을 설정해야 한다.
그러나 TSConfig Docs를 보면 옵션의 수가 굉장히 많다. 그만큼 다양하고 유연하게 다룰 수 있다는 뜻이겠지만 필요한 옵션을 찾고 있노라면 너무 피곤하다.

나는 jhj46456님의 Velog를 참고하여 tsconfig을 설정했다.

// tsconfig.json

{
  "compilerOptions": {
    /* Basic Options */
    "target": "es5",
    "module": "esnext",
    "allowJs": true,
    "jsx": "react",
    "sourceMap": true,
    "outDir": "./build",
    
    /* Strict Type-Checking Options */
    "strict": true,
    
    /* Module Resolution Options */
    "moduleResolution": "node",
    "esModuleInterop": true,
    
    /* Advanced Options */
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
  },
  "include": ["./src/**/*"]
}

필요한 설정은 끝났으니 이제 webpack을 실행한다.

아주 잘 된다. 만족스러운 결과다.


🍋

여기까지 했으면 CRA 없이도 babel, TypeScript, React를 사용한 프로젝트를 webpack으로 번들할 수 있는 개발환경을 갖춘 것이다. 고작 그것만으로도 자애심이 넘쳐흐른다. 내가 너무 기특하다.

다음 포스팅에서는 여기에 ESLint와 Prettier를 얹을 생각이다. 이전에 이런 비슷한 개발환경에 추가하려다가 뭐가 꼬였는지 적용은 못 하고 에러만 잔뜩 얻은 기억이 있어서 이번엔 차근차근 알아보면서 적용에 성공하고 싶기 때문이다.

profile
프론트엔드 꿈나무, 탐구자.

0개의 댓글