blog

2020年に知っておくべきマイクロ・フロントエンド

マイクロフロントエンドは異なる機能を異なる次元に従って複数のサブアプリケーションに分割します。これらのサブアプリケーションはメインアプリケーションを通してロードされます。 アプリケーションを複数のサブ...

Oct 25, 2020 · 9 min. read
シェア

2020年に知っておくべきマイクロフロントエンドとは?

最近、マイクロフロントエンドという言葉をよく耳にしませんか? とても高尚な響きに感じませんか?ところがですよ~。

マイクロフロントエンドは、実はとてもシンプルで、軌道に乗せるのがとても簡単で、とても控えめです。

では、マイクロ・フロントエンドとは何なのかを見てみましょう:

I.なぜマイクロフロントエンドが必要なのですか?

マイクロフロントエンドとは何か、3Wの説明です:

1.マイクロフロントエンドとは?

マイクロフロントエンドとは、異なる機能が異なる次元に従って複数のサブアプリケーションに分割されるものです。これらのサブアプリケーションはメインアプリケーションを通してロードされます。

マイクロフロントエンドの肝は、分解して元に戻すこと!

2.なぜ?

  • 異なるチーム間で、異なる技術スタックで同じアプリケーションを開発する問題をどのように解決しますか?
  • 各チームが独立して開発し、独立して配備できるようにするにはどうすればいいでしょうか?
  • 古いアプリケーション・コードを必要とするプロジェクトの何が問題なのでしょうか?

アプリをサブアプリに分割し、サブアプリを個別のlibにパッケージ化することは可能ではないでしょうか?パスを切り替えて別のサブアプリケーションをロードすれば、各サブアプリケーションは独立しているので、技術スタックは制限をする必要がありません!こうして、フロントエンドの共同開発の問題が解決します。

3.マイクロフロントエンドの着地方法は?

2018年、Single-SPAが誕生しました。single-spaは、フロントエンドのマイクロサービスのためのJavaScriptフロントエンドソリューションです。 ルートハイジャックとアプリケーションローディングを可能にします;

2019 qiankunは、Single-SPAをベースとした、スタックに依存せずシンプルにアクセスできる、よりアウトオブボックスなAPIを提供します。

要約すると、サブアプリケーションは独立して構築でき、実行時に動的にロードされ、メインのサブアプリケーションは完全に切り離され、プロトコルアクセスに依存する技術スタックは無関係です。

これが、あなたが抱いているかもしれない疑問に対する最初の答えです:

これはiframeではないのですか?

  • iframeを使用している場合、iframe内のサブアプリケーションがルートを切り替えたときに、ユーザーがページを更新するのは厄介です。

アプリケーション同士はどのように通信するのですか?

  • URLベースのデータ配信ですが、この方法ではメッセージの配信能力は低いです;

  • 通信はCustomEventに基づいて実装されています;

  • プロップスベースのマスター・サブアプリケーション間通信;

  • グローバル変数を使ったコミュニケーション、Redux。

国民の依存にどう対処するか?

  • CDN - externals

  • webpack フェデレーションモジュール

.SingleSpa

1.サブアプリケーションの構築

まず、vueのサブアプリケーションを作成し、single-spa-vue経由で必要なライフサイクルをエクスポートします:

vue create spa-vue 
npm install single-spa-vue 
import singleSpaVue from 'single-spa-vue';
const appOptions = {
 el: '#vue',
 router,
 render: h => h(App)
}
// サブアプリケーション以外のアプリケーションの通常のマウント
if(!window.singleSpaNavigate){
 delete appOptions.el;
 new Vue(appOptions).$mount('#app');
}
const vueLifeCycle = singleSpaVue({
 Vue,
 appOptions
});
// サブアプリケーションは次のライフサイクルをエクスポートしなければならない:ブートストラップ、マウント、アンマウント
export const bootstrap = vueLifeCycle.bootstrap;
export const mount = vueLifeCycle.mount;
export const unmount = vueLifeCycle.unmount;
export default vueLifeCycle;
const router = new VueRouter({
 mode: 'history',
 base: '/vue', //パス設定の変更
 routes
})

サブルートベースパスの設定

2.構成ライブラリのパッケージング

//vue.config.js
module.exports = {
 configureWebpack: {
 output: {
 library: 'singleVue',
 libraryTarget: 'umd'
 },
 devServer:{
 port:10000
 }
 }
}

サブモジュールのライブラリへのパッケージ化

3.メインアプリケーション棟

<div id="nav">
 <router-link to="/vue">vue </router-link> 
 <div id="vue"></div>
</div>

サブアプリケーションをid="vue "タグにマウントします。

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
const loadScript = async (url)=> {
 await new Promise((resolve,reject)=>{
 const script = document.createElement('script');
 script.src = url;
 script.onload = resolve;
 script.onerror = reject;
 document.head.appendChild(script)
 });
}
import { registerApplication, start } from 'single-spa';
registerApplication(
 'singleVue',
 async ()=>{
 //指定されたファイルをロードするプロトコルを以下に示す。
 await loadScript('http://://-.js');
 await loadScript('http://://.js');
 return window.singleVue
 },
 location => location.pathname.startsWith('/vue')
)
start();
new Vue({
 router,
 render: h => h(App)
}).$mount('#app')

4.サブアプリケーションのpublicPathを動的に設定します。

if(window.singleSpaNavigate){
 __webpack_public_path__ = 'http://:00/'
}

.qiankun

qiankunは現在、より完全なマイクロフロントエンドソリューションであり、それは十分にテストされ、洗練された、非常に堅牢なプロジェクトの数の中でアリになっています。ここでは公式サイトです。

1.主な申請書作成

<el-menu :router="true" mode="horizontal">
 <el-menu-item index="/"> </el-menu-item>
 <el-menu-item index="/vue">vue </el-menu-item>
 <el-menu-item index="/react">react </el-menu-item>
</el-menu>
<router-view v-show="$route.name"></router-view>
<div v-show="!$route.name" id="vue"></div>
<div v-show="!$route.name" id="react"></div>

2.サブアプリケーションの登録

import {registerMicroApps,start} from 'qiankun'
const apps = [
 {
 name:'vueApp',
 entry:'//localhost:10000',
 container:'#vue',
 activeRule:'/vue'
 },
 {
 name:'reactApp',
 entry:'//localhost:20000',
 container:'#react',
 activeRule:'/react'
 }
]
registerMicroApps(apps);
start();

3.サブビューアプリケーション

let instance = null;
function render(){
 instance = new Vue({
 router,
 render: h => h(App)
 }).$mount('#app')
}
if(window.__POWERED_BY_QIANKUN__){
 __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
if(!window.__POWERED_BY_QIANKUN__){render()}
export async function bootstrap(){}
export async function mount(props){render();}
export async function unmount(){instance.$destroy();}

ここでサブアプリケーションのフックエクスポートを忘れないでください。

module.exports = {
 devServer:{
 port:10000,
 headers:{
 'Access-Control-Allow-Origin':'*' //ドメインをまたいだアクセスを許可する
 }
 },
 configureWebpack:{
 output:{
 library:'vueApp',
 libraryTarget:'umd'
 }
 }
}

4.サブリアクトアプリケーション

ここでは、技術スタックにとらわれない性質を示すために、Reactプロジェクトを使用しています:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
function render() {
 ReactDOM.render(
 <React.StrictMode>
 <App />
 </React.StrictMode>,
 document.getElementById('root')
 );
}
if(!window.__POWERED_BY_QIANKUN__){
 render()
}
export async function bootstrap() {}
export async function mount() {render();}
export async function unmount() {
 ReactDOM.unmountComponentAtNode(document.getElementById("root"));
}

reactのwebpack設定ファイルの書き換え

yarn add react-app-rewired --save-dev
module.exports = {
 webpack: (config) => {
 config.output.library = `reactApp`;
 config.output.libraryTarget = "umd";
 config.output.publicPath = 'http://:00/'
 return config
 },
 devServer: function (configFunction) {
 return function (proxy, allowedHost) {
 const config = configFunction(proxy, allowedHost);
 config.headers = {
 "Access-Control-Allow-Origin": "*",
 };
 return config;
 };
 },
};

.envファイルの設定

PORT=20000
WDS_SOCKET_PORT=20000

リアクト・ルーティングの設定

import { BrowserRouter, Route, Link } from "react-router-dom"
const BASE_NAME = window.__POWERED_BY_QIANKUN__ ? "/react" : "";
function App() {
 return (
 <BrowserRouter basename={BASE_NAME}>
 <Link to="/"> </Link>
 <Link to="/about"> </Link>
 <Route path="/" exact render={() => <h1>hello home</h1>}></Route>
 <Route path="/about" render={() => <h1>hello about</h1>}></Route>
 </BrowserRouter>
 );
}

.CSS分離スキーム

サブアプリケーション間のスタイル分離

  • ダイナミック・スタイルシートは、アプリケーションの切り替え時に古いアプリケーション・スタイルを削除し、新しいアプリケーション・スタイルを追加することで、ある時点で1つのアプリケーション・スタイルシートのみが有効になるようにします。

メイン・アプリケーションとサブ・アプリケーションの間のスタイル分離

  • BEM 国際大会 プロジェクト接頭辞
  • CSS-Modulesは、パッケージング時に競合しないセレクタ名を生成します。
  • シャドーDOMの真の分離
  • css-in-js
let shadowDom = shadow.attachShadow({ mode: 'open' }); // open/closeウィンドウのプロパティを外部から取得できるかどうかを設定する
let pElement = document.createElement('p');
pElement.innerHTML = 'hello world';
let styleElement = document.createElement('style');
styleElement.textContent = `
 p{color:red}
`
shadowDom.appendChild(pElement);
shadowDom.appendChild(styleElement)

シャドウDOM内の要素はシャドウDOM外の要素に影響を与えないため、真の分離が可能になります。

サブアプリケーションを実行する際は、内部サンドボックス環境で実行する必要があります。

  • サンドボックスをスナップショットし、アプリケーション・サンドボックスがマウントまたはアンマウントされたときにスナップショットを記録し、切り替え時にスナップショットに基づいて環境を回復します。
  • グローバル環境に影響を与えないプロキシのサンドボックス化

1.スナップショットサンドボックス

  • 1.起動時に現在のウィンドウプロパティをスナップショットします。

  • 2.スナップショットの内容を、非アクティブ化された現在のウィンドウプロパティと比較します。

  • 3.プロパティが変更された場合、それをmodifyPropsMapに保存し、スナップショットでウィンドウプロパティを復元します。

  • 4.再び起動したら、再度スナップショットを取り、変更した結果でウィンドウのプロパティを復元します。

class SnapshotSandbox {
 constructor() {
 this.proxy = window; 
 this.modifyPropsMap = {}; // どのプロパティが変更されるのか?
 this.active();
 }
 active() {
 this.windowSnapshot = {}; // windowオブジェクトのスナップショット
 for (const prop in window) {
 if (window.hasOwnProperty(prop)) {
 // ウィンドウのプロパティを撮影する
 this.windowSnapshot[prop] = window[prop];
 }
 }
 Object.keys(this.modifyPropsMap).forEach(p => {
 window[p] = this.modifyPropsMap[p];
 });
 }
 inactive() {
 for (const prop in window) { // diff  
 if (window.hasOwnProperty(prop)) {
 // 写真の結果を現在のウィンドウ・プロパティと比較する。
 if (window[prop] !== this.windowSnapshot[prop]) {
 // 変更結果を保存する
 this.modifyPropsMap[prop] = window[prop]; 
 // ウィンドウを復元する
 window[prop] = this.windowSnapshot[prop]; 
 }
 }
 }
 }
}
let sandbox = new SnapshotSandbox();
((window) => {
 window.a = 1;
 window.b = 2;
 window.c = 3
 console.log(a,b,c)
 sandbox.inactive();
 console.log(a,b,c)
})(sandbox.proxy);

スナップショット・サンドボックスはシングル・インスタンス・シナリオにのみ使用でき、複数のインスタンスが同時にマウントされている場合は解決できません。

class ProxySandbox {
 constructor() {
 const rawWindow = window;
 const fakeWindow = {}
 const proxy = new Proxy(fakeWindow, {
 set(target, p, value) {
 target[p] = value;
 return true
 },
 get(target, p) {
 return target[p] || rawWindow[p];
 }
 });
 this.proxy = proxy
 }
}
let sandbox1 = new ProxySandbox();
let sandbox2 = new ProxySandbox();
window.a = 1;
((window) => {
 window.a = 'hello';
 console.log(window.a)
})(sandbox1.proxy);
((window) => {
 window.a = 'world';
 console.log(window.a)
})(sandbox2.proxy);

各アプリケーションはウィンドウ・オブジェクトを表すプロキシを作成します。 各アプリケーションは比較的独立しており、グローバル・ウィンドウのプロパティを直接変更する必要がないという利点があります。

2020年 使い方だけマスターして原理を知らないなんて......」ということで、マイクロフロントエンドの実践的な部分を初めて紹介します。2020年、マイクロフロントエンドを知らなければならない~」と題して、マイクロフロントエンドフレームワークの手書き方法を伝授していきますので、ご期待ください!

Read next

アルゴリズムの復習:プラス1

最も大きい桁は配列の一番上に格納され,配列の各要素には1桁だけが格納されます.この整数は、整数0を除いて、0から始まることはないと考えてよいでしょう。 問題によると、1を足す必要があります!そうです、プラス1が重要なのです。ちょうどプラス1なので、2つのケースが考えられます:9以外のすべての数字が1に加えられる通常のケースと、9が1に加えられる特別なケースです。 もちろん...

Oct 25, 2020 · 2 min read