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

[RxJS 5](https://github.com/Reactive-Extensions/RxJS)はES Modulesの `import` の書き方で容量が変わる。
利便性と容量のトレードオフの関係⚖

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

[公式ReadMe](https://github.com/ReactiveX/rxjs/blob/master/README.md)にも書かれていることだが、バンドルツールで検証したので数値結果とともに4通りの記述方法を示す。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/2544/93e28ace-452a-bec4-f1ad-562bb78b4d45.png)


## 1. 全部まるっとimport

```ts
import * as Rx from 'rxjs';

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

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

[👉出力結果のファイルはこちら](https://github.com/ics-creative/170330_webpack/blob/master/tutorial-babel-rxjs5/dist/import_all.js)



## 2. Observable だけimport

```ts
import {Observable} from 'rxjs';

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

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

[👉出力結果のファイルはこちら](https://github.com/ics-creative/170330_webpack/blob/master/tutorial-babel-rxjs5/dist/import_observable.js)

<blockquote class="twitter-tweet" data-conversation="none" data-lang="ja"><p lang="ja" dir="ltr">import {Observable} from &#39;rxjs&#39;と書くだけでwebpack等で小さくバンドルして欲しいものの、現状はできない。<br><br>一つの理由として、node_modules内のRx.jsはCommonJSなのでTreeShaking不可能だから。</p>&mdash; 池田 泰延 (@clockmaker) <a href="https://twitter.com/clockmaker/status/906044661348704256">2017年9月8日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

## 3. 個別import

```ts
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 されるので容量が小さく収まる。

[👉出力結果のファイルはこちら](https://github.com/ics-creative/170330_webpack/blob/master/tutorial-babel-rxjs5/dist/import_each.js)

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

```ts
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](https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md)」を参考にして欲しい。

```ts
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 されるので容量が小さく収まる。

[👉出力結果のファイルはこちら](https://github.com/ics-creative/170330_webpack/blob/master/tutorial-babel-rxjs5/dist/import_pipe.js)

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

誤り（容量が膨らむ）

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

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

```js
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となった。

```js
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となる。

```json
"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の両方を用意している。

- [Babel + RxJSのサンプル](https://github.com/ics-creative/170330_webpack/tree/master/tutorial-babel-rxjs5)
- [TypeScript + RxJSのサンプル](https://github.com/ics-creative/170330_webpack/tree/master/tutorial-typescript-rxjs5)

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