자바스크립트 번들링의 핵(♚), Webpack

웹팩의 등장배경과 플러그인까지 가볍게 정리합니다.

hankyeolk

specially thanks to 김정환 개발자님.
이 내용은 저의 개인 github 레포에서 정리중인 TIL에 기록된 내용을 정리하였습니다.

웹팩의 등장 배경. 모듈과 브라우져. IE는 잘가요~✋

포스트 모듈 시스템 시절

모듈 시스템이 없던 시절에서는 HTML 파일에 로직을 담당하는 함수가 담긴 스크립트 파일을 불러와서 사용해야 했다. 전역 공간에서 모든 함수가 노출될 수 있었다. 함수의 이름이 동일하다면 충돌이 이는 것은 당연했다. (브라우져의 콘솔에서도 함수를 사용할 수 있을정도로) 그래서 ES2015부터 모듈 구문이 사용되게 되었다.

위대한 ES6+ 이후 시절

  • IIFE 방식으로 함수에 임의의 스코프를 형성했다. 함수 스코프를 만들어서 외부에서 해당 함수를 접근하지 못하도록 가둘 수 있다.
  • IIFE는 즉시 실행 함수를 말한다. 해당 함수 내부로 우리가 정의한 함수의 스코프를 제한할 수 있다. 전역에서도 동일한 함수에 대한 충돌이 없다.
const math = math || {};

(function () {
  function sum(a, b) {
    return a + b;
  }
  math.sum = sum; // math 객체에 메서드 형식으로 등록
})();

  • 모듈 방식은 흔히 알고 있는 const = require 형태의 CommonJS가 유명하다. 서버 사이드 런타임인 node.js에서 사용된다. Express.js에서 주로 많이 사용했다.
  • exports 키워드로 모듈을 생성하고 require() 함수로 이 모듈을 다른 파일에서 불러다 쓰는 형태이다.
// math.js
exports function sum(a, b) { return a + b };

// app.js
const math = require('./math.js');
math.sum(1, 2) // 3

  • ES2015의 표준 모듈 시스템은 export 키워드로 모듈을 생성하고 import 키워드로 불러오는 형태이다.
  • export한 모듈은 import { name } from './root' 형태로 불러와 사용할 수 있다.
  • export default 키워드로 모듈을 내보내면 {}를 달지 않아도 된다.

웹팩의 등장에 핵심적으로 영향을 미친 것은 브라우져에서 모듈을 지원하는지에 대한 여부였다. IE와 같은 브라우져는 그 버전에 따라 지원이 너무 되지 않는 케이스가 많았다. 그래서 우리는 웹팩을 이용하게 되었다. 엔트리 포인트를 시작으로 (번들링이 시작되는 파일) 프로덕트의 코드를 모듈로 연결하여 하나의 결과물로 아웃풋 경로에 만드는 것이 웹팩이 하는 일이다. 웹팩의 도움으로 우리는 ES6의 모듈 시스템을 적극적으로 사용할 수 있다.


Webpack cli를 활용해서 가볍게 번들링을 해보자.

npm i -D webpack webpack-cli

npm을 통해서 웹팩 터미널 도구를 설치하면 node_modules/.bin 디렉토리에 webpack-cli에서 사용할 수 있는 명령어들이 보일 것이다. node_modules/.bin/webpack --help를 입력하면 웹팩 상에서 사용할 수 있는 터미널 명령어가 나온다.


  • --mode 옵션은 웹팩이 번들링을 동작시키는 모드를 의미하는데, 개발에서 사용한다면 development를 지정해주면 된다.
  • --entry 옵션은 말 그대로 번들링이 처음 시작될 하나의 시작점을 지정해주는 것이다. app.js에서 파일 번들링이 시작된다면 --entry <경로/app.js> 형태로 명령어를 작성하면 된다.
  • -o, --output-path 옵션은 번들링 결과물이 저장될 위치를 말한다.
node_modules/.bin/webpack --mode development --entry src/app.js --output dist/main.js

위의 명렁어를 실행하면 dist/main.js 경로에 번들링 결과물이 저장되는 것이다.


webpack.config.js 파일로 웹팩 설정 경로 지정하기

webpack.config.js 파일로 웹팩의 번들링 환경을 쉽게 구축해줄 수 있다. 설정 파일이 있다면 매번 노드 모듈 폴더의 웹팩 모듈을 찾지 않고, npm 스크립트 명령어로 번들링을 진행할 수 있다.

// output 절대 경로를 만들어주기 위해서 노드 내장 모듈인 path를 불러온다.
const path = require("path");

module.exports = {
  mode: "development",
  entry: {
    main: "src/app.js",
  },
  output: {
    filename: "[name].js",
    path: path.resolve("./dist"),
  },
};
  • mode, entry에 대한 설정은 cli에서 사용한 것과 동일하다.
  • output 옵션의 경우 번들링이 될 파일 이름을 [name].js 형태로 하여, entry 옵션에서 지정해준 키값 ‘main’을 받게 되는 형식이다.
  • output 옵션에서 path의 경우 절대 경로를 사용하게되어, 노드 내장 모듈인 path의 resolve 함수를 사용해주었다.
  • package.json에 스크립트 명령어로 웹팩 번들링하는 구문을 넣어줄 수 있다. "bundle" : "webpack"


Loader : 자바스크립트에서 모듈을 불러올 수 있게 만든다.

로더는 말 그대로 불러오게 해주는 어떤 도구다. 프로젝트에 존재하는 파일을 모듈로 인식하게 해주는 것이 웹팩 로더의 역할이다. 그것이 CSS건 이미지 파일이건 모두 모듈로 인식해서 자바스크립트 파일에 불러와 사용하게 해준다. React 프로젝트를 할 때, 컴포넌트 파일에 css를 불러와 사용하는 것이 대표적인 예 일것이다. 역시나 불러올 때는 import 키워드를 사용하면 된다.

웹팩의 로더는 다른 언어로 작성된 파일을 자바스크립트 문법으로 변환해주거나 (예를 들면 타입스크립트로 작성된 코드를 자바스크립트로 변환한다던지), 적은 용량의 이미지는 html 태그의 인라인 data URL 형식으로 반환해주는 등의 역할을 한다.

webpack.config.js 설정 파일에서 로더의 동작을 module이라고 하는 객체 내부에서 정의해주면 된다.

module: {
  rules: [
    {
      test: /\.js$/, // .js확장자로 끝나는 모든 파일에 이 로더를 적용한다는 의미 (정규표현식이 온다.)
      use: ["로더 이름"], // 사용할 로더를 순서대로 반영한다.
      loader: "로더 이름", // 단독으로 사용되는 로더 규칙을 정의 할 경우
      option: {},
    },
  ];
}
  • module.rules[0].use 에 정의된 로더 배열은 뒤에서부터 앞의 순서로 로더가 동작하게 된다. 그래서 특정 번들링 순서가 중요하다면 가장 먼저 적용되어야 할 로더를 맨 뒤에 요소로 넣어줘야 한다.
  • use라는 키를 사용하지 않고 직접적으로 사용할 로더의 이름을 loader: "로더이름" 형태로 정의해줄 수 있다.
  • option 키에 로더가 동작할 때 동적으로 줄 수 있는 옵션을 정의한다. (ex. publicPath, name, limit 등)


자주 사용되는 로더

css-loader

  • 말 그대로 스타일시트(css)에 작성된 css를 자바스크립트에 모듈로 불러오게 해주는 로더
  • css-loader로 css를 모듈화하면 번들링된 파일에서 자바스크립트 코드로 변환된 것을 볼 수 있다.

style-loader

  • css-loader가 변환해준 스타일 시트를 브라우저가 이해할 수 있도록 dom에 추가해주는 로더
  • 일단 css를 자바스크립트에 모듈로 불러와 변환해주고 그 다음에 dom에 넣어주는 순서가 중요하다. use: ["style-loader", "css-loader"]

file-loader

  • 프로젝트 소스 코드에서 사용되는 모든 파일을 모듈로 사용할 수 있게 해주는 로더


dist 폴더의 번들링 결과물에 어떤 처리를 하는, 플러그인.

로더가 번들링할 파일들을 모듈화하여 번들링 시키는 동작을 도와주는 웹팩의 도구라면, 플러그인은 번들링된 파일에 특정 처리를 하는 도구다. 웹팩 플러그인을 활용하면 번들링이 된 main.js 파일을 한 줄로 만들어 난독화 시킬 수 있다. 또는 헤드의 타이틀에 개발중인지 아닌지를 표시할 수 도 있다. (html을 직접 건들지 않고)

로더가 함수의 형태였다면, 플로그인은 인스턴스를 만드는 클래스로 제작된다. new 키워드로 플러그인 인스턴스 생성자 함수를 부르면 된다. webpack.config.js 설정 파일에서 plugins : [ new 플러그인 ] 형태로 사용해주면 된다.


자주 사용되는 플러그인

웹팩 플러그인을 직접 만들어서 사용하는 경우는 극히 드물다. 웹팩 내장 플러그인을 불러와서 붙이거나 서드파티 플러그인을 설치해서 사용한다. 웹팩 내장 플러그인의 문서는 여기에서 볼 수 있다.

BannerPlugin

  • 웹팩 내장 플러그인
  • 빌드된 파일에 빌드 당시의 정보들을 기입할 수 있게 해준다.
  • 빌드 정보, 커밋 헤드 정보, 날짜 등을 입력할 수 있다.
// webpack.config.js
const webpack = require("webpack");
const childProcess = require("child_process");

module.exports = {
  plugins: [
    new webpack.BannerPlugin({
      banner: `
        Build Date: ${new Date().toLocaleString()}
        Commit Version: ${childProcess.execSync("git rev-parse --short HEAD")}
        Author: ${childProcess.execSync("git config user.name")}
      `,
    }),
  ],
};


DefinePlugin

  • 웹팩 내장 플러그인
  • 개발 환경에 따라서 달라지는 환경 변수값들을 관리해주는 플러그인이다.
  • 흔히, 개발, 프로덕션 환경에서 api 요청을 보내는 url이 다르다면 배포때마다 수정을 하는 것보다는 자동으로 처리될 수 있게 웹팩 설정을 건드리면 된다.
  • 기본적으로 process.env.NODE_ENV 값이 디폴트이다. 지정되지 않았다면 웹팩 mode 옵션의 환경이 기준이 된다.
  • 이 플러그인은 웹팩이 컴파일을 하여 빌드되는 시점에 결정된 개발 환경을 인식해서 값을 조정한다.
module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      "api.domain":
        process.env.NODE_ENV === "development"
          ? JSON.stringify("http://dev.api.domain")
          : JSON.stringify("http://product.api.domain"),
    }),
  ],
};


HtmlWebpackPlugin

  • 서드파티 플러그인 npm i -D html-webpack-plugin
  • 빌드 이후의 html 파일을 후처리 하는 과정에 사용되는 플러그인이다.
  • 번들링된 html 파일에 공백을 날려 난독화 하거나, 타이틀에 개발중인 점을 표시한다던지, 주석을 날릴 수도 있다.
const HtmlWebpackPlugin = require("html-webpack-plugin");

plugins: [
  new HtmlWebpackPlugin({
    template: "./src/index.html",
    templateParameters: {
      env: process.env.NODE_ENV === "development" ? "(개발중)" : "",
    },
    minify:
      process.env.NODE_ENV === "production"
        ? {
            collapseWhitespace: true, // 프로덕션 환경이면 공백을 날린다.
            removeComments: true, // 프로덕션 환경이면 주석을 날린다.
          }
        : false,
  }),
];


CleanWebpackPlugin

  • 서드파티 플러그인
  • 번들링된 파일이 담기는 웹팩 아웃풋 경로를 깔끔하게 유지시켜주는 플러그인이다.
  • 새롭게 빌드된 파일 이전의 내용이 남아있는 것을 방지해서 빌드때마다 dist 폴더를 새롭게 구성한다는 개념이다.
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

plugins: [new CleanWebpackPlugin()];