CRA 없이 React 프로젝트 만들기

ggong·2021년 5월 17일
0

지금까지 리액트로 프로젝트를 하면서 아예 제로베이스에서 환경 구성을 해본 적이 없었다. 전 회사에서는 CRA 환경으로 구성한 프로젝트를 거의 메인으로 담당했었고, 배포 환경을 개선해도 기능 추가 정도였다. 습관처럼 프로젝트 생성 후 yarn start를 때리다보니 자꾸 템플릿에 의존하는 바보가 되는 것 같아서, 기본적인 React 환경을 CRA 없이도 만들어봐야겠다는 생각이 들었다.

엄청 설명이 잘 되어 있는 다른 블로그가 있어서 많이 참고함!

Step 1. 폴더 생성하고 init 하기

원하는 경로에 빈 폴더 생성 후, yarn을 초기화 해준다.

yarn init -y

package.json 파일이 생성되었다. 필요한 폴더들도 수동으로 설치한다.

mkdir src public dist
  • src: 실제 작업하는 소스들이 들어가는 폴더
  • public: html 등 정적 파일들을 넣을 폴더
  • dist: 번들링 결과물이 위치할 폴더

public 안에는 index.html을 만들고 아래처럼 기본적인 내용을 넣는다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>CRA 없이 설정하기</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

src 디렉토리 안에는 리액트 컴포넌트를 렌더링할 기본 index.js를 만들어 준다.

Step 2. React 설치하기

이제 의존성 모듈을 직접 설치해줘야 한다. 먼저 React를 설치한다.

yarn add react react-dom

React 엘리먼트는 DOM이 아니라 객체이기 때문에 이를 브라우저에 실제로 렌더링하는 DOM을 구성하려면 React-dom이 필요하다. 이제 package.json의 dependencies에 소박한 두 줄이 생겼다.

Step 3. Babel 설치하기

다음으로는 바벨을 설치해 준다. 바벨은 JSX와 ES6+를 브라우저가 읽을 수 있도록 es5로 변환해주는 트랜스파일러다. 터미널에 커맨드를 입력해서 사용할거라면 @babel-cli도 같이 설치한다. 애플리케이션 실행 시에 필요한 것은 아니고 빌드 시에 필요하니까 개발 의존성(devDependencies)로 설치한다.

yarn add @babel/core @babel/cli -D

물론 이 두개만 추가한다고 트랜스파일이 되지는 않는다. babel/core는 트랜스파일을 진행해주는 코어 기능만 있고 실제 코드를 변환할때는 각각의 문법을 트랜스파일 할 수 있는, 기능별로 확장된 플러그인들이 필요하다. 기능별로 플러그인을 모두 찾아서 설치하기는 귀찮기 때문에 보통 프리셋을 설치해서 해결한다. 보통 플러그인은 규칙 하나 하나를 미세하게 적용할 때 사용하고, preset은 여러 개의 규칙을 한 번에 적용할 때 사용한다.

아래 두 개의 모듈을 추가 설치한다.

yarn add @babel/preset-react @babel/preset-env -D

@babel/preset-react : 리액트의 JSX를 트랜스파일링
@babel/preset-env : ES6 이상(ES2015+)의 문법으로 작성된 코드를 ES5 문법의 코드로 변환해주는 가장 범용적인 프리셋

이제 package.json은 아래처럼 예쁘게 되었다.

{
  "name": "react-without-cra",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  },
  "devDependencies": {
    "@babel/cli": "^7.13.16",
    "@babel/core": "^7.14.2",
    "@babel/preset-env": "^7.14.2",
    "@babel/preset-react": "^7.13.13"
  }
}

기본적으로 babel로 트랜스파일링을 할 때는 '어떤 규칙에 따라서 문법을 변환해라'라는 것을 알려줘야 한다. 만약 아래처럼 ES6 arrow function으로 작성된 js를 바벨로 트랜스파일링 한다고 생각해보자.

"use strict";

[1, 2, 3].map(n => n + 1);

npx babel src/index.js라고 cli에서 트랜스파일링을 시도해도 결과는 똑같다. 제대로 변환하려면 npx babel src/index.js --presets=@babel/env와 같이 어떤 프리셋을 사용하는지 옵션으로 알려줘야 한다. 이것은 매우 귀찮기 때문에 대신 Babel 설정 파일을 하나 만들어서 거기에서 지정해 준다.

최상위 디렉토리에 .babelrc 파일을 생성하고, 아래처럼 기본으로 사용할 프리셋을 설정한다. (babel.config.js로 작성하는 경우도 있는데, 이 두 개가 지역설정/전체 설정으로 약간 다르다. 여기에 대해서는 나중에 따로 정리해야지)
아래처럼 미리 설정해두면 바벨 실행시에 프리셋에 맞게 컴파일이 진행된다.

{
  "presets": ["@babel/preset-react", "@babel/preset-env"]
}

Step 4. 웹팩 설정하기

이번에는 모듈 번들러인 웹팩을 설치해보자.

yarn add -D webpack webpack-cli webpack-dev-server

webpack : 웹팩의 코어
webpack-cli : 웹팩을 커맨드라인에서 사용할 수 있게 함
webpack-dev-server : 웹팩을 메모리 상에 빌드하여 개발 서버를 구동

이 중에서 webpack-dev-server는 변경에 따라 새로고침되는 개발 서버를 띄우기 위해 필요하다. webpack-dev-server의 동작 방식은 아래와 같다.

  1. 서버 실행 시 소스 파일들을 번들링하여 메모리에 저장소스 파일을 감시
  2. 소스 파일이 변경되면 변경된 모듈만 새로 번들링
  3. 변경된 모듈 정보를 브라우저에 전송
  4. 브라우저는 변경을 인지하고 새로고침되어 변경사항이 반영된 페이지를 로드

webpack-dev-server는 디스크에 저장되지 않는 메모리 컴파일을 사용하기 때문에 컴파일 속도가 빠르다. 추가적인 옵션은 webpack.config.js안에서 devServer 옵션을 통해 수정할 수 있다.

webpack이 설치되면 번들링에 필요한 로더들도 설치한다. 기본적으로 webpack은 자바스크립트만 읽기 때문에, 웹팩이 웹 애플리케이션을 해석할 때 자바스크립트 파일이 아닌 웹 자원(HTML, CSS, Images, 폰트 등)들을 변환할 수 있도록 도와주는 로더(Loader) 속성을 추가한다.

yarn add -D babel-loader css-loader style-loader file-loader

babel-loader : JSX 및 ES6+ 문법을 트랜스파일링
css-loader : CSS 파일을 자바스크립트가 이해할 수 있도록 변환
style-loader : 변환된 CSS 파일을 <style> 태그로 감싸서 삽입
file-loader : 이미지 및 폰트 등의 파일 로딩

번들링을 한 후에 적용할 플러그인들도 설치한다.

yarn add -D html-webpack-plugin clean-webpack-plugin

html-webpack-plugin : HTML 파일에 번들링된 자바스크립트 파일을 삽입해주고 번들링된 결과가 저장되는 폴더에 옮겨줌
clean-webpack-plugin : 번들링을 할 때마다 이전 번들링 결과를 제거함

이번에는 웹팩 설정 파일을 만들어줘야 한다. 늘 override만 했었던 웹팩 설정 파일이지만 오늘은 처음부터 하나씩 만들어보자. 최상위 디렉토리에 webpack.config.js 파일을 만들고 일단 아래처럼 입력한다.

module.exports = {
//// 최초 진입점
  entry: {
    main: './src/index.js'
  }
};

위에 있는 entry는 웹팩에서 웹 자원을 변환하기 위해 필요한 최초 진입점이자 자바스크립트 파일 경로이다. 엔트리 포인트는 이름을 지정할 수 있고 여러개 만들 수도 있다. 엔트리 포인트를 분리하는 경우는 싱글 페이지 애플리케이션이 아닌 특정 페이지로 진입했을 때 서버에서 해당 정보를 내려주는 형태의 멀티 페이지 애플리케이션에 적합하다.

const path = require('path'); //// output에서 사용할 path

module.exports = {
  entry: {
    main: './src/index.js'
  },
//// 번들링 된 js 파일이 저장될 경로와 이름
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, './dist') 
  },
//// source-map 설정
  devtool: 'eval-cheap-source-map',
};

output은 웹팩을 돌리고 난 결과물의 파일 경로와 파일 이름을 지정한다. 파일 이름에 해쉬를 붙이는 경우도 있으며, 경로는 절대경로로 지정한다.
devtool은 source-map을 설정하는 부분이다. 소스 맵(source map)이란 배포용으로 빌드한 파일과 원본 파일을 서로 연결시켜주는 기능이다. 에러가 발생했을 때 번들링된 파일에서 어느 부분에 에러가 났는지를 쉽게 확인할 수 없지만 소스 맵을 이용하면 배포용 파일의 특정 부분이 원본 소스의 어떤 부분인지 매핑되기 때문에 추적이 쉽다.

이번에는 아까 웹팩과 같이 설치한 웹팩 로더들을 설정해주자.

const path = require('path');

module.exports = {
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, './dist') 
  },
  devtool: 'eval-cheap-source-map',
//// 아까 설치한 loader를 설정해줌
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: '/node_modules/',
        loader: 'babel-loader'
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.jfif$/,
        loader: 'file-loader',
        options: {
          name: '[name].[ext]',
        }
      },
    ],
  },
};

로더들은 module.rules 안에 객체 배열 형태로 추가된다. 특정 파일에 대해 여러 개의 로더를 사용하는 경우 로더는 기본적으로 오른쪽에서 왼쪽 순으로 적용된다.

test : 로더를 적용할 파일 유형 (일반적으로 정규 표현식 사용)
loader : 로더가 1개일 때 해당 파일에 적용할 로더
use : 로더가 2개 이상일 때 해당 파일에 적용할 로더의 배열
exclude: 로더를 제외할 대상

const path = require('path');
//// 사용할 플러그인 불러오기
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, './dist') 
  },
  devtool: 'eval-cheap-source-map',
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: '/node_modules/',
        loader: 'babel-loader'
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.jfif$/,
        loader: 'file-loader',
        options: {
          name: '[name].[ext]',
        }
      },
    ],
  },
//// webpack-dev-server 옵션 설정 
  devServer: {
    hot: true,
    overlay: true,
    writeToDisk: true,
  },
//// 플러그인 적용
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
};

devServer는 아까 webpack-dev-server에 대한 설정이다.

hot: 모듈의 변화된 부분만 자동으로 리로딩하는 HMR(Hot Module Replacement) 기능을 사용할 것인지에 대한 옵션
overlay: 에러가 발생했을 때 브라우저에 띄울 것인지 여부
writeToDisk: 메모리 뿐만 아니라 직접 파일로 만들 것인지에 대한 옵션

플러그인은 plugins 안에 배열로 넣는다. 여기서 HtmlWebpackPlugin에 전달한 template 객체에는 번들링 파일을 주입할 대상 HTML 파일을 명시한다. 우리는 번들링된 js가 public 폴더 안에 있는 HTML에 붙을 것이므로 해당 경로를 적는다.

기본적인 웹팩 설정이 끝났다.(나노무힘드로...)
이제 명령어만 설정해주면 된다.

Step 5. package.json 파일 수정

package.json 파일로 가서 yarn start와 같은 스크립트를 사용할 수 있게 지정해 준다.

{
  "scripts": {
    "dev": "webpack-dev-server --progress --mode development",
    "build": "webpack --progress --mode production"
  }
}

여기까지 하고 간단한 App.jsx, App.css, index.js를 구성하고 yarn dev를 하면!!! (컴포넌트 구성하는 부분은 따로 하지 않음)

아래와 같은 에러를 뱉는다.

Error: Cannot find module 'webpack-cli/bin/config-yargs'

짜증나.. 찾아보니까 웹팩 관련 패키지들끼리 버전이 안맞으면 이런 에러가 난다고 한다.
아래처럼 버전을 조정해서 다시 yarn 했더니 이제 잘 된다!

"webpack": "^4.40.2",
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.11.0"


참고:
[React] CRA 없이 리액트 환경 만들기
(https://baeharam.netlify.app/posts/react/React-CRA-%EC%97%86%EC%9D%B4-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%99%98%EA%B2%BD-%EB%A7%8C%EB%93%A4%EA%B8%B0)
바벨(Babel7) 기본 사용법
(https://www.daleseo.com/js-babel/)
웹팩 핸드북
(https://joshua1988.github.io/webpack-guide/)

profile
파닥파닥 FE 개발자의 기록용 블로그

0개의 댓글