🐙

Ionic AngularのStandalone構成を深掘りして理解する

2023/12/19に公開

Angular v17からデフォルトでStandalone構成が導入されました。 ng generate @angular/core:standalone で自動移行できるので、すでに多くの方がプロジェクトの構成を変更しているのではないでしょうか。Moduleを学ぶ必要がなくなり、より直感的にアプリを構築できるようになりましたね。

Standalone構成移行の波は、プロジェクト単位ではなく、Angularライブラリにも波及しました。Ionic Angularも例外ではなく、v7.5.0でStandalone構成に対応しました。Ionic AngularのStandalone構成を理解するために、Ionic Angularを読み解いてみましょう。

Ionic AngularのStandalone構成

Ionic AngularのStandalone構成を理解するためには、今までのModule構成を理解する必要があります。CLIでスターターテンプレートを利用するため、今まであまり意識することはなかったかもしれませんが、Ionic Angularは、 app.module.ts で、forRoot メソッドを使うことで初期化していました。

app.module.ts
@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, IonicModule.forRoot()],
  providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
  bootstrap: [AppComponent],
})
export class AppModule {}

APP_INITIALIZER を使って初期化しているのですが、そこらへんは 「[Angular] APP_INITIALIZERで初期化ロジックを定義」 の記事が詳しいのでこちらを参照してください。

そして、Ionicコンポーネントを使うModuleでは、 IonicModule を利用して、Ionicコンポーネントを利用できるようにしていました。 IonicModule にはすべてのIonicコンポーネントが登録されています。例えば、 ion-title だとこういったComponentが登録されていました。

@ProxyCmp({
  inputs: ['color', 'size']
})
@Component({
  selector: 'ion-title',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: '<ng-content></ng-content>',
  inputs: ['color', 'size'],
})
export class IonTitle {
  protected el: HTMLElement;
  constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
    c.detach();
    this.el = r.nativeElement;
  }
}

@ProxyComp は、Ionic Angularが定義しているデコレーターで、とても簡単にいうと、コンポーネントのメソッドや属性を定義することができるようになっています。これは一例ですが、こういったコンポーネントが一揃い登録されていたModuleが IonicModule でした。

では、Ionic AngularのStandaloneではこれはどのようになったのでしょうか。まず、Angularは main.ts で、以下のようにアプリを起動するようになりました(※ app.config.ts を経由する方法もありますがここでは触れません)。

main.ts
bootstrapApplication(AppComponent, {
  providers: [
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
    provideIonicAngular(),
    provideRouter(routes),
  ],
});

IonicModuleprovideIonicAngular に置き換わっていることがわかります。 provideIonicAngular では、 APP_INITIALIZER を使ったIonicの初期化等は行われますが、コンポーネントの登録は行われません。では、コンポーネントはどのように変わったでしょうか。ion-title は、以下のようになりました。

  @ProxyCmp({
+   defineCustomElementFn: defineIonTitle,
    inputs: ['color', 'size']
  })
  @Component({
    selector: 'ion-title',
    changeDetection: ChangeDetectionStrategy.OnPush,
    template: '<ng-content></ng-content>',
    // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
    inputs: ['color', 'size'],
+   standalone: true
  })
  export class IonTitle {
    protected el: HTMLElement;
    constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
      c.detach();
      this.el = r.nativeElement;
    }
  }

Standalone構成になったので当たり前ですが、 standalone: true になっています。このことで、Ionic AngularのStandalone構成では、Ionicコンポーネントも、Standaloneコンポーネントとして登録する必要が生まれました。まぁ、そりゃそうだよねという変更ですが、確認しておくことは重要です。 また、 defineCustomElementFn が追加されました。今までは、 IonicModuleAPP_INITIALIZER で定義していたCustomElementを、コンポーネント単位でロードするようになったためです。

IonIconのBundle化

またIonic AngularがStandalone構成に移行したタイミングで、IonIconが assets から削除されました。スターターテンプレートの angular.json をみてもらうとわかります。

  "assets": [
    {
      "glob": "**/*",
      "input": "src/assets",
      "output": "assets"
-   },
+   }
-   {
-     "glob": "**/*.svg",
-     "input": "node_modules/ionicons/dist/ionicons/svg",
-     "output": "./svg"
-   }
],

今まではすべてのIonIconをnode_modulesからコピーしてassetsフォルダに格納して、表示時はそれをロードしていました。 Standalone構成では、必要最低限のIonIconをBundleに埋め込むために、 addIcons を使い、Bundleに埋め込むIonIconを指定することを推奨しています。

example.ts
import { addIcons, closeCircleOutline } from 'ionicons';

addIcons({ closeCircleOutline });

addIcons は、Angularのビルダーに、IonIconをBundleに埋め込むように指示するための関数です。 また、引き続き assets にすべてのアイコンをコピーしている場合は、従来の方法を引き続き使うことも可能です。

このことによってできるようになったこと

多くの人にとって、「Ionic AngularのStandalone構成」は、IonicがAngular標準に則った一方で、記述量は増えてしまう億劫な変更になってしまいます。では、このことで享受できるメリットは何でしょうか。

ひとつめはツリーシェイキングがAngular標準に則ったため(今まではIonicは独自のLazy Loadingの仕組みでコンポーネントをロードしていました)、ほとんどのアプリケーションのバンドルサイズは減少します。また、独自路線をやめたことにより、Ionicが原因で起こるライブラリのバグも発生しづらくなるというのは大きなメリットです。また、アプリケーションの負荷も改善されます。Lazy Loadingの仕組みが2つ動いてたものが1つになるので、当然のことですよね。(※著者は負荷の比較は行っておりません。「負荷改善」は、Ionicの公式ブログを参考にしています)。 みっつめが一番うれしいのではないでしょうか。Ionicのコンポーネントを静的に解析できるようになったため、Angularの新しいビルドシステムであるESBuildを利用できるようになります。実際、手元で試したところビルドスピードは明らかに向上していました。

まとめ

ライブラリの進化への追随は、開発者にとっては億劫であることが往々にしてあることですが、これをキャッチアップしないと将来的な負債を生み出す原因になりますので、細かくチェックして、ひとつずつ追っていきたいですよね。近々、Ionic AngularのStandalone構成への自動移行について記事を書きますので、そちらもご参考になると幸いです。

それではまた。

Discussion