Bootstrap과 Webpack

Bootstrap과 Webpack (Bootstrap and Webpack)

Webpack 번들러를 사용해서 Bootstrap 프로젝트를 처음부터 구성하고 실행하는 방법을 단계별로 안내하는 가이드예요.

출처: 문서

본문

바로 결과를 보고 싶으신가요? 이 가이드의 소스 코드와 동작 데모를 twbs/examples 저장소에서 다운로드할 수 있어요. StackBlitz에서 예시를 열어 실시간 편집을 해 볼 수도 있어요.

Webpack이란? (What is Webpack?)

Webpack은 모듈과 그 의존성을 처리해서 정적 자산을 생성하는 JavaScript 모듈 번들러예요. 여러 파일과 의존성을 가진 복잡한 웹 애플리케이션 관리를 단순화해 줘요.

설정 (Setup)

Bootstrap으로 Webpack 프로젝트를 처음부터 만들 거라서, 실제로 시작하기 전에 몇 가지 사전 준비와 초기 단계가 필요해요. 이 가이드는 Node.js가 설치되어 있고 터미널에 어느 정도 익숙하다는 것을 전제로 해요.

  1. 프로젝트 폴더를 만들고 npm을 설정해요. my-project 폴더를 만들고, 대화형 질문을 피하기 위해 -y 인자로 npm을 초기화할게요.
mkdir my-project && cd my-project
npm init -y
  1. Webpack을 설치해요. 다음으로 Webpack 개발 의존성을 설치해야 해요. Webpack의 핵심인 webpack, 터미널에서 Webpack 명령을 실행하게 해 주는 webpack-cli, 로컬 개발 서버를 실행할 수 있게 해 주는 webpack-dev-server가 필요해요. 또한 index.html을 기본 dist 디렉터리 대신 src 디렉터리에 저장할 수 있게 html-webpack-plugin도 설치할게요. 이 의존성들이 개발용으로만 쓰이고 프로덕션용이 아니라는 것을 표시하기 위해 --save-dev를 사용해요.
npm i --save-dev webpack webpack-cli webpack-dev-server html-webpack-plugin
  1. Bootstrap을 설치해요. 이제 Bootstrap을 설치할 수 있어요. 드롭다운, 팝오버, 툴팁이 위치 계산에 의존하기 때문에 Popper도 함께 설치할게요. 그런 컴포넌트를 사용할 계획이 없다면 여기서 Popper는 생략해도 돼요.
npm i --save bootstrap @popperjs/core
  1. 추가 의존성을 설치해요. Webpack과 Bootstrap 외에도 Webpack으로 Bootstrap의 CSS와 JS를 제대로 import하고 번들링하려면 몇 가지 의존성이 더 필요해요. 여기에는 Sass, 일부 로더(loader), Autoprefixer가 포함돼요.
npm i --save-dev autoprefixer css-loader postcss-loader sass sass-loader style-loader

이제 필요한 의존성이 모두 설치됐으니, 프로젝트 파일을 만들고 Bootstrap을 import하는 작업을 시작할 수 있어요.

프로젝트 구조 (Project structure)

이미 my-project 폴더를 만들고 npm을 초기화했어요. 이제 src와 dist 폴더도 만들어서 프로젝트 구조를 완성할게요. my-project에서 다음을 실행하거나, 아래에 보이는 폴더와 파일 구조를 직접 만들어도 돼요.

mkdir {src,src/js,src/scss}
touch src/index.html src/js/main.js src/scss/styles.scss webpack.config.js

완료되면 전체 프로젝트가 이렇게 보일 거예요.

my-project/
├── src/
│   ├── js/
│   │   └── main.js
│   ├── scss/
│   │   └── styles.scss
│   └── index.html
├── package-lock.json
├── package.json
└── webpack.config.js

이 시점에서 모든 게 올바른 위치에 있지만, 아직 webpack.config.js를 채우지 않았기 때문에 Webpack은 동작하지 않을 거예요.

Webpack 구성 (Configure Webpack)

의존성이 설치되고 프로젝트 폴더가 코딩을 시작할 준비가 됐으니, 이제 Webpack을 구성하고 프로젝트를 로컬에서 실행할 수 있어요.

  1. 편집기에서 webpack.config.js를 열어요. 비어 있으므로 서버를 시작하려면 약간의 기본 구성(boilerplate)을 추가해야 해요. 이 구성 부분은 프로젝트의 JavaScript를 어디서 찾을지, 컴파일된 코드를 어디로 출력할지(dist), 개발 서버가 어떻게 동작해야 하는지(dist 폴더에서 가져오고 핫 리로드를 쓰는)를 Webpack에 알려 줘요.
'use strict'

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

module.exports = {
  mode: 'development',
  entry: './src/js/main.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  devServer: {
    static: path.resolve(__dirname, 'dist'),
    port: 8080,
    hot: true
  },
  plugins: [
    new HtmlWebpackPlugin({ template: './src/index.html' })
  ]
}
  1. 다음으로 src/index.html을 채워요. 이 HTML 페이지는 Webpack이 브라우저에서 로드해서, 이후 단계에서 추가할 번들된 CSS와 JS를 활용할 페이지예요. 그전에 먼저 렌더링할 내용을 주고, 이전 단계의 output JS를 포함해야 해요.
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Bootstrap w/ Webpack</title>
  </head>
  <body>
    <div class="container py-4 px-3 mx-auto">
      <h1>Hello, Bootstrap and Webpack!</h1>
      <button class="btn btn-primary">Primary button</button>
    </div>
  </body>
</html>

여기 div class="container"와 <button>으로 Bootstrap 스타일을 조금 넣어서, Webpack이 Bootstrap CSS를 로드했는지 확인할 수 있게 했어요.

  1. 이제 Webpack을 실행할 npm 스크립트가 필요해요. package.json을 열고 아래에 보이는 start 스크립트를 추가해요(테스트 스크립트는 이미 있을 거예요). 이 스크립트로 로컬 Webpack 개발 서버를 시작할 거예요. 프로젝트를 빌드하려면 아래에 보이는 build 스크립트도 추가할 수 있어요.
{
  // ...
  "scripts": {
    "start": "webpack serve",
    "build": "webpack build --mode=production",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  // ...
}
  1. 마지막으로 Webpack을 시작해요. 터미널에서 my-project 폴더에 들어가서 방금 추가한 npm 스크립트를 실행해요.
npm start

이 가이드의 다음이자 마지막 섹션에서는 Webpack 로더를 설정하고 Bootstrap의 모든 CSS와 JavaScript를 import할 거예요.

Bootstrap import (Import Bootstrap)

Webpack에 Bootstrap을 import하려면 첫 번째 섹션에서 설치한 로더가 필요해요. npm으로 설치했지만, 이제 Webpack이 그들을 사용하도록 구성해야 해요.

  1. webpack.config.js에서 로더를 설정해요. 이제 구성 파일이 완성됐고 아래 스니펫과 일치해야 해요. 새로 추가된 부분은 module 섹션뿐이에요.
'use strict'

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

module.exports = {
  mode: 'development',
  entry: './src/js/main.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  devServer: {
    static: path.resolve(__dirname, 'dist'),
    port: 8080,
    hot: true
  },
  plugins: [
    new HtmlWebpackPlugin({ template: './src/index.html' })
  ],
  module: {
    rules: [
      {
        test: /\.(scss)$/,
        use: [
          {
            // Adds CSS to the DOM by injecting a `<style>` tag
            loader: 'style-loader'
          },
          {
            // Interprets `@import` and `url()` like `import/require()` and will resolve them
            loader: 'css-loader'
          },
          {
            // Loader for webpack to process CSS with PostCSS
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: [
                  autoprefixer
                ]
              }
            }
          },
          {
            // Loads a SASS/SCSS file and compiles it to CSS
            loader: 'sass-loader',
            options: {
              sassOptions: {
                // Optional: Silence Sass deprecation warnings. See note below.
                silenceDeprecations: [
                  'mixed-decls',
                  'color-functions',
                  'global-builtin',
                  'import'
                ]
              }
            }
          }
        ]
      }
    ]
  }
}

이 모든 로더가 필요한 이유를 정리하면, style-loader는 CSS를 HTML 페이지의 <head>에 있는 <style> 요소로 주입하고, css-loader는 @import와 url() 사용을 돕고, postcss-loader는 Autoprefixer에 필요하며, sass-loader는 Sass를 사용할 수 있게 해 줘요.

참고: 최신 버전의 Dart Sass로 소스 Sass 파일을 컴파일하면 Sass 사용 중단 경고가 표시돼요. 이는 Bootstrap의 컴파일이나 사용을 막지 않아요. 우리는 장기적인 해결책을 준비 중이지만, 그동안 이 사용 중단 공지는 무시해도 돼요.

  1. 이제 Bootstrap의 CSS를 import해요. Bootstrap 소스 Sass 전체를 import하려면 src/scss/styles.scss에 다음을 추가해요.
// Import all of Bootstrap's CSS
@import "bootstrap/scss/bootstrap";

원한다면 스타일시트를 개별로 import할 수도 있어요. 자세한 내용은 Sass import 문서를 참고하세요.

  1. 다음으로 CSS를 로드하고 Bootstrap의 JavaScript를 import해요. CSS를 로드하고 Bootstrap의 모든 JS를 import하려면 src/js/main.js에 다음을 추가해요. Popper는 Bootstrap을 통해 자동으로 import돼요.
// Import our custom CSS
import '../scss/styles.scss'

// Import all of Bootstrap's JS
import * as bootstrap from 'bootstrap'

필요에 따라 JavaScript 플러그인을 개별로 import해서 번들 크기를 줄일 수도 있어요.

import Alert from 'bootstrap/js/dist/alert'

// or, specify which plugins you need:
import { Tooltip, Toast, Popover } from 'bootstrap'

Bootstrap 플러그인을 사용하는 방법에 대한 자세한 내용은 JavaScript 문서를 참고하세요.

  1. 끝났어요! 🎉 Bootstrap 소스 Sass와 JS가 모두 로드되면 로컬 개발 서버가 이렇게 보일 거예요.

이제 사용하고 싶은 Bootstrap 컴포넌트를 마음대로 추가하면 돼요. 추가 커스텀 Sass를 포함하고, 필요한 Bootstrap CSS·JS 일부만 import해서 빌드를 최적화하는 방법은 전체 Webpack 예시 프로젝트를 꼭 확인해 보세요.

프로덕션 최적화 (Production optimizations)

설정에 따라 프로덕션에서 프로젝트를 실행하는 데 유용한 몇 가지 추가 보안·속도 최적화를 구현하고 싶을 수도 있어요. 참고로 이러한 최적화는 Webpack 예시 프로젝트에는 적용되지 않으며, 직접 구현해야 해요.

CSS 추출 (Extracting CSS)

위에서 구성한 style-loader는 CSS를 번들로 편리하게 내보내서 dist/index.html에서 CSS 파일을 수동으로 로드할 필요가 없게 해 줘요. 하지만 이 접근 방식은 엄격한 Content Security Policy와는 호환되지 않을 수 있고, 번들 크기가 커져서 애플리케이션의 병목이 될 수도 있어요.

dist/index.html에서 직접 로드할 수 있도록 CSS를 분리하려면 mini-css-extract-loader Webpack 플러그인을 사용하세요.

먼저 플러그인을 설치해요.

npm install --save-dev mini-css-extract-plugin

그런 다음 Webpack 구성에서 플러그인을 인스턴스화하고 사용해요.

--- a/webpack.config.js
+++ b/webpack.config.js
@@ -3,6 +3,7 @@
 const path = require('path')
 const autoprefixer = require('autoprefixer')
 const HtmlWebpackPlugin = require('html-webpack-plugin')
+const miniCssExtractPlugin = require('mini-css-extract-plugin')

 module.exports = {
   mode: 'development',
@@ -17,7 +18,8 @@ module.exports = {
     hot: true
   },
   plugins: [
-    new HtmlWebpackPlugin({ template: './src/index.html' })
+    new HtmlWebpackPlugin({ template: './src/index.html' }),
+    new miniCssExtractPlugin()
   ],
   module: {
     rules: [
@@ -25,8 +27,8 @@ module.exports = {
         test: /\.(scss)$/,
         use: [
           {
-            // Adds CSS to the DOM by injecting a `<style>` tag
-            loader: 'style-loader'
+            // Extracts CSS for each JS file that includes CSS
+            loader: miniCssExtractPlugin.loader
           },
           {

npm run build를 다시 실행하면 src/js/main.js가 import한 모든 CSS를 담은 새 파일 dist/main.css가 생길 거예요. 지금 브라우저에서 dist/index.html을 보면 스타일이 빠져 있는데, 이제 그 스타일이 dist/main.css에 있기 때문이에요. 생성된 CSS를 dist/index.html에 이렇게 포함할 수 있어요.

--- a/dist/index.html
+++ b/dist/index.html
@@ -3,6 +3,7 @@
   <head>
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width, initial-scale=1">
+    <link rel="stylesheet" href="./main.css">
     <title>Bootstrap w/ Webpack</title>
   </head>
   <body>

SVG 파일 추출 (Extracting SVG files)

Bootstrap의 CSS는 인라인 data: URI를 통해 SVG 파일을 여러 번 참조해요. 프로젝트에 이미지의 data: URI를 차단하는 Content Security Policy를 정의했다면 이런 SVG 파일은 로드되지 않을 거예요. Webpack의 asset modules 기능을 사용해서 인라인 SVG 파일을 추출하면 이 문제를 피할 수 있어요.

인라인 SVG 파일을 추출하도록 Webpack을 이렇게 구성해요.

--- a/webpack.config.js
+++ b/webpack.config.js
@@ -23,6 +23,14 @@ module.exports = {
   },
   module: {
     rules: [
+      {
+        mimetype: 'image/svg+xml',
+        scheme: 'data',
+        type: 'asset/resource',
+        generator: {
+          filename: 'icons/[hash].svg'
+        }
+      },
       {
         test: /\.(scss)$/,
         use: [

npm run build를 다시 실행하면 SVG 파일이 dist/icons로 추출되고 CSS에서 올바르게 참조되는 걸 볼 수 있어요.


여기 잘못됐거나 오래된 내용이 보인다면 GitHub에 이슈를 열어 주세요. 문제 해결이 필요하다면 GitHub에서 검색하거나 토론을 시작하세요.

더 알아보기 (Learn more)