[개발환경 구축하기] babel과 Webpack

juyeong-s·2022년 8월 27일
1

개발환경구축

목록 보기
1/2

웹 프로젝트를 새로 시작하기 위해서는 라이브러리를 받아와 개발환경을 구축하는 일부터 시작해야 한다.
처음에는 CRA(create-react-app)으로 개발환경을 구축했지만 "CRA 없이 개발환경을 구축해보기" 공부를 하기 위해 작성해본다.

1. package.json 생성

첫 번째는 package.json 파일부터 생성해야한다.

npm init -y 명령어를 사용하여 package.json를 생성하자. yarn init -y 을 사용해도 된다. 

npm init 만 입력하게 되면 사용자에게 package.json파일의 name이나 version, license등을 묻는데, -y 명령을 붙이면 npm의 package.json 기본값에 따라 파일이 생성된다.

자세한 내용은 npm init docs에서 확인하자.

npm init -y 로 생성된 파일은 아래와 같다.

// package.json

{
  "name": "babel-webpack-practice",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

이제 필요한 라이브러리를 붙여나가면 된다.


2. babel 설치

babel 라이브러리를 설치하기 위해서 npm install [설치할 라이브러리] 명령어를 입력하자.

우선 설치해야 할 라이브러리는 @babel/core, @babel/cli이다.
@babel/core 는 babel이 트랜스파일을 할 수 있게 해주는 역할이고,
@babel/cli 는 CLI(Command Line Interface, git bash나 cmd 같은 거임)에서 babel을 실행할 수 있게 해주는 역할이다.
나는 npm install --save-dev @babel/core @babel/cli 명령어를 사용했다.

--save-dev(줄여서 -D) 명령어는 이 라이브러리들을 개발단계에서만 사용하겠다는 뜻이다.

-D 명령어를 쓰지 않고 라이브러리를 설치하면 package.json의 dependencies 객체에 라이브러리의 이름이 추가된다. 이 패키지가 배포되어서 설치됐을 때 dependencies 객체에 포함된 라이브러리들이 같이 설치된다.

-D 명령어를 사용하고 라이브러리를 설치하면 devDependencies 객체에 라이브러리가 추가되고, 이 라이브러리들은 배포된 패키지에 포함되지 않는다.

babel은 상위 ES 문법이 사용된 js파일을 하위 ES 문법인 js파일로 변환시켜주는 라이브러리이기 때문에 개발단계에서만 사용한다. 그렇기 때문에 -D 명령어를 사용해서 설치하자.

// pacakage.json

{
  "name": "babel-webpack-practice",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.16.8",
    "@babel/core": "^7.16.12"
  }
}

이렇게 devDependencies에 설치한 라이브러리가 추가되었다.
하지만 이것만으로 babel을 사용할 수는 없고 plugin이 필요하다.


2-1. plugin과 preset

babel의 plugin은 실제로 코드를 변환시키는 기능을 담당한다.
babel plugin Docs를 보면 plugin이 세세하게 나뉘어져 있어 설치하는 것이 번거롭고, 내가 필요한 plugin을 하나하나 찾는 것도 힘들다.
(예를 들면, 내가 ES6의 화살표 함수 문법을 사용했다면, 이를 변환시키기 위해 @babel/plugin-transform-arrow-functions 라이브러리의 설치가 필요하고,
블럭 스코프를 사용했다면 @babel/plugin-transform-block-scoping 라이브러리의 설치가 필요하다.)

그래서 preset이 등장하게 된다.
preset은 목적에 따라 plugin들을 모아놓은 라이브러리다.

preset도 여러가지가 있는데, 그 중에서도 @babel/preset-env 라이브러리가 targets 옵션을 이용해서 어떤 브라우저에도 유연하게 대응할 수 있기 때문에 가장 자주 쓰인다.
그럼 @babel/preset-env을 설치해서 사용해보자!

npm install -D @babel/preset-env 로 설치한다. 그 다음 babel.config.js 파일을 만들어서 내가 preset을 사용하겠다는 것을 선언하자.

// babel.config.js

const presets = [
  [
    "@babel/preset-env",
    {
      targets: {
        chrome: "87",
      },
      useBuiltIns: "usage",
      corejs: "3.6.4",
    },
  ],
];

module.exports = { presets };

옵션 중, useBuiltIns는 polyfill을 사용할 것인지 아닌지를 선택할 수 있는 옵션이다.
useBuiltIns 옵션은 "usage" | "entry" | false 중 하나를 선택할 수 있고 default는 false다.

"usage"는 내 코드를 분석해서 필요한 polyfill plugin만을 적용시킨다.
"entry"는 targets 브라우저에 필요한 polyfill plugin을 모두 적용시킨다.
false는 polyfill을 사용하지 않는다는 뜻이다.

corejs 옵션은 polyfill을 사용할 때 삽입되는 corejs의 버전을 선택하는 옵션이다.
useBuiltIns 옵션이 활성화되어 있을 때 적용가능하다.
2, 3 혹은 { version: 2 | 3, proposals: boolean } 중 하나를 선택할 수 있고 default는 2다.
2는 업데이트가 중단되어서 3을 사용하는 것이 좋다.

@babel/preset-env의 옵션들과 자세한 정보는 @babel/preset-env Docs에서 확인하자.

이렇게 preset 옵션 배열을 module.exports 해주면 preset 적용이 완료된다.


3. webpack

webpack은 entry파일을 지정해서 entry파일이 의존성을 띄고 있는 모듈들을 모두 하나로 묶어내는 라이브러리다.

npm install -D webpack webpack-cli 명령어로 webpack과 webpack-cli를 설치한다.

이제 번들링할 js파일을 만든다.

// src/index.js

const pow = function (a) {
  const result = a * a;
  return result;
};

console.log(pow(3));

그리고 webpack.config.js 파일을 만들어서 옵션을 설정한다.

// webpack.config.js

const path = require("path");

module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist/js"),
    filename: "bundle.js",
  },
  devtool: "inline-source-map",
  mode: "development",
};

entry 옵션은 번들링의 시작점이 될 파일을 설정하는 옵션이다. 배열이나 객체의 형태로 여러 개의 entry를 지정할 수도 있다.

output 옵션은 번들링이 완료된 파일에 대한 옵션이다.
output.path는 번들링된 파일이 저장될 위치이고, output.filename은 번들링된 파일의 이름이다.
(위의 예시에서는 dist/js/bundle.js 경로로 파일이 생성될 것이다.)

devtool 옵션에는 여러 종류의 source-map 중 하나를 입력할 수 있다. source-map은 번들링 된 코드의 원본 위치를 알려주는 역할을 한다.
아래 사진의 맨 오른쪽을 보면 index.js:6이라고 써있는데, index.js 파일의 6번째 줄에서 콘솔에 실행됐음을 알 수 있다.
뭐 .. 이런 역할을 한다. 우리가 주로 개발하면서 많이 참고했던 것들인데, 이렇게 환경 구축에 따라 달라질 수도 있음을 느낀다.

mode옵션은 "production" | "development"  두 가지 옵션이 있다. 목적에 맞게 코드를 최적화시켜주는 역할을 한다.

이제 npx webpack 명령어로 webpack을 실행하자.

그럼 dist/js/bundle.js 이 생성된다.
bundle.js가 제대로 작동하는지 확인하기 위해 index.html 을 만들어서 bundle.js를 script로 불러온다.

// index.html

<!DOCTYPE html>
<html>
<body>
  <script src="./dist/js/bundle.js"></script>
</body>
</html>

그리고 index.html을 브라우저에 띄워 보면 (그냥 클릭해서 띄움) 위의 사진처럼 콘솔에 9가 찍히게 된다.
source-map 덕분에 이 console.log가 index.js:6에서 발생했다는 것도 알 수 있다.
여기까지가 webpack의 기본적인 기능이다. 이제 webpack의 엄청난 기능들에 대해 알아보자.


3-1. loader

webpack은 entry 파일이 의존성을 띄고 있는 모든 것을 모듈로 받아들여 묶어낸다.
내가 React를 쓰고 있고, app 컴포넌트가 css나 scss를 import하거나 혹은 TypeScript를 사용해서 js가 아닌 ts나 tsx를 사용한다면?

궁금해졌으니 css 파일을 entry로 지정해서 webpack을 사용해보자.

/* src/css/main.css */

@import url("./background.css");

/* src/css/background.css */

body {
  background-color: thistle;
}

아래는 webpack.config.js 설정이다. entry에 ./src/css/main.css 를 추가해줬다.

// webpack.config.js

const path = require("path");

module.exports = {
  entry: ["./src/js/main.js", "./src/css/main.css"],
  output: {
    path: path.resolve(__dirname, "dist/js"),
    filename: "bundle.js",
  },
  devtool: "source-map",
  mode: "development",
};

이 상태에서 webpack을 작동시키면 에러가 난다. 이 유형의 파일을 처리하려면 loader가 필요할 것이며, 현재 이 파일에 대한 loader가 설정되어있지 않다는 설명과 함께.


3-1-1. css loader

그럼 css에는 어떤 loader가 필요할까? css를 처리하기 위해서는 css-loader와 style-loader가 필요하다.

npm install -D css-loader style-loader 로 설치한 뒤 적용해보자.

// webpack.config.js

const path = require("path");

module.exports = {
  entry: ["./src/js/main.js", "./src/css/main.css"],
  output: {
    path: path.resolve(__dirname, "dist/js"),
    filename: "bundle.js",
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
  devtool: "source-map",
  mode: "development",
};

loader를 적용하려면 module 객체안에 rules 배열을 두어야한다. rules 배열 안에는 객체들이 들어있고, 객체는 필수적으로 test와 use를 가져야 한다.
test는 loader를 적용시킬 파일 확장자의 정규표현식이고,
use는 test 확장자명을 가진 파일에 적용시킬 loader다.
use는 배열을 사용해서 하나 이상의 loader를 적용시킬 수 있고, 배열의 오른쪽에 있는 loader부터 적용된다.
결국, 이 옵션은 .css로 끝나는 파일에 1. css-loader 2. style-loader 순서로 loader를 적용시키겠다는 뜻이다.
css-loader는 css 스타일시트를 JS로 변환시켜주고, style-loader는 JS로 변환된 css 스타일시트를 DOM에 동적으로 추가시켜준다.

이제 다시 webpack을 실행시킨다. (npx webpack)
그럼 배경화면 색상이 바뀐 것을 확인할 수 있다.


3-1-2. babel loader

이제 webpack으로 묶어낼 때 js에 babel을 적용시키기 위해 babel-loader를 사용하겠다.

npm install -D babel-loader 명령어를 사용하고 webpack.config.js에서 babel-loader를 적용시키자.

// webpack.config.js

const path = require("path");

module.exports = {
  entry: ["./src/index.js", "./src/css/main.css"],
  output: {
    path: path.resolve(__dirname, "dist/js"),
    filename: "bundle.js",
  },
  module: {
    rules: [{
        test: /\.js$/,
        use: [
            {
                loader: "babel-loader",
                options: {
                    presets: [
                        [
                            "@babel/preset-env",
                            {
                                useBuiltIns: "usage",
                                corejs: "3.6.4",
                                targets: {
                                chrome: "87",
                                },
                            },
                        ],
                    ],
                },
            },
        ],
        exclude: /node_modules/,
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
  devtool: "inline-source-map",
  mode: "development",
};

rules 배열에 새 객체가 생겼다.
1. 먼저 test는 .js 파일 확장자를 가진 파일들을 대상으로 하고 있다.
2. use는 css-loader를 사용할 때와는 다른 형태다. use 배열안에 객체가 들어있다. 이것은 babel-loader의 option을 설정하기 위해서다.

이 객체를 살펴보면,
3. loader에는 적용할 loader인 "babel-loader"가 들어있다.
4. options는 babel-loader의 옵션을 담는 객체다. presets 배열에 사용할 preset들을 입력한다.
나는 @babel/preset-env를 사용할 것이다.
이외에도 plugin과 preset-env의 polyfill 기능도 설정할 수도 있다.
5. exclude는 webpack의 번들에서 제외할 폴더나 파일이다.
node_modules 폴더에는 수 십 수 백개의 라이브러리들이 들어있기 때문에 보통 node_modules는 .js 확장자에 적용시킬 loader에서 제외시킨다.

webpack loaders Docs에서 더 자세히 살펴볼 수 있다.


3-2. plugin

plugin은 webpack의 편의성을 높여준다. 그 중에서 편리하다고 생각되는 것 몇 개만 적용시켜 보자.

3-2-1. HtmlWebpackPlugin

HtmlWebpackPlugin은 webpack을 실행한 뒤 번들링 된 js파일을 포함한 html을 자동으로 생성해준다.
이전에 bundle.js 파일을 webpack으로 생성한 뒤, index.html 파일을 직접 만들어서 bundle.js를 포함시켰다. 이 과정을 자동으로 해주는 것이다.

npm install -D html-webpack-plugin 명령어로 설치한 뒤 적용시킨다.

// webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: ["./src/index.js", "./src/css/main.css"],
  output: {
    path: path.resolve(__dirname, "dist/js"),
    filename: "bundle.js",
  },
  module: {
    rules: [{
        test: /\.js$/,
        use: [
            {
                loader: "babel-loader",
                options: {
                    presets: [
                        [
                            "@babel/preset-env",
                            {
                                useBuiltIns: "usage",
                                corejs: "3.6.4",
                                targets: {
                                chrome: "87",
                                },
                            },
                        ],
                    ],
                },
            },
        ],
        exclude: /node_modules/,
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
  plugins: [new HtmlWebpackPlugin()],
  devtool: "inline-source-map",
  mode: "development",
};

두번째 줄의

const HtmlWebpackPlugin \= require("html-webpack-plugin");

과 plugins 를 추가해주었다.

plugins: [new HtmlWebpackPlugin()],

이제 또 npx webpack을 실행시켜주면, output 경로 안에 index.htm 이 자동으로 생성된다.
HtmlWebpackPlugin Docs에서 옵션에 대해 자세히 볼 수 있다.

3-2-2 CleanWebpackPlugin

webpack을 사용하다 보면 이전에 번들링했던 결과물이 계속 남아있어 불편한 때가 있다.
CleanWebpacPlugin은 새로 번들링했을 때 이전에 번들링한 결과물을 없애준다.


3-3. WebpackDevServer

방금 webpack으로 실습을 하면서 계속 반복중인 것이 있다.

  1. webpack.config.js 설정 수정
  2. webpack을 실행
  3. 번들된 js파일을 index.html에 적용시킨 후 확인

이 과정을 계속 반복중이다.
불편함을 개선하기 위해 webpack 자체에서 실시간으로 코드의 변경을 반영해주는 WebpackDevServer라는 기능을 제공하고 있다.

npm install -D webpack-dev-server 명령어로 webpack-dev-server 를 설치 후, npx webpack serve 명령어로 webpack을 실행하면 번들된 js 파일이 자동으로 index.html에 삽입되어 localhost에서 실행된다.

WebpackDevServer Docs에서 자세한 설명을 보도록 하자!

여기까지 babel, webpack 에 대해 알아봤다.
정말 개발은 쉽지않다..ㅎ 이런거 하나 하나 공부할게 너무 많은 것 같다.

profile
frontend developer

0개의 댓글