2020年你必须要会的微前端 -(实战篇)


2020年你必需要会的微前端 -(实战篇)

最近你有没有经常听到一个词,微前端? 是不是听上去感觉非常地巨大上!然而~

微前端其实非常地简略,非常地简略落地,而且也非常不巨大上~

那么就来一起看看什么是微前端吧:

一.为什么需要微前端?

这儿咱们经过3W(what,why,how)的办法C c Y { N t ~ *来解说什么是微前端:

1.What?什么是微前端?

2020年你必须要会的微前端 -(实战篇)

微前端便是将不同的功用依照不同的维度拆分红多个子运用。经过主运用来加载这些子运用。

微前端的核心在于, 拆完后再!

2j L ] d M C p ~ .Why?为什么去运V { O用他?

  • 不同团队间开发同一个运用技y @ T I s d d {能栈不同怎样破?
  • 期望每个团队都能够独立开发,独立部署怎样破?
  • 项目中还需要老的运用代码怎样破?

咱们是不是能够将一个运用划分红K o / _ t W w L N若干个子运用,再将子运用打包成一Z V n J个个的lib呢?当途径切换时加载不同的子运用,这样每个子运用都是独立的,技能栈也就不用再做约束了!然后n b ( / R j处理了前端M t 2 ^ 6 G z y协同开发的问题。l d L %

3.How?怎样落地微前端?

2020年你必须要会的微前端 -(实战篇)

2018m H M P # A [ – p年 Single-SPA诞生了, single-spa是一个用于前端微Y / U 4服务化的JavaScript前端处理方案 (本身没有处理款式阻隔、js执行阻隔) 完成了G I U * ,路由劫持和运用加载;

2019年 qiankun根据Single-SPA, 提供了愈7 ; 6 I N加开箱即用的 APIsingle-spa + sandbox + import-html-entry. i l c m + & | b,它 做到了技能栈无关,而且接入简略(有多简略呢,像iframe一样简略)。

总结:子运用能够独立构建,运行时动态加载,主子运用完全解耦,而且技能栈无关,靠的是协议接入(这儿提前着重一下:子运用有必要导出 bootstrap、mount、unmount三个办法)。

这儿先回答一下大家可能会有的疑问:

这不是iframe吗?

  • 假如运用的是iframe,当iframe中的子运用切换路由时用户刷新页面就尴尬了。

运用间怎么通讯?

  • 根据URL来进行数据传递,可是这种传递音讯的办法能力较_ z = $ i ! / 7 7弱;

  • 根据CustomEvent完成通讯;

  • 根据props主子运用间通讯;

  • 运用全局变量、Redux进行通讯。

怎么处理公共依靠?

  • CDN – exC # 6 kternals

  • webpack联邦模块

二 .SingleSpa实战

1.构建子运用

K z G要创立一个vue子运用,并经过single-spa-vue来导出必要的生命周期:

vue createZ P : { ; C J m spa-vu^ @ o T T 3 1 ?e
npm install single-spa-vue
import singleSpaVue from 'single-spa-vue';
const appOptions = {
   el: '#vue',
   router,
   render: h => h(App)( l , p % -
}
// 在非子运用中正y ; R O c P (^ 1 X挂载运用
if(!window.singleSpaNav* e & igate){
 delete ap8 C y X y MpOptiO h R 7 ( N , - bons.el;
 new Vue(appOptions).$mount('#1 c y l v n = p app'J N N _ : 3 x *);
}
const vueLifeCycle = singleSpaVue({
   Vue,
   appOptions
});
// 子运用有必要导出以下生命] h + B # a周期: bootstrap、mount、unmount
export constM s ` E bootstrap = vueLifeCy7 - . v i 5 1 H cle.bootstrap;
export co~ [  ` d v w %nst mount = vueLifeCycle.mount;
export const unmount = vueLif? ` ; ; s keC` q V _ H G { Eycle.unmount;
export default vueLifeCycle;
cQ M t # Yonst router = new VueRouter({
  mode: 'history',
  base: '/vue',   //改动途径装备
  rC O k ! k o | :outeT - . os
})

装备1 M }] ] 1 R P o H ! m路由根底途径

2.装备库打包

//vue.config.jsv j l e ? :
module.exports = {
    configureWebpacK l U Ck: {
        output: {
            library: 'singley Q 6Vue',
            libraryTarget: 'umd'
        },
        devServer:{
            port:10000
        }
    }
}

将子模块打包成类库

3.主运用建立

<div id=X Q )"n, F G 1 D , ^ W 2av">
    <router-link to="/B 5 Dvue">vue项目</router-link>
    <div id="vue"></div>[ A g;
</div>

将子运用挂载到id="vue"标签中

import Vue from 'vue'
import App from './App.vue'
im& 2 % ! u Tport router from './router'
import ElementV 3 U H C ^UI: @ v W w p from 'elemen { Jt-ui';
import 'eld I i C j a r wement-ui/libH & _ V ~ B + g/theme-chalk/index.css';b h m 7 4 = u W
Vue.use(ElementUI);
const loadScript = async (url)=> {
  await neE ] : 3 . O d cw Pr@ 8 gomise((res[ 9 = f 5 V : Zolve,rejecc ! @ _t)=>{
    const script = document.createElement('scrih D d Q q tpt'( ? F k . N);
    script.src = url;
    script.onload = resolve;
    script.onerror = reject;
    document.head.appendChild(script)
  });
}
import { regi] V 8 z _sterv j (Application,+ 2 g j sX Y e G F [tart } from 'single-sp7 D g i i @ B |a';
registerApplicati ^ B ; t - Z Z ton(
    'singleVue',
    async ()=>{
        //这儿经过协议来加载指定文件
        await loadScript('http) . T i s e @ `://localhost:10000/js/chunk-vendors.js');
        await loadScri o Y ^ m h ! Zpt; + &('http://localhost:10000/js/app.js');
        return window.singleVue
    },
    location => location.pathnB 5 C w m 1 t Rame.startsWith('/vue')
)
start();
new Vue({
  router,
  render: h => h(App)
}).$mount(? H D S'#app')

4.动态设置子运用publicPath

if(window.singleSpaNavigate){
  __webpack_public_path__ = 'v [ o i phttp://localhost:10000/'
}

三.qiankun实战

qiankun是目前比较完善的# } 8 ) 0 Z J = !一个微前端处理方案,它已在蚂蚁内部饱尝过足够大量的项目检3 u ` { { z R &测及打磨,非常强健。这儿附上官网。

1.主运用编写

<el-menu :router="true" mode t w f 8 m %="horizontal">Y )  | z;
    <el-menu-item index="/">主页</el-menu-item>
    <el-menu-item index="/vue">vue运用</el-menu-item>
    <el-menu-item index="/reh , 1 E _ @ 5 o gact">react运用</el-menu-item>
</el-menu>
<router-view v-show="$route.name"></router] ; 7 M a-view>
<div v-show="!$a 0 ` . ]route.name" id=t } R ` j ?"vue"></div>
<div v-show="!$rout( y 4 / t ue.name" id="react"></div| k ) e ; r n>

2.注册子运用

import {registerMicroApps,start} from 'qiankun'
const apps = [
  {
    name:'vueApp',
    entry:'//localhost:10000',
    container:'#vue',
    activeRule:'/vue'
  },
  {} Q O
    name:'reactJ f K ! BApp% u + ',
    entry:'//localhost:20000',b M ) U
    cI W container:'#react',
    actv : ( civeRule:'/rel w Z s Hact'
  }
]
registerMicroApps(apo l j x X V {ps);
start();

3.子Vue运用

let io - 2 3 ) lnstance = null;
function render(){
  instance = new Vue({
    router,
    rende] ; = X &r: h => h(App)
  }).$mount('#app')
}
iN @ 6 $f(window.__POWERED_B1 y z T | / gY_QIANKUN__){
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
if(!window.__POWERED_BY_QIANKUN__){render()}
export aA 7 9 U ~ 5 3 4sync function bootstrap(){}
export async function mount(props){render();}
export async function unmount(){iN & Q nstance.$des$ l * 3 htroy();}

这儿不要忘记子运用的钩子导出。

module.exports = {
    devServer:{
        porw @ [ { ; + ; Z pt:10000,
        heaZ g X c 9ders:{
            'Access-Ca l Oont R . # 2 7trol] m i q G G ; ?-Allow-Origin':'*' //允许拜访跨域
        }
    },
    confige e  D V P t - bureWebpack:{
        output:{
            library:'vueApp',
            libraryT* } u I n 2 L l .arget:'umd'
        }
    }
}

4.子React运I q o d

再起一个子运用,& 2 R为了标明技能栈无关特性,这儿运用了一个React项目:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
function render() {
  ReactDOM.render(
    <Rea& C H L d p Y f Tct.StrictMode>
      <App />
    </React.StrictMode>* p q I  w & 3 a,
    document.getElementById('root')
  );
}
if(!window.__POWERED_BY_QIG 8 3 N } I VANKUN__){
  render()
}
export async function bootstrap() {}
export async function mount() {render();}
export async function unmount() {
  Rj 1 UeactDOM.unmountComponentAtNode(W B pdocumet q Qnt.getElementById("root"));
}

重写react中的webpack装备文件 (config-overrides.js)

yarn add react-app-rewir0 N % { =ed --save-dev
module.exports = {
  webpack: (config) => {
    config.output.library+ Z T ^ B = `reactApp`;
    config.output.libraryTarget = "umd";
    config.output.publicPath = 'http://localhost:20000/'
    return config
  },
  devServer: function (configFunction) {
    return function (proxy, allowedHost) {
      const config = configFun? } ] { = X [ 6 Hction(proxy,K ] B V B j & allowedHost);
      config.headers = {
        "Access-Control-Allow-Origin": "*",
      };
      return config;
    };
  },
};

装备.env文件

PORT=20000
WDS_SOCKET_f G . 9 oPORT=20000

React路由装备

import { BrowserRW T L i 4 9 f l wouter, Route, Link } from "react-router-dom"
const BASE_NAME = window.__POWERED_BY_QIANKUN__ ? "/react" : "";
function App() {
  return (
    <BrowserRou% ` p IteL ; x 1 @ f ^r basename={BASE_NAM5 4 S 4 s 9 2E}&{ % f | Y B a D Zgt;
      <Link to="/">主页</Link>
      <Lin4 , H 5 = j a  @k to="/about">{ $ s 9 ] $ m v I</Link>
      <Route@ L = v path="/" exactN n * f render={() => <h1>hello home</h1>}></Route>
      <Rou; : q l y 5 W Xte path! L p="/about" render={() => <h1>hello about&* L `lt;/h1>}></Route>
    &l[ Y R - K vt;/BrowserRouter>
  );
}

四.CS) A f D U PS阻隔方案

子运用之间款式阻隔

  • Dynamic Stylesheet动态款式表,当运用切换时移除掉老运用款式,再增加新运用款式,保证在一个时间点内只有一个运用的款式表收效

主运用和子运用之间的款式阻隔

  • BEM(BlocP 3 % 6k Element Modifier) 约定项目前缀
  • CSS-Modules 打包时? W { u S } & 3生成不冲突的选择器名
  • Shadow DOM 真实意K W { ! G / & d义上的阻隔
  • css-in-js
2020年你必须要会的微前端 -(实战篇)
let shadowDom = shadow.attachShadow({ mode: 'open' }); // open/close设置可否从外部获取
let pElement = document.9 U  fcreateElement('p');
pElement.innerHTML = 'hello worl} N H #d';
let styleEleme: ) k & D u @nt = document.createElement('style')% K  3 ~;
styleElement.textContent = `
  p{color:red}
`
shadowDom.appendChild(pN N / [ C PElement);
shadowDom.appe, F i rndChild(styleElement)

shadow DOM 内部的元素始终不会影响到它的外部元素,能够完成真实意义上的阻隔

五.JS沙箱机制

2020年你必须要会的微前端 -(实战篇)

当运行子运用时应该跑在内部沙箱环境中

  • 快照沙箱,当运用沙箱挂载或卸载时记载快照,在切换时依据快照康复环境 (无法支撑多实例)
  • Proxy^ D k 1 j v s M 署理沙箱,不影响全局环境

1.快照沙箱

  • 1.激活时将当前window特点进行快照处理

  • 2.失活时用快照中的内容和当前wiS M K x bndow特点比对

  • 3.假如特点发生变化保存到modifyPropsM~ 6 W B j / G h Bap中,并用快照还原win@ d vdow特点

  • 4.再次激活时,再次进行快照,并用前次修改的成B Z o # , x h果还原windm C ] U Row特点

class SnapshotSandbox {
    constructor() {
        this.proxy = window;
        this.modifyPropsMap = {}; // 修改了哪些特点
        this.active();
    }
    active() {
        this.windowSnapshot = {}; // wi; { } $ ) = Fndow对象的快照
        for (const prop in window) {
            if (window.hasOwnProperty(prop)) {
                // 将windowo g [ a ; q i J S上的特点进行拍照
                this.windowSnapshot[prop] = window[pr6 = t } 2op];
            }
        }
        Object.keys(this.modifyPropsMap).forEach(p => {
            window[p] = this.modifyPropsMap[p];
        });
    }
    inactive() {
        for (z ] W V ( Cconst prop in windowj y 7 + ! d) {T  / 6 P T R W B // diff 差异
            if (window.hasOwnProperw W - ) ] u [ tty(prop)) {
                // 将前次拍照的成果和本次window特点做对比
                if (window[pro} U } 0 g B Q _p] !== this.wind@ 2 ? b owSnapshot[prop]) {
                    // 保存修改后的成果
                    this.modifyPropsMap[prop] = window[prop7 2 h Z A 1 M 9 :];
                    // 还原window
                    window[prop] = this.windowSnapshot[prop];
                }
            }
        }
    }
}
let saW 6 ; fndbX m ^ P u Qox = new SnapU & LshotSandbox();
((window) => {
    window.a = 1;
    window.b =E G { 2;
    window.c = 3
    console.logh 8 _ ( (a,b,c)
    sandbox.inactive(Y +   s S);
    console.log(a,b,c)
})(sandbox.proxy);

快照沙箱只能3 F Y s d k O * I针对单实例运用场景,g 3 8假如是多个实例同时挂载的状况则无法处理,这时只能经+ [ Y ( – q过Proxy署理沙箱来完成

2t g ? # u.Proxy 署理沙箱

class ProxySandbox {
    construC K { k d F k 1ctor() {
        con6 @ m ? . e v x 2st rawWindow = window;
        cou X D Q mnst fakeWindow = {}
        const proxy = new Proxy(fakeWindow, {I * . K 7 - S L
            set(target, p,2 6 B  % % , value)z 9 r k E {
                tarE Y 2 9get[p] = value;
                return true
            },
            get(target, p) {
                return target[p] || rawWindow[p];
            }
        });
        this.proxy = proxy
    }
}
let sandbox1 = new ProxySandbox();
let sandbox2 = new ProxySandbox();
window.) 5 b  3 t F Na = 1;
((window) => {
    window.a = 'hello';
    con3 r } S 0 Z J 4 ?sole.log(window.a)
})(sandbox1.pV 7 & S P ( { C Yroxy);
((window) => {
    window.a = 'world';
    console.log(window.a)
})(sandbox2.proxy);

每个运用都创立一个proxy来署理window对象/ W 3 v K B u Z,优点是每个I $ V l 6运用都是相对独立的,不需要直接更改全局的window特点。


微前端的实战就先介绍到这儿– 9 N , # f e U,2020年怎样能只把握9 0 L ; m用法而不懂得原理呢?后续将持续推出《2020年你必需要会的微前端 -(R r J 2 A原理篇)》,教你怎么手写一个微前端– | 3 r b | Z结构,敬请期待!

2020年你必须要会的微前端 -(实战篇)

本文运用 mdnicew } Q 排版

发表评论

提供最优质的资源集合

立即查看 了解详情