웹팩은 모든 파일을 모듈로 인식합니다. 자바스크립트 파일 뿐만 아니라 CSS 파일, 이미지, 폰트까지도 전부 모듈로 바라보기 때문에 import
구문을 통해 이들을 모두 자바스크립트 코드 내부에 가져올 수 있게 되는 것입니다.
이것이 가능한 이유는 웹팩의 Loader
덕분입니다. Loader
는 이미지를 data URL 형식의 문자열로 변환하거나 CSS 파일을 자바스크립트 코드에서 직접 로딩할 수 있도록 해줍니다.
로더는 파일 단위로 처리할 작업이 있을 경우 사용합니다
누군가가 이미 먼저 만들어둔 Loader
를 사용하기 전에 저희가 직접 로더를 만들어봅시다.
먼저 로더를 만들어보겠습니다.
저는 loader
디렉토리 안에 myloader.js
파일을 만들었습니다.
// loader/myloader.js
module.exports = function myCustomLoader(content) {
console.log("로더를 만들었다~!");
console.log(`content는 뭘까?`, content);
return content;
};
로더를 적용하기 위해서는 webpack.config.js
의 module.rules
속성의 배열에는 어떤 파일에 어떤 로더를 설정할 것인지를 나타내는 객체를 할당하면 됩니다.
test
속성과 use
속성을 가졌습니다.test
에는 로더를 적용시킬 파일을 정합니다. (파일명 뿐만 아니라 정규표현식으로 파일 패턴을 지정할 수 있습니다.)use
에는 test
에 해당하는 파일에 적용할 로더를 설정하는 부분입니다.우리가 하고자 하는 것은 webpack.config.js
을 통해 모든 js
파일에 myloader.js
라는 로더를 실행시키는 것입니다. 이를 위해 webpack.config.js
에 다음과 같은 옵션을 추가해줍니다.
module.exports = {
... // 이하 생략
module: {
rules: [
{
test: /\.js$/, //.js로 끝나는 파일을 찾아라
use: [path.resolve("./loader/myloader.js")], // 우리가 만든 커스텀 로더입니다!
},
],
}
}
지금까지 잘 따라왔다면 다음과 같은 폴더 구조를 가지고 있을 겁니다.
npm run build
명령어를 통해 webpack을 실행해봅시다.
웹팩은 모든 js 파일에 로더를 적용해주는 것을 볼 수 있습니다. 저희가 빌드할 파일은 app.js이고, app.js는 math.js를 사용하므로 2개의 js 파일에 커스텀 로더를 적용시킨 셈이죠.
또한, 로더의 첫 번째 인자로는 소스코드가 string 형태로 포맷되어 들어갔음을 확인할 수 있습니다.
loader
를 통해 소스코드를 변환시킬 수도 있습니다
myloader.js
파일을 아래와 같이 변경해봅시다.
module.exports = function myCustomLoader(content) {
return content.replace("console.log(", "alert(");
};
loader
를 통해서 console.log(
를 alert(
로 바꾸고자 합니다.
다시 빌드를 진행해보고 main.js에서 alert()
함수를 찾아보도록 하죠
확인해보니 console.log()
가 alert()
로 변환되었음을 확인하셨을 겁니다.
웹팩은 모든 파일들을 모듈로 인식하므로, CSS 파일도 import
문법으로 불러올 수 있습니다.
일단, css 파일을 자바스크립트에서 import
하려면 CSS를 모듈로 변환하는 작업이 필요합니다. css-loader
는 CSS 파일을 모듈처럼 불러와 사용할 수 있게 해줍니다.
먼저 loader를 설치해봅시다.
npm install -D css-loader
node_modules
에 css-loader
가 설치되었습니다.
css-loader
패키지의 index.js의 코드는 아래와 같습니다.
... // 생략된 코드
async function loader(content, map, meta) {
const rawOptions = this.getOptions(_options.default);
const callback = this.async();
if (this._compiler && this._compiler.options && this._compiler.options.experiments && this._compiler.options.experiments.css && this._module && (this._module.type === "css" || this._module.type === "css/auto" || this._module.type === "css/global" || this._module.type === "css/module")) {
this.emitWarning(new Error('You can\'t use `experiments.css` (`experiments.futureDefaults` enable built-in CSS support by default) and `css-loader` together, please set `experiments.css` to `false` or set `{ type: "javascript/auto" }` for rules with `css-loader` in your webpack config (now css-loader does nothing).'));
callback(null, content, map, meta);
return;
}
... // 이하 생략된 코드
}
loader의 첫 번째 인자로 content가 들어가는 게 보이네요.
이제 css 파일을 작성하고, js 파일에서 css 파일을 import 해봅시다.
// style.css
body {
background-color: green;
}
// app.js
import * as math from "./math.js";
import "./style.css";
console.log(math.sum(1, 2, 3));
이제 webpack.config.js
에 css-loader
를 추가해봅시다.
module: {
rules: [
{
test: /\.js$/, //.js로 끝나는 파일을 찾아라
use: [path.resolve("./loader/myloader.js")], // 우리가 만든 커스텀 로더입니다!
},
{
test: /\.css$/, // .css로 끝나는 파일을 찾아라
use: ["css-loader"], // css-loader를 사용해라
}
],
},
use
속성에 loader
의 경로를 지정해도 되지만 로더 패키지 이름을 문자열로 전달해도 됩니다.빌드를 해보면 css 코드가 JS로 변환되었음을 확인할 수 있습니다.
모듈로 변경된 CSS 파일을 돔에 추가되어야 브라우저가 해석을 할 수 있습니다. css-loader
는 CSS 파일을 JS 코드로 변경할 뿐, DOM에 적용하지 않으므로 스타일이 적용되지 않습니다.
style-loader
는 JS로 변경된 스타일을 동적으로 돔에 추가해주는 loader
입니다. CSS를 번들링하기 위해 css-loader
와 style-loader
를 함께 사용합니다.
먼저 style-loader
를 설치합시다.
npm i -D style-loader
그리고, 웹팩 설정에 loader
를 추가하면 되는데 여기서 use
에 배열로 loader
를 추가하게 되면, 배열의 뒤에서 앞으로 로더가 실행됩니다.
아래의 예제를 통해 설명을 하자면 css 파일엔 css-loader
와 style-loader
순서대로 로더를 적용합니다.
module: {
rules: [
{
test: /\.js$/, //.js로 끝나는 파일을 찾아라
use: [path.resolve("./loader/myloader.js")], // 우리가 만든 커스텀 로더입니다!
},
{
test: /\.css$/, // .css로 끝나는 파일을 찾아라
use: ["style-loader", "css-loader"], // css-loader, style-loader 순으로 적용
},
],
},
DOM에 적용되는지를 확인하기 위해서 루트 경로에 index.html 파일을 만들고, 빌드된 결과물을 연결해봅시다.
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./dist/main.js"></script>
</body>
</html>
HTML 파일을 열어보면 다음과 같이 body의 background에 색이 입혀진 것을 볼 수 있게 됩니다.
CSS 파일 뿐만 아니라 소스코드에서 사용한 모든 파일을 모듈로 사용할 수 있도록 도와주는 loader
입니다.
파일을 모듈 형태로 지원하고, 웹팩의 output에 파일을 옮겨줍니다.
url()
함수에 이미지 파일 경로를 지정할 수 있는데, 웹팩은 file-loader
를 사용해서 이미지 파일을 처리합니다.먼저 file-loader
를 설치해봅시다.
npm i -D file-loader
src 폴더 내부에 assets > images 폴더를 중첩하여 생성하고, images 폴더 내부에 png 확장자의 이미지를 넣습니다.
그 다음, css를 통해 png 확장자의 이미지를 배경으로 만듭니다.
body {
background: url(./assets/images/bg.png);
}
webpack.config.js
을 통해 png 확장자 파일에 file-loader를 적용하는 설정을 추가해봅시다.
module: {
rules: [
..., // 다른 loader 옵션들 생략
{
test: /\\.png$/,
loader: "file-loader",
},
],
},
이제 빌드를 해봅시다. 빌드 후에, dist 폴더에 해시코드를 파일명으로 가진 이미지가 출력됨을 알 수 있습니다.
왜 파일명을 해시코드로 변경해줄까?
성능을 위해 정적 파일(JS, CSS, 이미지, 폰트 등)의 경우, 브라우저에서 캐시를 해줍니다.
만약 동일한 파일명으로 반환해준다면, 브라우저의 캐시 기능으로 인해, 변경된 파일이 제대로 적용되지 않을 수 있기 때문입니다.
예를 들어, 산 그림이 있는 그림을 bg.png라는 파일명으로 저장해두고 빌드를 해두었는데 나중에 바다 그림이 있는 그림을 bg.png라는 파일명으로 대체한 경우, 동일한 bg.png이므로, 브라우저의 캐싱 정도에 따라 산 그림이 있는 bg.png를 렌더링할 수도 있습니다. 이를 막기 위해 출력 결과는 해시코드의 형태로 반환해주는 것이죠.
이대로 index.html 파일을 실행하면 이미지를 제대로 로딩하지 못함을 알 수 있습니다. 왜냐하면 CSS 파일을 로딩하면 background: url(./assets/images/bg.png);
코드에 의해 해당 경로에 있는 이미지를 찾으려고 시도할 것이기 때문입니다. 하지만 웹팩으로 빌드한 이미지 파일은 output
으로 지정한 dist
폴더 아래로 이동했으므로 이미지 로딩에 실패하게 됩니다.
이를 해결하기 위해 file-loader
의 옵션을 조정하여 경로를 찾을 수 있도록 해줘야 합니다.
module: {
rules: [
..., // 다른 loader 옵션들 생략
{
test: /\\.png$/,
loader: "file-loader",
options: {
publicPath: "./dist",
},
},
],
},
publicPath
: file-loader
가 처리하는 파일을 모듈로 사용할 때 경로 앞에 추가되는 문자열dist
폴더에 이미지 파일을 옮길 것이므로, ./dist
로 지정했습니다.background: url(./assets/images/bg.png);
이 코드가 background: url(./dist/<hashcode>.png)
로 변경됩니다.이제 실행해보면 다음과 같이 배경화면이 잘 적용되었음을 확인해볼 수 있습니다.
asset modules
는 로더를 추가로 구성하지 않아도 asset 파일 (폰트, 아이콘 등)을 사용할 수 있도록 해주는 모듈입니다
webpack 4 이전에는 다음과 같이 각 상황에 따라loader
를 설치해줬습니다.
raw-loader
: 파일을 문자열로 가져올 때url-loader
: 파일을 data URI 형식으로 번들에 인라인 추가할 때file-loader
: 파일을 출력 디렉터리로 내보낼 때webpack 5부터는 위의 로더를 대체하기 위해
asset 모듈
에 4개의 새로운 모듈 유형이 추가되었습니다.
asset/resource
: 별도의 파일을 내보내고 URL을 추출 (file-loader 대체)asset/inline
: asset의 data URI를 내보낸다. (url-loader 대체)asset/source
: asset의 소스 코드를 내보낸다. (raw-loader 대체)asset
은 data URI와 별도의 파일 내보내기 중에서 자동으로 선택
file-loader를 webpack 5로 업그레이드해보도록 하죠!
webpack.config.js 파일을 다음과 같이 변경해줍니다.
const path = require("path");
module.exports = {
mode: "development",
entry: {
main: "./src/app.js",
},
module: {
rules: [
..., // 다른 loader 설정
{
test: /\\.(png|jpg|jpeg?g)$/,
type: "asset/resource",
},
],
},
output: {
path: path.resolve("./dist"),
filename: "[name].js",
assetModuleFilename: "[name][ext]?[hash]",
},
};
type:"asset/resource"
는 file-loader
를 대체해줍니다.assetModuleFilename
를 통해 리소스에 접근하는 템플릿 형태를 변경해줄 수 있습니다.[hash][ext][query]
입니다.이렇게 변경하고 빌드하면, dist 폴더에는 아래와 같이 리소스가 생성됨을 확인할 수 있습니다.
그리고 html을 실행해서 body의 스타일을 보면 원하는 템플릿 형태로 잘 가져오고 있음을 알 수 있습니다.
사용하는 이미지 개수가 많다면 네트워크 리소스를 사용한다는 부담이 생기게 되고, 사이트 성능에 영향을 줄 수 있습니다. 만약 한 페이지에서 작은 이미지를 여러 개 사용할 경우 Data URI Scheme
을 이용하는 방법이 더 나을 수 있습니다. url-loader
를 통해 이런 처리를 자동화할 수 있습니다.
Data URI Scheme이란?
<img alt="" src="data:image/png;base64,iVBORw0KGgoAAA ANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4 //8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU 5ErkJggg==" style="width:36pt;height:36pt" />
와 같이 이미지 리소스를 Base64로 인코딩하여 문자열 형태로 소스코드에 넣는 방식입니다.
먼저 url-loader
를 설치해봅시다.
npm install -D url-loader
그 다음, webpack.config.js
에 웹팩 설정을 다음과 같이 추가해줍니다.
module: {
rules: [
...,// 다른 로더들
{
test: /(\.(jpg|jpeg)|\\.png)$/,
loader: "url-loader",
options: {
publicPath: "./dist",
// name: "[name].[ext]?[hash]",
limit: 20000, // 2kb
},
},
],
},
limit
보다 큰 파일은 fallback
옵션으로 지정한 loader
가 처리하는데, 기본값은 file-loader
입니다. 때문에 file-loader를 제거하고 url-loader의 limit을 잘 조정하면 file-loader는 알아서 실행됩니다.그리고 index.html에 이미지를 넣을 img 요소를 추가해봅시다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<img id="image" style="position: absolute; z-index: 100;"></img>
<script src="./dist/main.js"></script>
</body>
</html>
그리고 src/assets/images
에 20kb 미만의 이미지 파일을 넣어줍시다.
이 이미지 파일을 app.js에서 import 해봅시다.
import * as math from "./math.js";
import "./style.css";
import nyancat from "./assets/images/nyancat.jpg";
const imgElem = document.querySelector("#image");
imgElem.src = nyancat;
console.log(math.sum(1, 2, 3));
여기까지 왔다면 npm run build
명령어를 통해 빌드를 실행해보도록 합시다.
아래의 이미지를 보시면 dist 폴더에는 bg.png이 해시코드 형태의 이름으로 변환된 파일과 main.js
파일이 들어가 있음을 확인할 수 있습니다.
HTML을 실행해본 후에, image를 보면 아래처럼 Data URI 형태로 변환되어 있음을 확인하실 수 있습니다.
실행 결과는 다음과 같습니다!
url-loader
를 webpack 5로 업그레이드해보도록 하죠!
module: {
rules: [
...,// 다른 로더들 설정
{
test: /(\.(jpg|jpeg)|\\.png)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 20 * 1024,
},
},
},
],
},
output: {
path: path.resolve("./dist"),
filename: "[name].js",
assetModuleFilename: "[name][ext]?[hash]",
},
type
을 "asset"
으로 설정한 다음 parser
의 dataUrlCondition
에 dataUrl
로 변환시킬 최대 사이즈를 명시해주면 됩니다.
출처