본문으로 건너뛰기

webpack 에서 rollup 로

무료2017-09-30#Tool#webpack缺陷#rollup入门指南#slow __webpack_require__#rollup tutorial#babel guide

왜 webpack 을 버리고 rollup 로 전환하는가?

일.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 ScriptEvaluate Script 에 있으며, 나머지 부분은 무시할 수 있습니다. 아래 그림과 같습니다:

[caption id="attachment_1531" align="alignnone" width="844"]rollup-performance 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

umdcjs 보다 유리합니다. 이상해 보이지만 실제 결과는 확실히 그렇습니다. 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->AC 를 제안하여 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 이 유일한 선택입니다

참고 자료

댓글

아직 댓글이 없습니다

댓글 작성