IonicでLazy Loadingに対応する

Ionicは3系からモジュールのLazy Loadingに簡単に対応できるようになりました。Lazy Loadingに対応する手順のまとめです。

Ionic公式ブログが丁寧すぎてただの日本語まとめになってしまってるけどきにしない。

## 執筆時環境

```bash
$ ionic info
```

global packages:

    @ionic/cli-utils : 1.5.0
    Ionic CLI        : 3.5.0

local packages:

    @ionic/app-scripts              : 2.0.2
    @ionic/cli-plugin-ionic-angular : 1.3.2
    Ionic Framework                 : ionic-angular 3.5.3

## Lazy Loadingって

そもそもLazy Loadingとは何か、そしてそれを実装すると何が嬉しいのかを確認しておきます。

日本語にすると遅延読み込み。モジュールバンドラを用いてアプリを構築している場合、アプリの機能が増えるごとにバンドルファイルが肥大化し、結果としてページのロードに（起動に）時間がかかることになります。そこでコンポーネントを切り出して別のチャンク（かたまり）にしておき、ページの表示に必要になったときに読み込めばいいよねというのがLazy Loadingです。これによりメインのバンドルファイルサイズを抑え、起動の高速化が期待できます。

## 実装

IonicではNgModuleの仕組みを使ってLazy Loadingに対応します。

Page（root Component）の場合とそれ以外のComponent/Directive/Pipeの場合とで対応が若干異なるので、順番に見ていきます。

### Pageの場合

ここではHomePageというPageを別チャンクに切り出すことを考えます。

まずはコアモジュールからHomePageを取り除きます。

```ts:src/app/app.module.ts
@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [ ... ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [ ... ]
})
export class AppModule {}
```
元々`src/app/app.module.ts`のNgModuleにおいてdeclarationsとentryComponentsで`HomePage`を参照してるはずなので、そのどちらからもHomePageの記述を削除します。


`HomePage`を含むモジュールを作成します。`src/pages/home`下に`home.module.ts`ファイルを作成し、次のように記述します。

```ts:home.module.ts
import { NgModule } from '@angular/core';
import { IonicPageModule } from 'ionic-angular';
import { HomePage } from './home';

@NgModule({
  declarations: [ HomePage ],
  imports: [ IonicPageModule.forChild(HomePage) ]
})
export class HomePageModule {}
```

次に`src/pages/home/home.ts`に`@IonicPage`Decoratorを追加します。

```ts:src/pages/home/home.ts
import { Component } from '@angular/core';
import { IonicPage, NavController } from 'ionic-angular';

@IonicPage()
@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  constructor(public navCtrl: NavController) {
  }
}
```

最後にrootへの配置やページ遷移などで`HomePage`を参照していた箇所を修正します。
下記のように文字列に変更しimport文も削除します。

```ts:例
//before
rootPage:any = HomePage;
//after
rootPage:any = 'HomePage';

//before
this.navCtrl.push(HomePage);
//after
this.navCtrl.push('HomePage');
```

以上がPageの対応手順です。

#### ビルド結果を確認する

ここまでで一旦ビルドにより生成されたファイルを確認しておきます。www/buildの中を見ると、メインのバンドルファイルであるmain.jsのサイズが従来より小さくなり、0.jsというファイルが別途生成されていることが確認できます。0.jsがHomePageModuleに相当します。

### Component/Directive/Pipeの場合

Page以外のComponent/Directive/Pipeの場合は2通りの方針があります。

(a) Component/Directive/Pipeそれぞれに個別にモジュールを定義する
(b) 共有モジュールを定義する

方針aは*.module.tsを個別に作る手間がかかりますが、読み込みは必要最小限に出来ます。 
方針bは取り回しが楽ですが、余分な読み込みが生じます。

それぞれの開発方針に応じて選択すればよいと思います。

#### (a)Component/Directive/Pipeそれぞれに個別にモジュールを定義する

Page同様に各Componentに*.module.tsファイルを用意します。

ここでは次のようなディレクトリ構成で考えます。

```
/src
├── app
├── assets
├── components
│   ├── child-component
│   │   ├── child-component.html
│   │   ├── child-component.module.ts
│   │   ├── child-component.scss
│   │   └── child-component.ts
│   └── parent-component
│       ├── parent-component.html
│       ├── parent-component.module.ts
│       ├── parent-component.scss
│       └── parent-component.ts
├── directives
│   ├── my-directive.module.ts
│   └── my-directive.ts
...
```

ParentComponentは子要素にChildComponentを持ちます。

```ts:child-component.ts
@Component({
  selector: 'child-component',
  templateUrl: 'child-component.html',
})
export class ChildComponent {...}
```

```html:parent-component.html
<ion-item>
  <h2 my-directive>Parent</h2>
  <child-component></child-component>
</ion-item>
```

このとき、`parent-component.module.ts`、`child-component.module.ts`及び`my-directive.module.ts`は以下のようになります。

```ts:parent-component.module.ts
import { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { ParentComponent } from './parent-component';
import { ChildComponentModule } from '../child-component/child-component.module';

@NgModule({
  declarations: [ ParentComponent ],
  imports: [ 
      IonicModule,
      ChildComponentModule
  ],
  exports:[ ParentComponent ]
})
export class ParentComponentModule {}
```

```ts:child-component.module.ts
import { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { ChildComponent } from './child-component';

@NgModule({
  declarations: [ ChildComponent ],
  imports: [ IonicModule ],
  exports:[ ChildComponent ]
})
export class ChildComponentModule {}
```

```ts:my-directive.module.ts
import { NgModule } from '@angular/core';
import { MyDirective  } from './my-directive';

@NgModule({
  declarations: [ MyDirective ],
  exports: [ MyDirective ]
})
export class MyDirectiveModule {}
```


`IonicModule`をimportsに指定し、また親では子のModuleもimportsに指定しておくのがポイントです。

DirectiveやPipeの場合も同様に*.module.tsファイルを作成し、NgModuleを宣言すればOKです。Directive/PipeではIonicModuleをimportする必要はありません。

#### (b)共有モジュールを定義する

Component用の共有モジュール、Directive用の共有モジュール、Pipe用の共有モジュールをひとつずつ定義します。

以下では次のようなディレクトリ構成で考えます。

```
./src
├── app
├── assets
├── components
│   ├── child-component
│   │   ├── child-component.html
│   │   ├── child-component.scss
│   │   └── child-component.ts
│   ├── components.module.ts
│   └── parent-component
│       ├── parent-component.html
│       ├── parent-component.scss
│       └── parent-component.ts
├── directives
│   ├── directives.module.ts
│   └── my-directive.ts
├── pages
├── pipes
...
```

`components.module.ts`は次のようになります。

```ts:components.module.ts
import { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { ParentComponent } from './parent-component/parent-component';
import { ChildComponent } from './child-component/child-component';

@NgModule({
  declarations: [ 
      ParentComponent,
      ChildComponent
  ],
  imports: [ 
      IonicModule
  ],
  exports:[ 
      ParentComponent,
      ChildComponent
  ]
})
export class ComponentsModule {}
```

`directives.module.ts`は次のようなります。

```ts:directives.module.ts
import { NgModule } from '@angular/core';
import { MyDirective } from './my-directive';

@NgModule({
  declarations: [ 
      MyDirective
  ],
  exports:[ 
      MyDirective
  ]
})
export class DirectivesModule {}
```

`pipes.module.ts`は省略しますが、`directives.module.ts`と同じ構成です。

以上が共有モジュール方針での構成です。

そこまでコンポーネント数が増えないのであれば(b)を、コンポーネント数がかなりの数になりそうであれば(a)を検討するのがよさそうです。