[Webpack] 쉽게 정리한 webpack

Info Notice:
안녕하세요. HwanSeok입니다.
본 포스팅은 Webpack의 기초 개념을 다지는 포스팅입니다.

포스팅 컨셉

웹팩을 이해하기 위한 키워드의 의미를 중심으로 정리합니다. 목표는 공식 docs를 이해하는데 어려움이 없도록 키워드에 익숙해지는 것입니다.

모듈

모듈 내보내기, 가져오기(commonJS와 ESM)

Module A에는 함수 a,b가 정의되어 있을 때 Module B가 함수 a를 사용할 수 있습니다. 모듈을 사용하기 위해서는 모듈을 인식하는 모듈 시스템과 모듈을 다루는 키워드가 제공되어야 합니다. 모듈을 사용하는 방법은 모듈이 어떤 표준을 따르냐에 따라 달라집니다. 여기서는 두 가지 표준만 언급하겠습니다.

  1. nodejs가 채택한 commonJS
  2. ECMAScript 2015~에서 채택한 ESM

모듈을 다루는 키워드는 내보내기가져오기가 있습니다.

내보내는 방법에는 두 가지가 있습니다.

  1. 내보낼 값들을 객체에 넣고, 객체를 내보내는 방법
  2. 내보낼 값들에 각각 개별적인 키워드를 적용해서 내보내는 방법

commmonJS에서는 require(모듈의 경로)를 사용해서 모듈을 가져옵니다. commonJS에서는 전역객체 module이 존재하는데 내보낼 때는 module.exports()에 넣으면 됩니다. 모듈도 하나의 객체로 취급되기 때문에 아래와 같이 사용할 수 있습니다.

1
2
3
4
module.exports = { ... } // 특정 객체을 내보내기
module.exports = 객체 // 특정 객체을 내보내기
module.exports.{key} = { value } // 특정 값을 내보내기
exports.{key} = { value } // 축약형

ESM 표준과의 차이점은 아래와 같습니다.

1
2
3
4
5
6
// commonJS 표준
const mathUtil = require('./mathUtils');
const {getCircleArea} = require('./mathUtils');

// ESM 표준
import { getCircleArea} from './mathUtils';

모듈 종류

  1. built-in core module(ex. nodejs module)
  2. community-based module(ex. npm)
  3. local module( 특정 프로젝트에 정의된 모듈 )

bundle

다양한 확장자의 수 많은 모듈들이 서로 참조하고 있는 상태에서 bundle을 진행하면 웹펙을 통해 확장자 별로 하나의 파일로 만들어집니다. 즉, 참조관계에 있는 것을 하나로 모아서 번들로 만들어줍니다. 하지만 여전히 하나의 파일 안에서 모듈들 간의 의존성은 그대로 유지됩니다.

번들이 중요한 이유

  1. 모든 모듈을 로드하기 위해 검색하는 시간이 단축됩니다. 각각의 모듈도 하나의 파일인데, 파일에 접근하는 횟수가 줄어들어서 시간이 단축됩니다.
  2. 사용하지 않는 코드는 제거됩니다. 실제로 참조하는 코드들로만 이루어진 번들이 만들어집니다. 파일 크기가 감소해서 리소스를 더 빠르게 불러올 수 있습니다.
  3. 각각의 모듈들이 여러 파일로 나누어진 상태에서 압축하는 것보다, 번들링 된 상태에서 압축하는 것이 파일 크기가 더 효율적으로 줄어듭니다. 웹팩이 파일 압축도 해줍니다.

Webpack

모듈 번들러, 웹팩

웹팩은 모듈 번들러라고 불리는 도구입니다.

기본 구조

entry

엔트리는 모듈의 의존 관계를 이해하기 위한 시작점을 설정하는 역할을 합니다. 모듈 1이 모듈 2,3에 정의된 객체를 사용한다면 모듈 1이 entry가 됩니다. 웹팩은 모듈 1을 기준으로 다른 모듈과의 의존선 관계를 파악합니다.

Output

웹팩이 생성하는 bundle.js에 대한 정보를 설정하는 부분입니다. 번들 파일에 사용될 파일과 번들파일의 이름을 설정합니다.

아래 명령어를 실행하면 package.json이 생성됩니다.

1
npm init -y

그리고 webpack을 설치합니다.

1
npm install webpack webpack-cli --save-dev

웹팩이 설치되면 웹팩이 의존하는 다양한 노드 모듈들이 설치가 됩니다. package.json에도 devDependencies가 추가됩니다. node_modules 안에 .bin 이 존재하는데 여기에 포함된 모듈들은 패키지를 실행해주는 역할을 합니다. 일일이 찾아서 실행하지 않고 npx 명령어를 사용하면 더 간편하게 사용할 수 있습니다.

1
npx webpack

위와 같이 entry/output 설정 없이 웹팩을 쓰기 위해서는 웹팩의 기존 설정을 따라야 합니다. entry로 사용될 js 파일은 index.js로 이름을 바꾸고 src폴더에 넣어줍니다. 그리고 src와 같은 depth에 dst 폴더도 생성해줍니다. 참조 관계에 있는 js 파일에서 node의 내장 모듈을 사용하고 있으면 webpack이 이해할 수 있도록 node 정보를 전달해야 합니다. 이는 –target=node라는 값을 추가하면 됩니다. node module을 사용하고 있는 경우에만 추가하면 됩니다.

1
npx webpack --target=node

웹팩은 dist라는 폴더에 main.js라는 번들링된 파일을 생성해줍니다.

1
(()=>{"use strict";const o=2*2*3.14;console.log(o)})();

mode

npm init -y으로 package.json을 생성했었습니다. 여기서는 모듈이 크게 두 가지고 구분되어서 관리됩니다.

  1. 앱 내부에 직접 포함되는 모듈
    • package.json에 dependencies라고 표기
    • ex) jquery
    • –save
  2. 개발 과정에 필요한 모듈
    • package.json에 devDependencies라고 표기
    • ex) 코드 컨벤션 툴
    • –save-dev

loader

웹팩이 인식하는 타입은 기본적으로 js와 json입니다. css 등의 다른 파일을 웹팩에 연결하기 위해서는 loader가 필요합니다.

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

test는 어떤 파일들이 loader의 대상이 되는지 정규표현식으로 패턴매칭할 수 있습니다. 아래와 같이 설정하면 css 파일을 모듈로 적용할 수 있는 상태가 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// webpack.config.js
const path = require('path');

module.exports = {
    entry : './index.js',
    output : {
        path : path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    module: {
        rules: [{
            test: /\.css$/i,
            use : [
                'style-loader',
                {
                    loader: "css-loader"
                }
            ]
        }]
    },
    mode : 'none'
}

css module

브라우저별로 제일 먼저 적용되는 user-agent style을 통일 시켜야합니다. style 작업시 개별적으로 우선 적용 되는 스타일이 있는데 브라우저마다 각각 다른 모습으로 보일 수 있는 문제가 있기 때문입니다. user-agent style을 통일시키는 방법은 두 가지가 있습니다.

  1. reset css : 대부분의 css를 삭제함
  2. normalize css : 다르게 적용되는 스타일만 동일하게 만들어 줌

npm으로 css를 받아서 사용할 수도 있습니다.

1
npm install normalize.css --save
1
import 'normalize.css';

css 선택자는 전역환경에 적용되어서 앱이 커지면 중복되는 문제가 발생할 수 있습니다. css-loader의 option에서 modules를 true로 적용하면, 클래스 이름을 자바스크립트 파일에 직접 불러와서 사용할 수 있어 충돌을 막을 수 있습니다. 자세한 내용은 여기에서 찾아볼 수 있습니다. The modules option enables/disables the CSS Modules specification and setup basic behaviour.라고 나와있고, CSS Modules에는 A CSS Module is a CSS file in which all class names and animation names are scoped locally by default.라고 나와있네요!

style-loader는 처리하는 css 파일별로 style tag를 만드는 역할을 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//webpack.config.js
const path = require('path');

module.exports = {
    entry : './index.js',
    output : {
        path : path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    module: {
        rules: [{
            test: /\.css$/i,
            use : [
                'style-loader',
                {
                    loader: "css-loader",
                    options: {
                        modules: true
                    }
                }
            ]
        }]
    },
    target: 'node',
    mode : 'none'
}
1
2
3
4
5
6
7
8
9
10
11
12
import 'normalize.css';
import styles from './index.css';

function component() {
    const element = document.createElement('div');
    element.innerText = 'world';
    console.log(styles) // {helloWebpack: "X8scn2ckjakj2d"}
    element.classList = styles.helloWebpack;
    return element;
}

document.body.appendChild(component());

style-loader를 아래와 같이 바꾸면 webpack이 rendering하는 html의 head에, css 파일 별로 style tag가 생기지 않고 하나의 태그 안에 모든 내용이 다 포함됩니다.

1
2
3
4
5
6
{
    loader: "style-loader",
    options: {
        injectType:'singletonStyleTag'
    }
}

plugin

프로젝트 전반에 걸쳐 필요한 기능을 수행해줍니다.

html-webpack-plugin는 bundler를 위한 html 파일을 자동 설정 및 생성해주는 역할을 합니다.

1
>npm i html-webpack-plugin -D
1
2
3
4
5
plugins: [
    new HtmlWebpackPlugin({
        template: "./template.html"
    })
],

기존의 index.html을 template.html로 바꾸고, 위와 같이 플로그인 설정을 한 뒤 npm run build를 하면 dist 파일에 template.html에 bundle.js 지정된 script tag가 포함된 html 파일이 생성됩니다. output 설정에서 입력한 내용이 template으로 지정한 파일에 적용되어서 결과가 생성되는 것입니다.

cache

webpack으로 bundle.js를 생성해서 브라우저에 전달할 때 캐시를 사용합니다. bundling을 할 때 고유한 해시값을 생성하고 bundle.해시값.js라는 이름으로 브라우저에게 전달합니다. 브라우저는 경로에 따라서 데이터의 변경 유무를 확인하고 캐싱하기 때문입니다. 기본적으로는 js파일만 수정이 되어도 css파일도 같이 bundling이 되어서 css의 해시값도 바뀌고 cache되었던 값이 다시 쓰이지 못하는 경우가 있습니다. 이 때는 해당 파일의 내용에 따라서 해시값을 만든다는 contenhash를 사용합니다. 여기에서 확인할 수 있습니다.

chunk

모듈이 많아지면 bundle.js가 너무 커지면 오히려 불편해집니다. 이 때 적절한 기준에 따라서 bundle.js를 여러 파일로 분할합니다. 이 파일들을 chunk라고 부릅니다. chunk로 나누는 기준은 두 가지 입니다.

  1. runtime chunk
    • bundle.js 안에 여러 모듈들을 적절한 순서에 따라서 읽을 수 있도록 마련된 초기화 코드가 존재합니다. 앱에 메모리를 받아서 실행되는 환경을 runtime이라고 부르는데, runtime에서 module들을 안전하게 사용하게 해주는 초기화 코드는 모듈의 갯수와 상관없이 일정합니다. 이 runtime에 사용되는 코드만 독립적인 chunk로 만들어서 cache를 적용할 수 있게 됩니다.
  2. vendor chunk
    • 외부 패키지에 해당합니다. 버전업이 되지 않으면 다시 빌드되지 않고, 그래서 해시값이 변하지 않아 cache되기 좋습니다. 프로젝트 내부에서 생성한 모듈을 제외한 외부 패키지나, 수정이 잘 없는 내용을 독립적인 chunk로 나누어 cache를 유도합니다.

minification & mangling

사용자에게 전달되는 리소스는 불필요한 내용을 정리한 최적화된 형태여야 합니다. 소스코드와 리소스를 최적화하는 내용입니다.

주석, console.log를 삭제하고 들여쓰기와 변수명을 줄이는 과정으로 minification을 진행합니다.

Production Mode & Development Mode

개발 환경에서 loader와 plugin 등을 추가할 때마다 npm run build의 시간이 길어집니다. 프로덕션 환경에서는 꼭 필요한 기능만 추가시키고 최적화를 진행해야합니다. 즉, 개발환경과 프로덕션 환경을 구분해서 빌드 할 수 있어야 합니다.

설정 파일을 1. 공통설정 2. 개발 설정 3. 프로덕션 설정으로 나눕니다. 이 때는 webpack-merge라는 모듈이 사용됩니다.

파일 분할을 하는 방법은 구글링에서 찾아서 적용했다고 가정하겠습니다.

package.json의 아래 부분에서 dev와 prod를 구분해서 따로따로 빌드할 수 있습니다.

1
2
3
4
"scripts": {
  "dev": "webpack --config webpack.dev.js"
  "prod": "webpack --config webpack.config.js"
},

개발환경인 경우 webpack-dev-server를 적용하면 더 편리하게 개발할 수 있습니다.

Success Notice:
수고하셨습니다. :+1:

Leave a comment