CRA 없이 프로젝트 구성하기 1 - 초기설정 with React18, webpack, babel

Seungrok Yoon (Lethe)·2023년 7월 19일
0
post-thumbnail

언제부턴가 creat-react-app과 Next.js의 편안함에 익숙해져 프로젝트의 기본이 되는 번들링에 대해 제대로 알고있지 못한 것 같다는 생각이 들었어요.

여러분들은 creat-react-app, creat-next-app 없이 리액트 프로젝트를 설정할 수 있나요?

저는 혼자 하기는 버거울 것 같아요. 그러니 저랑 같이 해보실래요?ㅎㅎ

제가 작업을 한 레포는 여기 입니다! 커밋이력을 참고하시면 이해해 더 많은 도움이 될 것이라 생각합니다.

리액트 설치하기

yarn add react react-router-dom

Webpack 설치 및 초기화하기

yarn add webpack webpack-cli -D
npx webpack init

package.json 작성하기

https://docs.npmjs.com/cli/v9/configuring-npm/package-json

Webpack config 파일을 작성하기 전에, 공식문서 찬찬히 톺아보기

https://webpack.kr/guides/getting-started/

loader?

웹팩을 처음 접하는 사람들은 loader라는 것이 굉장히 낯설고, 설정 과정에서 애를 먹는 경우가 많습니다. 저도 그랬어요.

loader는 공식문서에서 이렇게 정의하고 있습니다.

In addition to that webpack supports modules written in a variety of languages and preprocessors via loaders. Loaders describe to webpack how to process non-native modules and include these dependencies into your bundles. - 원문

loader를 통해서 웹팩은 다양한 언어로 쓰인 모듈들, 전처리기구들을 처리할 수 있도록 지원해줍니다. 로더는 외부 모듈들을 처리하는 방법, 그리고 이러한 의존성 모듈들을 번들에 추가하기 위한 방법을 웹팩에 알려주는 역할을 한다고 하네요.

웹팩이 번들링을 하기 전에, 다양한 파일들을 번들링이 가능한 형태로 변환하는 전처리작업을 해주는 것이 loader라고 생각하면 되겠네요!

Rule - webpack configuration object

webpcak.config.js 파일에서 export 하는 설정객체(webpack configuration object이니 편의상 config객체 라 부르겠습니다)의

Module객체는, 프로젝트 내부에 있는 수많은 파일(모듈들)이 어떻게 webpack에 의해 처리될지를 결정하는 옵션들을 담고 있습니다.

config 객체에서 module.rules 의 값으로 저장되는 객체는 Rule객체입니다.

Rule객체는 아래처럼 module.rules 내부에 대입됩니다.

//webpack.config.js

...
module:{
    rules: Rule[]
}
...

Rule객체는 Conditions,Results,nested Rules이렇게 세 부분으로 이루여져 있습니다.

  • Rule Condition : UseEntry에서 test 프로프티에 들어가는 정규식이 바로 Condition입니다. 사실 정규식이 아닐 수도 있지만요.
...
module: {
    rules: [
      {
        loader: 'babel-loader',
        test: /\.(js,jsx)$/i, <-요부분이 Condition
        exclude: /node_modules/,
        options: {
          presets: [
            ['@babel/preset-env'{targets:'defaults' }]
          ],
        }
      }//UseEntry 하나
    ]//module.rules
 ...

Rule 에는 test,include,exclude,resource가 resource(요청된 파일의 절대경로)와 매칭되고, issuer프로퍼티가 issuer(파일을 요청한 파일의 절대경로)과 연결됩니다.

  • Rule Results : 위 Condition을 만족했다면 Rule Results가 사용됩니다. 위에 예시를 든 코드에서는 .js, .jsx 에 해당하는 모듈들이 test 조건을 통과하게되고, webpack은 해당 Rule에 명시되어있는 loader의 도움을 받아 관련 작업을 진행하겠네요. loader는 Rule객체 내부의 loader, options,use프로퍼티에 있는 값들에 의해 영향을 받습니다.

Rule.loader

Rule.loaderRule.use: [ { loader } ]의 단축형 표현이라 합니다. Rule.loaderRule.use는 같이 쓰이면 안되겠군요.

Rule.loader를 사용한 UseEntry와 Rule.use를 사용한 UseEntry는 각각 아래와 같겠네요.

module: {
    rules: [
      {
        loader: 'babel-loader',
        test: /\.(js,jsx)$/i,
        exclude: /node_modules/,
        options: {
          presets: [
            ['@babel/preset-env'{targets:'defaults' }]
          ],
        }
      }//UseEntry 하나
    ]//module.rules

아래 코드에서 확인할 수 있다시피 Rule.use에는 UseEntry[] 가 대입됩니다.

...
module: {
    rules: [
      {
        test: /\.(js,jsx)$/i,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: [
                ['@babel/preset-env', { targets:'defaults' }]
              ],
            },
          },
        ],
      }//UseEntry 하나
   ]//module.rules

UseEntry

webpack-UseEntry는 반드시 문자열타입의 loader프로퍼티를 가져야 합니다.

UseEntry를 이해한 바를 간단히 설명하자면

  • loader:어떤 loader를 사용할 것인지 - string
  • options: 이 loader에 반영할 옵션들 - string,object
    입니다.

Babel (아직 작성중인 파트입니다)

https://babeljs.io/

https://medium.com/age-of-awareness/setup-react-with-webpack-and-babel-5114a14a47e9#9b0c

npx webpack init

을 하면 기본적인 webpack config 파일을 생성해주지만, React를 번들링하는데 쓰려면 몇 가지 더 설정을 해 주어야 합니다.

@babel/preset-react

진행과정에서 겪은 에러들

에러 1 - 오타

아래와같은 에러가 발생했습니다.

You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders

| const root = createRoot(domNode);
| root.render(
   <StrictMode>
|     <App />
|   </StrictMode>

원인은 정규식 오타였습니다.

 {
        test: /\.(js,jsx)$/i, <-오타발생
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: ['env', 'react'],
            },
          },
        ],
      },
 {
        test: /\.(js|jsx)$/i, <-수정
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: ['env', 'react'],
            },
          },
        ],
      },

에러 2 - 제 이름은 @bable/preset-env입니다!

저는 babel-preset-env를 설치했는데...왜 못찾는다고 할까요?

프리셋명을 풀네임으로 명시해주었더니 해결되었습니다.

presets: ['@babel/preset-env', '@babel/preset-react'],

에러 3 - 응애 저는 확장자를 아직 몰라요

App.jsx를 확장자가 없는 App으로 읽으려니 웹팩이 탈이 난 것입니다.

그럴 때는 resolve.extentions에 ['.js', '.jsx']를 추가하여 웹팩이 js파일과 jsx파일의 확장자를 알아차리게 해줍시다.

초기파일이 렌더링되었어요!

우리는 드디어 첫 화면을 보게되었습니다.

지금까지 고생 많으셨습니다.

다음 시간에는 타입스크립트를 이 프로젝트에 적용해보도록 하죠!

import React from 'react'매번 쓰는 것이 귀찮다!

끝내기 전에 마지막으로 이것 하나만 더 하죠.

create-react-app 사용하실 때 컴포넌트 작성할 때마다 React 관련 API를 호출하지도 않는데 import React를 코드에 추가하신 기억이 있으신가요? 아마 없을겁니다.

그렇지만 우리가 이제껏 작성한 파일들에서는 무조건 import React from 'react를 써야 제대로 프로젝트가 구동되고 있습니다. 만약 실수로라도 이 구문을 빼버린다면 에러가 나죠.

가령, index.js에서는 React를 사용할 일이 없어서 무심코 아래처럼 코드를 작성하고 프로젝트를 구동하면...

//index.js
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';

const domNode = document.getElementById('root');
const root = createRoot(domNode);
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

"React is not defined" 에러를 콘솔창에서 발견하시게 될 겁니다.

왜 이런걸까요?

원인은 JSX 때문!

JSX는 내부적으로 React.createElement()로 변환되기 때문에 위에서 언급한 import문이 필요했습니다. React 17버전 전까진 말이죠.

해결 - "React automatic runtime"

이 문제에 대한 해결법은 생각보다 간단합니다."React automatic runtime"이라는 것을 사용하면 됩니다.

이 기능은 React 17버전 에서 추가되었습니다.

https://so-so.dev/react/import-react-from-react/

바벨에서도 이런 변화가 있는데요
https://babeljs.io/blog/2020/03/16/7.9.0#a-new-jsx-transform-11154

https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html#how-to-upgrade-to-the-new-jsx-transform

Manual Babel Setup
Support for the new JSX transform is available in Babel v7.9.0 and above.

yarn upgrade @babel/core @babel/preset-react

바벨을 업데이트하고, @babel/preset-react 의 옵션 중 "runtime":"automatic"으로 설정해주시면됩니다.

//.bablerc
{
  "plugins": ["@babel/syntax-dynamic-import"],
  "presets": [
    "@babel/preset-env",
    ["@babel/preset-react", { "runtime": "automatic" }]
  ]
}

그러면 이제 React를 import하는 구문들은 쓸모가 없어졌죠?

https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html#removing-unused-react-imports

여기에 나와있는 커맨드를 실행해주시면 놀랍게도 React를 import 하는 부분만 쏙 빼준답니다!

그럼 다음 시간에 만나요!

코멘트와 댓글은 언제나 환영입니다! 😎

profile
안녕하세요 개발자 윤승록입니다. 내 성장을 가시적으로 기록하기 위해 블로그를 운영중입니다.

2개의 댓글

comment-user-thumbnail
2023년 7월 19일

아주 잘 작성된 글이었습니다.

1개의 답글