Post

[js] 웹팩의 생태계를 지원하는 기반도구 npm과 웹자동화 및 모듈관리 기능을 갖춘 웹팩

NodeJS와 npm (node package manager)

🐀 npm은 모든 자바스크립트 라이브러리가 있는 공개저장소이다

🍍 라이브러리: 개발자가 편하게 가져다 쓸 수 있는 미리 만들어 놓은 기능의 집합

npm 명령어로 자바스크립트 라이브러리를 설치하고 관리할 수 있다.
npm을 사용하면, 🧆 package.json에 설치한 라이브러리 버전과 의존성을 한눈에 보기좋게 관리할 수 있다.

cdn (content delivery network)을 사용한 자바스크립트 라이브러리 로딩방식을 썼을 때보다 덜 번거롭다.
또한, 필요한 라이브러리 스크립트를 html 중간에 집어넣는 경우에는 라이브러리 간 의존관계를 파악하기 힘들다.

npm 초기화 명령어
🧆 package.json 생성
nodejs에서 사용하는 모듈들을 패키지로 만들어 관리하고 배포하는 역할을 한다.
npm init -y
1
2
3
4
5
6
7
8
9
10
11
12
{
  "name": "npm",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": ""
}

라이브러리 설치 명령어, npm i

🥯 local install  /  global install

지역설치를 하면 해당 프로젝트 하위의 🍡 node_modules 밑에 관련 라이브러리 폴더가 생긴다.
하지만, 전역설치를 하게 되면 해당 라이브러리 폴더는
%USERPROFILE%\AppData\Roaming\npm\node_modules 여기에 설치된다.

프로젝트 레벨이 아닌 시스템 레벨에서 사용할 자바스크립트 라이브러리를 설치할 때 전역설치를 한다.
ex. npm i gulp -g

배포용 라이브러리와 개발용 라이브러리

🥯 --save-prod (default)  /  --save-dev

배포용 라이브러리애플리케이션의 로직 및 화면조작과 직접적인 연관이 있는 라이브러리를 말한다.

npm run build로 빌드했을 때, 배포용 라이브러리는 최종 애플리케이션 코드안에 포함된다.
즉, 개발용 라이브러리는 최종 애플리케이션 코드안에 포함시키지 않음으로써, 빌드속도를 높일 수 있다.

npm i webpack -D 이렇게 개발용 라이브러리로 설치했을 때,
🧆 package.jsondependencies 키가 아닌 devDependencies 키의 목록으로 추가된다.

🧆 package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
    "name": "npm",
    "version": "1.0.0",
    "main": "index.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "description": "",
    "dependencies": {
        "jquery": "^3.7.1",
        "jquery-ui": "^1.14.1"
    },
    "devdependencies": {
        "webpack": "^5.96.1",
        "webpack-cli": "^5.1.4",
    }
}

웹팩 (webpack)

🐁 웹팩이란 모듈번들러이다

모듈번들링이란 웹애플리케이션을 구성하는 자원들의 연관관계를 파악한 뒤, 여러 개의 파일을 병합해서 하나로 합쳐주는 동작을 말한다.

cdn을 이용한 자바스크립트 라이브러리(ex. lodash, 데이터조작을 쉽게해주는 api를 제공하는 유틸리티 라이브러리) 로딩방식을 썼을 때,
index.html 페이지를 띄우기 위해 요청한 network 패널을 보면

1
2
3
4
index.html
lodash.js (https://unpkg.com/lodash@4.16.6/lodash.js)
index.js
ws

lodash 라이브러리를 가져오기 위한 요청 lodash@4.16.6을 통해 lodash.js 파일을 가져온다.

하지만 웹팩으로 라이브러리를 설치하고 index.html을 띄우기 위해 요청한 network 패널을 보면

1
2
3
index.html
bundle.js
ws

두번의 요청으로 받았던 두개의 js 파일을,
한번의 요청으로 병합된 main.js파일을 받아 웹페이지를 띄운다.
즉, 웹팩이 내부적으로 두개였던 파일을 합쳐서 페이지를 위한 한번의 요청만을 보낸다.
네트워크 request가 줄어듬으로써 페이지 로딩속도가 빨라진다.
결국, 웹팩은 브라우저를 위한 사전 컴파일 도구라 볼 수 있다.
⚠️ 참고로 브라우저별 HTTP 요청 숫자의 제약이 있다.

NOT JUST FOR JAVASCRIPT
자바스크립트에만 국한된 것이 아닌, 웹페이지를 구성하는 모든 리소스에 대해서
웹팩으로 동작을 실행시킬 수 있다.

웹팩을 통한 빌드를 위해, 커스텀 명령어를 다음과 같이 작성했을 때 웹팩으로 빌드(= 변환, = 모듈번들링)한 결과물이 들어있는 dist/bundle.js 파일을 보면 웹팩이 빌드 시 모듈별로 병합한 것을 볼 수 있다.
웹팩에서 웹서비스를 구성하는 각각의 파일 또는 모듈을 번호로 관리한다.
즉시 실행함수의 구조 안에 웹팩이 담겨있다. IIFE(Immediately Invoked Function Expression)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/******/ (function (modules) {
  /******/
  /* 웹팩 내부 로직*/
  /******/
})(
  /******/ [
    /* 0 */
    /***/ function (module, __webpack_exports__, __webpack_requires__) {
      "use strict";
      /* 생략 */
      /***/
    },
    /* 1 */
    /***/ function (module, exports, __webpack_requires__) {
      /* 생략 */
      /***/
    }
    /******/
  ]
);

커스텀 명령어와 webpack.config.js

🐙 package.json의 scripts에 커스텀 명령어를 정의할 수 있다

🧆 package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
    "name": "npm",
    "version": "1.0.0",
    "main": "index.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "build": "webpack --mode=none --entry=src/index.js --output=dist/bundle.js"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "description": "",
    "devdependencies": {
        "webpack": "^5.96.1",
        "webpack-cli": "^5.1.4",
    }
}

특히 웹팩을 사용할 때 --mode옵션으로 현재 어떤 모드인지를 알려줘야 한다. (none, development, production) 없으면 🐖 에러 발생
해당 옵션 이외에도, build 커스텀 명령어처럼 필요한 옵션을 붙이다보면 한 라인이 길어질 수 밖에 없다.
옵션들을 여기에 붙이는 대신 웹팩에서 제공하는 웹팩 설정파일에 작성할 수 있다.

🎳 webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
var path = require('path');

module.exports = {
    mode: 'none',
    entry: './src/index.js',
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist')
    }
    devtool: 'source-map'
}
🍪 source-map
웹팩을 통해 빌드했다 하더라도, 원본파일에 연결시켜준다.
즉, 빌드한 결과물을 빌드되기 전과 연결해주는 역할을 한다.

웹팩과 모듈

🍝 웹팩의 등장배경

웹애플리케이션을 배포하기 위해서는 파일유형별로 전처리, 변환, 압축최적화 과정이 필요하다.
이를 grunt / gulp 같은 웹개발 작업 자동화도구 web task manager를 통해 스크립트를 직접 작성해줘야한다.

또한, 웹팩이 등장하기 전까지는 🐖 파일단위로 변수의 유효범위가 구분되어 있지 않았다.
중복선언으로 인한 의도치 않은 동작이 발생되기도 했기 때문에, 파일단위의 자바스크립트 모듈 관리의 필요성이 제기되었다.

하지만, 웹팩에는 웹 자동화 뿐만 아니라 모듈관리 기능도 모두 포함되어있다.
즉, 웹팩에 진입점 파일만을 알려주면 나머지 연관관계들은 알아서 해석해서 합쳐준다.
따라서, 웹팩은 성능이 보장된 모듈번들러이다.

더불어, 웹팩은 기본적으로 🗣️ ‘ 필요한 자원은 그때 요청하자 ‘라는 철학을 가지고 있다.
웹팩의 🍳 code splitting을 통해 동적으로 원하는 순간에 모듈을 로딩하여 이를 구현가능하고,
🦥 Lazy Loading을 지원하기 때문에 초기 페이지 로딩속도를 높일 수 있다.

📘 모듈이란
특정 기능을 갖는 작은 코드 단위로써 파일 기반으로 변수와 함수를 선언하여 독립적으로 관리하고 싶을 때 사용한다.
웹팩에서의 모듈이란 자바스크립트 모듈에만 국한되지 않고 웹애플리케이션을 구성하는 모든 자원을 의미한다.
📍 모듈화를 위한 문법
export { 변수, 함수 }
import { 변수, 함수 } from '파일경로'
export default 컴포넌트 (vue나 react에서 컴포넌트를 통채로 꺼낼 때 사용)
☑️ 바벨(babel)이란
자바스크립트 최신문법 ECMASCRIPT6 을 최대한 많은 브라우저에 호환될 수 있게 변환해주는 도구이다.

웹팩의 주요속성 4가지

🌋 웹팩의 주요속성 4가지
  • entry
    빌드할 대상 파일을 정의한다.
    웹자원을 변환하기 위한 최초 진입점이 되는 js 파일 경로이다.
  • output
    웹팩으로 빌드한 결과물의 js 파일 경로를 정의한다.
  • loader (modules)
    웹애플리케이션 모듈 간의 관계를 파악할 때,
    js 파일이 아닌 웹자원들을 변환(= 빌드, = 번들링)할 수 있도록 도와주는 속성이다.
    즉, 비js파일을 웹팩에 이식시켜줄 수 있는 속성이다.
    ※ entry에서 output으로 변환되는 그 과정에 개입
  • plugins
    결과물에 대한 형태를 변형해서 제공할 수 있다.
    ※ 해당 속성의 배열 안에는 생성자 함수로 생성한 객체 인스턴스만 추가될 수 있다.

🚂 npm i webpack webpack-cli css-loader style-loader mini-css-extract-plugin html-webpack-plugin -D

🎳 webpack.config.js

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
27
28
29
30
31
32
33
var path = require("path");
var MiniCssExtractPlugin = require("mini-css-extract-plugin");
var HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  mode: "production",
  entry: "./src/index.js",
  output: {
    filename: "[chunkhash]bundle.js", // 브라우저 캐싱때문에 같은 파일을 화면에 뿌리지 않도록, 강제 새로고침 없이도 내용이 변화되었을 때,
    // 고유값으로 파일의 변화를 인식시킨다.
    path: path.resolve(__dirname, "./dist")
  },
  modules: {
    rules: [
      {
        // 하나의 로더 규칙
        test: /\.css$/, // 해당 규칙을 가진 모든 파일을 대상으로
        use: [{ loader: MiniCssExtractPlugin }, "css-loader"] // 웹팩으로 변환될 때 적용될 로더들을 추가할 수 있다.
      },
      {
        test: /\.js$/,
        use: ["babel-loader"]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin(),
    new HtmlWebpackPlugin({
      template: "index.html" // index.html 템플릿을 기반으로 빌드 결과물을 추가
    })
  ],
  devtools: "source-map"
};
🔨 loader
css-loader는 css가 웹팩안으로 들어갈 수 있게 한다.
style-loader는 웹팩 안에 들어가 있는 style code를 head 태그 안에 인라인 style로 박아주는 역할을 한다.
이 때, 로더의 순서가 영향을 미치고, 항상 오른쪽에 작성된 로더부터 적용이 된다.
해당 웹팩옵션을 설정한 뒤, 웹팩으로 빌드했을 때 결과파일을 보면 css파일에 있던 코드가 js 안으로 들어간 것을 볼 수 있는데, 이는 🔨 loader의 역할이다.
js가 아닌 파일을 웹팩 안으로 변환되어 들어갈 수 있게 적용해주는 속성🔨 loader이다.
즉, 웹팩에서 인식할 수 있게, js가 아닌 파일유형에 대해서 모두 로더를 적용하면 된다.
🧩 plugin
MiniCssExtractPlugin을 썼을 때, css 결과물이 js 파일 밖으로 분리된다. 결과물의 형태를 변형
HtmlWebpackPlugin을 썼을 때, index.html 파일을 만들어주고, 그 안에 빌드내용을 포함한다. 결과물의 형태를 변형

웹팩 데브 서버

🍝 웹팩 데브 서버가 필요한 이유

빌드 대상파일의 코드가 변경되었을 때마다, 빌드를 해주어야 반영이 되는데,
웹팩 데브 서버를 사용하면 빌드 대상 파일이 변경되었을 때, 매번 웹팩 빌드 명령어를 실행하지 않고,
저장만 해도 자동으로 웹팩으로 빌드한 후, 브라우저를 새로고침 해준다.

다만, 웹팩 데브 서버를 실행하여 웹팩 빌드를 하는 경우에는
빌드한 결과물이 프로젝트 폴더에 보이지 않는다.
이는, 결과물은 그저 메모리에 저장되고 파일로 생성하지 않기 때문이다.
즉, 인메모리 기반으로 웹팩 빌드 결과물을 올려놓을 뿐이다.

🍍 컴퓨터 구조 관점에서 파일입출력보다는 메모리 입출력이 더 빠르고 자원도 덜 소모된다.

🚂 npm i wepack webpack-cli webpack-dev-server -D

🧆 package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
    "name": "npm",
    "version": "1.0.0",
    "main": "index.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "dev": "webpack-dev-server",
        "build": "webpack"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "description": "",
    "devdependencies": {
        "webpack": "^5.96.1",
        "webpack-cli": "^5.1.4",
        "webpack-dev-server": "^5.1.0"
    }
}

🎳 webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var path = require("path");

module.exports = {
  mode: "none",
  entry: "./index.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist")
  },
  devServer: {
    // 포트를 지정할 수 있다.
    port: 9000
  },
  modules: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"]
      }
    ]
  }
};
This post is licensed under CC BY 4.0 by the author.