この記事は最初に公開されました: https://github.com/bigo-frontend/blog/ フォローと再投稿を歓迎します。
マイクロフロントエンドの共有を実践する
要求シーン
Brpc サービス管理プラットフォームは、オープンソース プロジェクトのJeager (分散リンク追跡システム) の機能を統合し、独自のコール チェーン プラットフォームを構築し、Brpc フレームワークを使用するユーザーが独自のサービス コール チェーンをクエリできるようにしたいと考えています。、二次開発、アクセス 同社の分散ログシステムの機能により、ユーザーはこのプラットフォーム上で業務の痕跡や業務ログを簡単に閲覧し、関連情報を把握し、問題点を迅速に発見することができます。
なぜマイクロフロントエンドなのか
自分で開発してみませんか
要件はオープンソースプロジェクトの既成機能と基本的に同じであり、開発期間は単純に見積もってもすべて自社開発の場合、少なくとも1か月はかかります。
なぜ iframe を使わないのか
- URL が同期していません。ブラウザは iframe URL の状態を更新すると失われ、「戻る」ボタンと「進む」ボタンが使用できなくなります。
- UI は同期されず、DOM 構造は共有されません。画面の右下隅の iframe にマスク レイヤーを持つ行頭文字ボックスがあるとします。同時に、ブラウザーに行頭文字ボックスを中央に表示し、ブラウザーのサイズを変更すると自動的に中心に配置されるようにする必要があります。
- グローバル コンテキストは完全に分離されており、メモリ変数は共有されません。iframe の内部および外部システムの通信およびデータ同期要件では、ログインを回避する効果を得るために、メイン アプリケーションの Cookie が異なるルート ドメイン名を持つサブアプリケーションに透過的に送信される必要があります。
- 遅い。各サブアプリケーション エントリは、ブラウザ コンテキストの再構築とリソースの再読み込みのプロセスです。
https://www.yuque.com/kuitos/gky7yw/gesexv
コードを移動するだけではどうでしょうか
このプロジェクトも、react と antd に基づいて開発されています。ただし、コードを直接移動すると、redux の使用や API のインターセプト方法など、コードが受け入れられにくくなり、2 つのシステムが自己完結型になる一方で、更新が不便になります。その後、オープンソース プロジェクトがメジャー アップデートを行ったと仮定し、その機能が再度必要になった場合は、コードを移動する必要があります。
マイクロフロントエンド-qiankunを使用する
上記の問題を踏まえると、マイクロフロントエンドは比較的優れた解決策であると言えますが、マイクロフロントエンドの概念は次のとおりです。
- 高い分離性: メイン アプリケーションとサブアプリケーションは、互いに影響を与えることなくサンドボックス内で実行され、アプリケーションのテクノロジ スタックも異なる可能性があるため、テクノロジ スタックは独立しています。
- 低結合: 各アプリケーションは独立して開発、展開、アクセスできます。
- 高い拡張性:サブアプリケーションの抜き差しが容易で、柔軟に拡張できます。
- 低侵入性: メインアプリケーションでもサブアプリケーションでも、侵入するコードの量は非常に少なく、アクセスコストが低くなります。
私はqiankunを使用しています。このソリューションの利点は次のとおりです。
- Jaeger と brpc サービス管理プラットフォームは、相互に影響を与えることなく、個別に開発および展開できます。
- Yeter は brpc サービス管理プラットフォームにサブプロジェクトとして組み込まれており、cas を共有できます。
- iframe に存在する問題は、マイクロフロントエンドによってうまく解決できます。たとえば、アプリケーション通信では、qiankun には独自のアプリケーション通信 API があり、リソースのキャッシュ、リソースのロードは 1 回だけで済み、ブラウザの前進および後進機能の使用によって状態が失われることはありません。
- 開発時間を節約し、マイクロ フロントエンドを使用し、オープンソースの二次開発に基づいて、研究計画から実装までの合計時間は 1 週間強です。
銭君アクセス
ドキュメントを見ると、従来のアクセス方法と比較して、空のプロジェクトがメイン アプリケーション (ベース アプリケーションと呼ばれることが多い) として使用され、ルーティング スイッチを監視することでさまざまなサブアプリケーションが起動されます。
しかし、私の需要シナリオは比較的特殊です。メイン アプリケーションとして Brpc サービス管理プラットフォームを使用し、マイクロ アプリケーションとして jaeger を使用します。ルーティング スイッチを監視することで、jaeger コール チェーン プラットフォームがアクティブ化され、コンテンツ レンダリング領域に埋め込まれます。メインアプリケーション。
コアAPI
// 注册微应用
registerMicroApps(apps, lifecycles);
// apps - Array<RegistrableApp> - 必选,微应用的一些注册信息
// lifeCycles - LifeCycles - 可选,全局的微应用生命周期钩子
// 添加全局异常捕获
addGlobalUncaughtErrorHandler(errHandle)
// 启动qiankun
start()
詳細API: qiankun-API
主なアプリケーション構成
- マイクロアプリを登録し、qiankunを起動します。
// 配置文件
import * as NProgress from "nprogress";
export const TRACE_JAEGER_ENTRY =
process.env.NODE_ENV === "development"
? "http://localhost:3001"
: `${window.location.origin}/trace`;
export const TRACE_JAEGER_NAME = "micro-app-jaeger";
export const MAIN_CONTENT_AREA_ID = "main-app-content";
export const MICRO_APPS = [
{
name: TRACE_JAEGER_NAME,
entry: TRACE_JAEGER_ENTRY,
container: `#${MAIN_CONTENT_AREA_ID}`,
activeRule: "/jaeger",
},
];
export const LIFE_CYCLES = {
// qiankun 生命周期钩子 - 微应用加载前
beforeLoad: () => {
// 加载微应用前,加载进度条
NProgress.start();
return Promise.resolve();
},
// qiankun 生命周期钩子 - 微应用挂载后
afterMount: () => {
// 加载微应用前,进度条加载完成
NProgress.done();
return Promise.resolve();
},
};
// index.tsx
// 注册微应用
registerMicroApps(MICRO_APPS, LIFE_CYCLES);
/**
* 添加全局的未捕获异常处理器
*/
addGlobalUncaughtErrorHandler((event: Event | string) => {
const { message: msg } = event as any;
// 加载失败时提示
if (msg && msg.includes("died in status LOADING_SOURCE_CODE")) {
message.error("微应用加载失败,请检查应用是否可运行!");
}
});
// 启动qiankun
start();
- コンテンツ領域にマイクロアプリのマウント ポイントを追加しました。
// App.tsx
const App: React.FC<IProps> = ({ history }) => {
return (
<Layout style={
{ maxHeight: "100vh" }}>
<Sider collapsed={collapsed}>
<Menu
theme="dark"
selectedKeys={[menuKey]}
defaultOpenKeys={menuOpenKeys}
mode="inline"
onClick={onClikcMenus}
>
主应用menu菜单栏
</Menu>
</Sider>
<Layout className="site-layout">
<Header className="site-layout-header">header</Header>
<Content>
{/** 组件渲染区域 */}
<div className="site-layout-content">
{/** 微前端挂载点 */}
<div id={MAIN_CONTENT_AREA_ID} />
<Switch>
<Route exact path="/" component={Service} />
</Switch>
</div>
</Content>
<Footer className="site-layout-footer">
{`BIGO © 2020~${new Date().getFullYear()} 基础架构团队`}
</Footer>
</Layout>
</Layout>
);
};
export default withRouter(App);
- antd スタイルの分離
マイクロアプリ間のスタイルは分離されていますが、デフォルトでは、メイン アプリとマイクロアプリの間に競合があり、手動で解決する必要があります。解決策: メイン アプリ スタイルの prefixCls プレフィックスを構成します。
antd コンポーネントの DOM ノードのクラス名には、次の接頭辞が挿入されます。
// index.tsx
//设置 Modal、Message、Notification rootPrefixCls。(4.13.0+)
ConfigProvider.config({
prefixCls: "main-app",
});
// 设置其余组件、icon 的rootPrefixCls。
<ConfigProvider
prefixCls="main-app"
iconPrefixCls="main-app"
locale={zhCN}
>
<App />
</ConfigProvider>
実行またはパッケージ化するときに、スタイル ファイルのクラス名にプレフィックスを追加します。
// craco.config.js,主应用使用 craco,注入webpack配置。
// 新增配置
const CracoLessPlugin = require("craco-less");
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
javascriptEnabled: true,
modifyVars: {
"@ant-prefix": "main-app",
"@iconfont-css-prefix": "main-app"
},
},
},
},
},
],
サブアプリケーションの構成
- ルートノードIDを変更します。
// index.html,修改root节点id,防止冲突
<div id="micro-app"></div>
- 実行時にパブリック パスを構成して、マイクロアプリによって動的に読み込まれるスクリプト、スタイル、およびイメージのアドレスが正しいことを確認します。
// src目录下,新建 public-path.js 文件
/* eslint-disable camelcase */
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
- アプリケーションのレンダリング方法を変更し、マイクロアプリケーションのライフサイクルを追加する
// index.js
// 在微应用顶部引入publicPath
import './public-path';
import React from 'react';
import {
BrowserRouter } from 'react-router-dom';
import ReactDOM from 'react-dom';
import JaegerUIApp from './components/App';
const UI_ROOT_ID = "#micro-app";
function render(props) {
const {
container } = props;
ReactDOM.render(
<BrowserRouter>
<JaegerUIApp />
</BrowserRouter>,
container ? container.querySelector(UI_ROOT_ID) : document.querySelector(UI_ROOT_ID)
);
}
// 支持独立访问
if (!window.__POWERED_BY_QIANKUN__) {
render({
});
}
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
render(props);
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount(props) {
const {
container } = props;
if(container){
ReactDOM.unmountComponentAtNode(container.querySelector(UI_ROOT_ID));
}
}
- Webパックの設定
// 微应用 webpack 配置// config-overrides.js,微应用使用的是customize-cra,注入webpack配置。// 新增配置function webpack(_config) { let config = _config; // 微应用包,`${packageName}-[name]`.func()调用微应用方法。 config.output.library = `${packageName}-[name]`; //umd通用规范,将 library 暴露为所有的模块定义下都可运行的方式。它将在 CommonJS, AMD 环境下运行,或将模块导出到 global 下的变量。 config.output.libraryTarget = 'umd'; // 用于异步加载(async loading)chunk的JSONP函数 config.output.jsonpFunction = `webpackJsonp_${packageName}`; // 当输出为 library 时,尤其是当 libraryTarget 为 'umd'时,此选项将决定使用哪个全局对象来挂载 library。 config.output.globalObject = 'window'; return config;}const devServerConfig = () => config => { return { ...config, headers: { // 开发环境跨域处理 'Access-Control-Allow-Origin': '*', }, };};module.exports = { webpack, devServer: overrideDevServer(devServerConfig())};
展開する
Nginx は、メイン アプリケーションとサブアプリケーションのパスを次のように構成します。
server {
listen 80;
server_name domain.test.com;
location / {
root /path/service_fe;
try_files $uri $uri/ /index.html;
index index.html;
}
location /trace {
alias /path/jaeger_fe/;
try_files $uri $uri/ /index.html;
index index.html;
}
}
乾坤原理
Qiankunは、アプリケーションとパッケージのライフサイクルを規定し、アプリケーションとパッケージのライフサイクル回転のプロセス (ルーティング マッチング メカニズム) をスケジュールするシングルスパに基づいて開発されています。アプリケーションとパッケージのロード機能、ルーティングマッチング戦略とそのライフサイクル機能については、開発者が手動で実装します。qiankun はシングルスパをベースにユーザーエクスペリエンスを最適化し、機能を拡張し、すぐに使える API を提供してアクセスを容易にします。
アプリケーションアクセスの原則
を使用するとimport-html-entry
、メイン フレームは HTML フェッチを通じてサブアプリケーションの静的リソースを取得し、同時に HTML ドキュメントを子ノードとしてメイン フレームのコンテナに挿入できます。サブアプリケーションがアクティブ化されると、importEntry メソッドが呼び出されます。
const {
template, execScripts, assetPublicPath } = await importEntry( entry, importEntryOpts);
3 つのフィールドを返します
- template: 処理された HTML テンプレート文字列、アウトオブライン スタイル ファイルはインライン スタイルに置き換えられ、テンプレートは DOM 操作を通じてメイン アプリケーションに追加されます。
- execScripts: execScripts メソッドを実行して、マイクロアプリによってエクスポートされたライフサイクル メソッドを取得します。また、JS のグローバル汚染の問題も解決します。これは、execScripts メソッドを実行するときに、JS の実行コンテキストをプロキシ パラメーターを通じて指定できるためです。 。execScripts(scripts, fetch, error) 関数が実行されると、指定されたすべての外部スクリプト、つまりスクリプトの内容を取得し、各スクリプトの実行コンテキスト グローバルを設定し、eval 関数を通じて実行します。
- assetPublicPath: 静的リソースのベースURL。
絶縁原理
CSS分離
qiankun は、さまざまな効果を備えたスタイル分離を実現する 3 つのモードを提供します。
-
動的ロード スタイル シート (デフォルト) : このモードでは、サブアプリケーションのすべてのスタイルを、サブアプリケーションによってマウントされた DOM ノードに直接ロードします。そのため、サブアプリケーションがアンインストールされるときに DOM ノードを削除すると、次のことが可能になります。サブアプリケーションで使用されている CSS を自動的に削除します。デフォルトでは、サンドボックスは単一インスタンス シナリオのサブアプリケーション間のスタイルの分離を保証できますが、メイン アプリケーションとサブアプリケーションの間、またはマルチインスタンス シナリオのサブアプリケーション間のスタイルの分離を保証することはできません。
-
Shadow DOM スタイル分離: 構成
sandbox.strictStyleIsolation = true
。このモードは、厳密なスタイル分離モードを有効にすることを意味します。このモードでは、マイクロアプリのスタイルが全体的な状況に影響を与えないように、 qiankun は各マイクロアプリ コンテナーのShadow domノードをラップします。 -
スコープ付き CSS スタイルの分離 (実験的ソリューション) : コード内の構成は次のとおりです
sandbox.experimentalStyleIsolation = true
。基本原則: サブアプリケーションによって追加されたスタイルを書き換えます。 すべてのスタイル ルールに特別なセレクター ルールを追加して、その影響範囲を制限します。vue スコープと同様に、分離されたアプリケーションのスタイル シートは、特定のルールによって次のモードに書き換えられます。 :
アプリケーションの名前をreact16とします。
app-main {
font-size: 14px;}
div[data-qiankun-react16] .app-main {
font-size: 14px;}
qiankun がデフォルトで最初のオプションを使用する理由:
2 番目と 3 番目のオプションを使用すると、一部のコンポーネントがシャドウ境界を越えて外部ドキュメント ツリーにノードを挿入する可能性があり、これらのノードのスタイルが失われます。たとえば、antd はノードを にレンダリングするため、Modal
スタイルducument.body
が失われますが、他のコンポーネント ライブラリやコードの一部でも同じ問題が発生し、追加の処理が必要になります。
いくつかの比較効果は次のとおりです。
jsの分離
js 分離を実現するために、qiankun フレームワークは、さまざまなシナリオに対応する 3 つのサンドボックス ( 、snapshotSandbox
、legacySandbox
および )を提供しますproxySandbox
。
legacySandbox
、proxySandbox
ES6 プロキシに基づいて実装されています。
- スナップショット サンドボックス (snapshotSandbox): IE ブラウザはデフォルトでこのサンドボックスを使用します。IE がサポートしていないためです
Proxy
。このサンドボックスの原理は、サブアプリケーションの起動時にベースウィンドウのスナップショットを作成し、それを変数に格納することであり、サブアプリケーションのウィンドウ操作は基本的にこの変数に対する操作となります。SnapshotSandbox
また、サブアプリケーションの実行中の変更が に保存されるmodifyPropsMap
ため、サブアプリケーションが作成および破棄されたときに復元できます。メリットとデメリット:窓が汚れます。 - プロキシ サンドボックス (レガシーサンドボックス):
legacySandbox
グローバル変数を記録するために 3 つのパラメーターが設定されており、サンドボックスで新しいグローバル変数を記録するaddedPropsMapInSandbox
、サンドボックスで更新されたグローバル変数を記録するmodifiedPropsOriginalValueMapInSandbox
、および更新された (新規および変更された) グローバル変数を継続的に記録することです。いつでもcurrentUpdatedPropsValueMap
。長所と短所: ウィンドウも汚染しますが、パフォーマンスはスナップショット サンドボックスよりも優れており、ウィンドウ オブジェクトをトラバースする必要がありません。 - プロキシ サンドボックス (proxySandbox): サンドボックスをアクティブ化した後、
window
値を確認するたびに、まず自分のサンドボックス環境の内部からそれを探します。fakeWindow
存在しない場合は(rawWindow
外部) からwindow
探します。window
オブジェクトが割り当てられている場合は、fakeWindow
オブジェクトに影響を与えることなく直接操作されますrawWindow
。長所と短所: ウィンドウには影響しません。
通信とルーティングの原則
通信原理
qiankun は内部的に通信用のインスタンスをinitGlobalState
登録するメソッドを提供しています。このインスタンスには次の 3 つのメソッドがあります。MicroAppStateActions
setGlobalState
: 新しい値を設定するとglobalState
、内部で浅い比較が実行され、変更がglobalState
検出されると、通知がトリガーされてすべてのオブザーバー関数に通知されます。onGlobalStateChange(state, prev)
: オブザーバー関数を登録し、globalState
の変更に応答し、globalState
変更が発生したときにオブザーバー関数をトリガーします。offGlobalStateChange
: オブザーバー関数をキャンセルすると、インスタンスはglobalState
変更に応答しなくなり、マイクロアプリのアンマウント時にデフォルトで呼び出されます。
上の図から、最初にオブザーバーをオブザーバー プールに登録し、コンポーネント間の通信の効果を実現するように変更することでglobalState
すべてのオブザーバー関数をトリガーできることがわかります。
ルーティング原理
ブラウザのルーティングはシングル スパ内でハイジャックされ、すべてのルーティング メソッドとルーティング イベントは、統合スケジューリングのために最初にシングル スパに入る必要があります。qiankun の場合、ルート ハイジャックは単一のスパで行われ、qiankun によって提供される機能は主にサブアプリケーションの読み込みとサンドボックス分離です。
window.addEventListener("hashchange", urlReroute);
window.addEventListener("popstate", urlReroute);
マイクロフロントエンドの適用可能なシナリオを要約する
アプリケーションの分離と分割
Jushi アプリケーションを、独立して開発、展開、アクセスできる複数のアプリケーションに分割します。
アプリケーションの集約
複数の機能に関連付けられたアプリケーションの入り口を集約することで、アプリケーションの機能が近くなり、ブラウザの異なるタブ間を行き来する必要がなく、操作プロセスがよりスムーズになります。
インクリメンタルリファクタリングを適用する
巨大なアプリケーションがあり、それを機能モジュールに従って分割してリファクタリングしたいとします。機能モジュールに従ってサブアプリケーションを開発し、開発後にメインアプリケーションに挿入して、元のモジュールを置き換えることができます。
引用
マイクロフロントエンドフレームワークであるqiankunの原理と実践
千字の長文 + 画像とテキスト + マイクロフロントエンドフレームワーク qiankun ソースコードの包括的分析 - qiankun 記事
以上、間違いがあれば修正してください〜
議論するメッセージを残してくださる皆様を歓迎します。スムーズな仕事と幸せな生活をお祈りしています。
私は bigo のフロントエンドです。次号でお会いしましょう。