Asset Management
처음부터 가이드를 따라왔다면 이제 "Hello webpack"을 표시하는 작은 프로젝트가 생성되었을 것입니다. 이제 이미지와 같은 다른 애셋을 통합하고, 애셋이 어떻게 처리되는지 살펴보겠습니다.
webpack 이전에 프런트엔드 개발자는 grunt와 gulp 같은 도구를 사용하여 애셋을 처리하고 /src 폴더에서 /dist 또는 /build 디렉터리로 옮겼습니다. JavaScript 모듈에도 동일한 아이디어가 사용되었지만, webpack과 같은 도구는 모든 의존성을 동적으로 번들합니다. (디펜던시 그래프로 알려진 것을 생성합니다). 이것이 좋은 이유는 이제 모든 모듈이 의존성을 명확하게 명시하고 사용하지 않는 모듈을 번들에서 제외할 수 있기 때문입니다.
webpack의 가장 멋진 기능 중 하나는 JavaScript 외에도 로더 또는 내장 애셋 모듈이 지원하는 다른 유형의 파일도 포함 할 수 있다는 것입니다. 즉, 위에 나열된 JavaScript의 이점(예: 명시적 의존성)을 웹 사이트 또는 웹 앱을 만드는 데 사용한 모든 것에 적용할 수 있습니다. 이미 설정에 익숙 할 수 있는 CSS부터 시작해 보겠습니다.
Setup
시작하기 전에 프로젝트를 조금 변경해 보겠습니다.
dist/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
- <title>Getting Started</title>
+ <title>Asset Management</title>
</head>
<body>
- <script src="main.js"></script>
+ <script src="bundle.js"></script>
</body>
</html>webpack.config.js
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
output: {
- filename: 'main.js',
+ filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};Loading CSS
Webpack은 CSS를 자체적으로 이해하므로 별도의 설치나 설정 없이 JavaScript 모듈에서 CSS 파일을 import할 수 있습니다.
import "./style.css";Webpack은 파일을 파싱하고 @import와 url() 참조를 리졸브한 뒤 번들 옆의 .css 출력 파일로 추출합니다. CSS Modules, minification, content hash도 아직 실험적인 동일한 내장 기능에서 제공합니다. 지원 범위와 여전히 loader가 필요한 항목은 What's built-in을 참고하세요.
Preprocessor는 계속 loader를 사용하며 module loader는 체인으로 연결할 수 있습니다. 체인의 각 loader는 처리하는 리소스에 변환을 적용하고 체인은 역순(오른쪽에서 왼쪽)으로 실행됩니다.
예를 들어 다음과 같은 규칙이 있다고 해보겠습니다.
export default {
module: {
rules: [
{
test: /\.scss$/i,
use: ["postcss-loader", "sass-loader"],
type: "css/auto",
},
],
},
};use 배열에서 postcss-loader가 sass-loader보다 앞에 있지만 webpack은 sass-loader를 먼저 실행하여 Sass를 CSS로 컴파일한 뒤 결과에 postcss-loader를 실행합니다. type: 'css/auto'는 체인에서 나온 CSS를 webpack 자체 CSS 파이프라인으로 처리하도록 지정합니다.
이 순서가 올바르게 유지되지 않으면 webpack에서 오류가 발생할 수 있습니다.
project
webpack-demo
├── package.json
├── package-lock.json
├── webpack.config.js
├── /dist
│ ├── bundle.js
│ └── index.html
├── /src
+ │ ├── style.css
│ └── index.js
└── /node_modulessrc/style.css
.hello {
color: red;
}src/index.js
import _ from 'lodash';
+import './style.css';
function component() {
const element = document.createElement('div');
// 이제 이 스크립트가 Lodash를 import합니다.
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+ element.classList.add('hello');
return element;
}
document.body.appendChild(component());이제 빌드 커맨드를 실행합니다.
$ npm run build
...
[webpack-cli] Compilation finished
asset bundle.js 69.6 KiB [emitted] [minimized] (name: main) 1 related asset
asset bundle.css 17 bytes [emitted] [minimized] (name: main)
runtime modules 1020 bytes 6 modules
cacheable modules 533 KiB (javascript) 23 bytes (css)
./src/index.js + 1 modules 313 bytes [built] [code generated]
./node_modules/lodash/lodash.js 533 KiB [built] [code generated]
css ./src/style.css 23 bytes [built] [code generated]
webpack 5.x.x compiled successfully in 1689 msWebpack은 CSS를 자체 파일인 bundle.css로 추출했습니다. 이름은 기본적으로 output.filename에서 파생되는 output.cssFilename을 따릅니다. 페이지에서 연결하세요.
dist/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Asset Management</title>
+ <link rel="stylesheet" href="bundle.css" />
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>브라우저에서 dist/index.html을 다시 열면 Hello webpack이 빨간색으로 표시됩니다.
Minification도 내장되어 있습니다. mode: 'production'에서 webpack은 별도 plugin 없이 출력 CSS를 최소화하고 browserslist target에 맞춰 vendor prefix를 관리할 수 있습니다. Minification을 참고하세요. 이와 별개로 postcss, sass, less 등 다양한 CSS용 loader도 제공됩니다.
Loading Images
이제 CSS는 가져왔는데, 배경이나 아이콘과 같은 이미지는 어떻게 할까요? 이미지도 webpack 5부터 내장된 Asset Modules를 사용하여 시스템에 쉽게 통합할 수 있습니다.
webpack.config.js
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
+ module: {
+ rules: [
+ {
+ test: /\.(png|svg|jpg|jpeg|gif)$/i,
+ type: 'asset/resource',
+ },
+ ],
+ },
};import MyImage from './my-image.png'를 사용하면 이미지가 처리되어 output 디렉터리에 추가되고 MyImage 변수에는 처리 후 이미지의 최종 URL이 포함됩니다. CSS 안의 url('./my-image.png')도 마찬가지입니다. webpack은 이를 로컬 파일로 인식하고 output 디렉터리의 최종 경로로 재작성합니다. experiments.html을 활성화하면 HTML 파일의 <img src="./my-image.png" />도 같은 방식으로 처리합니다. Native HTML을 참고하세요.
이제 프로젝트에 이미지를 추가하고 어떻게 작동하는지 살펴볼까요? 원하는 이미지를 아무거나 사용해도 좋습니다.
project
webpack-demo
├── package.json
├── package-lock.json
├── webpack.config.js
├── /dist
│ ├── bundle.js
│ └── index.html
├── /src
+ │ ├── icon.png
│ ├── style.css
│ └── index.js
└── /node_modulessrc/index.js
import _ from 'lodash';
import './style.css';
+import Icon from './icon.png';
function component() {
const element = document.createElement('div');
// Lodash, now imported by this script
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
element.classList.add('hello');
+ // 원래 있던 div 에 이미지를 추가합니다.
+ const myIcon = new Image();
+ myIcon.src = Icon;
+
+ element.appendChild(myIcon);
+
return element;
}
document.body.appendChild(component());src/style.css
.hello {
color: red;
+ background: url('./icon.png');
}새 빌드를 만들고 index.html 파일을 다시 엽니다.
$ npm run build
...
[webpack-cli] Compilation finished
asset bundle.js 70.1 KiB [emitted] [minimized] (name: main) 1 related asset
asset 86c447381066a936d5c5.png 7.67 KiB [emitted] [immutable] [from: src/icon.png] (auxiliary name: main)
asset bundle.css 58 bytes [emitted] [minimized] (name: main)
runtime modules 1.95 KiB 7 modules
cacheable modules 534 KiB (javascript) 58 bytes (css) 7.67 KiB (asset) 42 bytes (asset-url)
javascript modules 534 KiB
./src/index.js + 1 modules 511 bytes [built] [code generated]
./node_modules/lodash/lodash.js 533 KiB [built] [code generated]
css ./src/style.css 58 bytes [built] [code generated]
./src/icon.png 7.67 KiB (asset) 42 bytes (javascript) 42 bytes (asset-url) [built] [code generated]
webpack 5.x.x compiled successfully in 1684 ms모든 작업이 올바르게 진행되었다면 icon이 반복되는 배경으로 표시되고 Hello webpack 텍스트 옆에 img 요소가 나타납니다. 요소를 검사하면 실제 파일 이름이 86c447381066a936d5c5.png와 같은 형태로 변경된 것을 볼 수 있습니다. webpack이 src 폴더에서 파일을 찾아 처리했다는 의미입니다.
Loading Fonts
그렇다면 폰트와 같은 다른 애셋은 어떨까요? 애셋 모듈은 로드한 모든 파일을 가져와 빌드 디렉터리로 내보냅니다. 즉, 폰트를 포함한 모든 종류의 파일에 사용할 수 있습니다. 폰트 파일을 처리하도록 webpack.config.js를 업데이트해 보겠습니다.
webpack.config.js
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
+ {
+ test: /\.(woff|woff2|eot|ttf|otf)$/i,
+ type: 'asset/resource',
+ },
],
},
};프로젝트에 몇 개의 폰트 파일을 추가합니다.
project
webpack-demo
├── package.json
├── package-lock.json
├── webpack.config.js
├── /dist
│ ├── bundle.js
│ └── index.html
├── /src
+ │ ├── my-font.woff
+ │ ├── my-font.woff2
│ ├── icon.png
│ ├── style.css
│ └── index.js
└── /node_modules규칙을 설정하고 폰트를 배치한 뒤 @font-face 선언으로 사용할 수 있습니다. 이미지와 마찬가지로 webpack이 로컬 url(...) directive를 처리합니다.
src/style.css
+@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');
}이제 새 빌드를 실행하고 webpack이 폰트를 처리했는지 살펴보겠습니다.
$ npm run build
...
[webpack-cli] Compilation finished
assets by status 7.67 KiB [cached] 1 asset
assets by status 33.5 KiB [emitted]
asset f32e23c95fbf20947766.woff 18.8 KiB [emitted] [immutable] [from: src/my-font.woff] (auxiliary name: main)
asset f8668ded30a04fd1aed7.woff2 14.5 KiB [emitted] [immutable] [from: src/my-font.woff2] (auxiliary name: main)
asset bundle.css 237 bytes [emitted] [minimized] (name: main)
asset bundle.js 70.1 KiB [compared for emit] [minimized] (name: main) 1 related asset
runtime modules 1.95 KiB 7 modules
cacheable modules 534 KiB (javascript) 41 KiB (asset) 126 bytes (asset-url) 255 bytes (css)
modules by path ./src/ 553 bytes (javascript) 41 KiB (asset) 126 bytes (asset-url)
./src/index.js + 1 modules 511 bytes [built] [code generated]
./src/icon.png 7.67 KiB (asset) 42 bytes (javascript) 42 bytes (asset-url) [built] [code generated]
./src/my-font.woff2 14.5 KiB (asset) 42 bytes (asset-url) [built] [code generated]
./src/my-font.woff 18.8 KiB (asset) 42 bytes (asset-url) [built] [code generated]
./node_modules/lodash/lodash.js 533 KiB [built] [code generated]
css ./src/style.css 255 bytes [built] [code generated]
webpack 5.x.x compiled successfully in 1644 msdist/index.html을 다시 열고 Hello webpack 텍스트가 새 폰트로 변경되었는지 확인합니다. 모든 것이 잘되었다면, 변경된 폰트를 확인할 수 있을 것입니다.
Loading Data
로드할 수 있는 또 다른 유용한 애셋은 JSON 파일, CSV, TSV 및 XML과 같은 데이터입니다. JSON 지원은 기본으로 내장되어 있으며 NodeJS와 유사합니다. 즉, 기본적으로 import Data from './data.json'이 동작합니다. CSV, TSV 및 XML을 가져오려면 csv-loader 및 xml-loader를 사용할 수 있습니다. 세 가지 모두 로드해 보겠습니다.
npm install --save-dev csv-loader xml-loaderwebpack.config.js
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
+ {
+ test: /\.(csv|tsv)$/i,
+ use: ['csv-loader'],
+ },
+ {
+ test: /\.xml$/i,
+ use: ['xml-loader'],
+ },
],
},
};프로젝트에 데이터 파일을 추가합니다.
project
webpack-demo
├── package.json
├── package-lock.json
├── webpack.config.js
├── /dist
│ ├── bundle.js
│ └── index.html
├── /src
+ │ ├── data.xml
+ │ ├── data.csv
│ ├── my-font.woff
│ ├── my-font.woff2
│ ├── icon.png
│ ├── style.css
│ └── index.js
└── /node_modulessrc/data.xml
<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>Mary</to>
<from>John</from>
<heading>Reminder</heading>
<body>Call Cindy on Tuesday</body>
</note>src/data.csv
to,from,heading,body
Mary,John,Reminder,Call Cindy on Tuesday
Zoe,Bill,Reminder,Buy orange juice
Autumn,Lindsey,Letter,I miss you
이제 네 가지 데이터 유형(JSON, CSV, TSV, XML) 중 하나를 import 할 수 있으며, 가져오는 Data 변수에는 파싱된 JSON이 포함되어 쉽게 사용할 수 있습니다.
src/index.js
import _ from 'lodash';
import './style.css';
import Icon from './icon.png';
+import Data from './data.xml';
+import Notes from './data.csv';
function component() {
const element = document.createElement('div');
// Lodash, now imported by this script
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
element.classList.add('hello');
// 기존 div에 이미지를 추가합니다.
const myIcon = new Image();
myIcon.src = Icon;
element.appendChild(myIcon);
+ console.log(Data);
+ console.log(Notes);
+
return element;
}
document.body.appendChild(component());npm run build 명령을 다시 실행하고 dist/index.html을 엽니다. 개발자 도구의 콘솔에 가져온 데이터가 기록되는 것을 볼 수 있습니다!
// 경고 없음
import data from "./data.json";// 스펙에서 허용하지 않으므로 경고가 노출됨
import { foo } from "./data.json";Customize parser of JSON modules
특정 webpack 로더 대신 [커스텀 파서](/configuration/modules# ruleparserparse)를 사용하여 toml, yaml 또는 json5 파일을 JSON 모듈로 가져올 수 있습니다.
src 폴더에 data.toml, data.yaml 및 data.json5 파일이 있다고 가정해 보겠습니다.
src/data.toml
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
organization = "GitHub"
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
dob = 1979-05-27T07:32:00Z
src/data.yaml
title: YAML Example
owner:
name: Tom Preston-Werner
organization: GitHub
bio: |-
GitHub Cofounder & CEO
Likes tater tots and beer.
dob: 1979-05-27T07:32:00.000Zsrc/data.json5
{
// comment
title: "JSON5 Example",
owner: {
name: "Tom Preston-Werner",
organization: "GitHub",
bio: "GitHub Cofounder & CEO\n\
Likes tater tots and beer.",
dob: "1979-05-27T07:32:00.000Z",
},
}
먼저 toml, yamljs 및 json5 패키지를 설치합니다.
npm install toml yamljs json5 --save-dev그리고 webpack 설정에 추가합니다.
webpack.config.js
import path from 'node:path';
import { fileURLToPath } from 'node:url';
+import toml from 'toml';
+import yaml from 'yamljs';
+import json5 from 'json5';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
{
test: /\.(csv|tsv)$/i,
use: ['csv-loader'],
},
{
test: /\.xml$/i,
use: ['xml-loader'],
},
+ {
+ test: /\.toml$/i,
+ type: 'json',
+ parser: {
+ parse: toml.parse,
+ },
+ },
+ {
+ test: /\.yaml$/i,
+ type: 'json',
+ parser: {
+ parse: yaml.parse,
+ },
+ },
+ {
+ test: /\.json5$/i,
+ type: 'json',
+ parser: {
+ parse: json5.parse,
+ },
+ },
],
},
};src/index.js
import _ from 'lodash';
import './style.css';
import Icon from './icon.png';
import Data from './data.xml';
import Notes from './data.csv';
+import toml from './data.toml';
+import yaml from './data.yaml';
+import json from './data.json5';
+
+console.log(toml.title); // output `TOML Example`
+console.log(toml.owner.name); // output `Tom Preston-Werner`
+
+console.log(yaml.title); // output `YAML Example`
+console.log(yaml.owner.name); // output `Tom Preston-Werner`
+
+console.log(json.title); // output `JSON5 Example`
+console.log(json.owner.name); // output `Tom Preston-Werner`
function component() {
const element = document.createElement('div');
// Lodash, now imported by this script
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
element.classList.add('hello');
// Add the image to our existing div.
const myIcon = new Image();
myIcon.src = Icon;
element.appendChild(myIcon);
console.log(Data);
console.log(Notes);
return element;
}
document.body.appendChild(component());npm run build 명령을 다시 실행하고 dist/index.html을 확인합니다. 가져온 데이터가 콘솔에 기록되는 것을 볼 수 있습니다!
Global Assets
위에서 언급 한 모든 것 중 가장 멋진 점은 이러한 방식으로 애셋을 로드하면 모듈과 애셋을 보다 직관적인 방식으로 그룹화할 수 있다는 것입니다. 모든 것을 포함한 글로벌 /assets 디렉터리에 의존하는 대신 애셋을 사용하는 코드와 그룹화할 수 있습니다. 예를 들어 다음과 같은 구조가 유용할 수 있습니다.
- ├── /assets
+ └── /components
+ └── /my-component
+ ├── index.jsx
+ ├── index.css
+ ├── icon.svg
+ └── img.png이러한 설정은 밀접하게 연결된 모든 것이 함께 있기 때문에 코드를 다른 곳에 훨씬 더 쉽게 적용할 수 있도록 합니다. 다른 프로젝트에서 /my-component를 사용한다고 가정해 봅시다. 간단히 복사하거나 다른 프로젝트의 /components 디렉터리로 옮기면 됩니다. 외부 의존성을 설치하고 설정에 동일한 로더가 정의되어있는 한 아무 문제가 없습니다.
그러나 예전 방식을 고수하고 있거나 여러 컴포넌트(뷰, 템플릿, 모듈 등) 간에 공유되는 애셋이 있다고 가정해 보겠습니다. 이러한 애셋을 기본 디렉터리에 저장하는 것도 가능하며 aliasing을 사용하여 쉽게 import 할 수 있습니다.
Wrapping up
다음 가이드에서는 이 가이드에서 사용한 각기 다른 애셋을 모두 사용하지 않을 것이므로 다음 가이드인 Output Management 준비를 위해 정리를 해 보겠습니다.
project
webpack-demo
├── package.json
├── package-lock.json
├── webpack.config.js
├── /dist
│ ├── bundle.js
│ └── index.html
├── /src
- │ ├── data.csv
- │ ├── data.json5
- │ ├── data.toml
- │ ├── data.xml
- │ ├── data.yaml
- │ ├── icon.png
- │ ├── my-font.woff
- │ ├── my-font.woff2
- │ ├── style.css
│ └── index.js
└── /node_moduleswebpack.config.js
import path from 'node:path';
import { fileURLToPath } from 'node:url';
-import toml from 'toml';
-import yaml from 'yamljs';
-import json5 from 'json5';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
- module: {
- rules: [
- {
- test: /\.(png|svg|jpg|jpeg|gif)$/i,
- type: 'asset/resource',
- },
- {
- test: /\.(woff|woff2|eot|ttf|otf)$/i,
- type: 'asset/resource',
- },
- {
- test: /\.(csv|tsv)$/i,
- use: ['csv-loader'],
- },
- {
- test: /\.xml$/i,
- use: ['xml-loader'],
- },
- {
- test: /\.toml$/i,
- type: 'json',
- parser: {
- parse: toml.parse,
- },
- },
- {
- test: /\.yaml$/i,
- type: 'json',
- parser: {
- parse: yaml.parse,
- },
- },
- {
- test: /\.json5$/i,
- type: 'json',
- parser: {
- parse: json5.parse,
- },
- },
- ],
- },
};src/index.js
import _ from 'lodash';
-import './style.css';
-import Icon from './icon.png';
-import Data from './data.xml';
-import Notes from './data.csv';
-import toml from './data.toml';
-import yaml from './data.yaml';
-import json from './data.json5';
-
-console.log(toml.title); // output `TOML Example`
-console.log(toml.owner.name); // output `Tom Preston-Werner`
-
-console.log(yaml.title); // output `YAML Example`
-console.log(yaml.owner.name); // output `Tom Preston-Werner`
-
-console.log(json.title); // output `JSON5 Example`
-console.log(json.owner.name); // output `Tom Preston-Werner`
function component() {
const element = document.createElement('div');
- // Lodash, now imported by this script
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
- element.classList.add('hello');
-
- // Add the image to our existing div.
- const myIcon = new Image();
- myIcon.src = Icon;
-
- element.appendChild(myIcon);
-
- console.log(Data);
- console.log(Notes);
return element;
}
document.body.appendChild(component());그리고 추가했던 의존성을 제거합니다.
npm uninstall csv-loader json5 toml xml-loader yamljsNext guide
이제 Output Management로 넘어가 보겠습니다.
Further Reading
- SurviveJS의 Loading Fonts