RxJS 5はES Modulesの import
の書き方で容量が変わる。
利便性と容量のトレードオフの関係⚖
※注意:この記事はRxJS 5について言及したものだ。RxJS 5では import
文の書き方で容量が変わるが、RxJS 6ではツリーシェイキングが可能な構造になっているので、一般的な書き方で容量がコンパクトになる。RxJS 6についてもこの記事の末尾で検証結果を記載している。
公式ReadMeにも書かれていることだが、バンドルツールで検証したので数値結果とともに4通りの記述方法を示す。
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
という接頭語を書く必要が無くなる。
import {Observable} from 'rxjs'と書くだけでwebpack等で小さくバンドルして欲しいものの、現状はできない。
— 池田 泰延 (@clockmaker) 2017年9月8日
一つの理由として、node_modules内のRx.jsはCommonJSなのでTreeShaking不可能だから。
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によって、rxjs
とrxjs/Rx
としてimport文を書くことをブラックリストとして定義されている。RxJSの個別importは許可されているが、まるっとimportはLint Errorとなる。
"import-blacklist": [
true,
"rxjs",
"rxjs/Rx"
],
これは、rxjs
とまるっとimportすると容量が膨らむことを示唆しての禁止事項だと思う。
Angular-CLIは開発環境のお手本がくまなく定義されており学ぶべきものが多い。
検証サンプル
今回の検証サンプルはGitHubにアップ済み。TypeScriptとECMAScript 2017の両方を用意している。
webpack 4でバンドルしているが、webpackの使い方は記事「最新版で学ぶwebpack入門 - ICS MEDIA」を参照してほしい。Tree Shakingが有効になるようにproduction
モードを使っているが、上記の容量に関してはUglifyJSの影響を少なくするために敢えて optimization.minimize : true
を設定している。