はじめに
長く運用しているプロダクトほど、フロントエンドは一枚岩ではいられなくなります。古くからのモジュールはReactのまま、新しく参入したチームはVueで書きたい、別部署が作った管理画面をこの画面の中に埋め込みたい——そんな要望が積み重なると、「全部書き直す」か「iframeで無理やり同居させる」かの二択に追い込まれがちです。全面リライトは現実的でなく、iframeはルーティングや状態共有がうまくいかず、結局どちらも痛みを伴います。
Qiankunは、この板挟みに対する第三の選択肢です。既存のアプリをほぼそのままの形で「マイクロアプリ」として取り込み、iframeを使わずにiframeのような独立性(JavaScriptの実行環境やCSSのスコープ)を実現します。ベースになっているのはsingle-spaで、そこにHTMLエントリ方式のロード機構やサンドボックス、プリフェッチといった機能を積み上げて実用性を高めたのがQiankunです。
Qiankunとは
Qiankunは、Ant Group出身のkuitos氏が開発し、現在はUmiエコシステムでメンテナンスされているマイクロフロントエンドフレームワークです。名前の「乾坤」は中国語で「天地・宇宙」を意味し、あらゆるフレームワークのアプリを包み込めることに由来しています。GitHub上で1.6万以上のスターを集め、Ant Financial内外の本番環境で使われてきた実績があります。
主な特徴
- HTMLエントリ方式 - マイクロアプリ側のURLを指定するだけで取り込める。既存アプリの改修をほぼ不要にする仕組みで、他のマイクロフロントエンド手法に比べて導入コストが低い
- JavaScriptサンドボックス - Proxyベースのサンドボックスで各マイクロアプリのグローバル変数を隔離し、複数アプリが同時に動いても
window汚染を防ぐ - スタイル隔離 - Shadow DOMやCSSスコープ化によって、マイクロアプリ間のCSS衝突を防ぐオプションを用意
- プリフェッチ - ブラウザのアイドル時間を使ってマイクロアプリのリソースを先読みし、実際の起動を高速化
- フレームワーク非依存 - React・Vue・Angular・Svelteなど、メインアプリとマイクロアプリで異なるフレームワークを組み合わせられる
インストール
メインアプリ(ホスト側)にQiankunを追加します。
npm install qiankun -S
# または
yarn add qiankun
マイクロアプリ側にはQiankun自体のインストールは不要です。既存アプリのエントリファイルに、後述するライフサイクル関数を追加するだけで組み込めます。
基本的な使い方
メインアプリ側では、registerMicroApps()でマイクロアプリを登録し、start()で起動します。activeRuleにはURLのパスなどを指定し、そのルールに一致したときだけマイクロアプリがマウントされます。
// メインアプリ側
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'reactApp',
entry: '//localhost:7100',
container: '#subapp-container',
activeRule: '/react',
},
{
name: 'vueApp',
entry: '//localhost:7101',
container: '#subapp-container',
activeRule: '/vue',
},
]);
start();
マイクロアプリ側は、エントリファイルでbootstrap・mount・unmountの3つのライフサイクル関数をエクスポートします。bootstrapは初回のみ、mountとunmountはアプリが表示・非表示になるたびに呼ばれます。
// マイクロアプリ側(例: Reactアプリのentry.js)
export async function bootstrap() {
console.log('react app bootstraped');
}
export async function mount(props) {
ReactDOM.render(<App />, props.container.querySelector('#root'));
}
export async function unmount(props) {
ReactDOM.unmountComponentAtNode(props.container.querySelector('#root'));
}
なお、Qiankunはマウント先の実DOMを構築し、そこで各マイクロアプリのJavaScriptを実際に実行してブラウザのグローバル状態を書き換える仕組み上、単一のサンドボックス化されたコードプレイグラウンド上では本来の動作を再現できません。そのため本記事では、実際のプロジェクト構成を想定した静的なコード例で使い方を解説します。
実践的なユースケース
ルーティング連携でマイクロアプリを切り替える
SPAのURLパスに応じてマイクロアプリを出し分けたい場合、activeRuleに関数を渡すとより柔軟な判定ができます。単純な前方一致では対応しきれないルーティング要件(クエリパラメータの有無で切り替えるなど)に便利です。
registerMicroApps([
{
name: 'dashboardApp',
entry: '//localhost:7102',
container: '#subapp-container',
activeRule: (location) => location.pathname.startsWith('/dashboard'),
},
]);
loadMicroAppでルーティングに縛られずマウントする
モーダルやタブの切り替えなど、URLの変化を伴わないタイミングでマイクロアプリを起動したいこともあります。そうした場合はregisterMicroAppsではなくloadMicroApp()を直接呼び出します。戻り値のインスタンスに対してunmount()を呼べば、任意のタイミングで後片付けができます。
import { loadMicroApp } from 'qiankun';
const microApp = loadMicroApp({
name: 'settingsApp',
entry: '//localhost:7103',
container: '#modal-container',
props: { theme: 'dark' },
});
// モーダルを閉じるタイミングなどで呼ぶ
microApp.unmount();
プリフェッチとサンドボックスを調整する
start()のオプションでパフォーマンスと隔離レベルを調整できます。prefetchを有効にするとアイドル時間にマイクロアプリのリソースを先読みし、実際の切り替え時の待ち時間を短縮できます。sandbox.experimentalStyleIsolationを有効にすると、CSSセレクタを自動的にスコープ化してマイクロアプリ間のスタイル衝突を防げます。
start({
prefetch: 'all', // アイドル時間に全マイクロアプリを先読み
sandbox: {
experimentalStyleIsolation: true, // CSSをスコープ化して衝突を防ぐ
},
});
メインアプリからマイクロアプリへpropsを渡す
グローバルなユーザー情報やコールバック関数をマイクロアプリに引き渡したい場合は、登録時のpropsフィールドを使います。マイクロアプリ側はmount(props)の引数からこれを受け取れるので、Reduxストアのようなグローバルステートを都度Ajaxで取り直す必要がなくなります。
registerMicroApps([
{
name: 'reactApp',
entry: '//localhost:7100',
container: '#subapp-container',
activeRule: '/react',
props: {
globalState: { user: currentUser },
onUserAction: (action) => console.log(action),
},
},
]);
まとめ
Qiankunは、single-spaの考え方を土台にしながら、HTMLエントリ方式・JavaScriptサンドボックス・スタイル隔離・プリフェッチといった実運用に必要な機能を一通り揃えたマイクロフロントエンドフレームワークです。registerMicroAppsとstartによるルーティング連携、loadMicroAppによる任意タイミングでのマウント、propsを介したアプリ間連携など、統合の仕方を用途に応じて選べるのが強みです。既存のReact/Vue/Angularアプリを書き直さずに1つの画面へ統合したいときは、有力な選択肢になるはずです。
