LHJ

I'm a FE developer.

자주 사용하는 모듈(로더)

22 Sep 2020 » node_webpack2

자주 사용하는 모듈(로더)

css-loader

웹팩은 모든 것을 모듈로 바라보기 때문에 자바스크립트 뿐만 아니라 스타일시트로 import 구문으로 불러올 수 있다.

// app.js
import './style.css'
/* style.css */
body {
    background-color: green;
}

CSS 파일을 자바스크립트에서 불러와 사용하려면 CSS를 모듈로 변환하는 작업이 필요하다.
css-loader가 그런 역할을 하는데 우리 코드에서 CSS 파일을 모듈처럼 불러와 사용할 수 있게끔 해준다.

app.js에 css 파일을 import하고 npm run build를 하면 에러가 난다.
이는 웹팩이 CSS 파일을 파싱(해석)하지 못한다는 뜻이다.
css-loader는 CSS 파일을 웹팩이 모듈로 받아들여 처리할 수 있도록 해준다.

npm i -D css-loader
// webpack.config.js
const path = require('path');

module.exports = {
    mode: "development",
    entry: {
        main: "./src/app.js"
    },
    output: {
        path: path.resolve('./dist'),
        filename: "[name].js"
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'css-loader'
                ],
            }
        ]
    }
}

이렇게 에러가 안나고 결과물인 main.js 에 css 속성이 문자열로 들어가있음을 확인할 수 있다.

그런데 막상 index.html 파일을 열어보면, 자바스크립트 안엔 css 속성이 들어가있지만 페이지에는 적용이 안되어있다.
HTML 코드가 DOM 모습으로 변환되어야 브라우저에서 문서가 보이듯이 CSS 코드도 CSSOM 형태로 바뀌어야만 브라우저에서 모습을 드러낸다.
그렇게 하려면 HTML 파일에서 CSS 코드를 직접 불러오거나, 아니면 inline style로 넣어줘야된다.
하지만 아직 그런 처리를 하지 않고 자바스크립트 파일에만 CSS가 들어가 있어서 아직 브라우저에 적용되지 않은 거다.

그래서 나온 것이 style-loader이다.
style-loader는 자바스크립트로 변경된 CSS 스타일 코드를 HTML에 넣어주는 코드이다.
CSS 파일을 모듈로 사용하고 웹팩으로 번들링하려면 css-loader와 style-loader 이 두 가지를 한꺼번에 사용해야된다.

npm i -D style-loader
// webpack.config.js
const path = require('path');

module.exports = {
    mode: "development",
    entry: {
        main: "./src/app.js"
    },
    output: {
        path: path.resolve('./dist'),
        filename: "[name].js"
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ],
            }
        ]
    }
}

로더는 한 파일에서 여러개가 실행될 수 있다.
실행은 배열의 뒤에서부터 앞으로 실행된다.

file-loader

CSS 뿐만 아니라 소스 코드에서 사용하는 모든 파일을 모듈로 사용하게끔 할 수 있다.
파일을 모듈 형태로 지원하고 웹팩 아웃풋에 파일을 옮겨주는 것이 file-loader가 하는 일이다.
가령 CSS에서 url() 함수에 이미지 파일 경로를 지정할 수 있는데 웹팩은 file-loader를 이용해서 이 파일을 처리한다.

/* style.css */
body {
    background-image: url(bg.png);
}

웹팩은 엔트리 포인트인 app.js가 로딩하는 style.css 파일을 읽을 것이다.
그리고 이 스타일시트는 url() 함수로 bg.png를 사용하는데 이때 로더를 동작시킨다.

이렇게 npm run build를 실행하면 또 에러가 발생한다.

npm i -D file-loader
// webpack.config.js
const path = require('path');

module.exports = {
    mode: "development",
    entry: {
        main: "./src/app.js"
    },
    output: {
        path: path.resolve('./dist'),
        filename: "[name].js"
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ],
            },
            {
                test: /\.png$/,
                use: [
                    'file-loader'
                ]
            }
        ]
    }
}

결과물 이미지 파일명이 위와 같이 해시값으로 되어있다.
웹팩은 빌드를 할 때마다 유니크한 값을 생성하는데, 그것이 위의 이미지에서 볼 수 있는 해시값이다.
아마 캐시 갱신을 위해 이렇게 처리한 것일 것이다.
정적 파일인 경우엔, 브라우저에서 캐싱하는 경우가 흔하다. 자바스크립트나 CSS, Image, font 이런 것들을 성능을 위해서 캐싱을 하고 있는데, 파일 내용이 달라졌는데, 파일명은 계속 같으면 이전에 저장했던 파일내용을 브라우저가 사용한다.
그래서 그런걸 예방하는 방법 중 하나가 위와 같이 고유 해시값을 주어 매번 빌드 때마다 이름을 변경하는 것이다.

하지만 배경이미지가 적용이 안되었다.
콘솔창에보면 에러가 떴다.

그 이유는 위와 같이 index.html 파일이 들어있는 폴더에 이미지가 없기 때문이다.
CSS를 보면 같은 폴더 경로로 들어가있다.
이를 해결하기 위해선, 웹팩 환경설정 파일을 아래와 같이 수정해보자.

// webpack.config.js
const path = require('path');

module.exports = {
    mode: "development",
    entry: {
        main: "./src/app.js"
    },
    output: {
        path: path.resolve('./dist'),
        filename: "[name].js"
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ],
            },
            {
                test: /\.png$/,
                loader: 'file-loader',
                options: {
                    publicPath: './dist',
                    name: '[name].[ext]?[hash]'
                }
            }
        ]
    }
}

위에 publicPath라는 설정은 file-loader가 처리하는 파일을 모듈로 사용했을 때, 경로 앞에 추가되는 문자열이다.
name이라는 설정은 file-loader가 파일을 아웃풋으로 복사할 때, 사용하게되는 파일 이름이다.

  1. [name] : 원본 파일명
  2. [ext] : 확장자명
  3. [hash] : 캐시 무력화를 위해서 쿼리스트링으로 매번 달라지는 해시값을 입력

위와 같이 설정을 수정하고 빌드하면

위와 같은 결과물을 볼 수 있다.
해시값 붙은 파일은 이전 빌드 때 나온 결과물이므로 무시하고, bg.png를 결과물 main.js에서 검색하면 위와 같이 앞에 ./dist/ 라고 프리픽스를 달아뒀고 그 뒤엔 쿼리스트링이 매번 바뀌는 해시값으로 설정이 된 것을 볼 수 있다.

호출할 때마다 main.js 안에 해시값 뒤에 붙은 형식으로 매번 호출하기 때문에, 해시값이 달라져서 캐시와 관련된 문제를 해결할 수 있다.

url-loader

사용하는 이미지 갯수가 많다면 네트워크 리소스를 사용하는 부담이 있고 사이트 성능에 영향을 줄 수도 있다.
만약 한 페이지에서 작은 이미지를 여러개 사용한다면 Data URI Scheme을 이용하는 방법이 더 낫다.
이미지를 Base64로 인코딩하여 문자열 형태로 소스코드에 넣는 형식이다.
url-loader는 이러한 처리를 자동화해주는 로더이다.

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAFCAYAAA==" alt="Red dot">

Data URI Scheme(데이터 유알아이 스키마)란 위와 같은 형태를 말한다.
이미지에 src 속성에 문자열 형태로 넣는 것을 말한다.
이것은 특정 형식인데,

  1. data 포멧을 정하고 : image/png
  2. 인코딩 방식을 정하고 : base64
  3. 그 값을 뒤에 문자열로 적어주면

이걸 이미지로 랜더링한다.
작은 이미지 파일은 이러한식으로 HTML 코드에 넣어주는 것이 보다 더 효율적이다.
왜냐면 image src에 경로를 넣으면 한번 더 네트웤 통신을 하는데, 위와 같이 써주면 네트워크 통신을 안하고 바로 화면에 그려주기 때문이다.

실습을 위해 이번엔 작은 이미지 파일을 사용해보자.

// app.js
import './app.css';
import nyancat from './nyancat.jpg';

document.addEventListener('DOMContentLoaded', () => {
    document.body.innerHTML = `<img src="${nyancat}">`;
})
// webpack.config.js
const path = require('path');

module.exports = {
    mode: "development",
    entry: {
        main: "./src/app.js"
    },
    output: {
        path: path.resolve('./dist'),
        filename: "[name].js"
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ],
            },
            {
                test: /\.(png|jpg|gif|svg)$/,
                loader: 'file-loader',
                options: {
                    publicPath: './dist',
                    name: '[name].[ext]?[hash]'
                }
            }
        ]
    }
}

이미지 크기를 확인해보자.

ll src

nyancat.jpg 크기가 18kb 정도이다.
bg.png에 비해선 많이 작은 크기인데, 요런것은 파일을 옮길 필요없이 바로 base64 인코딩으로 넣어버리면 된다.
그렇게 하기위해선 url-loader가 필요하다.

npm i -D url-loader
// webpack.config.js
const path = require('path');

module.exports = {
    mode: "development",
    entry: {
        main: "./src/app.js"
    },
    output: {
        path: path.resolve('./dist'),
        filename: "[name].js"
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ],
            },
            {
                test: /\.(png|jpg|gif|svg)$/,
                loader: 'url-loader',
                options: {
                    publicPath: './dist',
                    name: '[name].[ext]?[hash]',
                    limit: 20000, // 20kb
                }
            }
        ]
    }
}

limit 2000의 의미는 url-loader가 png, jpg, gif, svg 파일들을 처리할 때 20kb 미만의 파일은 url-loader에서 base64 인코딩을 활용하여 처리하도록 하는 것이다.
만약 20kb 이상이면 어떻게 될까?
그럴 경우엔 file-loader가 실행되게 된다.

20kb 미만은 자바스크립트 문자열로 변환하고 그 이상은 파일을 복사하는 동작을 한다.

위와 같이 설정했을 하고 빌드를 하면 nyancat.jpg는 main.js 안에 들어가게 될 것이고, 20kb 보다 큰 bg.png는 dist 폴더로 복사될 것이다.
dist 폴더를 삭제하고 다시 npm run build를 해보자.

bg.png는 20kb 보다 용량이 크므로 file-loader 방식으로 처리했고, nyancat.jpg는

이런식으로 base64 인코딩 방식으로 들어가졌다.

확인해보면 아까랑 다르게 base64 인코딩 방식으로 들어가있는 것을 알 수 있다.
bg.png랑 처리 방식이 다르다는 것을 알 수 있다.

정리

  1. css-loader : CSS 파일을 자바스크립트 모듈로 취급 가능하게 해주는 로더
  2. style-loader : css-loader에 의해 자바스크립트 문자열로 들어가진 CSS 스타일을 HTML에 주입시켜 브라우저에 스타일이 적용되도록 하는 역할을 한다.
  3. file-loader : 이미지 파일 등 assets을 모듈로 사용할 수 있게 한다. 그리고 사용한 파일을 output 경로(예시선 dist)로 이동하는 역할을 한다.
  4. url-loader : 파일을 base64로 인코딩해서 그 결과를 자바스크립트 문자열로 변환한다. 처리할 파일의 크기제한을 둬서 일정 파일만 처리하고 나머진 file-loader로 위임하는 역할을 한다.