해당 글의 번역입니다 : https://lihautan.com/what-is-module-bundler-and-how-does-it-work/
아래 글을 읽기 전에 이해하고 오면 도움이 됩니다.
2022.11.27 - [웹성능최적화] - Javascript ES module과 순환 참조 해결하기
모듈 번들러가 뭔가요?
다음과 같은 이유로 모듈 번들러가 필요합니다.
- 브라우저가 모듈 시스템을 지원하지 않기 때문입니다.
- 코드의 종속성 관계를 관리하는 데 도움이 되며 종속성 순서대로 모듈을 로드합니다.
- 이미지 에셋, css 에셋 등 종속성 순서대로 에셋을 로드하는 데 도움이 됩니다.
<html>
<script src="/src/foo.js"></script>
<script src="/src/bar.js"></script>
<script src="/src/baz.js"></script>
<script src="/src/qux.js"></script>
<script src="/src/quux.js"></script>
</html>
각 파일에는 별도의 http 요청이 필요합니다.
이는 애플리케이션을 시작하기 위한 5번의 왕복 요청입니다.
5개의 파일을 모두 하나로 결합할 수 있다면 더 좋을 것입니다.
<html>
<script src="/dist/bundle.js"></script>
</html>
- 포함할 "파일"의 순서를 어떻게 유지하나요?
- "파일" 사이의 일종의 종속성 순서가 있으면 좋겠어요
- "파일" 간의 이름 충돌을 어떻게 방지하죠?
- 번들 내에서 사용되지 않은 "파일"을 어떻게 확인하죠?
- 어떤 파일이 다른 파일에 의존하나요?
- 파일에서 노출되는 인터페이스는 무엇인가요?
- 어떤 노출된 인터페이스가 다른 사람에 의해 사용되고 있나요?
이러한 정보는 주어진 문제를 각각 해결할 수 있습니다.
따라서 우리에게 필요한 것은 파일 간의 관계를 설명하는 선언적 방법이며,
이를 위해 JavaScript Module System이 탄생했습니다.
// CommonJS
const foo = require('./foo');
module.exports = bar;
// ES Modules
import foo from './foo';
export default bar;
코드를 어떻게 번들링하죠?
webpack과 rollup에 의해 생성된 번들을 면밀히 살펴보면,
가장 인기 있는 2개의 번들러가 번들링에서 완전히 다른 접근 방식을 취한다는 것을 알 수 있습니다.
"웹팩 방식"과 "롤업 방식"이라는 용어를 사용하겠습니다.
const PI = 3.141;
export default function area(radius) {
return PI * radius * radius;
}
export default function area(side) {
return side * side;
}
import squareArea from './square';
import circleArea from './circle';
console.log('Area of square: ', squareArea(5));
console.log('Area of circle', circleArea(5));
웹팩 방식
쉬운 설명을 위해 약간의 수정을 했습니다.
const modules = {
'circle.js': function(exports, require) {
const PI = 3.141;
exports.default = function area(radius) {
return PI * radius * radius;
}
},
'square.js': function(exports, require) {
exports.default = function area(side) {
return side * side;
}
},
'app.js': function(exports, require) {
const squareArea = require('square.js').default;
const circleArea = require('circle.js').default;
console.log('Area of square: ', squareArea(5))
console.log('Area of circle', circleArea(5))
}
}
webpackStart({
modules,
entry: 'app.js'
});
- 함수에 의해 래핑된 모듈 자체에 모듈 이름을 매핑하는 딕셔너리 입니다.
- "모듈 맵"은 레지스트리와 같으며 항목을 추가하여 모듈을 쉽게 등록할 수 있습니다.
- 이 함수는 모듈 스코프를 시뮬레이션 합니다
- 함수 자체를 "모듈 팩토리 함수"라고 합니다.
- 모듈이 인터페이스를 export 하고 다른 모듈에서 require할 수 있도록 몇 가지 매개 변수를 사용합니다.
- 종종 "런타임"이라고 하는 함수이며, 번들의 가장 중요한 부분입니다.
- "모듈 맵"과 엔트리 모듈을 사용하여 애플리케이션을 시작합니다.
webpack-bundle.js
쉬운 설명을 위해 약간의 수정을 했습니다.
function webpackStart({ modules, entry }) {
const moduleCache = {};
const require = moduleName => {
// if in cache, return the cached version
if (moduleCache[moduleName]) {
return moduleCache[moduleName];
}
const exports = {};
// this will prevent infinite "require" loop
// from circular dependencies
moduleCache[moduleName] = exports;
// "require"-ing the module,
// exported stuff will assigned to "exports"
modules[moduleName](exports, require);
return moduleCache[moduleName];
};
// start the program
require(entry);
}
- webpackStart는 "require" 함수와 모듈 캐시라는 두 가지를 정의합니다.
- "require" 함수는 CommonJS의 require와 다릅니다.
- "require"는 모듈 이름을 가져오고 모듈에서 내보낸 인터페이스를 반환합니다.
- 예: circle.js의 경우 { default: function area(radius){ ... } }입니다.
- 내보낸 인터페이스는 모듈 캐시에 캐시됩니다.
- 동일한 모듈 이름의 "require"를 반복해서 호출하면 "모듈 팩토리 함수"가 한 번만 실행됩니다.
- "require"가 정의된 상태에서 응용 프로그램을 시작하면 엔트리 모듈을 "require"합니다.
주 : 위와 같이 웹팩의 핵심 기능은 번들링임.
로더랑 플러그인은 번들링 이전 파일단위, 이후 전체 결과물에 대한 전처리 후처리만 시행할 뿐임.
웹팩은 js 번들러이기 때문에 js만 이해할 수 있기 때문.
아래 게시물에 로더, 플러그인에 대한 설명이 정리되어 있다.
롤업 방식
이제 webpack 번들이 어떻게 생겼는지 확인했습니다.
"롤업 방식" 번들을 살펴보겠습니다.
더 쉬운 설명을 위해 약간의 수정을 했습니다.
const PI = 3.141;
function circle$area(radius) {
return PI * radius * radius;
}
function square$area(side) {
return side * side;
}
console.log('Area of square: ', square$area(5));
console.log('Area of circle', circle$area(5));
첫째, 롤업 번들의 주요 차이점은 웹팩 번들에 비해 훨씬 작다는 것입니다.
- 모듈 맵이 없습니다.
- 모듈의 함수 래핑이 없습니다.
- 모듈 내에서 선언된 모든 변수/함수는 이제 전역 범위로 선언됩니다.
개별 모듈 범위에서 선언된 모든 것이 이제 전역 범위로 선언되었는데,
2개의 모듈이 동일한 이름의 변수/함수를 선언하면 어떻하죠?
모듈을 함수로 래핑하지 않는 것의 부작용 중 하나는 eval의 동작입니다. 자세한 설명은 여기(here)를 참조하세요.
둘째, 번들 내 모듈 순서가 중요합니다.
대체로 "롤업 방식"이 "웹팩 방식"보다 나은 것 같았습니다.
모듈 로더 에뮬레이션 방식을 제거하여, 더 작은 번들과 더 적은 런타임 오버헤드를 갖습니다.
"롤업 방식"의 단점은 뭘까요?
- 순환 참조를 잘 해결해주지 않습니다.
- (주 : 이건 다른 모듈 명세도 똑같은 것 같긴 합니다.)
const circle = require('./circle');
module.exports.PI = 3.141;
console.log(circle(5));
const PI = require('./shape');
const _PI = PI * 1
module.exports = function(radius) {
return _PI * radius * radius;
}
// cirlce.js first
const _PI = PI * 1; // throws ReferenceError: PI is not defined
function circle$Area(radius) {
return _PI * radius * radius;
}
// shape.js later
const PI = 3.141;
console.log(circle$Area(5));
이에 대한 해결책이 있나요?
짧은 대답은 '아니오'입니다.
const PI = require('./shape');
const _PI = () => PI * 1; // to be lazily evaluated
module.exports = function(radius) {
return _PI() * radius * radius;
}
// cirlce.js first
const _PI = () => PI * 1;
function circle$Area(radius) {
return _PI() * radius * radius;
}
// shape.js later
const PI = 3.141;
console.log(circle$Area(5)); // prints 78.525
요약
- 모듈 번들러는 여러 JavaScript 모듈을 하나의 JavaScript 파일로 결합하는 데 도움이 됩니다.
- 각 번들러는 다른 번들링 전략을 취합니다, 우리는 최신 번들러, 웹팩 및 롤업 중 2가지를 살펴보았습니다.
- "웹팩 방식":
- 모듈 맵 사용 함수를 사용
- 각 모듈을 함수로 래핑합니다.
- 모듈을 함께 붙이는 런타임 코드가 있습니다.
- "롤업 방식":
- 평탄화된 작은 번들 크기
- 평탄화라는건 단 하나의 파일에서 정의한 것처럼 변경한다는 것
- 모듈을 함수로 래핑하지 않습니다.
- 모듈의 임포트 순서가 중요합니다, 의존성의 순서를 정렬해야 합니다.
- 순서 문제 종속성에 따라 정렬 필요 순환 종속성이 작동하지 않을 수 있습니다.
- 평탄화된 작은 번들 크기
'FrontEnd' 카테고리의 다른 글
[번역] Rehydration(재수화)의 위험과 문제 해결하기 (0) | 2022.11.28 |
---|---|
[Vue3] 일반적인(intrinsic한) 컴포넌트 만들기 with typescript (0) | 2022.11.27 |
Javascript ES module과 순환 참조 해결하기 (0) | 2022.11.27 |
OpenAPI 3.0 튜토리얼 (0) | 2022.11.24 |
타입스크립트 프로젝트 도입을 통해 얻을수 있는 효과와 아닌것 (0) | 2022.11.20 |