JavaScript
TypeScript
RxJS
webpack
RxJS5

RxJS v5を小さくバンドルする方法。import文の書き方で容量が変わる(v6の書き方も紹介)

RxJS 5はES Modulesの import の書き方で容量が変わる。
利便性と容量のトレードオフの関係⚖

※注意:この記事はRxJS 5について言及したものだ。RxJS 5では import 文の書き方で容量が変わるが、RxJS 6ではツリーシェイキングが可能な構造になっているので、一般的な書き方で容量がコンパクトになる。RxJS 6についてもこの記事の末尾で検証結果を記載している。

公式ReadMeにも書かれていることだが、バンドルツールで検証したので数値結果とともに4通りの記述方法を示す。

image.png

1. 全部まるっとimport

import * as Rx from 'rxjs';

Rx.Observable.interval(200)
    .take(9)
    .map(x => x + '!!!')
    .bufferCount(2)
    .subscribe(value => console.log(value));

結果、953KB。RxJS全部が出力ファイルに格納される。

👉出力結果のファイルはこちら

2. Observable だけimport

import {Observable} from 'rxjs';

Observable.interval(200)
    .take(9)
    .map(x => x + '!!!')
    .bufferCount(2)
    .subscribe(value => console.log(value));

結果、953KB。RxJS全部が出力ファイルに格納される。
Rxという接頭語を書く必要が無くなる。

👉出力結果のファイルはこちら

3. 個別import

import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/take';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/bufferCount';

Observable.interval(200)
    .take(9)
    .map(x => x + '!!!')
    .bufferCount(2)
    .subscribe(value => console.log(value));

結果、85KB。使っているものだけが import されるので容量が小さく収まる。

👉出力結果のファイルはこちら

ただし、import {Observable} from 'rxjs/Observable'; だけだとオペレーターが入らないので個別にオペレーターを含める必要がある。これは個人的には好かない。

import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/take';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/bufferCount';

バインドオペレーターを使うと少し改善されるかもしれないが、ES2017時点で入っていない機能なので上記の記法は仕方がないと諦める。

4. Pipeable Operatorsでのimportを使う

RxJS 5.5 (平成29年10月)からの機能だが、pipe()メソッドがある。この方法だと、いい感じにかける。詳しくは公式リポジトリの「rxjs/pipeable-operators.md at master · ReactiveX/rxjs」を参考にして欲しい。

import {interval} from 'rxjs/observable/interval';
import {bufferCount} from 'rxjs/operators/bufferCount';
import {map} from 'rxjs/operators/map';
import {take} from 'rxjs/operators/take';

interval(200)
    .pipe(
        take(9),
        map(x => x + '!!!'),
        bufferCount(2),
    )
    .subscribe(value => console.log(value));

結果、78KB。使っているものだけが import されるので容量が小さく収まる。

👉出力結果のファイルはこちら

ただし、import文の書き方は細かくかかなければならない。まとめてimport文を書こうとすると容量が膨らんでしまう。

誤り(容量が膨らむ)

import {interval} from 'rxjs/observable/interval';
import {bufferCount, map, take} from 'rxjs/operators';

ベストプラクティス(容量が小さくなる)

import {interval} from 'rxjs/observable/interval';
import {bufferCount} from 'rxjs/operators/bufferCount';
import {map} from 'rxjs/operators/map';
import {take} from 'rxjs/operators/take';

RxJS 6ではpipe式だけになる

ちなみにRxJS 6(平成30年4月25日リリース)では、pipe式を使う方法になっている。次に示すのはRxJS 6のコードだ。このコードをwebpackでビルドすると、容量は83KBとなった。

import {interval} from 'rxjs';
import {bufferCount, map, take} from 'rxjs/operators';

interval(200)
  .pipe(
    take(9),
    map(x => x + '!!!'),
    bufferCount(2)
  )
  .subscribe(value => console.log(value));

考察(重要)

利便性を考慮すると全部まるっとimportするほうが手間がかからない。しかし、Too Fatな設計を避けるため個別importするほうが望ましい。

補足だが、Angular-CLIで作ったプロジェクトだとtslintによって、rxjsrxjs/Rxとしてimport文を書くことをブラックリストとして定義されている。RxJSの個別importは許可されているが、まるっとimportはLint Errorとなる。

"import-blacklist": [
  true,
  "rxjs",
  "rxjs/Rx"
],

https://github.com/angular/devkit/blob/master/packages/schematics/angular/application/files/tslint.json#L19-L23

これは、rxjs とまるっとimportすると容量が膨らむことを示唆しての禁止事項だと思う。

Angular-CLIは開発環境のお手本がくまなく定義されており学ぶべきものが多い

検証サンプル

今回の検証サンプルはGitHubにアップ済み。TypeScriptとECMAScript 2017の両方を用意している。

webpack 4でバンドルしているが、webpackの使い方は記事「最新版で学ぶwebpack入門 - ICS MEDIA」を参照してほしい。Tree Shakingが有効になるようにproductionモードを使っているが、上記の容量に関してはUglifyJSの影響を少なくするために敢えて optimization.minimize : trueを設定している。