일.webpack 을 포기하는 이유
1.webpack 모듈 가독성이 너무 낮음
// 모듈 참조
var _myModule1 = __webpack_require__(0);
var _myModule2 = __webpack_require__(10);
var _myModule3 = __webpack_require__(24);
// 모듈 정의
/* 10 */
/***/function (module, exports, __webpack_require__) {...}
// 소스 코드
_myModule2.default.xxx()
이러한 코드를 읽는 것은 매우 어렵습니다. 먼저 _myModule2 에 해당하는 __webpack_require__ id 를 찾고, 해당하는 모듈 정의를 찾은 후, 해당 모듈의 exports 에 무엇이 있는지 확인합니다. 이 모듈 정의 부분이 매우 성가셔서 참조 체인 읽기를 길게 합니다
물론 일반적으로 bundle 을 읽을 필요가 없으므로 이는 치명적이지 않습니다
2.파일이 매우 큼
위에서 언급한 바와 같이, 이러한 추가 bundle 코드 (서브모듈 정의, 서브모듈 참조 등) 로 인해 파일 크기가 팽창합니다. 이유는 다음과 같습니다:
-
소스 코드의 각 독립 파일 바깥에 모듈 정의 레이어가 감싸져 있음
-
모듈 내의 다른 모듈에 대한 참조에
__webpack_require__선언이 삽입됨 -
__webpack_require__도구 함수 자체의 크기
파일 크기는 전송 부하를 가져올 뿐만 아니라 Compile 시간에도 영향을 미칩니다. 패키징 솔루션의 bundle size 는 중요한 지표입니다
3.실행이 매우 느림
서브모듈 정의와 런타임 의존성 처리(__webpack_require__) 는 파일 크기 증가뿐만 아니라 성능도 크게 저하시킵니다. 아래 그림과 같습니다:

(이미지는 webpack_require is too slow 에서)
패키징 솔루션은 성능에 큰 영향을 미칩니다. 이는 가장 치명적이고 견딜 수 없는 점입니다
이.rollup 의 장점
1.파일이 매우 작음
거의 불필요한 코드가 없으며, 필요한 cjs, umd 헤더 외에는 bundle 코드가 기본적으로 소스 코드와 차이가 없고, 이상한 __webpack_require__, Object.defineProperty 등이 없습니다
bundle 크기 비교는 다음과 같습니다:
webpack 132KB
rollup 82KB
2.실행이 매우 빠름
위에서 언급한 바와 같이, 불필요한 코드가 거의 없으므로 webpack bundle 은 크기가 클 뿐만 아니라, 비비즈니스 코드 (__webpack_require__, Object.defineProperty) 실행 시간도 무시할 수 없습니다
rollup 은 이러한 추가적인 것을 생성하지 않으므로 실행 시간은 주로 Compile Script 와 Evaluate Script 에 있으며, 나머지 부분은 무시할 수 있습니다. 아래 그림과 같습니다:
[caption id="attachment_1531" align="alignnone" width="844"]
rollup-performance[/caption]
3.es 모듈 및 iife 형식 지원
// rollup
amd – Asynchronous Module Definition, used with module loaders like RequireJS
cjs – CommonJS, suitable for Node and Browserify/Webpack
es – Keep the bundle as an ES module file
iife – A self-executing function, suitable for inclusion as a <script> tag. (If you want to create a bundle for your application, you probably want to use this, because it leads to smaller file sizes.)
umd – Universal Module Definition, works as amd, cjs and iife all in one
// webpack
"var" - Export by setting a variable: var Library = xxx (default)
"this" - Export by setting a property of this: this["Library"] = xxx
"commonjs" - Export by setting a property of exports: exports["Library"] = xxx
"commonjs2" - Export by setting module.exports: module.exports = xxx
"amd" - Export to AMD (optionally named - set the name via the library option)
"umd" - Export to AMD, CommonJS2 or as property in root
es6 모듈 패키징을 지원하며, 베이스 라이브러리 등에 적합합니다. es6 프로젝트는 일반적으로 babel 로 한 번 변환하므로 한 번 의 통일된 babel 변환을 보장할 수 있습니다
iife 로 패키징하는 것을 지원하며 매우 작습니다. 또한 최종 bundle 크기를 보면:
default uglify
cjs 81KB 34K
amd 81KB 30KB
iife 81KB 30KB
umd 82KB 30KB
umd 가 cjs 보다 유리합니다. 이상해 보이지만 실제 결과는 확실히 그렇습니다. bundle 차이는 주로 함수명 단순화에 있으며, cjs bundle 에는 많은 긴 함수명이 유지되어 난독화되지 않았습니다
삼.rollup 의 단점
최신 버전 (0.50.0) 은 여전히 0.x 의 불안정한 상태에 있으며 버전 관련 문제가 많습니다 (심지어 일부 문제는 버전 다운그레이드로 해결해야 합니다)
- 플러그인 생태계 가 상대적으로 약하여 일부 일반적인 요구 사항을 충족할 수 없습니다
예를 들어 여러 의존성 라이브러리를 패키징하고 공통 의존 항목을 추출하는 것 (webpack 의 CommonsChunkPlugin)
-
이전 버전 (0.43) 은 순환 의존성 처리가 잘 안 되어 패키징/실행 오류가 발생할 수 있습니다
-
문서가 상대적으로 적어 문제를 빠르게 해결할 수 없습니다
예를 들어 일반적인 오류 'foo' is not exported by bar.js (imported by baz.js), Troubleshooting 은 FAQ 이지만 상세하고 신뢰할 수 있는 해결책을 제공하지 않습니다 (즉, 따라해도 해결되지 않을 수 있습니다)
사.babel 설정
babel 변환은 일반적으로 필수적이며 rollup/webpack 패키징 프로세스의 중간 처리环节으로, 해당하는 래퍼 플러그인을 제공하여 babel 설정을 포함할 수 있습니다. 실제로 파악해야 할 것은 babel 설정입니다
babel preset
In Babel, a preset is a set of plugins used to support particular language features.
일반적인 것은 다음과 같습니다:
-
es2015: ES6 기능만 지원.preset 에 이 항목이 있으면 ES6 구문을 ES5 로 변환합니다
-
stage-0: 최신 es7 및 es8 기능도 지원.실제로는 ES Stage 0 Proposals 를 의미합니다.preset 에 이 항목이 있으면 ESn 을 ES6 로 변환합니다
-
react: React JSX 지원
stage-0 은 가장 급진적인 접근 방식으로, babel 이 변환할 수 있는 모든 JS 신기능을 사용하는 것을 의미하며 안정성 여부는 관계없습니다. es2015 는 가장 보수적이며 사양이 이미 릴리스되어 기능 불안정 위험이 없습니다. stage-0 처럼 사용할 수 있는 것이 4 개 더 있습니다 (TC39 사양 제정 프로세스):
- stage-0 - Strawman: just an idea, possible Babel plugin.
- stage-1 - Proposal: this is worth working on.
- stage-2 - Draft: initial spec.
- stage-3 - Candidate: complete spec and initial browser implementations.
- stage-4 - Finished: will be added to the next yearly release.
P.S.최근 babel 은 babel-preset-env 를 제공하여 대상 플랫폼 환경에 따라 자동으로 preset 을 추가합니다. 따라서一堆의 esxxx 를 설치할 필요가 없지만 ES 지원만 제공하며 react 와 polyfill 은 내장되지 않았습니다. env 에 대한 자세한 정보는 babel-preset-env: a preset that configures Babel for you 를 참조하십시오
주의, 각 preset 은 한 단계 변환만 담당합니다.예를 들어 stage-0 은 ESn 을 ES6 로 변환할 수 있지만 ES5 는 아닙니다.즉, 구문이 매우 급진적인 프로젝트를 ES5 로 변환하려면 다음과 같은 babel 설정이 필요합니다:
{
"presets": [
["stage-0"],
["es2015", {"modules": false}]
],
"plugins": [
"external-helpers"
]
}
P.S.그 중 {"modules": false} 는 rollup 에 필요하며 babel-preset-es2015-rollup 을 대체하는 데 사용됩니다. external-helpers 의 역할은 뒤에 소개합니다
ES6 스타일을 유지하려면 다음과 같은 babel 설정이 필요합니다:
{
"presets": [
["stage-0"]
],
"plugins": [
"external-helpers"
]
}
변환 후 얻어지는 것은 프로젝트의 각 모듈 파일을 함께 연결한ES6 모듈이며, 코드의 class, const, let 은 모두 유지됩니다.ES6 는 이러한 기능을 지원하지만 async&await 등의 더 고급 기능은 ES6 로 변환됩니다
babel plugin
babel 의 3 가지 처리环节에서:
parsing -> transforming -> generation
플러그인은 2 번째环节 (transforming) 에 작용합니다.즉, 소스 구문解析 완료 후 이를 동등한 대상 구문으로 변환하는 단계에서 플러그인을 통해 추가 처리를 할 수 있습니다.예를 들어 간단한 것:
// 식별자 멤버 액세스를 리터럴 형식으로 변환,예: a.catch -> a['catch']
es3-member-expression-literals
// 식별자 멤버 선언을 리터럴 형식으로 변환,예:{catch: xxx} -> {'catch': xxx}
es3-property-literals
또한 일반적인 것은 다음과 같습니다:
// class 정적 속성 및 인스턴스 속성 지원,예:class A{instanceProp = 1; static staticProp = 2;}
transform-class-properties
// babel 이 사용하는 공통 메서드를 추출,예:_createClass, _inherits 등
external-helpers
// 상수 수정 확인,const 선언된 상수가 수정될 때 오류 보고
check-es2015-constants
따라서 babel plugin 은 대략3 가지 유형으로 분류됩니다:
-
ES5/ES6 패치,더 낮은 환경 관련 문제 수정 (es3-xxx, es2015-xxx)
-
정적 확인,예:const 수정 오류를 "컴파일" 단계로 앞당김
-
위험 기능,예:class-properties 등 stage 에 포함하기 적합한 논란의 기능
패치는 프로덕션 환경을 대상으로 하며,정적 확인은 품질 보증의 일부이며,위험 기능은 더 급진적인 JS 구문입니다
babel polyfill
babel 이 ESn 고급 구문을 ES5/ES3 로 변환할 때 4 가지 상황에 직면합니다:
-
간단한 문법 설탕.무뇌 변환,예:
for...of, arrow function -
복잡한 문법 설탕.도구 함수 처리 필요,예:
createClass, inherits -
낮은 환경에 누락된 기본 기능.polyfill 필요,예:
Symbol, Promise, String.repeat -
polyfill 할 수 없는 기능.예:
Proxy
낮은 환경에 누락된 기본 기능에 대해,babel 은 기본적으로 polyfill 을 제공하지 않습니다(babel 변환 결과에 polyfill 이 포함되지 않음).babel-polyfill 을 도입하거나 원하는 특수 polyfill(더 가볍고 작은 것,또는 더 신뢰할 수 있는 무거운 것) 을 도입할 수 있습니다
babelHelpers
babel 에는 변환 관련 도구 함수가 있습니다.예를 들어:
_typeof
_instanceof
_createClass
_interopRequireDefault
_classCallCheck
_inherits
asyncGenerator
이러한 도구 함수는 모두 babelHelpers 에 속하며,완전한 helpers 는 명령으로 생성할 수 있습니다:
npm install babel-cli --save-dev
// type 선택 가능 global/umd/var
./node_modules/.bin/babel-external-helpers -t umd > helpers.js
P.S.babelHelpers 생성에 대한 자세한 정보는 External helpers 를 참조하십시오
기본 설정에서는 이러한 도구 함수가 여러 번 생성되며,즉 bundle 내에 여러 _createClass 선언이 존재하여 중복 코드입니다.플러그인 설정으로 최적화하거나 제거할 수 있습니다
기본 설정,bundle 내에 여러 helper 선언 존재:
{
"presets": [
["es2015"]
]
}
external-helpers 플러그인을 추가하여 helper 선언을 bundle 상단으로 추출하고 여러 선언이 없도록:
{
"presets": [
["es2015"]
],
"plugins": [
"external-helpers"
]
}
외부 babelHelpers 를 참조하고 bundle 내에 helper 선언이 없음:
{
"presets": [
["es2015"]
],
"plugins": [
"external-helpers"
],
externalHelpers: true
}
일반적으로 external-helpers 를 추가하여 helper 를 bundle 상단으로 추출하면 최적화 요구 사항을 충족할 수 있으므로 babel 설정은적어도 external-helpers 플러그인을 추가하여 중복 helper 코드를 제거하는 것을권장합니다
externalHelpers: true 는여러 bundle(multi entry) 상황을 위한 것으로,추가하지 않으면 각 bundle 상단에 하나의 helper 선언이 있고,추가 후에는 bundle 이 모두 외부 helper 를 참조합니다.예를 들어:
babelHelpers.createClass(xxx)
babelHelpers 는 bundle 내에서 정의되지 않았으므로 미리 도입해야 합니다.예를 들어 web 환경:
<script src="babelHelpers.js"></script>
<script src="bundle.js"></script>
오.요약
webpack 과 비교하여 rollup 은 비할 데 없는 성능 이점을 가지고 있습니다.이는 의존성 처리 방식에 의해 결정되며,컴파일 시 의존성 처리 (rollup) 는 당연히 런타임 의존성 처리 (webpack) 보다 성능이 좋지만,순환 의존성 처리는 100% 신뢰할 수 없습니다.내부 구현 (또는 설계) 을 통해 피하도록 노력해야 하며,순환 의존성을 해결하는 일반적인기법은 다음과 같습니다:
-
의존성 향상,상호 의존하는 부분을 한 단계 향상
-
의존성 주입,런타임에 모듈 외부에서 의존성 주입
-
의존성 검색,런타임에 모듈 내부에서 의존성 검색
의존성 향상은 불합리한 설계를 위한 것으로,이러한 순환 의존성은 본래 피할 수 있습니다.예를 들어 A->B, B->A 는 C 를 제안하여 A->C, B->C 로 변환할 수 있습니다
피할 수 없는 순환 의존성의 경우 런타임 의존성 주입과 의존성 검색으로 해결할 수 있습니다.예를 들어 factory->A, A->factory, 간단한 의존성 주입 방안은:
// factory.js
import A from './A';
export create() {
// 생성자 함수 주입
return new A(create);
// 속성 주입
// let a = new A();
// a._createFromFactory = create;
// return a;
}
// A.js
class A {
constructor(create) {
this._createFromFactory = create;
}
// factory 에서 주입됨
_createFromFactory() {
return null;
}
}
따라서 순환 의존성은 설계/구현에서 해결할 수 있으며 큰 문제가 아닙니다
응용 장면而言、rollup 은 단일 파일 패키징에 가장 적합합니다.현재 rollup 은 multi entry 에 그다지 우호적이지 않기 때문입니다 (공통 의존 항목을 추출할 수 없음).또한 안정성 및 플러그인 생태계,문서 등은 webpack 보다 못하지만,성능을 엄격히 요구하는 장면에서는 rollup 이 유일한 선택입니다
아직 댓글이 없습니다