Babel & Webpack (23/02/27)

nazzzo·2023년 2월 27일

1. Babel

Babel은 자바스크립트 컴파일러입니다

Babel은 JSX 문법을 사용할 수 있게끔 만들어주는 툴로써 사용됩니다
그런데 왜 인터프리터 언어에 별도의 컴파일러가 필요한 걸까요?

우선 그 이유에 대해 알아보겠습니다



1-1. ES6 → ES5


자바스크립트 문법은 꾸준히 진화해왔습니다

// ES5 -> ES6
var -> let, const...

그런데 신문법으로 작성된 코드의 경우 브라우저(크롬 버전)에 따라서 실행이 되지 않을 수 있습니다

그렇다면 let, const나 화살표 함수 등을 읽을 수 없는 환경에서 신문법으로 작성한 코드를 실행하려면 어떻게 해야 할까요
단순히 var, function 등으로 코드를 수정하는 것이 아닌, 호이스팅과 같은 순서상의 이슈까지 모두 고려해야 합니다

이 때 babel을 이용하면 손쉽게 신문법을 과거 문법으로 변환할 수 있습니다


확인을 위해 babel을 설치 & 사용해보겠습니다


1. Babel 설치


npm init -y
npm install @babel/core @babel/cli @babel/preset-env

npx babel
// = node ./node_../..../index.js

npm을 통해 설치를 마치면 npx 명령어로 실행할 수 있습니다



2. Babel 환경 설정

.babelrc 파일을 생성합니다
rc(Run Commands / Run Controller)는 환경파일을 의미합니다

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

(위 프리셋은 ES6 → ES5 변환을 가능하게 해주는 설정입니다)


3. ES6 문법으로 코드 작성

ES6 문법인 스프레드 연산자를 사용해서 예제 코드를 작성합니다

const fn = (message) => {
    const arr = [1,2,3,4,5]
    const arr2 = [6,7,8,9,0]

    const arr3 = [...arr, ...arr2]
    console.log(...arr3, message)
}

fn('hello world!')

4. Babel 실행 (컴파일)

npx babel [바꿀 파일] --out-file [보낼 파일]
npx babel example.js --out-file dist/example.js

npx babel...의 실행결과는 다음과 같습니다

"use strict";

function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
var fn = function fn(message) {
  var _console;
  var arr = [1, 2, 3, 4, 5];
  var arr2 = [6, 7, 8, 9, 0];
  var arr3 = [].concat(arr, arr2);
  (_console = console).log.apply(_console, _toConsumableArray(arr3).concat([message]));
};
fn('hello world!');

이제 구 버전의 브라우저에서도 사용할 수 있는 코드가 되었습니다

그리고 위 코드는 document나 module과 같은 각 런타임 전용 코드를 사용하지 않았기 때문에
브라우저에서도, NodeJs에서도 사용할 수 있습니다



1-2. import → require


자바스크립트는 런타입 환경에 따라 사용하는 코드가 일부분 달라집니다

예를 들어, 브라우저의 자바스크립트의 전역객체는 window입니다
이 전역객체에 내장된 document 객체 등은 브라우저 환경에서만 사용할 수 있습니다

반면 NodeJS의 자바스크립트의 전역객체는 global이며,
그 안에 내장된 module 객체나 require와 같은 함수들은 브라우저에서 사용할 수 없습니다

이렇게 모듈을 작성하고 로드하는데 사용되는 자바스크립트의 표준을 CommonJS라고 합니다

CommonJS의 모듈 정의 규칙

  • 모듈은 module.exports 객체를 통해 공개됩니다
  • 다른 모듈을 가져오려면 require() 함수를 사용합니다
  • 모듈을 정의하려면 exports 객체를 사용합니다

그런데 과거(ES6 이전) window 객체 안에는 module 객체가 없었지만
지금의 자바스크립트에는 모듈을 내보내고 불러오는 데 사용할 수 있는 import 구문이 생겼습니다

  • Broswer(ES6) : exports, require
  • NodeJS(CommonJS) : export, import

하지만 여전히 런타임 환경에 따라 사용하는 모듈이 다르기 때문에 우리는 babel과 같은 컴파일러를 사용합니다
예를 들어 노드 환경에서 import문으로 코드를 작성 후 babel을 통해 컴파일 과정을 거치면,
해당 코드가 NodeJS가 이해할 수 있는 코드로 변환됩니다
(다만 이것은 코드를 각각의 환경이 이해할 수 있는 언어로 변환해준다는 뜻이지, 코드를 실행시켜준다는 뜻은 아닙니다)



1. 관련 패키지 설치

npm install @ babel/core @babel/cli
npm install @babel/plugin-transform-modules-commonjs

2. Babel 환경 설정

아까와 같이 .babelrc 파일을 생성합니다

{
    "plugins" : ["@babel/plugin-transform-modules-commonjs"]
}

3. import문으로 코드 작성

[server.js]

import express from 'express'
// const express = require('express')

const app = express()

app.listen(3000, () => {
    console.log('listening on port 3000')
})

4. Babel 실행

npx babel server.js --out-file dist/server.js

컴파일 결과 코드가 다음과 같이 변환되는 것을 확인할 수 있습니다

"use strict";

var _express = _interopRequireDefault(require("express"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

const app = (0, _express.default)();
app.listen(3000, () => {
  console.log('listening on port 3000');
});

터미널에서 server.js 파일을 실행시키니 console.log가 잘 찍히네요

node server
// listening on port 3000



1-3. JSX → JS


간단한 리액트 컴포넌트 코드입니다

const React = require('react')
const ReactDOM = require('react-dom')

class App extends React.Component {
    render() {
        return (
            <div>
          		<input value="hello world" />
                <ul>
                    <li>
                        <a hef="#">menu</a>
                    </li>
                </ul>
            </div>
        )
    }
}

const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(<App />)

↑ 브라우저든 NodeJS든, 자바스크립트는 꺽쇠(<)를 읽는 순간 구문 에러를 발생시킵니다

이제 Babel을 써서 위 코드를 변환해보겠습니다


1. 관련 패키지 설치

리액트에 관한 프리셋을 설치합니다
(*프리셋은 Babel 플러그인과 설정을 미리 정의된 패키지로 묶어놓은 것입니다)

npm init -y
npm install react
npm install react-dom
npm install @babel/core @babel/cli @babel/preset-react

2. babel 환경 설정

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

3. babel 실행

npx babel app.js --out-file dist/app.js

컴파일 결과는 아래와 같습니다

const React = require('react');
const ReactDOM = require('react-dom');
class App extends React.Component {
  render() {
    return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("input", {
      value: "hello world"
    }), /*#__PURE__*/React.createElement("ul", null, /*#__PURE__*/React.createElement("li", null, /*#__PURE__*/React.createElement("a", {
      hef: "#"
    }, "menu"))));
  }
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render( /*#__PURE__*/React.createElement(App, null));

하지만 여전히 이 코드는 실행할 수 없습니다

브라우저에는 require 함수가 존재하지 않기 때문입니다
또한 document 객체를 사용하고 있으니 NodeJS에서 실행하는 것도 불가능합니다

이에 대해서는 몇 가지 해결책이 있을 수 있겠지만,
빠른 문제 해결을 위해 Webpack의 개념에 대해 알아보겠습니다



2. Webpack


브라우저에서는 require 함수를 사용할 수 없고,
NodeJS에서 document 객체를 사용할 수 없는 문제를 해결하려면 별도의 모듈 시스템이 필요합니다
Webpack은 자바스크립트 애플리케이션에서 필요한 모듈들을 합쳐서 하나의 자바스크립트 파일로 만들어주는 도구입니다
(*Webpack 외에도 다양한 모듈 번들러가 존재합니다)

Webpack을 사용하면 require 함수 대신 import 문으로 모듈을 불러올 수 있고,
document 객체와 같은 브라우저 전용 기능을 사용하더라도,
target 속성을 이용하여 브라우저 환경에서 실행할 수 있는 코드로 번들링할 수 있습니다

공식문서에서는 Webpack에 대해 이렇게 설명하고 있습니다

웹 애플리케이션을 구성하는 다양한 자원들을 하나의 파일로 번들링하고,
그 결과로 웹페이지를 로딩하는데 필요한 최소한의 파일을 제공합니다
이를 통해서 로딩속도를 개선할 수 있습니다

  • 모듈 : 모듈이란 프로그램을 구성하는 구성요소로, 관련된 데이터와 함수들을 하나로 묶은 단위
    (controller, service, repository)
  • 번들러 : 의존성이 있는 모듈 코드를 하나의 파일로 묶어주는 도구
    (controller, service, repository → module.js)

<App />
<Comment />
<CommentList />
<CommentForm />
<CommentItem />

↓

<script src='./bundle.js'></script>

이와 같이 Webpack을 이용해서 파일을 하나로 묶으면 여러 파일의 연결로 인한 리소스 손실을 최소화할 수 있습니다

Webpack의 속성

  • entry : 엔트리포인트를 지정합니다 (어느 파일을 기점으로 파일을 불러올 것인지 ...server.js)
  • output : 어떤 파일로 내보낼 것인지 지정합니다
  • loaders : 모듈을 번들링할 때, 특정한 파일들을 불러와서 처리하는 역할을 합니다
  • plugins : Webpack이 번들링하는 동안, 추가적인 작업을 수행할 수 있도록 도와주는 도구입니다
    (예를 들어 HTMLWebpackPlugin은 HTML 파일을 생성하고, CSS 파일을 추출하는 등의 작업을 수행)

Webpack의 실행 흐름은 entry → loaders → plugins → output 순으로 이어집니다
entry를 통해 모듈들을 로딩하고, 로딩된 모듈들에 대해 설정된 loaders를 통해 변환 및 처리하고,
이후 plugins를 통해 추가적인 처리를 수행한 뒤, output으로 결과물을 생성합니다


2-1. Webpack 기본 실행


1. 관련 패키지 설치

npm init -y
npm install webpack webpack-cli

2. 프로젝트 구성


3. webpack 설정 파일

webpack.config.js라는 이름으로 파일을 생성한 뒤 해당 파일을 root 디렉토리 안에 넣습니다

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        // 절대경로로 써야 합니다
        path: path.join(__dirname, 'dist')
    },
}

4. 예제코드 작성

[index.js]

const React = require('react');
const ReactDOM = require('react-dom');
const home = require("./pages/home.js")

console.log(home.name)
console.log(`react :`, React)
console.log(ReactDOM)


const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(React.createElement("div", null, "hello world"))

pages/home.js

module.exports = {
  name: "web7722",
}

5. Webpack 실행

npx webpack
cd dist
node bundle.js

// web7722
// react : { Children : {...}}



2-2. Webpack Loaders


Webpack 설정 파일에 있는 Loaders 설정 내용은 다양한 유형의 파일을 모듈화할 수 있습니다

CSS 파일도 모듈화하여 번들링할 수 있습니다


1. 관련 패키지 설치

npm init -y
npm install webpack-cli css-loader style-loader

2. 프로젝트 구성

[src/index.js]

import './index.css'

console.log("hello world")

[src/index.css]

* {
	margin: 0;
    padding: 0;
    background: red;
}

3. webpack.config.js 설정

const path = require("path");

module.exports = {
  entry: "./src/index.js",
  module: {
    rules: [
        {
            // 정규식을 사용합니다
            test : /\.css$/,
            use: ["style-loader", "css-loader"]
        }
    ]
  },
  output: {
    filename: "bundle.js",
    path: path.join(__dirname, "dist"),
  },
};

4. Webpack 실행

html 파일을 작성해서 bundle.js를 불러오면 css까지 함께 실행되는 것을 확인할 수 있습니다

<body>
    <script src="bundle.js"></script>
</body>



2-3. Webpack Plugin


1. 관련 패키지 설지

npm init -y
npm install react react-dom
npm install webpack webpack-cli html-webpack-plugin

2. 디렉토리 구성

[src/index.html]

<body>
    <div id="root"></div>
</body>

[src/index.js]

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

const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(React.createElement(App));

[src/app.js]

import React from 'react'

class App extends React.Component {
    render() {
        return React.createElement("div", null, "hello world")
    }
}

export default App

3. webpack.config.js 설정

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

module.exports = {
    mode: "development",
    entry: './src/index.js',
    plugins: [
        new HtmlWebpackPlugin({
            // = entry
            template: 'src/index.html',
            // = output
            filename: "index.html",
        })
    ],
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist')
    },
}

4. Webpack 실행

npx webpack

실행 결과 dist 디렉토리 안에 index.html과 bundle.js 파일이 생성됩니다


+) ↓ 사진과 같은 오류는 환경설정에서 모드 설정을 따로 하지 않았을 경우에 발생합니다. 번들링에는 문제 X



3. Babel + Webpack


배운 내용을 정리하면서 Babel과 Webpack을 함께 사용해보겠습니다


1. 관련 패키지를 설치합니다

npm install @babel/preset-env @babel/preset-react
npm install @babel/core babel-loader
npm install react react-dom
npm install webpack webpack-cli html-webpack-plugin
npm install webpack-cli css-loader style-loader

2. 루트 디렉토리에 .babelrc 파일을 생성합니다

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

3. webpack.config.js를 설정합니다

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

module.exports = {
    mode: "development",
    entry: './src/index.js',
    module: {
        rules: [
            {
                test: /\.(js|jsx|css)$/,
                // 번들에서 제외할 내용을 지정합니다
                exclude: /node_modules/,
                // 어떤 loader를 사용할 것인지를 지정합니다
                use: ["babel-loader","style-loader", "css-loader"]
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            // = entry
            template: 'src/index.html',
            // = output
            filename: "index.html",
        })
    ],
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist')
    },
}



4. 정리


npx create-react-app project

사실 오늘 배운 내용은 개념에 대한 이해를 위한 것이지, 실제로 위 코드를 직접 타이핑해서 작성할 일은 없을지도 모릅니다

하지만 웹팩의 개념과 코드의 흐름만큼은 꼭 이해해둡시다!


개발은 노드JS 환경에서
프론트에 관한 모든 코드의 실행은 브라우저가


0개의 댓글