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年、マイクロフロントエンドを知らなければならない~」と題して、マイクロフロントエンドフレームワークの手書き方法を伝授していきますので、ご期待ください!





