react 시작하기 + webpack + babel + sass 설정

리액트를 정리하기 위한 블로그입니다.
잘못된 개념의 경우는 피드백 주시면 감사하겠습니다.

리액트 왜 쓸까요?

사용자 인터페이스를 만들기 위한 Javascript 라이브러리
프레임워크라고 생각하고 있습니다.

사용자 인터페이스가 영어로 줄이면 UI죠.
버튼, 메뉴, 링크 이런걸 얘기하겠죠.

image.png

특징은 SPA( Single Page Application )입니다.
서버에서 html를 찍어서 보내주는 형식이 아닙니다.
React 웹사이트가 React로 만들어졌기 때문에 화면 깜박이는거 없고, 자연스럽게 넘어갑니다.
장단점이 있습니다.
레퍼런스 아하 프론트 개발기(1) — SPA와 SSR의 장단점 그리고 Nuxt.js - Sangyoung Lee

기존구조 만들기

react는 결국에는 js파일입니다.
그 안에 html 마크업도 들어가고요, css도 다 들어간 형태라고 말할 수 있습니다.

react를 사용환경 구축

npm를 통해 react를 환경을 구성하겠습니다.
npm은 Node.js (자바스크립트코드가 브라우저가 아닌 곳에서도 동작할 수 있게 해준는 런타임 환경입니다.)에서 개발을 편하게 할 수 있도록 많은 사람들의 코드를 사용할 수 있게 해주는 프로그램이라고 생각하시면 됩니다.

npm은 Node.js를 설치하실때 같이 설치 됩니다.

가장 먼저 package.json을 만들기 위해 프로젝트 루트 디렉토리에서 터미널을 열고 다음과 같은 명령어를 입력합니다.
npm init, yarn init 둘다 결국 결과는 같습니다.
저는 yarn을 사용하겠습니다. yarn이 설치 되지 않았다면, command not found 에러표시가 난다면,
brew install yarn로 yarn인스톨해줍니다.
https://yarnpkg.com/lang/en/docs/install/#mac-stable

yarn init -y

그 결과 package.json이 생성됩니다.

image.png

react, react-dom 설치
설치시 package.json에 name프로퍼티의 공백문자나, 키워드가 같은 문자가 들어가면 에러나니 그 부분만 주의합니다.

yarn add react react-dom

react :리액트 라이브러리
react-dom :browser, dom, webapp 관리

babel 설치

yarn add -D @babel/core babel-loader @babel/preset-env @babel/preset-react @babel/plugin-proposal-class-properties

babel을 사용하는 이유는요. babel이 있으면 자바스크립트 코드가 어떤 환경에서 돌아갈지를 설정만 함으로써 코드를 트랜스파일 할 수 있습니다.

  • @babel/core : 리액트는 es6를 사용하므로 여러 브라우저에서 사용가능하도록 es5이하 버전으로 syntax를 변경해줍니다.
  • @babel/preset-react : jsx를 쓸 수 있습니다.
    jsx는 react 공식홈페이지 나옵니다. 자바스크립트 확장판이랍니다.
  • @babel/preset-env : es6 -> es5이하로 설정에 맞게 트랜스파일됩니다.
  • babel-loader : 자바스크립트 파일을 babel preset/plugin과 webpack을 사용하여 es5로 컴파일 해주는 plugin
    • jsx -> javascript 로 컴파일
    • html webpack plugin
  • @babel/plugin-proposal-class-properties : class property 관련된 문제를 해결하기 위해 사용합니다. hooks를 쓰실때는 없어도 됩니다.

레퍼런스:babel-preset-env는 무엇이고 왜 필요한가?

webpack 설치

yarn add -D webpack webpack-dev-server webpack-cli html-webpack-plugin
  • webpack
    • 모든 리액트 파일을 하나의 컴파일된 하나의 자바스크립트 파일에 넣기 위해
  • webpack-dev-server
  • webpack-cli
    • build 스크립트를 통해 webpack 커맨드를 사용하기 위해
  • html-webpack-plugin
    • html에 bundle된 자바스크립트 파일을 <script>...</script>형태로 추가해줍니다.
  • Loaders : 코드 불러옴

    • transform the source code of a module.
      • style-loader css를 <style>로 추가
      • sass-loader SASS파일을 CSS로 컴파일
      • babel-loader babel로 javascript 코드를 transpile
    • do the pre-processing transformation of virtually any file format when you use sth like require("my-loader!./my-awesome-module") in your code
  • Plugins :컴파일러

    • Webpack의 핵심 기능
    • Webpack은 기본적으로 대부분의 source 코드를 bundle파일로 번환한다.
    • loader가 부족하면 pluging을 사용하여 webpack의 기능을 추가한다.
  • preset :plugin의 집합

    • plugin이 필요할 때마다 매번 webpack에서 설정하는 것은 귀찮은 일이므로 plugin들을 모아놓은 preset을 한번에 추가하여 관리할 수 있습니다.

webpack.config.js

웹팩을 다루기 위한 설정파일 만듭니다.
webpack.config.js 기본적으로 이 이름이여야합니다.
다른이름을 사용시 웹팩의 옵션을 설정해주어야 합니다.

const path = require('path'); // core nodejs 모듈 중 하나, 파일 경로 설정할 때 사용
const HtmlWebpackPlugin = require('html-webpack-plugin'); // index.html 파일을 dist 폴더에 index_bundle.js 파일과 함께 자동으로 생성, 우리는 그냥 시작만 하고싶지 귀찮게 index.html 파일까지 만들고 싶지 않다.!!

module.exports = {
  entry: ["@babel/polyfill", "./src/index"], // 리액트 파일이 시작하는 곳
  resolve: {
    extensions: ['.jsx', '.js'],
  },
  output: {
    // bundled compiled 파일
    path: path.join(__dirname, '/dist'), //__dirname : 현재 디렉토리, dist 폴더에 모든 컴파일된 하나의 번들파일을 넣을 예정
    filename: 'index_bundle.js',
  },
  module: {
    // javascript 모듈을 생성할 규칙을 지정 (node_module을 제외한.js 파일을 babel-loader로 불러와 모듈을 생성
    rules: [
      {
        test: /\.jsx?/, // .js, .jsx로 끝나는 babel이 컴파일하게 할 모든 파일
        exclude: /node_modules/, // node module 폴더는 babel 컴파일에서 제외
        use: {
          loader: 'babel-loader', // babel loader가 파이프를 통해 js 코드를 불러옴
          options: {
            presets: ['@babel/preset-env', '@babel/preset-react'],
            plugins: ['@babel/proposal-class-properties'],
          },
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html', // 생성한 템플릿 파일
    }),
  ],
};
  • __dirname : 노드 변수로 현재 모듈의 디렉토리를 리턴합니다.

  • HtmlWebpackPlugin : 컴파일 이후 index.html 설정한 파일에 js, css 스크립트를 생성해줍니다.

    • template에 지정된 index.html에 모든 static 파일들을 긁어모은 index_bundle.js 파일을 <script src='index_bundle.js'></script> 형식으로 연결해줍니다.
  • resolve: 확장자를 생략할 수 있습니다.

    • entry에 들어갈 파일이나, import 할때, js, jsx 생략가능합니다.
  • entry : 컴파일 할 파일 ( resolve를 사용했기 때문에 확장자까지 쓰지 않았습니다.)

  • output : 컴파일 이후 파일위치

    • __dirname/dist/index_bundle.js
  • module : 모듈의 컴파일 형식

    • es6 문법을 es5으로 바꾸기 위해 webpack이 js, jsx를 포함한 모든 파일을 babel을 통하여 컴파일 시킵니다.
  • plugin : 사용할 plugins

    • 여기서는 htmlwebpackPlugin을 사용하여 index.html과 index_bundle.js에 연결해줍니다.

html-webpack-plugin은 script 태그안에 넣은 webpack 번들 파일과 함께 html5 파일을 생성합니다.
HTMLWebpackPlugin이 index.html의 script 태그안에 컴파일된 bundle 파일을 심어줄 것입니다.
new HtmlWebpackPlugin()을 위의 예시처럼 그냥 사용하는 것도 좋지만 이렇게 템플릿을 만들어 놓으면 커스터마이징 하기 편합니다.
https://webpack.js.org/plugins/html-webpack-plugin/

babelrc

  • babel-preset-env와 babel-preset-react와 같이 preset을 사용하고 싶으면 root폴더에 .babelrc을 생성하여 사용하고자할 preset을 설정하면 됩니다.

  • plugin들을 각각의 npm dependency를 가지고 있습니다. 하지만 설치시 매번 .bablrc에 설정을 해야하므로 그 두가지를 모두 해결해줄 preset을 사용하면됩니다. preset을 설치하고 설정하므로서 preset이 가진 plugin들을 설정할 필요없이 사용할 수 있게 됩니다.

.babelrc 를 만들지 않고 webpack.config.js에 추가해도 됩니다.

  module: {
    // javascript 모듈을 생성할 규칙을 지정 (node_module을 제외한.js 파일을 babel-loader로 불러와 모듈을 생성
    rules: [
      {
        test: /\.jsx?/, // .js, .jsx로 끝나는 babel이 컴파일하게 할 모든 파일
        exclude: /node_module/, // node module 폴더는 babel 컴파일에서 제외
        use: {
          loader: 'babel-loader', // babel loader가 파이프를 통해 js 코드를 불러옴
          options: {
            presets: ['@babel/preset-env', '@babel/preset-react'],
            plugins: ['@babel/proposal-class-properties'],
          },
        },
      },
    ],
  },

바벨 - 추가내용

바벨을 사용하면 새로운 문법을 구형 자바스크립트 문법으로만 바꿔줍니다.
바벨 그 자체로는 ES2015의 새로운 객체(Promise, WeakMap)과 메소드(Array.from, Object.assign 등등)을 사용할 수 없습니다. 왜냐하면 ES2015에서 처음 생긴 거라 구형 자바스크립트에는 그에 상응하는 코드가 없거든요. 그래서 babel-polyfill을 설치해야 새로운 기능을 사용할 수 있습니다.

@babel/polyfill 패키지를 먼저 설치합니다. 6버전까지는 babel-polyfill이었습니다.

설치한후, 웹팩 설정도 바꿔줘야합니다. 레퍼런스에 나옵니다.

레퍼런스: BabelJS(바벨) - zerocho

index.html 결국에는 html에 webpack으로 합쳐진 js파일을 붙이는 방식입니다.

프로젝트 디렉토리 구조를 다음과 같이 만듭니다.
이 방식은 프로젝트 만들때마다 똑같습니다.

>node_modules
>src
    >components
        App.jsx
    index.html
    index.jsx

프로젝트 루트 디렉토리에서 src이름의 디렉토리를 만들고, 그 안에 index.html, index.jsx를 만듭니다.

/src/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Responsive Nav Bar</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

body태그 아래에 <div id="root"></div> 태그를 생성해줍니다. 여기로 ReactDOM이 붙을거니까요.

/src/index.jsx

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

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

/src/components/App.jsx

import React from "react";

class App extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, React</h1>
      </div>
    );
  }
}
export default App;

마지막으로 npm run scripts

package.json에 npm run scripts를 추가하여 webpack을 통해 프로젝트를 빌드하고 실행해봅시다.

  "scripts": {
    "start":"webpack-dev-server --mode development --open --hot",                    // webpack-dev-server, --open : 자동으로 브라우저 열어줌, --hot : hot realod 저장했을 때 자동적으로 reload 해줌
    "build":"webpack --mode production"                                              // dist 폴더에 컴파일된 파일 다 넣어줌
  },

루트 디렉토리에서
npm start 명령을 통해 react app을 실행합니다.

css적용하기

sass를 통해 css를 사용해보겠습니다. 또한 mini-css-extract-plugin를 이용해서 js안에 css를 내장시키지 않고, css링크를 따로 빼냈습니다.

우선 필요한 패키지들을 설치합니다.

$ yarn add -D node-sass css-loader sass-loader --save-dev mini-css-extract-plugin

src디렉토리와 같은 위치에 sass디렉토리를 만들어주고요. main.scss 파일을 만들어줍니다.

main.scss

$fontColor: #2196f3;
$fontSize: 52px;

.title {
  color: $fontColor;
  font-size: $fontSize;
  text-align: center;
}

webpack.config.js를 수정합니다.

  entry: ["./src/index", "./src/sass/main"], <-sass
  resolve: {
    extensions: [".jsx", ".js", ".scss"]
  },
  ...,  
  module: {
    rules: [
      {
        test: /\.jsx?/,
        exclude: /node_module/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["@babel/preset-env", "@babel/preset-react"]
          }
        }
      },
      {
        test: /\.scss$/,
        exclude: /node_module/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"] 
      } <- 새로운 rule를 추가해줍니다.
    ]
  },
plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html"
    }),
    new MiniCssExtractPlugin({ <- 이부분 넣어야하고요.
      filename: "style.css"
    })
  ]

저는 css rule에 style-loader를 넣었다가 에러가 발생해서
여길 참조해서 해결했습니다.

MiniCSSExtractPlugin extracts CSS into separate files and adds the assets to webpack's asset map. Style-Loader, on the other hand, embeds the CSS as a string into the JS bundle itself and then, at runtime, injects it into the DOM with a style tag. These methods conflict, hence the error.

해석하면 mini-css-extract-plugin은 CSS 파일을 분리하는데, style-loader는 css를 문자열로 JS 번들에 내장시킨다네요.
style-loader는 <style>태그로 만든다는 것으로 이해했습니다.

그리고 구현해보시면 컴포넌트가 많아지고 css가 중복되는 현상이 발생해요.
그럴땐 css도 지역적으로 사용하게끔 css module 방식을 이용해요
css module이 무엇인가?를 클릭하셔서 보셔도 됩니다.

사용방법은 간단합니다.
보통 css import 할때
import from "./css/component이름"; 이렇게 사용했는데요.

이제는 import styles from "./sass/App.module.scss";
이렇게 사용할 수 있습니다.

추가 : css module 적용하기

간단하게 되는줄 알았는데, 적용이 안됐더라고요:)

와 조금 헤맸는데요.ㅎㅎ
webpack.config.js를 조금 수정하였습니다.
정확히 알고 사용했다기보다는 css-loader 공식문서에서 이렇게 사용하라 해서 사용했습니다. 시간을 내서 더 자세히 봐야겠습니다.

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  entry: ["./src/index"],
  resolve: {
    extensions: [".jsx", ".js", ".scss"]
  },
  output: {
    path: path.join(__dirname, "/dist"),
    filename: "index_bundle.js"
  },
  module: {
    rules: [
      {
        test: /\.jsx?/,
        exclude: /node_module/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["@babel/preset-env", "@babel/preset-react"]
          }
        }
      },
      {
        test: /\.scss$/,
        exclude: /node_module/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: "css-loader", <-이 부분을 수정했습니다.
            options: {
              modules: {
                localIdentName: "[path][name]__[local]"
              }
            }
          },
          "sass-loader"
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html"
    }),
    new MiniCssExtractPlugin({
      filename: "style.css" <- build된 디렉토리에 style.css가 생성될 것입니다.
    })
  ]
};

css module 적용하는 법 webpack 공식 홈페이지에서 loader에서
css-loader를 찾아서 localIdentName 을 공홈에서 알려준대로 작성하면 됩니다.
https://webpack.js.org/loaders/css-loader/#localidentname

image.png

현재는 localIdentName에 맞게 css module이 naming 되고 있습니다.
모든지 찾아볼때, 블로그도 좋지만 공식문서도 같이 봐야지
덜 헤매는거 같습니다.

css module이 정말 잘 동작하는지 예제를 통해 해봤습니다.

프로젝트 구조

src
    -components
        App.jsx
        comp-a.jsx
        comp-b.jsx
    -sass
        comp-a.module.scss
        comp-b.module.scss

src/components/App.jsx

import React from "react";
import CompA from "./comp-a";
import CompB from "./comp-b";

class App extends React.Component {
  render() {
    return (
      <div>
        <CompA />
        <CompB />
      </div>
    );
  }
}
export default App;

src/components/comp-a.jsx

import React from "react";
import styles from "../sass/comp-a.module.scss";

const CompA = () => (
  <div className={styles.wrapper}>
    <h3 className={styles.title}>Component A</h3>
    <button className={styles.button} type="button">
      ...
    </button>
  </div>
);

export default CompA;

src/components/comp-b.jsx

comp-b.jsx
import React from "react";
import styles from "../sass/comp-b.module.scss";

const CompB = () => (
  <div className={styles.wrapper}>
    <h3 className={styles.title}>Component B</h3>
    <button className={styles.button} type="button">
      ...
    </button>
  </div>
);

export default CompB;

src/sass/comp-a.module.scss

.wrapper {
  color: blue;
}

.title {
  font-size: 20px;
}

.button {
  padding: 10px;
}

src/sass/comp-b.module.scss

.wrapper {
  color: blue;
}

.title {
  font-size: 20px;
}

.button {
  padding: 10px;
}

참고:
레퍼런스: React webpack 밑바닥에서 부터 설정해보기 - @pop8682
레퍼런스: Babel과 Webpack을 이용한 ES6 환경 구축
레퍼런스: React 개발 환경을 구축하면서 배우는 웹팩(Webpack) 기초 - jeff0720
레퍼런스 : 다양한 방식의 리액트 컴포넌트 스타일링 방식 CSS, Sass, CSS Module, styled-components - Velopert
레퍼런스: Using CSS Modules in React - mosh
레퍼런스: webpack-contrib/css-loader
레퍼런스: 웹팩의 기본 개념 - 김정환 블로그
레퍼런스: Webpack4 for React (리액트를 위한 웹팩4) - 1 - @padakim