[Angular]Routing単位でcode splittingする

## 背景

- Angular初心者です
- Angularでアプリを作っていますが規模が大きくなってくると初回ロード時のファイルサイズが気になってきました
- code splittingして必要なコードを必要な時に取得するようにすると良いと聞いたことがあったので調べて試してみました

## やったこと

- Routerで設定しているページの単位にJSファイルを出力するようにしました
- ページ遷移の都度そのページの表示に必要なコードだけ取得するイメージですね

## やり方

- code splittingする場合としない場合の差分を見た方がわかりやすいと思うので比較してみます
- HomeとAboutの2つのルーティングを持ったアプリを例とします
- コードの差分だけ見たければこちらをご覧ください
    - https://github.com/ozaki25/ng-code-splitting-sample/compare/before...after

### サンプルアプリの作成

- AngularCLIで雛形とコンポーネントを作ります

```zsh
# サンプルコードが見やすいようにhtmlとcssはインラインにしてる
ng new ng-code-splitting-sample --routing=true --inline-style=true --inline-template=true --skip-tests=true --interactive=false
cd ng-code-splitting-sample
ng generate component home
ng generate component about
```

- `src/app`内は以下のようになっています
- 必要なところを修正していきます

```
% tree src/app/
src/app/
├── app-routing.module.ts
├── app.component.ts
├── app.module.ts
├── about
│   └── about.component.ts
└── home
    └── home.component.ts
```

- ルーティングの設定を追加

```ts:src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
```

- 不要なhtmlを削除しておく

```ts:src/app/app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: '<router-outlet></router-outlet>',
  styles: [],
})
export class AppComponent {
  title = 'ng-code-splitting-sample';
}
```

- ページ遷移できるように各コンポーネントにリンクを追加

```ts:src/app/home/home.component.ts
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-home',
  template: `
    <p>home works!</p>
    <a routerLink="about">About</a>
  `,
  styles: [],
})
export class HomeComponent implements OnInit {
  constructor() {}

  ngOnInit() {}
}
```

```ts:src/app/about/about.component.ts
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-about',
  template: `
    <p>about works!</p>
    <a routerLink="/">Home</a>
  `,
  styles: [],
})
export class AboutComponent implements OnInit {
  constructor() {}
  ngOnInit() {}
}
```

- ビルドしてみる

```zsh
npm run build
```

- アプリ本体のファイルは`main-es2015.js`と`main-es5.js`です
- code splittingするようにしていないため全てmainに入っています

```zsh
# mapファイルは省略
% tree dist/ng-code-splitting-sample
dist/ng-code-splitting-sample
├── favicon.ico
├── index.html
├── main-es2015.js
├── main-es5.js
├── polyfills-es2015.js
├── polyfills-es5.js
├── runtime-es2015.js
├── runtime-es5.js
├── styles-es2015.js
├── styles-es5.js
├── vendor-es2015.js
└── vendor-es5.js
```

## code splittingするように修正

- code splittingするためにはmoduleを分ける必要があります
- ルーティングごとにmoduleを作ります
    - すでにファイルがあるので今回は-fしてます

```zsh
ng generate module home --module app --route home -f
ng generate module about --module app --route about -f
```

- いくつかファイルが追加されました

```
% tree src/app/
src/app/
├── app-routing.module.ts
├── app.component.ts
├── app.module.ts
├── about
│   ├── about-routing.module.ts
│   ├── about.component.ts
│   └── about.module.ts
└── home
    ├── home-routing.module.ts
    ├── home.component.ts
    └── home.module.ts
```

- ルーティングの設定の確認と修正をします
    - 修正前のRoutesの設定が残っているので削除します
    - 自動でmoduleを非同期に読み込む行が追加されています

```ts:src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';

// ここを以下のように変更
const routes: Routes = [
  { path: '', loadChildren: () => import('./home/home.module').then(m => m.HomeModule) },
  { path: 'about', loadChildren: () => import('./about/about.module').then(m => m.AboutModule) },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}
```

- こうすることでbuild時にcode splittingされるようになります
- ビルドして確認してみます

```zsh
npm run build
```

- Routing単位にファイルが生成されてることが確認できます

```zsh
# mapファイルは省略
% tree dist
dist
└── ng-code-splitting-sample
    ├── about-about-module-es2015.js
    ├── about-about-module-es5.js
    ├── favicon.ico
    ├── home-home-module-es2015.js
    ├── home-home-module-es5.js
    ├── index.html
    ├── main-es2015.js
    ├── main-es5.js
    ├── polyfills-es2015.js
    ├── polyfills-es5.js
    ├── runtime-es2015.js
    ├── runtime-es5.js
    ├── styles-es2015.js
    ├── styles-es5.js
    ├── vendor-es2015.js
    └── vendor-es5.js
```

## まとめ

- コンポーネントの作り方を少し変えるだけでRouting単位にcode splittingすることができました
- 設定ファイルなどいじらずできるのがいいですね

## 参考記事

https://web.dev/route-level-code-splitting-in-angular/