atama plus techblog

Standalone時代のIonic Angularでion-iconをスマートに扱う!

に公開

Standalone時代のIonic Angularでion-iconをスマートに扱う! 〜ion-icon-angular-standaloneのご紹介〜

atama plusという教育の会社でエンジニアをしている@ippeiukaiです。

Ionic AngularのStandalone構成が導入され、コンポーネントの取り扱い方が大きく変わりました。特に、ion-iconコンポーネントに関しては、使用するアイコンのSVGデータを事前にロードし、ioniconsライブラリのaddIcons関数を使って登録する必要が生じ、以前のようにname属性を指定するだけでは表示されなくなりました。この変更はバンドルサイズを削減するためのものですが、開発者にとってはアイコンを使うたびにaddIconsを呼び出す手間が増え、登録を忘れるとビルド時ではなく実行時にエラーが発生するため、原因の特定が難しくなるという課題をもたらしました。

この課題に対する1つの解決策として、ion-icon-angular-standaloneというライブラリを作って社内で利用していますが、このたび、社外にも公開したのでご紹介します。

Standalone構成におけるIonIconの新しい課題と、先駆的な解決策

Standalone構成では、使用するAngularコンポーネントでIonicコンポーネントを1つずつインポートし、imports配列に追加する必要があります。ion-iconも例外ではなく、IonIconコンポーネント自体をインポートする必要があります。しかし、これに加えて、表示したいアイコンのSVGデータも別途読み込み、addIconsで登録するという追加の手順が必要になりました。

この課題に対し、@rdlaboこと榊原昌彦さんが素晴らしい解決策を提示されています。それがionic-angular-collect-iconsライブラリです。榊原さんの記事(こちら)を拝見し、テンプレートで使用されているion-iconname属性値をビルド前に自動収集し、addIconsの呼び出しコードを自動生成するというアプローチは、まさにこの課題に対するズバリな解決法だと思いました。課題認識と解決へのアプローチを大変参考にさせていただきました。

ionic-angular-collect-iconsは、開発中は全てのアイコンを登録することでストレスフリーな開発を実現し、本番ビルド前には使用しているアイコンのみを収集してバンドルサイズを最適化するという賢い仕組みです。prebuildスクリプトにコマンドを追加することで、このプロセスを自動化することも推奨されています。

ion-icon-angular-standaloneのアプローチ

ionic-angular-collect-iconsが「addIconsの呼び出しを自動生成する」というアプローチであるのに対し、私が開発したion-icon-angular-standaloneは、「addIconsの呼び出し自体を開発者が意識しなくて済むようにする」という別のアプローチで、同じ課題の解決を目指しています。

このライブラリは、オリジナルのIonIconコンポーネントの代わりに、各アイコンに対応する専用のラッパーコンポーネントを提供します。例えば、logo-ionicアイコンを使いたい場合、@ionic/angular/standaloneからIonIconをインポートするのではなく、ion-icon-angular-standaloneからIonIcon_logoIonicというコンポーネントをインポートして使用します。

使用例

import { Component } from '@angular/core';

// @ionic/angular/standalone から IonIcon をインポートする代わりに...
// import { IonIcon } from '@ionic/angular/standalone';
// import { addIcons } from 'ionicons';
// import { logoIonic } from 'ionicons/icons';

// ion-icon-angular-standalone から対応するアイコンコンポーネントをインポートします
import { IonIcon_logoIonic } from 'ion-icon-angular-standalone';

@Component({
  selector: 'app-my-component',
  template: `
    <!-- テンプレートでの記述は同じ -->
    <ion-icon name="logo-ionic"></ion-icon>
  `,
  standalone: true,
  imports: [
    // インポートするコンポーネントを変更
    IonIcon_logoIonic, // IonIcon の代わりに個別のアイコンコンポーネントをインポート
  ],
})
export class MyComponent {
  constructor() {
    // addIconsを自分で呼び出す必要はありません
    // addIcons({ logoIonic });
  }
}

アイコンごとのラッパーコンポーネントの内部で、そのアイコンが必要とするaddIconsの呼び出しが行われるため、開発者はアプリケーションコードで明示的にaddIconsを書く必要がありません。

ion-icon-angular-standalone/brandedのアプローチ

また、ionic-angular-collect-iconsが対応していない[name]バインディングを使って動的にアイコン名を指定したいケースにも対応しています。

この場合、ion-icon-angular-standalone/brandedから提供されるIonIconWithBrandedNameコンポーネントを使用します。このコンポーネントはアイコン名をIconNameというBranded Typeで扱うため、動的バインディングでもaddIconsによる登録漏れを防ぐことができます。IconName型のアイコン名は同ライブラリのaddIcon関数を使うことで得られます。addIcon関数はaddIcons関数を呼び出してアイコンを登録しつつ、アイコン名をIconName型で返します。

使用例

import { Component } from '@angular/core';

// @ionic/angular/standalone から IonIcon をインポートする代わりに...
// import { IonIcon } from '@ionic/angular';
// import { addIcons } from 'ionicons';
// import { logoIonic } from 'ionicons/icons';

// ion-icon-angular-standalone/branded の仕組みをインポートします
import { addIcon, IconName, IonIconWithBrandedName } from 'ion-icon-angular-standalone/branded';
import { logoIonic } from 'ionicons/icons';

@Component({
  selector: 'app-my-icon',
  template: `
    <!-- テンプレートでの記述は同じ -->
    <ion-icon [name]="iconName ?? fallbackIconName"></ion-icon>
  `,
  standalone: true,
  imports: [
    // インポートするコンポーネントを変更
    IonIconWithBrandedName, //  IonIcon の代わりにIonIconWithBrandedNameをインポート
  ],
})
export class MyIconComponent {
  // IconName型を指定することで、addIconsで登録済みのアイコン名であることを確認できます
  @Input() iconName: IconName | undefined = undefined;
  
  // addIconでアイコンを登録して、IconName型のアイコン名を取得します
  readonly fallbackIconName = addIcon({ logoIonic }); // IconName型の'logo-ionic'値
}

なお、ion-icon-angular-standaloneのコンポーネントとion-icon-angular-standalone/brandedのコンポーネントはセレクターが重なるため、同じコンポーネントのimports配列には同時に指定できません。

ion-icon-angular-standaloneの特徴

1. ビルドプロセスがシンプルで、アプリごとの設定やメンテナンスの手間が少ない

ionic-angular-collect-iconsは、標準のAngularビルドとは別に、アイコン収集のためのprebuildステップが必要です。

ion-icon-angular-standaloneは、Angularの標準的な依存解決メカニズムを通じて機能します。コンポーネントをインポートするだけなので、アイコンのために別途prebuildステップを用意する必要がなく、モノレポ全体のビルドパイプラインをシンプルに保つことができます。

この点は特にモノレポ環境で複数のIonic Angularアプリを管理しているようなケースで優位性があります。ionic-angular-collect-iconsは、モノレポ内の各Ionicアプリに対して、prebuildスクリプトの設定や、生成されるアイコンリストファイルのパス設定など、ビルドプロセスへの組み込みが必要です。ion-icon-angular-standaloneなら、ライブラリをインストールし、各コンポーネントでIonIconの代わりに個別のアイコンコンポーネントをインポートするだけで導入が完了します。アプリ全体のエントリーポイント (main.tsapp.component.ts) で特別な設定や追加のビルド前スクリプトの実行は不要です。アイコンの使用箇所に近いコンポーネント単位での変更のみで済むため、モノレポ内の各アプリへの導入や管理がシンプルになります。

2. Angularの標準的な開発ワークフローとの整合性が高い

AngularのStandalone構成では、使用するコンポーネントやディレクティブをそのコンポーネントのimports配列にインポートするのが基本的な開発スタイルです。

ion-icon-angular-standaloneは、「使いたいアイコンに対応するコンポーネントをインポートする」という形をとるため、このAngularの標準的なワークフローに自然に馴染みます。addIconsというAngularのComponentモデルとは少し異なる概念を直接扱う必要がありません。これは、必ずしもIonicに熟練していない複数のチームが開発している場合や、新しい開発者がプロジェクトに参加する場合に、学習コストやルールの理解コストを抑えることにつながります。

3. より細かいツリーシェイキングの可能性

ionic-angular-collect-iconsは、最終的にアプリ全体で使用されているアイコンをリスト化し、それをまとめてaddIconsで登録します。もしaddIconsをアプリケーションのエントリーポイントで行う場合、登録されたアイコンのデータは初期バンドルに含まれる可能性があります。

ion-icon-angular-standaloneは、アイコンを個別のコンポーネントとして提供し、それを各コンポーネントでインポートします。Angularのツリーシェイキングはコンポーネント間のインポート関係に基づいて不要なコードを排除するため、使用されていない個別のアイコンコンポーネント(およびそれに付随するSVGデータ)が最終的なバンドルに含まれないように、よりコンポーネントレベルでのツリーシェイキングを促進する可能性があります。特に遅延ロードされるFeature ModuleやStandalone Routeでアイコンが使用されている場合、そのアイコンのSVGデータは、そのルートがロードされるまでバンドルに含まれない可能性が高まります。

ESLintのルール

ion-icon-angular-standaloneでは、オリジナルのIonIconコンポーネントが誤って使われないように、ESLintのno-restricted-importsルールを使ってインポートを制限することを強く推奨しています。これにより、モノレポ全体でアイコンの利用方法を統一し、addIconsの登録漏れを防ぐための仕組みをコードレベルで強制できます。

{
  "no-restricted-imports": [
    "error",
    {
      "paths": [
        {
          "name": "@ionic/angular",
          "message": "Use @ionic/angular/standalone instead."
        },
        {
          "name": "@ionic/angular/standalone",
          "importNames": ["IonIcon"],
          "message": "Use components from ion-icon-angular-standalone instead."
        }
      ]
    }
  ]
}

ion-icon-angular-standalone はどこで入手できるのか?

ion-icon-angular-standalone は、npmパッケージとして公開されています。

https://www.npmjs.com/package/ion-icon-angular-standalone

以下のコマンドでインストールできます。

npm install ion-icon-angular-standalone

また、ソースコードはGitHubのリポジトリで公開されています。

https://github.com/atamaplus-public/ion-icon-angular-standalone

まとめ

Ionic AngularのStandalone構成におけるion-iconの扱い方の変更は、バンドルサイズ削減というメリットがある一方、開発者に新たな手間と課題をもたらしました。

ionic-angular-collect-iconsはビルドプロセスを拡張してこの課題を自動化する素晴らしいライブラリであり、参考にさせていただきました。

一方、ion-icon-angular-standaloneは、アイコンを個別のStandaloneコンポーネントとして提供することで、Angularの標準的な開発ワークフローに沿ってこの課題を解決しようというアプローチをとっています。特にモノレポのように複数のアプリケーションを管理し、各アプリの設定やビルドプロセスをシンプルに保ちたい環境では、設定の手間が少なく、ビルドプロセスへの影響が小さく、Angularワークフローとの親和性が高いion-icon-angular-standaloneが有効な選択肢になると信じています。

ぜひ、あなたのIonic Angularプロジェクトでion-icon-angular-standaloneを試してみてください!

atama plus techblog
atama plus techblog

Discussion