안녕하세요 이번에는 미루고 미뤘던 번들러에 대해서 공부를 해보고 글을 써보려고 합니다.
번들러가 뭘까요 ?
"Bundle
" 은 무슨 말일까요 ?
번역기의 힘을 빌려 한국어 뜻을 확인해 보니 묶음이라는 의미군요.
번들러란 이름에서도 유추할 수 있듯 어플리케이션에 필요한 여러 모듈들을 하나로 묶는 도구입니다.
왜 번들러를 사용해야 하나요 ?
그러게요
왜 모듈들을 묶어야 할까요 ? 그냥 없이 개발하면 안될까요 ?
한번 번들러 없이 만들어보겠습니다 !
번들러 없이 만들어보기
이 서비스는 덧셈, 뺄셈 기능이 있고 각각 input으로 받을 값을 계산하는 계산기입니다.
index.html
<html>
<head>
<meta charset="UTF-8" />
<title>계산기</title>
</head>
<body>
<header>
<h1>계산기</h1>
</header>
<main>
<form id="addForm">
<h2>덧셈</h2>
<input id="addFirstInput" />
<input id="addSecondInput" />
<button id="addButton">버튼</button>
</form>
<form id="minusForm">
<h2>뺄셈</h2>
<input id="minusFirstInput" />
<input id="minusSecondInput" />
<button id="minusButton">버튼</button>
</form>
</main>
</body>
</html>
음 좋습니다.
이제 기능을 더해보겠습니다.
add.js
const fristInput = document.querySelector("#addFirstInput");
const secondInput = document.querySelector("#addSecondInput");
const form = document.querySelector("#addForm");
const handleSubmit = (e) => {
e.preventDefault();
const num1 = Number(fristInput.value ?? 0);
const num2 = Number(secondInput.value ?? 0);
alert(num1 + num2);
};
form.addEventListener("submit", handleSubmit);
이제 add 기능을 테스트해 보겠습니다.
굳 잘 동작하네요 !
이제 minus 동작을 추가해 보겠습니다.
minus.js
const fristInput = document.querySelector("#minusFirstInput");
const secondInput = document.querySelector("#minusSecondInput");
const form = document.querySelector("#minusForm");
const handleSubmit = (e) => {
e.preventDefault();
const num1 = Number(fristInput.value ?? 0);
const num2 = Number(secondInput.value ?? 0);
alert(num1 - num2);
};
form.addEventListener("submit", handleSubmit);
이제 한번 실행시켜볼까요 ?
흠 에러가 발생했네요.
에러를 한번 확인해 보겠습니다.
문제 1 전역 변수
Uncaught SyntaxError: Identifier 'fristInput' has already been declared (at minus.js:1:1)
fristInput이 이미 선언되었다고 하네요...
음 분명 minus.js 파일에는 fristInput라는 변수가 하나밖에 없는데 왜 이런 문제가 발생할까요 ?
원인을 분석해 보면 add.js에 fristInput라는 변수가 있어서 저런 에러가 발생했네요.
즉시 실행 함수
즉시 실행 함수를 통해서 문제를 해결할 수 있을 거 같습니다.
(() => {
const fristInput = document.querySelector("#minusFirstInput");
const secondInput = document.querySelector("#minusSecondInput");
const form = document.querySelector("#minusForm");
const handleSubmit = (e) => {
e.preventDefault();
const num1 = Number(fristInput.value ?? 0);
const num2 = Number(secondInput.value ?? 0);
alert(num1 - num2);
};
form.addEventListener("submit", handleSubmit);
})();
이제 확인해 볼까요 ?
에러가 사라졌네요 좋습니다.
즉시 실행 함수로 스코프를 하나 만들어 전역스코프에 변수가 선언 안 되게 막아서 중복 선언 문제가 해결됐네요 !
다른 해결 방법은 없을까요 ?
type="module"
<html>
<head>
<meta charset="UTF-8" />
<title>계산기</title>
</head>
<body>
<header>
<h1>계산기</h1>
</header>
<main>
<form id="addForm">
<h2>덧셈</h2>
<input id="addFirstInput" />
<input id="addSecondInput" />
<button id="addButton">버튼</button>
</form>
<form id="minusForm">
<h2>뺄셈</h2>
<input id="minusFirstInput" />
<input id="minusSecondInput" />
<button id="minusButton">버튼</button>
</form>
</main>
<script type="module" src="./add.js"></script>
<script type="module" src="./minus.js"></script>
</body>
</html>
이렇게 type="module"
로 모듈로 바꾸면 됩니다.
모듈은 각각 독립된 스코프를 갖고 있기 때문이죠.
이제 이러면 정말 끝일까요 ? 다른 문제는 없을까요 ?
서비스를 다시 확인해 보겠습니다 !
문제 2 여러 번의 네트워크 요청
음 네트워크 요청을 여러 번 하네요.
기능이 점점 많아지면 여러 개의 파일이 생길거고 요청하는 파일의 수가 점점 많아질 거 같습니다.
그러면 다 한 파일로 짜면 되지 않을까요 ?
한파일로 짜기
const addFristInput = document.querySelector("#addFirstInput");
const addSecondInput = document.querySelector("#addSecondInput");
const addForm = document.querySelector("#addForm");
const handleAddSubmit = (e) => {
e.preventDefault();
const num1 = Number(addFristInput.value ?? 0);
const num2 = Number(addSecondInput.value ?? 0);
alert(num1 + num2);
};
addForm.addEventListener("submit", handleAddSubmit);
const minusFristInput = document.querySelector("#minusFirstInput");
const minusSecondInput = document.querySelector("#minusSecondInput");
const minusForm = document.querySelector("#minusForm");
const handleSubmit = (e) => {
e.preventDefault();
const num1 = Number(minusFristInput.value ?? 0);
const num2 = Number(minusSecondInput.value ?? 0);
alert(num1 - num2);
};
minusForm.addEventListener("submit", handleSubmit);
지금은 간단한 기능 2개밖에 없어서 별 문제가 없지만 나중에 서비스가 커질수록 관리가 힘들어질 거에요.
번들러 적용시켜보기
이러한 문제들을 번들러로 해결할 수 있을까요 ?
한번 최소한의 세팅만 해서 확인해 보겠습니다 !
webpack.config.js
const path = require("path");
module.exports = {
entry: "./index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
mode: "none",
};
이제 결과를 확인해 볼까요 ?
dist/bundle.js
/******/ (() => {
// webpackBootstrap
/******/ var __webpack_modules__ = [
,
/* 0 */ /* 1 */
/***/ () => {
const fristInput = document.querySelector("#addFirstInput");
const secondInput = document.querySelector("#addSecondInput");
const form = document.querySelector("#addForm");
const handleSubmit = (e) => {
e.preventDefault();
const num1 = Number(fristInput.value ?? 0);
const num2 = Number(secondInput.value ?? 0);
alert(num1 + num2);
};
form.addEventListener("submit", handleSubmit);
/***/
},
/* 2 */
/***/ () => {
const fristInput = document.querySelector("#minusFirstInput");
const secondInput = document.querySelector("#minusSecondInput");
const form = document.querySelector("#minusForm");
const handleSubmit = (e) => {
e.preventDefault();
const num1 = Number(fristInput.value ?? 0);
const num2 = Number(secondInput.value ?? 0);
alert(num1 - num2);
};
form.addEventListener("submit", handleSubmit);
/***/
},
/******/
];
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/
}
/******/ // Create a new module (and put it into the cache)
/******/ var module = (__webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {},
/******/
});
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](
module,
module.exports,
__webpack_require__
);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/
}
/******/
/************************************************************************/
/******/ /* webpack/runtime/compat get default export */
/******/ (() => {
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = (module) => {
/******/ var getter =
module && module.__esModule
? /******/ () => module["default"]
: /******/ () => module;
/******/ __webpack_require__.d(getter, { a: getter });
/******/ return getter;
/******/
};
/******/
})();
/******/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for (var key in definition) {
/******/ if (
__webpack_require__.o(definition, key) &&
!__webpack_require__.o(exports, key)
) {
/******/ Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
});
/******/
}
/******/
}
/******/
};
/******/
})();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) =>
Object.prototype.hasOwnProperty.call(obj, prop);
/******/
})();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __webpack_require__.r = (exports) => {
/******/ if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, {
value: "Module",
});
/******/
}
/******/ Object.defineProperty(exports, "__esModule", { value: true });
/******/
};
/******/
})();
/******/
/************************************************************************/
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be in strict mode.
(() => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _add__WEBPACK_IMPORTED_MODULE_0__ =
__webpack_require__(1);
/* harmony import */ var _add__WEBPACK_IMPORTED_MODULE_0___default =
/*#__PURE__*/ __webpack_require__.n(_add__WEBPACK_IMPORTED_MODULE_0__);
/* harmony reexport (unknown) */ var __WEBPACK_REEXPORT_OBJECT__ = {};
/* harmony reexport (unknown) */ for (const __WEBPACK_IMPORT_KEY__ in _add__WEBPACK_IMPORTED_MODULE_0__)
if (__WEBPACK_IMPORT_KEY__ !== "default")
__WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = () =>
_add__WEBPACK_IMPORTED_MODULE_0__[__WEBPACK_IMPORT_KEY__];
/* harmony reexport (unknown) */ __webpack_require__.d(
__webpack_exports__,
__WEBPACK_REEXPORT_OBJECT__
);
/* harmony import */ var _minus__WEBPACK_IMPORTED_MODULE_1__ =
__webpack_require__(2);
/* harmony import */ var _minus__WEBPACK_IMPORTED_MODULE_1___default =
/*#__PURE__*/ __webpack_require__.n(_minus__WEBPACK_IMPORTED_MODULE_1__);
/* harmony reexport (unknown) */ var __WEBPACK_REEXPORT_OBJECT__ = {};
/* harmony reexport (unknown) */ for (const __WEBPACK_IMPORT_KEY__ in _minus__WEBPACK_IMPORTED_MODULE_1__)
if (__WEBPACK_IMPORT_KEY__ !== "default")
__WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = () =>
_minus__WEBPACK_IMPORTED_MODULE_1__[__WEBPACK_IMPORT_KEY__];
/* harmony reexport (unknown) */ __webpack_require__.d(
__webpack_exports__,
__WEBPACK_REEXPORT_OBJECT__
);
})();
/******/
})();
음 뭐가 엄청 많아졌지만 add.js, minus.js가 하나의 파일로 잘 합쳐졌다는것은 확실하게 보이네요.
브라우저 콘솔창도 확인해 볼까요 ?
처음에 발생했던 전역 변수 문제는 발생하지 않네요 !
번들 결과물에서 각각 파일에 따른 지역 스코프를 자동으로 생성을 해줍니다.
파일이 하나니 네트워크 요청도 한 번만 발생하네요 !
번들러의 기능들
이제 번들러의 기능에 대해서 좀 더 알아보겠습니다 !
Resolution
Resolution은 의존성을 분석할 때 경로와 확장자를 정해주는 기능입니다. 쉽게 말하면 파일 위치를 정확하게 찾게 해주는 기능입니다.
예를 들어
import { useState } from 'react'
이런 코드가 있다면react
라는 라이브러리, react.tsx, react.ts, react.js 어디서 가져오는지 정해주는 기능을 합니다.
만약 이 계산기를 개발할 때 계산 로직이 있는 파일들은 .calc.js
라는 확장자로 써야 한다면
const path = require("path");
module.exports = {
entry: "./index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
mode: "none",
resolve: {
extensions: [".calc.js"],
},
};
이런 식으로 설정할 수 있습니다.
Load
Load는 무슨 기능일까요 ?
이 기능에 대해서 알아보기 전에 javascript로 작성한 코드를 typescript로 바꿔보겠습니다!
그리고 webpack.config.js를 수정해 보겠습니다.
const path = require("path");
module.exports = {
entry: "./index.ts",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
mode: "none",
resolve: {
extensions: [".ts", ".js"],
},
};
자 이제 빌드를 해볼까요 !
???
빌드에 실패했네요.
에러 메시지를 살펴볼까요 ?
Module parse failed: Unexpected token (1:60)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
"구문 분석에 실패했고 이 파일 형식을 처리하려면 로더가 필요하다."라네요.
로더는 Javascript가 아닌 다른 파일도 처리할 수 있게 해주는 기능입니다.
위에서 빌드 할 때 에러가 났던 원인은 Javascript가 아닌 Typescript를 처리하려고 해서 발생했습니다.
Typescript를 처리할 수 있게 Loader 세팅을 해볼까요 ?
module: {
rules: [{ test: /\.ts$/, use: "ts-loader" }],
},
이 코드를 webpack.config.js에 추가 후 빌드를 다시 해봤습니다.
이제 잘 동작하네요 !
Optimization
번들러에는 어떤 최적화 기법이 있을까요 ?
우선 현재 번들링 된 결과물을 살펴볼게요
dist/bundle.js
/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ([
/* 0 */
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
__exportStar(__webpack_require__(1), exports);
__exportStar(__webpack_require__(2), exports);
/***/ }),
/* 1 */
/***/ ((__unused_webpack_module, exports) => {
Object.defineProperty(exports, "__esModule", ({ value: true }));
const fristInput = document.querySelector("#addFirstInput");
const secondInput = document.querySelector("#addSecondInput");
const form = document.querySelector("#addForm");
const handleSubmit = (e) => {
var _a, _b;
e.preventDefault();
const num1 = Number((_a = fristInput.value) !== null && _a !== void 0 ? _a : 0);
const num2 = Number((_b = secondInput.value) !== null && _b !== void 0 ? _b : 0);
alert(num1 + num2);
};
form.addEventListener("submit", handleSubmit);
/***/ }),
/* 2 */
/***/ ((__unused_webpack_module, exports) => {
Object.defineProperty(exports, "__esModule", ({ value: true }));
const fristInput = document.querySelector("#minusFirstInput");
const secondInput = document.querySelector("#minusSecondInput");
const form = document.querySelector("#minusForm");
const handleSubmit = (e) => {
var _a, _b;
e.preventDefault();
const num1 = Number((_a = fristInput.value) !== null && _a !== void 0 ? _a : 0);
const num2 = Number((_b = secondInput.value) !== null && _b !== void 0 ? _b : 0);
alert(num1 - num2);
};
form.addEventListener("submit", handleSubmit);
/***/ })
/******/ ]);
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
/******/
/******/ // startup
/******/ // Load entry module and return exports
/******/ // This entry module is referenced by other modules so it can't be inlined
/******/ var __webpack_exports__ = __webpack_require__(0);
/******/
/******/ })()
;
Minification
Minification은 코드를 최소화하는 최적화 기법이에요.
webpack에서는 mode 값을 production으로 활성화시킬 수 있습니다.
webpack.config.js
const path = require("path");
module.exports = {
entry: "./index.ts",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
mode: "production",
resolve: {
extensions: [".ts", ".js"],
},
module: {
rules: [{ test: /\.ts$/, use: "ts-loader" }],
},
};
이제 결과를 확인해 볼까요 ?
dist/bundle.js
(()=>{"use strict";var e={618:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0});const r=document.querySelector("#addFirstInput"),n=document.querySelector("#addSecondInput");document.querySelector("#addForm").addEventListener("submit",(e=>{var t,u;e.preventDefault();const o=Number(null!==(t=r.value)&&void 0!==t?t:0),i=Number(null!==(u=n.value)&&void 0!==u?u:0);alert(o+i)}))},277:function(e,t,r){var n=this&&this.__createBinding||(Object.create?function(e,t,r,n){void 0===n&&(n=r);var u=Object.getOwnPropertyDescriptor(t,r);u&&!("get"in u?!t.__esModule:u.writable||u.configurable)||(u={enumerable:!0,get:function(){return t[r]}}),Object.defineProperty(e,n,u)}:function(e,t,r,n){void 0===n&&(n=r),e[n]=t[r]}),u=this&&this.__exportStar||function(e,t){for(var r in e)"default"===r||Object.prototype.hasOwnProperty.call(t,r)||n(t,e,r)};Object.defineProperty(t,"__esModule",{value:!0}),u(r(618),t),u(r(345),t)},345:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0});const r=document.querySelector("#minusFirstInput"),n=document.querySelector("#minusSecondInput");document.querySelector("#minusForm").addEventListener("submit",(e=>{var t,u;e.preventDefault();const o=Number(null!==(t=r.value)&&void 0!==t?t:0),i=Number(null!==(u=n.value)&&void 0!==u?u:0);alert(o-i)}))}},t={};!function r(n){var u=t[n];if(void 0!==u)return u.exports;var o=t[n]={exports:{}};return e[n].call(o.exports,o,o.exports,r),o.exports}(277)})();
오 코드가 엄청 읽기 어려워졌네요.
불필요한 띄어쓰기, 줄바꿈이 사라졌네요.
안에 코드들도 확인해볼까요 ?
확인이 어려워서 prettier로 읽기 좋게 바꾸고 내용을 확인해 보겠습니다.
dist/bundle.js(prettier 적용 후)
(() => {
"use strict";
var e = {
618: (e, t) => {
Object.defineProperty(t, "__esModule", { value: !0 });
const r = document.querySelector("#addFirstInput"),
n = document.querySelector("#addSecondInput");
document.querySelector("#addForm").addEventListener("submit", (e) => {
var t, u;
e.preventDefault();
const o = Number(null !== (t = r.value) && void 0 !== t ? t : 0),
i = Number(null !== (u = n.value) && void 0 !== u ? u : 0);
alert(o + i);
});
},
277: function (e, t, r) {
var n =
(this && this.__createBinding) ||
(Object.create
? function (e, t, r, n) {
void 0 === n && (n = r);
var u = Object.getOwnPropertyDescriptor(t, r);
(u &&
!("get" in u
? !t.__esModule
: u.writable || u.configurable)) ||
(u = {
enumerable: !0,
get: function () {
return t[r];
},
}),
Object.defineProperty(e, n, u);
}
: function (e, t, r, n) {
void 0 === n && (n = r), (e[n] = t[r]);
}),
u =
(this && this.__exportStar) ||
function (e, t) {
for (var r in e)
"default" === r ||
Object.prototype.hasOwnProperty.call(t, r) ||
n(t, e, r);
};
Object.defineProperty(t, "__esModule", { value: !0 }),
u(r(618), t),
u(r(345), t);
},
345: (e, t) => {
Object.defineProperty(t, "__esModule", { value: !0 });
const r = document.querySelector("#minusFirstInput"),
n = document.querySelector("#minusSecondInput");
document.querySelector("#minusForm").addEventListener("submit", (e) => {
var t, u;
e.preventDefault();
const o = Number(null !== (t = r.value) && void 0 !== t ? t : 0),
i = Number(null !== (u = n.value) && void 0 !== u ? u : 0);
alert(o - i);
});
},
},
t = {};
!(function r(n) {
var u = t[n];
if (void 0 !== u) return u.exports;
var o = (t[n] = { exports: {} });
return e[n].call(o.exports, o, o.exports, r), o.exports;
})(277);
})();
좀 더 자세하게 확인해 볼까요 ?
Before
const fristInput = document.querySelector("#addFirstInput");
const secondInput = document.querySelector("#addSecondInput");
const form = document.querySelector("#addForm");
const handleSubmit = (e) => {
var _a, _b;
e.preventDefault();
const num1 = Number(
(_a = fristInput.value) !== null && _a !== void 0 ? _a : 0
);
const num2 = Number(
(_b = secondInput.value) !== null && _b !== void 0 ? _b : 0
);
alert(num1 + num2);
};
form.addEventListener("submit", handleSubmit);
기존에는 이렇게 직접 만들었던 변수 이름이 그대로 보존됩니다.
After
const r = document.querySelector("#addFirstInput"),
n = document.querySelector("#addSecondInput");
document.querySelector("#addForm").addEventListener("submit", (e) => {
var t, u;
e.preventDefault();
const o = Number(null !== (t = r.value) && void 0 !== t ? t : 0),
i = Number(null !== (u = n.value) && void 0 !== u ? u : 0);
alert(o + i);
});
적용 후에는 변수 이름이 엄청 짧아졌네요. 또한 form같이 다른곳에서 사용 안 하는 변수는 사라지는 현상도 보여집니다.
전체적인 구조가 e
라는 객체 안에 숫자의 key에 함수가 담기고 277함수에서 다른 모듈들을 불러오는 역할을 하고 즉시 실행 함수 마지막 부분에서 277함수를 실행시키는 구조입니다.
파일 크기도 확인해 볼까요 ?
4KB => 1KB
로 많이 크기가 많이 감소했네요 !
Tree Shaking
Tree Shaking은 사용하지 않는 코드를 제거하는 최적화 기법입니다.
기능을 확인해 보기 위해서 utils.ts 파일을 추가하고 적용해 보겠습니다.
utils.ts
export const stringToNumber = (str: string) => {
console.log(`${str}을 숫자로 변환`);
return Number(str);
};
export const numberToString = (num: number) => {
console.log(`${num}을 문자열로 변환`);
return String(num);
};
add.ts
import { stringToNumber } from "./utils";
const fristInput = document.querySelector("#addFirstInput") as HTMLInputElement;
const secondInput = document.querySelector(
"#addSecondInput"
) as HTMLInputElement;
const form = document.querySelector("#addForm");
const handleSubmit = (e: Event) => {
e.preventDefault();
const num2 = stringToNumber(secondInput.value ?? "0");
const num1 = stringToNumber(fristInput.value ?? "0");
alert(num1 + num2);
};
form.addEventListener("submit", handleSubmit);
export {};
minus.ts 파일도 적용시켰습니다.
이제 빌드를 하고 결과물을 확인해 볼까요 ?
dist/bundle.js의 일부
748: (e, t) => {
Object.defineProperty(t, "__esModule", { value: !0 }),
(t.numberToString = t.stringToNumber = void 0),
(t.stringToNumber = function (e) {
return console.log("".concat(e, "을 숫자로 변환")), Number(e);
}),
(t.numberToString = function (e) {
return console.log("".concat(e, "을 문자열로 변환")), String(e);
});
},
음 분명 stringToNumber
이 함수만 사용하는데 왜 번들 결과에 numberToString
함수도 포함되어 있을까요 ?
원인은 Common js
때문입니다.
Common js는 런타임에 모듈을 동적으로 로드하고 해석합니다. 즉 모듈의 어떤 부분이 사용될지 빌드 할 때 결정할 수 없고 번들러가 트리쉐이킹하기 어렵습니다.
ESM으로 전환을 하면 문제가 해결될까요 ? 한번 전환해서 확인해 보겠습니다. tsconfig.json파일을
before
"module": "CommonJS",
after
"module": "ESNext",
이렇게 바꾸면 됩니다.
이제 바꿨으니 결과를 확인해 볼까요 ?
dist/bundle.js
(() => {
"use strict";
var e = function (e) {
return console.log("".concat(e, "을 숫자로 변환")), Number(e);
},
t = document.querySelector("#addFirstInput"),
u = document.querySelector("#addSecondInput");
document.querySelector("#addForm").addEventListener("submit", function (n) {
var r, o;
n.preventDefault();
var l = e(null !== (r = u.value) && void 0 !== r ? r : "0"),
d = e(null !== (o = t.value) && void 0 !== o ? o : "0");
alert(d + l);
});
var n = document.querySelector("#minusFirstInput"),
r = document.querySelector("#minusSecondInput");
document.querySelector("#minusForm").addEventListener("submit", function (t) {
var u, o;
t.preventDefault();
var l = e(null !== (u = r.value) && void 0 !== u ? u : "0"),
d = e(null !== (o = n.value) && void 0 !== o ? o : "0");
alert(d - l);
});
})();
결과를 보면
stringToNumber함수는 이렇게 잘 남아있고
var e = function (e) {
return console.log("".concat(e, "을 숫자로 변환")), Number(e);
},
사용되지 않는 numberToString는 결과에 포함되지 않습니다 !
이번에는 번들러에 매우 기본적인 내용에 대해서 공부해 봤는데요. 이 내용뿐만 아니라 더 많은 기능들도 있고 여러 번들러들도 있습니다.
혹시 잘못된 내용이 있거나 부족한 부분이 있으면 댓글로 알려주세요 !
공부했던 자료들 출처
https://youtu.be/QfU5REp8sjQ?si=ixqWAuex1edJlhQx
https://www.heropy.dev/p/x8iedW
https://blog.leehov.in/24
https://webpack.kr/
'frontend' 카테고리의 다른 글
🚀 "우당탕탕 도서관" 프론트엔드 개발자 글쓰기 커뮤니티 2기 모집 안내 🚀 (0) | 2024.05.04 |
---|---|
??? : 추상화 잘합니다. (대충 할 말 빙빙 돌려 말한다는 뜻) (0) | 2024.04.01 |
폼 미쳤다... React Hook Form (0) | 2024.03.25 |
React Suspense 알아보기 (1) | 2024.03.25 |
SSR 진짜 CSR보다 빠를까 ? (1) | 2024.03.25 |