Standalone時代のIonic Angularでion-iconをスマートに扱う!
ion-icon
をスマートに扱う! 〜ion-icon-angular-standalone
のご紹介〜
Standalone時代のIonic Angularで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-icon
のname
属性値をビルド前に自動収集し、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.ts
やapp.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パッケージとして公開されています。
以下のコマンドでインストールできます。
npm install ion-icon-angular-standalone
また、ソースコードはGitHubのリポジトリで公開されています。
まとめ
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
を試してみてください!
Discussion