フロントエンドJSサンドボックスを実装するいくつかの方法についての簡単な説明

フロントエンドJSサンドボックスを実装するいくつかの方法についての簡単な説明

序文

マイクロフロントエンドの分野では、サンドボックスは非常に重要なものです。たとえば、マイクロフロントエンドフレームワークの single-spa は js サンドボックスを実装していません。大規模なマイクロフロントエンドアプリケーションを構築する場合、変数の競合が発生しやすく、アプリケーションの信頼性に大きなリスクをもたらします。マイクロ フロントエンドには、ドキュメント、場所、その他のオブジェクトなど、すべてのアプリケーション間で共有する必要があるグローバル オブジェクトがいくつかあります。サブアプリケーションの開発中は、複数のチームが作業する可能性があり、グローバル変数の使用を制限することは困難です。ページによっては複数の異なるサブアプリケーションが存在する場合があり、複数のサンドボックスをサポートする必要があり、各サンドボックスには読み込み、アンロード、および復元の機能が必要です。

iframeはサンドボックスを実装します

フロントエンドには、比較的重要な HTML タグ iframe があります。実際、iframe オブジェクトを使用して、contentWindow を通じてネイティブ ブラウザー オブジェクトを取り出すことができます。このオブジェクトは当然すべてのプロパティを持ち、メインのアプリケーション環境から分離されています。下のコードを見てみましょう

iframe を document.createElement('iframe',{src:'about:blank'}) に設定します。
document.body.appendChild(iframe);
const sandboxGlobal = iframe.contentWindow;

注: 同じドメイン内の iframe のみが対応するコンテンツウィンドウを取得できます。iframe の src は about:blank に設定されており、同じドメイン内にあるためリソースの読み込みは行われません。iframe src を参照してください。

序文で、マイクロ フロントエンドは、分離されたウィンドウ環境を持つことに加えて、いくつかのグローバル オブジェクトを共有する必要があることを述べました。現時点では、プロキシを使用してこれを実現できます。下のコードを見てみましょう

クラス SandboxWindow {
    /**
     * コンストラクタ * @param {*} context 共有するオブジェクト * @param {*} frameWindow iframeのウィンドウ
     */
    コンストラクタ(コンテキスト、フレームウィンドウ) {
        
        新しいProxy(frameWindow, { を返す
            get(ターゲット、名前) {
                if (name in context) { // 最初に共有オブジェクトを使用します return context[name];
                }
                ターゲット[名前]を返します。
            },
            set(ターゲット、名前、値) {
                if (name in context) { // 共有オブジェクトの値を変更します return context[name] = value;
                }
                ターゲット[名前] = 値;
            }
        })
    }
}

// グローバルに共有する必要がある変数 const context = { document:window.document, history:window.history }

// サンドボックスを作成します。const newSandboxWindow = new SandboxWindow(context, sandboxGlobal); 

// サンドボックス上のオブジェクトがグローバルオブジェクトと等しいかどうかを判断します console.log('equal',newSandboxWindow.document === window.document)

newSandboxWindow.abc = '1'; // サンドボックスにプロパティを追加 console.log(window.abc); // プロパティをグローバルに表示 console.log(newSandboxWindow.abc) // サンドボックス上のプロパティを表示

実行して結果を見てみましょう

上記のように、iframe サンドボックスを使用すると、次の機能を実現できます。

  • setTimeout、location、reactの異なるバージョンなどのグローバル変数の分離
  • ルーティングの分離: アプリケーションは独立したルーティングを実装するか、グローバルルーティングを共有することができます。
  • 複数のインスタンス、複数の独立したマイクロアプリケーションを同時に実行できます

diffメソッドを使用したサンドボックスの実装

プロキシをサポートしていないブラウザでは、diff を通じてサンドボックス化を実践できます。アプリケーションの実行中、スナップショット ウィンドウ オブジェクトが保存され、現在のウィンドウ オブジェクトのすべてのプロパティがスナップショット オブジェクトにコピーされます。サブアプリケーションがアンインストールされると、ウィンドウ オブジェクトが変更されて差分が作成され、異なるプロパティが modifyMap に保存されます。再度マウントされると、これらの変更されたプロパティが追加されます。コードは次のとおりです。

クラス DiffSandbox {
  コンストラクタ(名前) {
    this.name = 名前;
    this.modifyMap = {}; // 変更されたプロパティを保存します。this.windowSnapshot = {};
  }
  アクティブ() {
    // アクティブな状態のサンドボックスをキャッシュします this.windowSnapshot = {};
    for (ウィンドウ内のconst項目) {
      this.windowSnapshot[item] = window[item];
    }

    Object.keys(this.modifyMap).forEach(p => {
      ウィンドウ[p] = this.modifyMap[p];
    })

  }

  非アクティブ(){
    for (ウィンドウ内のconst項目) {
      if (this.windowSnapshot[item] !== window[item]) {
        //変更を記録する this.modifyMap[item] = window[item];
        // ウィンドウを復元
        window[item] = this.windowSnapshot[item];
      }
    }
  }
}

const diffSandbox = new DiffSandbox('diff サンドボックス');
diffSandbox.active(); // サンドボックスをアクティブにする window.a = '1'
console.log('サンドボックスを開く:',window.a);
diffSandbox.inactive(); //サンドボックスを非アクティブ化 console.log('サンドボックスを非アクティブ化:', window.a);
diffSandbox.active(); // 再アクティブ化 console.log('再度アクティブ化', window.a);

実行して結果を見てみましょう

このメソッドは、実行時にすべてのプロパティがウィンドウに保存されるため、複数のインスタンスもサポートできません。

プロキシに基づく単一インスタンスサンドボックスの実装

ES6 では、プロキシを介してオブジェクトをハイジャックできます。基本レコードは、ウィンドウ オブジェクトの変更を通じても記録されます。これらのレコードは、アプリケーションがアンインストールされると削除され、アプリケーションが再度アクティブ化されると復元され、サンドボックス環境をシミュレートする目的を達成します。コードは次のとおりです

// ウィンドウのプロパティを変更するパブリックメソッド const updateWindowProp = (prop, value, isDel) => {
    if (値 === 未定義 || isDel) {
        ウィンドウ[prop]を削除します。
    } それ以外 {
        window[prop] = 値;
    }
}

クラス ProxySandbox {

    アクティブ() {
        // レコードに基づいてサンドボックスを復元します this.currentUpdatedPropsValueMap.forEach((v, p) => updateWindowProp(p, v));
    }
    非アクティブ(){
        // 1 サンドボックス中に変更されたプロパティを元のプロパティに復元します this.modifiedPropsMap.forEach((v, p) => updateWindowProp(p, v));
        // 2 サンドボックス中に追加されたグローバル変数を削除します。this.addedPropsMap.forEach((_, p) => updateWindowProp(p, undefined, true));
    }

    コンストラクタ(名前) {
        this.name = 名前;
        this.proxy = null;
        //新しく追加されたグローバル変数を保存します。this.addedPropsMap = new Map(); 
        //サンドボックス中に更新されたグローバル変数を保存します。this.modifiedPropsMap = new Map();
        // サンドボックスがアクティブ化されたときに使用される新しいグローバル変数と変更されたグローバル変数があります。this.currentUpdatedPropsValueMap = new Map();

        const { addedPropsMap、currentUpdatedPropsValueMap、modifiedPropsMap } = this;
        const fakeWindow = Object.create(null);
        const proxy = 新しいProxy(fakeWindow, {
            set(ターゲット、プロパティ、値) {
                if (!window.hasOwnProperty(prop)) {
                    // ウィンドウにプロパティがない場合、新しく追加されたプロパティを // デバッガーに記録します。
                    追加されたPropsMap.set(prop, value);
                } そうでない場合 (!modifiedPropsMap.has(prop)) {
                    // 現在のウィンドウ オブジェクトにこのプロパティがあり、更新されていない場合は、ウィンドウのこのプロパティの初期値を記録します。const originalValue = window[prop];
                    プロパティを元の値に設定します。
                }
                // 変更されたプロパティと変更された値を記録します currentUpdatedPropsValueMap.set(prop, value);
                // グローバル ウィンドウに値を設定します updateWindowProp(prop, value);
                true を返します。
            },
            get(ターゲット、プロパティ) {
                window[prop]を返します。
            },
        });
        this.proxy = プロキシ;
    }
}


const newSandBox = 新しい ProxySandbox('プロキシサンドボックス');
const proxyWindow = newSandBox.proxy;
プロキシウィンドウ.a = '1'
console.log('サンドボックスを開きます:', proxyWindow.a, window.a);
newSandBox.inactive(); //サンドボックスを非アクティブ化 console.log('サンドボックスを非アクティブ化:', proxyWindow.a, window.a);
newSandBox.active(); //サンドボックスを非アクティブ化 console.log('サンドボックスを再アクティブ化:', proxyWindow.a, window.a);

コードを実行して結果を見てみましょう

この方法では、一度にアクティブにできるサンドボックスは 1 つだけです。そうでない場合、グローバル オブジェクトの変数が 2 つ以上のサンドボックスによって更新され、グローバル変数の競合が発生します。

プロキシに基づくマルチインスタンスサンドボックスの実装

単一インスタンスのシナリオでは、fakeWindow は変数を格納する機能を持たない空のオブジェクトです。マイクロアプリケーションによって作成された変数は、最終的にはウィンドウにマウントされるため、同時にアクティブ化できるマイクロアプリケーションの数が制限されます。

クラスMultipleProxySandbox {

    アクティブ() {
        this.sandboxRunning = true;
    }
    非アクティブ(){
        this.sandboxRunning = false;
    }

    /**
     * コンストラクタ * @param {*} name サンドボックス名 * @param {*} context 共有コンテキスト * @returns 
     */
    コンストラクタ(名前、コンテキスト = {}) {
        this.name = 名前;
        this.proxy = null;
        const fakeWindow = Object.create({});
        const proxy = 新しいProxy(fakeWindow, {
            設定: (ターゲット、名前、値) => {
                (this.sandboxRunning)の場合{
                    if (Object.keys(context).includes(name)) {
                        コンテキスト[名前] = 値;
                    }
                    ターゲット[名前] = 値;
                }
            },
            取得: (ターゲット、名前) => {
                // 共有オブジェクトを優先する if (Object.keys(context).includes(name)) {
                    context[name]を返します。
                }
                ターゲット[名前]を返します。
            }
        })
        this.proxy = プロキシ;
    }
}

const コンテキスト = { ドキュメント: window.document };

const newSandBox1 = new MultipleProxySandbox('プロキシサンドボックス1', context);
新しいサンドボックス1.アクティブ();
定数 proxyWindow1 = newSandBox1.proxy;

const newSandBox2 = new MultipleProxySandbox('プロキシサンドボックス2', コンテキスト);
新しいサンドボックス2.アクティブ();
const proxyWindow2 = newSandBox2.proxy;
console.log('共有オブジェクトは等しいですか?', window.document === proxyWindow1.document, window.document === proxyWindow2.document);

proxyWindow1.a = '1'; // プロキシ 1 の値を設定します proxyWindow2.a = '2'; // プロキシ 2 の値を設定します window.a = '3'; // ウィンドウの値を設定します console.log('出力値を印刷', proxyWindow1.a, proxyWindow2.a, window.a);


newSandBox1.inactive(); newSandBox2.inactive(); // 両方のサンドボックスが非アクティブ化されています proxyWindow1.a = '4'; // proxy1 の値を設定します proxyWindow2.a = '4'; // proxy2 の値を設定します window.a = '4'; // ウィンドウの値を設定します console.log('非アクティブ化後に印刷された値', proxyWindow1.a, proxyWindow2.a, window.a);

newSandBox1.active(); newSandBox2.active(); // 再度アクティブ化 proxyWindow1.a = '4'; // プロキシ 1 の値を設定 proxyWindow2.a = '4'; // プロキシ 2 の値を設定 window.a = '4'; // ウィンドウの値を設定 console.log('非アクティブ化後に印刷された値', proxyWindow1.a, proxyWindow2.a, window.a);

コードを実行すると、次の結果が得られます。

この方法では、一度にアクティブ化できるサンドボックスは 1 つだけなので、マルチインスタンス サンドボックス化が実現します。

結論

上記はマイクロフロントエンドでよく使われるサンドボックス実装方法ですが、本番環境で使用する場合は多くの判断と制約が必要になります。次の記事では、マイクロフロントエンドフレームワーク Qiankun がソースコードを通じてサンドボックスをどのように実装するかを見ていきます。上記のコードはgithubにあります。表示するにはjs-sandboxにアクセスしてください。

参照する

iframe ソース
ES6 プロキシ

フロントエンド JS サンドボックスを実装するいくつかの方法についての記事はこれで終わりです。より関連性の高い JS サンドボックス コンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • Quickjs は JavaScript サンドボックスの詳細をカプセル化します
  • JavaScript サンドボックスの探索
  • JavaScript Sandboxについての簡単な説明
  • Node.jsサンドボックス環境についての簡単な説明
  • Node.js アプリケーション用の安全なサンドボックス環境の設定
  • JS実装クロージャにおけるサンドボックスモードの例
  • JS サンドボックス モードの例の分析
  • JavaScript デザインパターン セキュリティ サンドボックス モード
  • WebWorkerはJavaScriptサンドボックスの詳細をカプセル化します

<<:  Linux teeコマンドの使い方の詳しい説明

>>:  MySQL 8.0.16 圧縮パッケージのインストールと設定方法のグラフィックチュートリアル

推薦する

クエリでのMySQLのユニークキーの使用と関連する問題

1. テーブルステートメントを作成します。 テーブル「従業員」を作成します( `emp_no` in...

基礎知識: ウェブサイトのアドレスの前の http はどういう意味ですか?

HTTPとは何ですか?ウェブサイトを閲覧したいときは、ブラウザのアドレス バーにウェブサイトのアド...

URL 内の特殊記号の意味を知っていますか?

1.# # は Web ページ内の場所を表します。右側の文字はその位置の識別子です。たとえば、ht...

JS の配列トラバーサルについて、一般的なループをいくつ知っていますか?

序文基本的なデータ構造として、配列とオブジェクトはさまざまなプログラミング言語で重要な役割を果たしま...

MySQL データベース インデックスが B+ ツリーの使用を選択するのはなぜですか?

MySQL データベース インデックスが B+ ツリーを使用する理由をさらに分析する前に、データ構...

Ubuntu 18.04 MySQL 8.0 のインストールと設定方法のグラフィックチュートリアル

この記事では、MySQL 8.0のインストールと設定方法を参考までに紹介します。具体的な内容は以下の...

IE6 で CSS スタイルの div または li の背景のタイリングと境界の破損を解決する方法

IE6 で CSS スタイルの div または li の背景のタイリングや境界の破壊を解決するには、...

MySQL インデックスに関するヒントのまとめ

目次1. インデックスの基礎知識1.1 インデックスの利点1.2 インデックスの有用性1.3 インデ...

単一のMySQLテーブルを復元する手順

休憩中に、眠気を完全に吹き飛ばす電話がかかってきました。「開発者が更新 SQL を書くときに whe...

CSS 水平方向の中央揃えと最大幅の制限

CSS レイアウトとスタイルに関する質問: 水平方向の中央揃えと最大幅の制限のバランスをとる方法最近...

CentOS 7 での Docker プロキシの設定 (Linux での Systemd サービスの環境変数設定)

Docker デーモンは、 HTTP_PROXY 、 HTTPS_PROXY 、およびNO_PRO...

Windows で MySQL 5.7.17 圧縮バージョンをインストールするときに遭遇する落とし穴

まず、Windows 64 ビット用の最新の MySQL 5.7.17 コミュニティ圧縮バージョンを...

ブラウザでTIF形式の画像を表示する方法

ブラウザはTIF形式の画像を表示しますコードをコピーコードは次のとおりです。 <html>...

JavaScript で配列の変更を監視する方法

序文以前、defineProperty を紹介したとき、オブジェクトの変更のみを監視でき、配列の変更...

Linux サーバーに Python3 をインストールする 2 つの方法

最初の方法Alibaba Cloud および Baidu Cloud サーバーが利用可能です。 ! ...