안녕하세요. 이번 시간에도 지난 시간에 이어 웹팩5 강좌를 계속 하겠습니다. 오늘은 CSS와 기타 이미지, 폰트 파일을 번들링하는 방법에 대해 알아보겠습니다.
지난 시간 JS 번들링에 대해서는 상식적으로 이해가 가죠? entry에서 시작해서 loader과 plugins의 가공 과정을 거쳐 output으로 나오는 거요. entry의 파일들부터 시작해서 import나 require로 엮어진 모든 js들을 하나 또는 소수의 파일로 묶습니다.
놀라운 것은... import나 require로 css랑 기타 이미지, html 또는 폰트 파일도 묶을 수 있습니다. import랑 require은 자바스크립트 모듈 구문인데 어떻게 그게 가능하냐고요? 저도 정확한 내부 과정은 잘 모르겠지만 그게 가능합니다. 대신 그게 가능하도록 설정을 해주어야 합니다.
일단 CSS부터 번들링하도록 설정해주겠습니다.
npm i -D style-loader css-loader mini-css-extract-plugin
css-loader는 css 파일들을 읽어주고 style-loader는 읽은 css 파일들을 style 태그로 만들어 head 태그 안에 넣어줍니다. 만약 style 태그 대신 css파일로 만들고 싶은 경우에 mini-css-extract-plugin을 사용하면 됩니다.
{
module: {
rules: [{
// 전 시간 babel-loader
}, {
test: /\.css$/,
use: ['style-loader', 'css-loader'],
}],
}
}
위와 같이 하면 됩니다. test 부분은 css파일에 적용하는 거라서 익숙한데 loader 대신 use가 있습니다. 여러 개의 로더를 동시에 사용할 때는 use를 사용합니다. (사실 loader 그대로 해도 됩니다) 아, 로더는 연달아서 사용이 가능합니다. babel-loader처럼 하나로 사용할 수도 있지만, style-loader과 css-loader처럼 두 개를 한 번에 사용할 수도 있습니다. 나중에 less-loader, sass-loader, postcss-loader과 함께 세 네개를 동시에 사용하기도 합니다. 뒤에서부터 실행되므로 먼저 css-loader로 처리한 뒤 그 결과물을 style-loader로 한 번 더 처리하는 겁니다.
이제 entry의 js파일 상단에서 require('app.css');
를 하면 알아서 읽어서 style 태그로 만들어줍니다. 신기하죠?
지금까지의 방법은 css를 style 태그로 만드는 거였습니다. 이제는 여러 css를 하나의 css 파일로 합치는 방법을 살펴보겠습니다. 아까 설치한 mini-css-extract-plugin을 사용합니다. webpack.config.js 제일 윗 부분에 require 해줍니다.
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
style-loader를 대체하지만 플러그인인만큼 module과 plugins에 모두 써줘야 합니다.
{
module: {
rules: [{
// 전 시간 babel-loader
}, {
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
}],
},
plugins: [
// 기타 플러그인
new MiniCssExtractPlugin({ filename: 'app.css' });
]
}
css-loader에서 처리한 결과물을 받아서 filename에 적힌 파일명으로 만들어줍니다.
이제 webpack을 실행하면 output에서 설정한 path 경로에 app.css라는 파일이 생겼을 겁니다. 그 파일을 기존에 css를 넣던 방식대로 link 태그로 head에 넣어주면 됩니다.
참고로 서버 사이드에서 사용할 때는 style-loader와 css-loader 대신 css-loader/locals로 적어주면 됩니다. css-loader 2버전부터는 로더 options로 exportOnlyLocals를 붙여주면 됩니다. 예를 들어 위의 코드에서 style-loader와 css-loader를 지우고 css-loader?exportOnlyLocals로 바꾸면 되는 것이죠. 서버는 css를 지원하지 않기 때문에 다르게 사용해야 합니다. 여기서 에러가 많이 나는데 절대로 style-loader를 넣어서는 안 됩니다! style-loader는 서버 사이드 렌더링을 지원하지 않습니다.
나중에 더 나아가 css modules(css-loader?modules, 물음표 뒤에 modules는 옵션을 의미합니다)나 sass(sass-loader), less(less-loader), postcss(postcss-loader)를 사용할 수도 있는데 그것까지는 다루지 않겠습니다. 공식 문서에 잘 나와있습니다.
이번에는 기타 파일들에 대한 번들링 방법을 알아보겠습니다. 먼저 추가적인 로더를 설치해줍니다.
npm i -D file-loader url-loader
file-loader는 특정 파일을 그대로 내보내줍니다. 아무리 그래도 png랑 js랑 합친다는 건 말이 안 되죠? 웹팩도 한계가 있습니다. url-loader가 좀 특이합니다. 설정한 사이즈보다 작은 이미지나 폰트 파일을 인라인화 합니다. 아니 이미지랑 폰트를 어떻게 인라인화 하냐고요? base64로 인코딩하면 됩니다. base64가 뭐냐고요? 다음과 같은 겁니다.
엄청나게 길죠? data-uri라고도 하는데요. 작은 파일은 따로 http 요청을 하느니 그냥 문자열로 인코딩해서 불러오겠다는 고육지책입니다. 원래라면 이미지 주소를 써야할 곳에 data:image/타입;base64, 이렇게 한 후 인코딩한 문자열들을 넣어주면 브라우저가 이미지로 인식합니다. 인라인화되어서 편한 것도 있지만 용량은 실제로 33% 정도 증가합니다.
{
module: {
rules: [{
... // 바벨로더와 css 로더들
}, {
test: /\.(ico|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'url-loader',
options: {
name: '[hash].[ext]',
limit: 10000,
},
}],
}
}
test 부분이 길어졌습니다. 다양한 파일들을 지원하려고 하는 만큼 정규식도 길어집니다. options가 중요합니다. limit(바이트 단위)보다 작은 파일은 base64로 인코딩해서 인라인화합니다. name에서는 지난 시간 output처럼 [hash], [name] 등을 쓸 수 있습니다. [ext]는 현재 확장자를 그대로 하겠다는 뜻입니다. 잠깐, 인라인화 하는데 결과물 이름은 왜 적어줄까요? 바로 limit보다 큰 파일은 알아서 file-loader가 처리하여 파일로 내보내주기 때문입니다.
역시 js 파일에서 require('./이미지경로/zero.png');
같이 하면 알아서 인라인화하거나 파일로 만들고 경로도 알아서 연결해줍니다. 아까 css보다 더 신기하죠? 리액트의 경우 보통 아래처럼 사용합니다.
<img src={require('이미지경로')} />
이미지나 폰트 외의 다른 파일(html, jade, pug, ejs)은 각각에 해당하는 로더가 다 있기 때문에 그걸 사용하시면 됩니다. 역시 import나 require을 하면 알아서 웹팩이 다 해결해줍니다. 웹팩 만세!
용량 관계로 다음 시간에 웹팩의 강력한 기능 중 하나인 코드 스플리팅과 청크에 대해 알아보겠습니다.
혼자서 한참 찾고 있었는데 정리 너무 잘 해주셔서 쉽게 했습니다