본문으로 건너뛰기
黯羽轻扬매일 조금씩

webpack 에서 rollup 로

무료2026-03-05#Tool#webpack缺陷#rollup入门指南#slow __webpack_require__#rollup tutorial#babel guide

일.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 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 이 유일한 선택입니다

참고 자료

댓글

아직 댓글이 없습니다

댓글 작성