Babel & Webpack (23/02/27)

nazzzo·2023년 2월 27일
0

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!');

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

그리고 위 코드는 documentmodule과 같은 각 런타임 전용 코드를 사용하지 않았기 때문에
브라우저에서도, 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의 실행 흐름은 entryloaderspluginsoutput 순으로 이어집니다
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개의 댓글