안녕하세요! 웹팩에 대한 포스팅입니다.
2000년대 초반의 웹 페이지는 페이지가 바뀔 때마다 새로운 HTML을 요청해서 화면을 그리는 방식이었습니다. 자바스크립트는 DOM을 조작하는 간단한 역할만 했기 때문에 HTML에 script태그로 넣는 것으로 충분했습니다.
AJAX가 유행했을 때도 자바스크립트 비중이 조금 더 커지긴했지만, 페이지당 자바스크립트 파일 몇 개 정도면 역시 충분했습니다.
SPA(single page application)은 하나의 HTML에 수십에서 수백개의 자바스크립트 파일을 포함하고, 자바스크립트의 비중 또한 예전과 비교할 수 없을 정도로 커졌습니다.
결론은 자바스크립트의 비중이 커지면서 자바스크립트 파일의 개수가 늘어난 것이 원인입니다.
자바스크립트 파일이 많아지게되면 기존에 생성된 전역변수를 덮어씌우는 실수가 생길 수도 있고 외부 패키지를 CDN방식으로 가져올 때에는 오타는 없는지 전전긍긍하며 찾아야합니다.
이 외에도 많은 자바스크립트 파일들에 의해 네트워크 병목현상이 생기는 등의 문제가 있습니다.
웹팩의 홈페이지에 방문하면 웹팩은 모던 자바스크립트 앱을 위한 정적 모듈 번들러라고 소개합니다.
각종 리소스들을 번들링 해주는 것입니다.
특히 번들링 된 하나의 번들파일을 이용하면 수 많은 자바스크립트파일을 요구하는 SPA방식 페이지에서 효과적입니다.
웹팩은 단순히 번들링만 해주는 것이 아니라 번들링을 수행하기전에 필요한 사전작업에 대한 설정들도 제공합니다.
물론 설정파일에서 옵션을 통해 설정합니다.
지금부터 한 번 직접 설정파일을 다루면서 알아보겠습니다.
먼저 실습할 폴더를 만들고 npm의로 프로젝트를 초기화 한뒤 웹팩관련 패키지들을 설치합니다.
mkdir webpack-test
cd webpack-test
npm init -y
npm i webpack webpack-cli
웹팩 설정파일은 webpack.config.js인데 따로 설정파일을 만들지 않으면 기본설정에 따라 번들리을 수행하게됩니다.
webpack-test 폴더 하위에 src폴더를 만들고 하위에 index.js를 만듭니다.
같은 위치에 util.js를 만듭니다.
// index.js
import { sayHello } from "./util";
function myFunc() {
sayHello('jack');
console.log('myFunc');
}
myFunc();
// util.js
export function sayHello(name) {
console.log('hello',name);
}
index.js를 엔트리포인트로 가지는 하나의 모듈이 완성됐습니다.
웹팩은 엔트리 기본설정으로 ./src/index.js 모듈을 인식합니다.
그리고 번들링된 파일출력 기본설정으로 ./dist/main.js로 출력합니다.
npx webpack
폴더의 루드경로에서 npx webpack을 실행하면 번들링을 수행합니다.
// ./dist/main.js
!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t,r){"use strict";var n;r.r(t),n="jack",console.log("hello",n),console.log("myFunc")}]);
기본설정으로 코드압축이 되어있기 때문에 파일을 열어보면 한 줄의 긴 코드가 있습니다.
뒷 부분에 우리가 작성한 코드도 보입니다.
번들 파일 전체가 하나의 즉시실행함수 (IIFE : Immediately Invoked Function Expression)으로 이루어져있습니다.
웹팩 공식문서 webpack Documentation : IIFE 에 IIFE에 대해 나와있습니다.
요약하면 IIFE를 이용해 전체를 감싸줘서 전역 스코프에 의한 오염을 방지하는 것입니다. IIFE를 이용하면 함수로 감싸줘서 필요한 부분만 노출할 수 있습니다.
IIFE를 사용하지 않으면 전역스코프에서 예기치않은 변수명 충돌등이 일어날 수도 있습니다.
기본설정에 대해서 확인해봤으니 이제 직접 설정파일을 만들어보겠습니다.
// webpack.config.js
const path = require('path')
module.exports = {
entry : './src/index.js',
output : {
filename : 'main.js',
path : path.resolve(__dirname, 'dist')
},
mode : 'production',
optimization : {minimizer:[]}
}
path는 경로관련 기능들을 제공해주는 NodeJS 기본모듈입니다.
entry는 모듈의 시작점입니다.
output은 출력할 파일에 대한 설정입니다.
mode는 production, development등이 있습니다. 번들링 환경에 대한 설정입니다.
optimization은 최적화 관련 각종 설정들을 할 수 있습니다.
minimizer에 빈 배열을 입력하면 코드압축을 하지 않습니다.
유용한 레퍼런스
로더는 웹팩에서 중요한 부분을 차지합니다. 아무런 기능을 해주지않고 단순하게 번들링만 하면 효용성이 크게 떨어집니다.
번들링을 사용하지 않고 각각의 정적파일로 관리할 때의 장점들을 모두 차용하기 위해 Loader라는 것을 이용합니다.
Loader는 모듈을 입력받아 원하는 형태로 변환한 후 새로운 모듈을 출력해줍니다.
설정에서 옵션의 속성으로 관리할 수 있는 이 Loader는 바벨같은 트랜스파일러 뿐 아니라 이미지파일, CSS파일 등에 대한 작업도 할 수 있습니다.
먼저 몇 가지 패키지를 설치해보겠습니다.
npm i babel-loader @babel/core @babel/preset-react react react-dom
웹팩에서 babel을 loader로 사용하기 위한 babel-loader
babel의 핵심기능을 갖고있어 반드시 설치해야하는 @babel/core
babel의 react관련 트랜스파일링을 위한 모든 플러그인을 모은 프리셋 @babel/preset-react
그리고 리액트와 리액트DOM관련 react , react-dom입니다.
// ./src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
function App() {
return (
<div className='conatainer'>
<h3 className='title'>webpack example</h3>
</div>
)
}
ReactDOM.render(<App/>, document.getElementById('root'))
#root컴포넌트에 App컴포넌트를 로드하는 간단한 리액트코드입니다.
프로젝트 루트에 babel.config.js를 만듭니다.
// babel.config.js
const presets = ['@babel/preset-react'];
module.exports = {presets};
웹팩으로 번들링을 명령하면 옵션의 babel로더 관련설정이 있기 때문에 babel.config.js에서 작성한대로 jsx코드도 읽을 수 있게됩니다.
//webpack.config.js
const path = require('path')
module.exports = {
entry : './src/index.js',
output : {
filename : 'main.js',
path : path.resolve(__dirname, 'dist')
},
module:{
rules:[
{
test:/\.js$/,
exclude : /node_modules/,
use:'babel-loader',
},
],
},
mode : 'production'
};
nodejs 기본모듈인 path를 이용해 경로설정을 준비합니다.
entry는 시작점으로 프로그램의 진입점이 될 js파일입니다.
output은 번들파일을 출력할 위치를 적어줍니다.
module에는 각종 설정을 적어줍니다.
module.rules에는 loader설정, parser설정 등을 적습니다.
module.rules의 test와 exclude는 정규표현식을 사용했습니다.
.js로 끝나는 확장자에 대해서 babel-loader를 사용하겠다고 작성했습니다.
exclude는 제외할 항목입니다.
./dist/index.html
<!DOCTYPE html>
<html lang="ko">
<body>
<div id='root'>
</div>
<script src='./main.js'></script>
</body>
</html>
스크립트 태그에 번들링된 js파일을 꼭 적어주세요
테스트 서버를 실행하기위해 serve패키지를 사용하겠습니다.
npm i -g serve
serve -s dist
dist폴더 내의 정적파일들을 실행시켜줍니다.
npm i css-loader style-loader
/* ./src/App.css */
.container {
border: 1px solid blue;
}
.title {
color : red;
}
// ./src/index.js
// ...
import Style from './App.css'
// ...
모듈의 엔트리에 추가되도록 index.js파일에 import해서 의존성을 주입합니다.
번들링시에 로더가 App.css파일도 검사하고 로더의 기능을 수행합니다.
의존성을 주입하지 않으면 엔트리에 존재하지 않아 제외됩니다.
const path = require('path')
module.exports = {
// ...
module:{
rules:[
//...
{
test:/\.css$/,
use:['style-loader','css-loader'],
},
],
},
mode : 'production'
};
rules속성에 style-loader와 css-loader가 사용되도록 로더 설정을 넣어줬습니다. use속성에 배열을 입력하며 오른쪽항목부터 먼저 실행됩니다.
.css확장자에 css-loader와 style-loader를 순서대로 사용하라는 것인데,
css-loader가 먼저 CSS데이터를 생성합니다.
그런 뒤 style-loader가 HTML head에 style태그를 삽입해줍니다.
./src 폴더에 data.json,logo.png, data.txt라는 이름으로 파일들을 만들었습니다.
// data.json
{
"name":"jack",
"age":"30"
}
// data.txt
안녕하세용
npm i file-loader raw-loader
./src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Style from "./App.css";
import Icon from './logo.png';
import Json from './data.json';
import Text from './data.txt';
function App() {
return (
<div className='container'>
<h3 className='title'>webpack example</h3>
<div>{`name : ${Json.name}, age : ${Json.age}`}</div>
<div>{`text : ${Text}`}</div>
<img src={Icon}/>
</div>
)
}
ReactDOM.render(<App/>, document.getElementById('root'))
const path = require('path')
module.exports = {
//...
module:{
rules:[
// ...
{
test:/\.(png|jpg|gif)$/,
use : 'file-loader'
},
{
test : /\.txt$/,
use : 'raw-loader'
},
],
},
mode : 'production'
};
먼저 file-loader는 모듈을 그대로 복사해서 dist폴더 밑에 복사본을 만들고, 모듈을 사용하는 쪽에는 그 경로를 넘겨줍니다. 그러니까 번들링되고나면 상대위치가 바뀌므로 경로처리까지 해주는 것입니다.
raw-loader는 모듈의 내용을 자바스크립트 코드로 가져옵니다. data.txt의 내용이 string으로 넘어왔을겁니다.
npx webpack
npx serve -dist
번들링된 파일로도 잘 작동하는 모습입니다.
이미지파일을 번들파일에 포함시키면 브라우저의 파일요청횟수를 줄일 수 있습니다.
하지만 번들파일 크기가 너무 커지면 실행속도에 문제가 생길 수 있으므로 크기제한을 두는게 좋습니다.
url-loader을 이용하면 크기가 작은 이미지파일만 번들파일에 포함시킬 수 있습니다.
npm i url-loader
const path = require('path')
module.exports = {
entry : './src/index.js',
output : {
filename : 'main.js',
path : path.resolve(__dirname, 'dist')
},
module:{
rules:[
// ...
{
test:/\.(png|jpg|gif)$/,
use : [
{
loader:'url-loader',
options:{
limit:8192,
fallback:require.resolve('file-loader')
}
}
]
},
//...
],
},
mode : 'production'
};
file-loader부분을 url-loader가 대체하도록 바꿨습니다. url-loader는 options의
여기까지로 포스팅 마치겠습니다.
다음 포스팅에서는 플러그인에 대해서 공부해보겠습니다.
@skibidi toilet, 아주 멋진. 나는 필요한 정보를 찾았다고 생각합니다. 귀하의 게시물을 검토하고 일부 데이터를 참조하겠습니다.