웹팩 입문

소개


번들러이다.
웹팩을 이용하면 수백 개의 파일을 몇 개의 정적 파일, 그것도 아주 작은 용량으로 압축할 수 있다.
웹팩은 어떻게 이런 마법 같은 작업을 해낼까?
파일 간에 얽혀있는 import와 export 관계를 추적해서 해낸다.
간단히 설명하자면 HTML 파일에서는 <script> tag로 js 파일을 불러오고, 해당 js 파일은 다른 js 파일, css 파일, 이미지 파일 등을 import한다. import된 파일은 각기 또 다른 파일을 import해온다.

entry, output, loader, plugin, optimization 개념과 자주 쓰이는 라이브러리만 알면 어느 정도 혼자 공식 문서를 보고 따라할 수 있다.

(혼자 공부하면서 느낀 건데, webpack 5 이전과 이후는 많이 달랐다. 5 이전 버전 자료를 보고 공부하면 속터지는 경우가 많이 발생할 것이다...)

dev, prod 구별하기

어차피 개발을 하면 dev와 prod를 구별해야 하니깐 webpack-merge부터 설치하자
그 다음 루트 경로에 webpack.common.js, webpack.dev.js, webpack.prod.js 파일을 각각 만든다.
셋 다 웹팩 config 파일인데 common에는 이름 그대로 dev와 prod에서 공통적으로 쓰이는 설정을 적는다.

// ./webpack.common.js
module.exports = {
	entry: './src/index.js',
}
// ./webpack.dev.js
const common = require("./webpack.common")
const { merge } = require("webpack-merge")

module.exports = merge(common, {
	mode: "development",
})
// ./webpack.prod.js
const common = require("./webpack.common")
const { merge } = require("webpack-merge")

module.exports = merge(commmon, {
	mode: "production",
})

모드를 나눠놨으니 package.json의 scripts를 작성해보자.

webpack serve

코드를 수정할 때마다 빌드하는 과정이 귀찮으므로 dev mode일 때에는 webpack-dev-server의 도움을 받자.
이 패키지는 실제로 파일을 산출하는 게 아니라 메모리상에서 동작한다.

--config

--config 뒤에는 파일 경로명이 온다. (웹팩 config 파일명의 default값은 webpack.config.js임)

// ./package.json
"scripts": {
    "start": "webpack serve --config webpack.dev.js --open",
    "build": "webpack --config webpack.prod.js"
},

entry, output

entry는 이름 그대로 진입점이다. 보통 HTML에서 script tag로 불러올 파일을 적으면 된다.
dev 모드인지, prod 모드인지와 관계가 없기 때문에 common에 적는다.
진입점이 여러 개인 경우에는 객체로 만들고 key 자리에 이름을 적어주면 된다.

// 한 개인 경우
module.exports = {
	entry: './src/index.js',
}

// 여러 개인 경우
module.exports = {
	entry: {
    main: "./src/index.js",
    vendor: './src/vendor.js'
  },
}

output도 이름 그대로 산출물이다. 빌드를 끝낸 결과물을 어떤 이름으로 할 것인지, 어떤 경로에 저장할 것인지 등을 설정한다.

dev 모드, prod 모드 다르게 설정할 것이다.

파일명에 템플릿(예: [contenthash])을 쓴 이유는 캐싱 버스팅(cache busting) 때문이다. 예를 들어 코드를 수정하고 파일을 새로 빌드해봤자 동일한 파일명을 계속 쓰면, 사용자의 브라우저는 캐시를 이용하기 때문에 웹페이지의 수정사항이 반영되지 않는다. 이를 방지하고자 빌드할 때마다 파일명을 바뀌도록 설정해준다.

문제는 “이렇게 하면 HTML의 script tag의 src를 일일이 수정해줘야 하는 거 아닐까?”라는 의문이 든다. 다행히 이를 해결해주는 플러그인이 있다. html-webpack-plugin을 사용하면 된다. 빌드 대상인 src 폴더내 HTML 파일에는 script 태그를 적지 않는다. 빌드하고 나면 산출물인 HTML 파일의 script 태그내 src 경로를 확인해보면 변동하는 파일명에 맞게 지정된다.

// ./webpack.dev.js
const path = require("path");
const common = require("./webpack.common");
const { merge } = require("webpack-merge");

module.exports = merge(common, {
  mode: "development",
  output: {
    filename: "[name].bundle.js",
    path: path.resolve(__dirname, "dist")
  },
}
// ./webpack.prod.js
const path = require("path");
const common = require("./webpack.common");
const { merge } = require("webpack-merge");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = merge(common, {
  mode: "production",
  output: {
    filename: "[name].[contenthash].bundle.js",
    path: path.resolve(__dirname, "dist"),
    assetModuleFilename: "./imgs/[name].[hash].[ext]",
  },
	plugins: [
    new HtmlWebpackPlugin({
      template: "./src/template.html",
    })
  ],
}

loader

웹팩은 js 파일만 압축한다.

loader는 js가 아닌 다양한 정적 파일을 전처리하는 역할을 한다.

몇 가지 주의할 점이 있다.

  1. module: {rules: []} 형식으로 적는다.
  2. test에는 해당 파일의 확장자를 적는데 정규표현식으로 적는다.
  3. use에는 해당 loader를 적는다. 이때 적용순서는 “거꾸로”다.

prod 모드에서는 MiniCssExtractPlugin.loader를 사용했다. css를 별도 파일로 뽑아내야 html이 렌더링 속도가 빠르기 때문이다.

// ./webpack.common.js
module.exports = {
  entry: {
    main: "./src/index.js",
    vendor: './src/vendor.js'
  },
  module: {
    rules: [
      {
        test: /\.html$/,
        use: ["html-loader"]
      },
    ]
  }
};
// ./webpack.dev.js
const path = require("path");
const common = require("./webpack.common");
const { merge } = require("webpack-merge");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = merge(common, {
  mode: "development",
  output: {
    filename: "[name].bundle.js",
    path: path.resolve(__dirname, "dist")
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/template.html",
    })
  ],
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          "style-loader", //3. styles를 DOM에 주입
          "css-loader", //2. css를 commonjs로 변환
          "sass-loader" //1. SASS를 css로 변환
        ]
      },
    ]
  }
});
// ./webpack.prod.js
const path = require("path");
const common = require("./webpack.common");
const { merge } = require("webpack-merge");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = merge(common, {
  mode: "production",
  output: {
    filename: "[name].[contenthash].bundle.js",
    path: path.resolve(__dirname, "dist"),
    assetModuleFilename: "./imgs/[name].[hash].[ext]",
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].[contenthash].css"
    }),
  ],
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          MiniCssExtractPlugin.loader, //3. css를 별개 파일로 전환
          "css-loader", //2. css를 commonjs로 변환
          "sass-loader" //1. SASS를 css로 변환
        ]
      },
    ]
  }
});

optimization

말 그대로 최적화를 위한 설정이다. 보통 optimization 설정을 하게 되면 코드는 난독화가 되어 산출된다.

주의할 점: optimization은 덮어쓰기라고 생각해야 된다. css를 optimization하면, 기존의 난독화되어 빌드되던 HTML 파일이 더 이상 난독화가 되지 않는다. 따라서 optimization 설정에는 CSS, JS, HTML을 모두 다 적어줘야 한다.

// ./webpack.prod.js
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
	... ,
	optimization: {
    minimizer: [
      new OptimizeCssAssetsPlugin(), // css 최적화
      new TerserPlugin(), // js 최적화
      new HtmlWebpackPlugin({  // html 최적화
        template: './src/template.html',
        minify: {
          removeAttributeQuotes: true,
          collapseWhitespace: true,
          removeComments: true,
        }
      })
    ]
  },
}

plugin

빌드를 몇 번만 해도 dist 폴더가 지저분해진다.
clean-webpack-plugin은 이전 빌드 산출물을 알아서 지워준다.
이외에도 다양한 플러그인이 있다. 필요에 따라 찾아보기 바란다.

// ./webpack.prod.js
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

module.exports = {
	... ,
	plugins: [
    new CleanWebpackPlugin(),
  ],
}

좋은 웹페이지 즐겨찾기