Angular7でwebpack configを調整してバンドルサイズや挙動を自在に操る(ag-Grid入門付き!)

lovedと申します。
Qiitaコントリビューターの皆様、いつも大変ありがとうございます。

# TL;DR
Angular7では、Angular6で使用できた```ng eject```コマンドが廃止されました。
そこで今回はAngular7でAngular-CLI開発をする時のwebpack詳細設定の仕方を紹介します。
angular.jsonを編集した上で、webpack拡張設定ファイルを作成してビルドする方法です。

# 対象読者
・Angular7でデフォルトのビルド設定でカバーできない要件があり、
Angularの内部で動いているwebpack部分のconfigを調整する必要がある方。
・前準備でAngular7環境での初期構築を行っていますので、Angular7(とag-Grid)をこれから使用する/学ぶ予定がある方にもお楽しみいただけるかと思います。

# 環境
```bash

 >> ng version

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI: 7.3.0
Node: 9.3.0
OS: win32 x64
Angular:
...

Package                      Version
------------------------------------------------------
@angular-devkit/architect    0.13.0
@angular-devkit/core         7.3.0
@angular-devkit/schematics   7.3.0
@schematics/angular          7.3.0
@schematics/update           0.13.0
rxjs                         6.3.3
typescript                   3.2.2


```
･･･Nodeのバージョンが古いですが、気にしないでください。

今回はag-gridのチュートリアルを借りつつ手順を紹介します。
ag-gridはコミュニティエディション無料のgridモジュールです。
https://www.ag-grid.com/angular-getting-started/


# 手順
ではやっていきましょう。
前準備は上述チュートリアルページとほぼ同一です。
理解度の高い方は飛ばして頂いて問題ありません。

## 前準備
```typescript


// インストールとプロジェクトの作成
npm install -g @angular/cli
ng new my-app --style scss --routing false
cd my-app
// ag-gridのインストール
npm install --save ag-grid-community ag-grid-angular

// プロジェクト内 app.module.tsを編集
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { AgGridModule } from 'ag-grid-angular';// ←追加

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, AgGridModule.withComponents([])],// ←追加
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

// styles.scssを編集
@import "~ag-grid-community/dist/styles/ag-grid.css";
@import "~ag-grid-community/dist/styles/ag-theme-balham.css";

// app.component.ts 編集
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'my-app';
  
      columnDefs = [
        {headerName: 'Class', field: 'class' },
        {headerName: 'Name', field: 'name' },
        {headerName: 'Power', field: 'power'}
    ];

    rowData = [
        { class: 'Berserker', name: 'Nightingale', power: 35000 },
        { class: 'Assassin', name: 'Carmilla', power: 32000 },
        { class: 'Avenger', name: 'Jeanne alter', power: 72000 }
    ];
}
// app.component.html を以下に置き換え
<ag-grid-angular 
    style="width: 500px; height: 200px;" 
    class="ag-theme-balham"
    [rowData]="rowData" 
    [columnDefs]="columnDefs"
    >
</ag-grid-angular>
// トップページにgridが表示されていればOK。

```

## 本題
上記で作ったwebアプリをビルドしたいとします。
要件の1つに「1つのバンドルファイルのサイズが1MB未満であること」
があったとします。
さっそく```ng build --prod```しましょう。

```bash


>> ng build --prod

Date: 2019-02-07T15:49:28.086Z
Hash: 817914fa2637c6d3b8aa
Time: 66877ms
chunk {0} runtime.a5dd35324ddfd942bef1.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} es2015-polyfills.12eccedba99646c7daca.js (es2015-polyfills) 56.4 kB [initial] [rendered]
chunk {2} main.00849e0d189f184e52e5.js (main) 1.01 MB [initial] [rendered]
chunk {3} polyfills.407a467dedb63cfdd103.js (polyfills) 41 kB [initial] [rendered]
chunk {4} styles.eb752abcca03cc6fe597.css (styles) 115 kB [initial] [rendered]

```
おやおや。バンドルされたmain.jsのサイズが1MBを超えてしまいました・・・
(ここのサイズを強調したかったがためにag-Gridのimportを挟みました。
実際は多くのベンダーライブラリをimportしますから、さらに大きくなりますね)

じゃあ ```--vendorChunk``` オプションをつけてビジネスロジックとベンダー切り分ければいいだろ！
そうですね。ということでつけてみます。(茶番)

```bash


>> ng build --prod --vendorChunk

Date: 2019-02-07T15:58:37.946Z
Hash: 63108113a0539a610fb7
Time: 58052ms
chunk {0} runtime.a5dd35324ddfd942bef1.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} es2015-polyfills.12eccedba99646c7daca.js (es2015-polyfills) 56.4 kB [initial] [rendered]
chunk {2} main.dda08de101eb4da8c253.js (main) 6.04 kB [initial] [rendered]
chunk {3} polyfills.407a467dedb63cfdd103.js (polyfills) 41 kB [initial] [rendered]
chunk {4} styles.eb752abcca03cc6fe597.css (styles) 115 kB [initial] [rendered]
chunk {5} vendor.4d52dd9a82dee508d6a2.js (vendor) 1.01 MB [initial] [rendered]

```
うーん。結局vendor側が1MBを超えてしまいました。
Angular-CLIの設定だけでどうにかするのは厳しそうです。
そこで今回紹介する方法を試してみましょう。(やっと本題に･･･)

```typescript

// 使用する拡張モジュールをインストール
 npm -D install @angular-builders/custom-webpack
 npm -D install ngx-build-plus //←1行目のモジュールだけで動かなかったら入れてみて下さい
 npm -D install webpack //←上に同じ。

// angular.json を編集
　　　　 //"builder": "@angular-devkit/build-angular:browser",
          "builder": "@angular-builders/custom-webpack:browser", //←に変更
          "options": {
            "customWebpackConfig": {//←追加
              "path": "./extra-webpack.config.js"//←追加
            }, //←追加
            "outputPath": "dist/my-app",
            "index": "src/index.html",
            "main": "src/main.ts",
　　　　　　　//....
// 上で指定した場所(今回ならangular.jsonと同階層)にextra-webpack.config.jsを作成し、
// 追加のwebpack詳細設定を記載する。
// 記載した部分のみがangularのbuilderに追加で反映される
// 今回はmaxSizeを1MB未満にすることでチャンクファイルのサイズ上限を調整した。
const webpack = require('webpack');

module.exports = {
    output: {
        filename: "[name].js" // name of the generated bundle
    },
    optimization: {
        splitChunks: {
            chunks: 'async',
            minSize: 50000,
            maxSize: 900000,
            minChunks: 1,
            maxAsyncRequests: 5,
            maxInitialRequests: 3,
            automaticNameDelimiter: '.',
            name: true,
            cacheGroups: {
                vendors: {
                    test: /[\\/]node_modules[\\/]/,
                    priority: -10
                },
                default: {
                    minChunks: 2,
                    priority: -20,
                    reuseExistingChunk: true
                }
            }
        }
    }
};

```
いいですね。再度ビルドしましょう。

```bash


>> ng build --prod

Date: 2019-02-07T17:06:48.049Z
Hash: bbabf25c9a51d613d39a
Time: 41291ms
chunk {0} runtime.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} es2015-polyfills.d0ae3f07.js (es2015-polyfills.d0ae3f07) 56.4 kB [initial] [rendered]
chunk {2} main.31ecd969.js (main.31ecd969) 29.7 kB [initial] [rendered]
chunk {3} main.54813583.js (main.54813583) 278 kB [initial] [rendered]
chunk {4} main.65932fe3.js (main.65932fe3) 33.8 kB [initial] [rendered]
chunk {5} main.88436b06.js (main.88436b06) 191 kB [initial] [rendered]
chunk {6} main.8ff27ea1.js (main.8ff27ea1) 238 kB [initial] [rendered]
chunk {7} main.bc2eed23.js (main.bc2eed23) 29.9 kB [initial] [rendered]
chunk {8} main.d0ae3f07.js (main.d0ae3f07) 2.73 kB [initial] [rendered]
chunk {9} main.defd55b6.js (main.defd55b6) 238 kB [initial] [rendered]
chunk {10} polyfills.d0ae3f07.js (polyfills.d0ae3f07) 41 kB [initial] [rendered]
chunk {11} styles.d0ae3f07.eb752abcca03cc6fe597.css, styles.d0ae3f07.js (styles.d0ae3f07) 115 kB [initial] [rendered]

```

要件達成です！！おめでとうございます～
もちろんバンドルサイズの変更だけでなく、webpackでのみ設定できる項目なら、
先ほどの拡張configファイルに記載することで反映させることができます。
ビルドコマンドが特に変わらず```ng build --prod```だけで済むのがいいですね。

# まとめ
必要なモジュールをインストールした後、しかるべき設定ファイルを編集するだけです。
個人的には```ng eject```よりは楽だと思います。
Angularはバージョンアップに伴い、平気で手順変わってくるのでつらいですね。
Angular4での手順が全く役に立たなかったりとかはザラですし。寂しいですね。

Angular7の日本語ドキュメント充実に貢献出来たら幸いです。
御観覧頂きまして、ありがとうございました。