웹팩에 대해 알아보자 - loader란?

방구석 코딩쟁이·2024년 2월 1일
0

FE개발환경

목록 보기
3/5
post-thumbnail
post-custom-banner

로더

웹팩은 모든 파일을 모듈로 인식합니다. 자바스크립트 파일 뿐만 아니라 CSS 파일, 이미지, 폰트까지도 전부 모듈로 바라보기 때문에 import 구문을 통해 이들을 모두 자바스크립트 코드 내부에 가져올 수 있게 되는 것입니다.

이것이 가능한 이유는 웹팩의 Loader 덕분입니다. Loader는 이미지를 data URL 형식의 문자열로 변환하거나 CSS 파일을 자바스크립트 코드에서 직접 로딩할 수 있도록 해줍니다.

로더는 파일 단위로 처리할 작업이 있을 경우 사용합니다

누군가가 이미 먼저 만들어둔 Loader를 사용하기 전에 저희가 직접 로더를 만들어봅시다.

1) 커스텀 로더 만들기

먼저 로더를 만들어보겠습니다.
저는 loader 디렉토리 안에 myloader.js 파일을 만들었습니다.

// loader/myloader.js
module.exports = function myCustomLoader(content) {
  console.log("로더를 만들었다~!");
  console.log(`content는 뭘까?`, content);
  return content;
};

로더를 적용하기 위해서는 webpack.config.jsmodule.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()로 변환되었음을 확인하셨을 겁니다.

2) 많이 사용하는 로더들

(1) css-loader

웹팩은 모든 파일들을 모듈로 인식하므로, CSS 파일도 import 문법으로 불러올 수 있습니다.

일단, css 파일을 자바스크립트에서 import하려면 CSS를 모듈로 변환하는 작업이 필요합니다. css-loader는 CSS 파일을 모듈처럼 불러와 사용할 수 있게 해줍니다.

먼저 loader를 설치해봅시다.
npm install -D css-loader

node_modulescss-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.jscss-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에 적용하지 않으므로 스타일이 적용되지 않습니다.

(2) style-loader

style-loader는 JS로 변경된 스타일을 동적으로 돔에 추가해주는 loader입니다. CSS를 번들링하기 위해 css-loaderstyle-loader를 함께 사용합니다.

먼저 style-loader를 설치합시다.
npm i -D style-loader

그리고, 웹팩 설정에 loader를 추가하면 되는데 여기서 use에 배열로 loader를 추가하게 되면, 배열의 뒤에서 앞으로 로더가 실행됩니다.
아래의 예제를 통해 설명을 하자면 css 파일엔 css-loaderstyle-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에 색이 입혀진 것을 볼 수 있게 됩니다.

(3) file-loader

CSS 파일 뿐만 아니라 소스코드에서 사용한 모든 파일을 모듈로 사용할 수 있도록 도와주는 loader입니다.
파일을 모듈 형태로 지원하고, 웹팩의 output에 파일을 옮겨줍니다.

  • CSS의 url() 함수에 이미지 파일 경로를 지정할 수 있는데, 웹팩은 file-loader를 사용해서 이미지 파일을 처리합니다.
  • import 구문으로 이미지 파일을 자바스크립트로 가져와서 작업할 수 있데 도와줍니다.

먼저 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가 처리하는 파일을 모듈로 사용할 때 경로 앞에 추가되는 문자열
    output에 설정한 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의 스타일을 보면 원하는 템플릿 형태로 잘 가져오고 있음을 알 수 있습니다.

(4) url-loader

사용하는 이미지 개수가 많다면 네트워크 리소스를 사용한다는 부담이 생기게 되고, 사이트 성능에 영향을 줄 수 있습니다. 만약 한 페이지에서 작은 이미지를 여러 개 사용할 경우 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"으로 설정한 다음 parserdataUrlConditiondataUrl로 변환시킬 최대 사이즈를 명시해주면 됩니다.

출처

profile
풀스택으로 나아가기
post-custom-banner

0개의 댓글