[Webpack] 궁금해서 혼자 정리하는 웹팩

Dan·2022년 11월 14일
1

웹팩

목록 보기
1/8
post-thumbnail

모듈번들러는 무엇이더냐


파일을 여러개로 분리하는 과정을 모듈화라고 부르고, 파일을 모듈이라고 한다.

자바스크립트로 개발을 하다보면 코드의 재사용 및 유지보수 측면에서 모듈화해서 작업할 떄가 많다. 이렇게 모듈화 된 파일들을 브라우저에 보여주기 위해선 모듈마다 하나하나 서버에 요청해야하고 그만큼 네트워크 비용이 증가하고 페이지 로딩시간이 길어진다.

개발할 땐 여러개의 모듈로 나눠서 개발하고 웹서버에 배포하기 전에는 하나의 모듈로 묶는 이 과정을 번들링 이라고 한다. 그리고 모듈을 번들링 해주는 역할을 하는게 웹팩같은 모듈번들러이다.

웹팩 직접 사용해보며 익히기


웹팩 튜토리얼 을 보면서 따라 해보자

Getting Started


프로젝트 폴더를 생성하고 npm을 초기화와 동시에 webpack을 로컬에 설치해준다.

npm init -y
npm install webpack webpack-cli --save-dev

  • 프로젝트의 최상단에 webpack.config.js 파일을 생성해주고 아래와 같은 코드를 넣어보자.
const path = require('path');

module.exports = {
  // index.js를 통해서
  entry: './src/index.js',
  // 빌드시 dist 다이렉토리에 main.js라는 파일 이름으로 번들링을 해준다.
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
};
  • 최종적으로 프로젝트 구조는 아래와 같아야 한다.
webpack-demo
|- package.json
|- package-lock.json
|- webpack.config.js
|- /dist
  |- main.js
  |- index.html
|- /src
  |- index.js
|- /node_modules

Asset Management


이제 기본적인 웹팩의 번들링 과정을 살펴 보았으니 이미지와 같은 다른 Asset들을 통합하고 어떻게 처리되는지 알아 보자.

webpack과 같은 도구는 모든 의존성을 동적으로 번들합니다. 이것이 좋은 이유는 이제 모든 모듈이 의존성을 명확하게 명시하고 사용하지 않는 모듈을 번들에서 제외할 수 있기 때문입니다.

CSS

먼저 자바스크립트 모듈 내에서 CSS파일을 import 위해 style-loader 와 css-loader을 설치하자.

npm install --save-dev style-loader css-loader

그 다음, webpack.config.js 파일에 아래와 같이 module 설정을 추가해주자.

 const path = require('path');

 module.exports = {
   entry: './src/index.js',
   output: {
     filename: 'bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
  // css 모듈 설정 추가
  module: {
    rules: [
      {
        test: /\.css$/i,
>         use: ['style-loader', 'css-loader'],
      },
    ],
  },
 };

webpack은 정규 표현식을 사용하여 어떤 파일을 찾아 특정 로더에 전달해야 하는지 알아냅니다. 이 경우 .css로 끝나는 모든 파일은 style-loader와 css-loader에 전달됩니다.

이제 CSS를 index.js파일에서 import해서 빌드를 해보면 정상적으로 적용되는 모습을 볼 수 있을 것이다.

Images

이제 Image를 사용하기 위해서 webpack5부터 내장된 AssetMoudle를 통해 쉽게 관리해보자

const path = require('path');

module.exports = {
    // 빌드 경로 및 파일이름 지정
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist'),
    },
    module: {
        rules: [
            // css 모듈
            {
                test: /\.css$/i,
                use: ['style-loader', 'css-loader']
            },
            // 이미지 처리
            {
                test: /\.(png|svg|jpg|jpeg|gif)$/i,
                type: 'asset/resource'
            }
        ]
    }
};

이제 import myImage from "./img.png" 를 사용하면 해당 이미지는 빌드 될 때 output 다이렉토리에 추가되고 myImage라는 변수가 이미지의 최종 URL를 포함하게 된다. css-loader나 html-loader도 해당 이미지를 로컬 파일임을 인식하고 최종경로를 output으로 변경한다.

Fonts

에셋 모듈 같은 경우 로드한 모든 파일을 가져와 빌드 디렉터리로 내보기 때문에 폰트를 포함한 모든 종류의 파일에 사용 할 수 있다.

const path = require('path');

module.exports = {
    // 빌드 경로 및 파일이름 지정
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist'),
    },
    module: {
        rules: [
            // css 모듈
            {
                test: /\.css$/i,
                use: ['style-loader', 'css-loader']
            },
            // 이미지 처리
            {
                test: /\.(png|svg|jpg|jpeg|gif)$/i,
                type: 'asset/resource'
            },
          	// 폰트 처리
          	{
        		test: /\.(woff|woff2|eot|ttf|otf)$/i,
        		type: 'asset/resource',
      		},
        ]
    }
};

로더를 설정하고 폰트가 맞는 위치에 있으면 @font-face를 통해 적용할 수 있다.

@font-face {
  font-family: 'MyFont';
  src: url('./my-font.woff2') format('woff2'),
    url('./my-font.woff') format('woff');
  font-weight: 600;
  font-style: normal;
}

 .hello {
   color: red;
  font-family: 'MyFont';
   background: url('./icon.png');
 }

Data

또 다른 자주 쓰이는 에셋은 JSON, CSV, TSV 및 XML과 같은 데이터들이 있다. 여기서 JSON은 기본으로 지원되며 CSV,TSV,XML을 가져오기 위해 csv-loader 및 xml-loader을 설정해서 사용해보자.

npm install --save-dev csv-loader xml-loader

const path = require('path');

module.exports = {
    // 빌드 경로 및 파일이름 지정
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist'),
    },
    module: {
        rules: [
            // css 모듈
            {
                test: /\.css$/i,
                use: ['style-loader', 'css-loader']
            },
            // 이미지 처리
            {
                test: /\.(png|svg|jpg|jpeg|gif)$/i,
                type: 'asset/resource'
            },
            // font 처리
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/i,
                type: 'asset/resource',
            },
            // csv, tsv 처리
            {
                test: /\.(csv|tsv)$/i,
                use: ['csv-loader'],
            },
            // xml 처리
            {
                test: /\.xml$/i,
                use: ['xml-loader'],
            },
        ]
    }
};

Output Management


지금까지는 모든 애셋을 index.html 파일에 수동으로 포함했다. 하지만 애플리케이션이 커지면 index.html 파일을 수동으로 관리하기 어려워지는데 이를 쉽게 관리할 수 있게하는 플러그인들이 몇가지 존재한다.

Preparation

아래와 같이 프로젝트를 수정해보자.

webpack-demo
|- package.json
|- package-lock.json
|- webpack.config.js
|- /dist
|- /src
|- index.js
|- print.js <-- 추가
|- /node_modules

  • src/print.js
export default function printMe() {
  console.log('I get called from print.js!');
}
  • src/index.js
 import _ from 'lodash';
++import printMe from './print.js';

 function component() {
   const element = document.createElement('div');
   ++const btn = document.createElement('button'); 

   element.innerHTML = _.join(['Hello', 'webpack'], ' ');

  ++btn.innerHTML = 'Click me and check the console!';
  ++btn.onclick = printMe;

  ++element.appendChild(btn);

   return element;
 }

 document.body.appendChild(component());

그다음 웹픽이 엔트리를 분할할 수 있도록 dist/index.html를 수정해보자

 <!DOCTYPE html>
 <html>
   <head>
     <meta charset="utf-8" />
    -- <title>Asset Management</title>
    ++ <title>Output Management</title>
    ++ <script src="./print.bundle.js"></script>
   </head>
   <body>
    -- <script src="bundle.js"></script>
    ++ <script src="./index.bundle.js"></script>
   </body>
 </html>
  • webpack.config.js
 const path = require('path');

 module.exports = {
  -- entry: './src/index.js',
  ++ entry: {
    index: './src/index.js',
    print: './src/print.js',
  },
   output: {
    -- filename: 'bundle.js',
    ++ filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
 };

npm run build 를 실행하면 웹팩이 print.bundle.js 과 index.bundle.js를 생성하는 것을 볼 수 있을 것이다. 하지만 만약 엔트리 포인트 중 하나의 이름이 변경되거나 추가되면 번들은 빌드에서 이름이 변경되지만 index.html은 여전히 예전 이름을 참조하게 될 것이다. HtmlWebpackPlugin 사용하면 이와 같은 문제를 해결 할 수 있다.

npm install --save-dev html-webpack-plugin

  • webpack.config.js
 const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

 module.exports = {
   entry: {
     index: './src/index.js',
     print: './src/print.js',
   },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'Output Management',
    }),
  ],
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
 };

빌드를 해서 index.html을 확인해보면 HtmlWebpackPlugin이 완전히 새로운 파일을 생성하면서 모든 번들을 자동적으로 추가해준 것을 확인 할 수 있을 것이다.

Cleaning up the /dist folder

/dist 폴더가 상당히 복잡하고 더러워지는 것을 방지하기 위해 새로운 빌드때마다 이전 빌드를 삭제해주는 옵션인 output.clean을 넣어보자

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

 module.exports = {
   entry: {
     index: './src/index.js',
     print: './src/print.js',
   },
   plugins: [
     new HtmlWebpackPlugin({
       title: 'Output Management',
     }),
   ],
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
   ++clean: true,
   },
 };

Development


이 세팅은 우리가 실제로 개발할 때 좀 개발에 좀 더 편리할 수 있도록 하는 개발자 모드 환결 세팅이다.

오직 개발을 위한 세팅이니 프로덕션에서 사용하는 것은 피해야 한다.

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

 module.exports = {
   // 개발모드
  ++mode: 'development',
   entry: {
     index: './src/index.js',
     print: './src/print.js',
   },
   plugins: [
     new HtmlWebpackPlugin({
      --title: 'Output Management',
      ++title: 'Development',
     }),
   ],
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
     clean: true,
   },
 };

Using Source maps

개발을 하다보면 디버깅을 필시 하게된다. 이때 번들러를 사용하게되면 모든 모듈들이 하나의 번들로 묶여 bundle.js라는 한 파일로 소스파일 오류의 경로가 지정된다. 그렇기에 오류나 경고를 쉽게 추적할 수 없게 되는데 이를 해결하기 위해 컴파일된 코드를 원래 소스로 매핑하게 해주는 소스맵이 제공 된다.

방법은 아주 간단하게 아래 코드 한줄만 webpack.config.js 에 추가해주면 된다.

devtool: 'inline-source-map'

Choosing a Development Tool

코드를 컴파일 할떄마다 npm run build 커맨드를 수동을 실행하는게 굉장히 번거롭다. 그래서 웹팩에는 코드가 변경될 떄마다 자동으로 컴파일해주는 몇가지 옵션이 존재한다.

  1. webpack의 watch 모드
  2. webpack-dev-server
  3. webpack-dev-middleware

Using Watch Mode

webpack이 디펜던시 그래프 내의 모든 파일의 변경사항을 '감시'하도록 지시할 수 있다. 이런 파일 중 하나가 업데이트 되면 코드를 다시 컴파일하게 하는 옵션이 webpack의 'watch'모드 이다.

아래와 같이 npm 스크립트에 추가하여 사용할 수 있다. 유일한 단점이라면 변경사항을 확인하기 위해서는 브라우저를 새로고침해야한다는 것 이다.

  • package.json
 {
   "name": "webpack-demo",
   "version": "1.0.0",
   "description": "",
   "private": true,
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1",
    ++"watch": "webpack --watch",
     "build": "webpack"
   },
   "keywords": [],
   "author": "",
   "license": "ISC",
   "devDependencies": {
     "html-webpack-plugin": "^4.5.0",
     "webpack": "^5.4.0",
     "webpack-cli": "^4.2.0"
   },
   "dependencies": {
     "lodash": "^4.17.20"
   }
 }

커맨드라인에서 npm run watch를 통해 실행시킬 수 있다.

Using webpack-dev-server

webpack-dev-server는 간단한 웹서버와 실시간 다시 로딩기능을 제공한다.

npm install --save-dev webpack-dev-server

설정 파잉을 변경하여 개발 서버에 파일을 찾을 위치를 알려준다.

  • webpack.config.js
 const path = require('path');
 const HtmlWebpackPlugin = require('html-webpack-plugin');

 module.exports = {
   mode: 'development',
   entry: {
     index: './src/index.js',
     print: './src/print.js',
   },
   devtool: 'inline-source-map',
  ++devServer: {
    ++static: './dist',
  ++},
   plugins: [
     new HtmlWebpackPlugin({
       title: 'Development',
     }),
   ],
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
     clean: true,
   },
  ++optimization: {
    ++runtimeChunk: 'single',
  ++},
 };
  • package.json
 {
   "name": "webpack-demo",
   "version": "1.0.0",
   "description": "",
   "private": true,
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1",
     "watch": "webpack --watch",
  ++ "start": "webpack serve --open",
     "build": "webpack"
   },
   "keywords": [],
   "author": "",
   "license": "ISC",
   "devDependencies": {
     "html-webpack-plugin": "^4.5.0",
     "webpack": "^5.4.0",
     "webpack-cli": "^4.2.0",
     "webpack-dev-server": "^3.11.0"
   },
   "dependencies": {
     "lodash": "^4.17.20"
   }
 }

npm start를 통해 실행할 수 있으며 브라우저가 자동으로 페이지를 로드하는 것을 볼 수 있다.

Using webpack-dev-middleware

webpack-dev-middleware는 웹팩에서 처리한 파일을 서버로 내보내는 래퍼이다.기본적으로 webpack-dev-server에서 사용중이지만 사용자가 커스텀해서 사용하기를 원할 경우 아래와 같이 사용 해 볼 수 있다.

npm install --save-dev express webpack-dev-middleware

  • webpack.config.js
 const path = require('path');
 const HtmlWebpackPlugin = require('html-webpack-plugin');

 module.exports = {
   mode: 'development',
   entry: {
     index: './src/index.js',
     print: './src/print.js',
   },
   devtool: 'inline-source-map',
   devServer: {
     static: './dist',
   },
   plugins: [
     new HtmlWebpackPlugin({
       title: 'Development',
     }),
   ],
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
     clean: true,
   ++publicPath: '/',
   },
 };
  • server.js
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');

const app = express();
const config = require('./webpack.config.js');
const compiler = webpack(config);

// express에서 webpack-dev-middleware와 webpack.config.js를 사용하도록 설정하세요.
// 기본 설정 파일
app.use(
  webpackDevMiddleware(compiler, {
    publicPath: config.output.publicPath,
  })
);

// 포트 3000에서 파일 제공
app.listen(3000, function () {
  console.log('Example app listening on port 3000!\n');
});

이제 서버를 좀 더 쉽게 실행할 수 있도로고 npm 스크립트를 추가해보자.

 {
   "name": "webpack-demo",
   "version": "1.0.0",
   "description": "",
   "private": true,
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1",
     "watch": "webpack --watch",
     "start": "webpack serve --open",
   ++"server": "node server.js",
     "build": "webpack"
   },
   "keywords": [],
   "author": "",
   "license": "ISC",
   "devDependencies": {
     "express": "^4.17.1",
     "html-webpack-plugin": "^4.5.0",
     "webpack": "^5.4.0",
     "webpack-cli": "^4.2.0",
     "webpack-dev-middleware": "^4.0.2",
     "webpack-dev-server": "^3.11.0"
   },
   "dependencies": {
     "lodash": "^4.17.20"
   }
 }

npm run server을 통해 서버를 실행 할 수 있다.

profile
만들고 싶은게 많은 개발자

0개의 댓글