Micro front-end study notes

1. Why you should learn micro front-end

What is micro frontend

Insert image description here

Micro frontend is to split different functions into multiple sub-applications according to different dimensions. These sub-applications are loaded through the main application.

The core of micro front-end lies in disassembly, and then put it together after disassembly!

Why use micro frontends

  • How to solve the problem of different technology stacks when different teams develop the same application?
  • I hope each team can develop independently. How to break independent deployment?
  • How can I break the old application code that is still needed in the project?

Can we divide an application into several sub-applications and package the sub-applications into libs? Load the same sub-application when the path is switched. In this way, each sub-application is independent, and there is no need to limit the technology stack! Thus solving the problem of front-end collaborative development

How to implement micro front-end

Insert image description here

The inspiration of micro front-end comes from computer applications. Every time a user opens an application, it is equivalent to opening a new page.

  • Single-SPA was born in 2018. Single-spa is a JavaScript front-end solution for front-end microservices (it does not handle style isolation, js execution isolation) and implements route hijacking and application loading.
  • In 2019, qiankun provided a more out-of-the-box API (single-spa + sandbox + import-html-entry) based on Single-SPA. It has nothing to do with the technology stack and is easy to access (as simple as an iframe)

Summary: Sub-applications can be built independently and loaded dynamically at runtime. The main and sub-applications are completely decoupled and have nothing to do with the technology stack. They rely on protocol access (sub-applications must export bootstrap, mount, and unmount methods)

Here are the answers to your questions:

  • Isn't this an iframe?

    If iframe is used, it will be awkward for the user to refresh the page when the sub-application in the iframe switches routes.

    More referenceWhy Not Iframe

  • How to communicate between applications

    • Data transfer is based on URL, but the ability to transfer messages is weak
    • Implement communication based on CustomEvent
    • Communication between master and child applications based on props
    • Communicate using global variables, Redux
  • public dependency

    • CDN - externals
    • webpack federation module

The micro front-end architecture has the following core values:

  • Technology stack independent

    The main framework does not limit the technology stack of the access application, and micro-applications have complete autonomy.

  • Independent development and independent deployment

    The micro-application warehouse is independent, and the front-end and back-end can be developed independently. After the deployment is completed, the main framework automatically completes synchronous updates.

  • Incremental upgrade

    When faced with various complex scenarios, it is usually difficult for us to fully upgrade or reconstruct the technology stack of an existing system, and micro-frontends are a very good means and strategy for implementing progressive reconstruction.

  • standalone runtime

    The state of each micro application is isolated and the runtime state is not shared.

2. SingleSpa in practice

single-spa official website

Build a sub-application

We need the parent application to load the child application and need to expose three methods

1. bootstrap
2. mount
3. unmount
  1. Build a sub-application
vue create single-child
npm i --save single-spa-vue
// 在子应用的 main.js 中导入依赖并进行配置
import singleSpaVue from 'single-spa-vue'

const appOptions = {
    
    
  el: '#vue', // 挂载到父应用中的 id 为 vue 的标签中
  router,
  render: h => h(App)
}

const vueLifeCycle = singleSpaVue({
    
     // 返回single-spa 的生命周期也就是 bootstrap/mount/unmount
  Vue,
  appOptions
});

// single规定的协议,父应用会调用这些方法
export const bootstrap = vueLifeCycle.bootstrap;
export const mount = vueLifeCycle.mount;
export const unmount = vueLifeCycle.unmount;
// 这样做就有一个严重的问题,子应用无法启动了??
  1. Configure the packaging path in the sub-application
// 配置vue.config.js
module.exports = {
    
    
  configureWebpack: {
    
    
    output: {
    
    
      library: 'singleVue',
      libraryTarg: 'umd'
    },
    devServer: {
    
    
      port: 10000
    }
  }
};
  1. Configure routing for sub-applications
const router = new VueRouter({
    
    
  mode: 'history',
  base: '/vue', // 配置子应用路由的基础路径
  routes
})
  1. Parent application construction
vue create single-parent
npm i --save single-spa // 注意这里是 single-spa
  1. Mount the sub-application into id="vue"the container of
<div id="app">
  <!-- 当路由切换到 /vue 时加载子应用 -->
  <router-link to="/vue">加载vue引用</router-link>
  <router-view/>
  <!-- 子应用加载的位置 -->
  <div id="vue"></div>
</div>
  1. Configure the parent application to load the child application
// 在父应用中导入依赖
import {
    
     registerApplication, start } from 'single-spa'

async function loadScript(url) {
    
     // 异步加载子组件中的脚本
  return new Promise((resolve, reject) => {
    
    
    let script = document.createElement('script');
    script.src = url;
    script.onload = resolve;
    script.onerror = reject;
    document.head.appendChild(script);
  });
}

// 注册一个应用
registerApplication(
  'myVueApp',
  async () => {
    
    
    console.log('加载模块');
    // 加载子应用中的脚本
    await loadScript(`http://localhost:10000/js/chunk-vendors.js`)
    await loadScript(`http://localhost:10000/js/app.js`)
    // 这里需要要返回 bootstrap/mount/unmount
    return window.singleVue
  },
  location => location.pathname.startsWith('/vue'), // 此路径用来判断当前路由切换到 /vue 的路径下,需要加载我们定义的子应用
  {
    
     a: 1 } // 选传,传给子应用 props 的参数,可以是对象或值
);

start(); // 启动应用
  1. Configure the path of the sub-application
// 设置路径
if (window.singleSpaNavigate) {
    
     // 如果是父应用加载子应用,那会自动挂载一个属性,值为true
  __webpack_public_path__ = 'http://localhost:10000/'
}
  1. If you want the sub-application to run independently, add a configuration in the sub-application
if(!window.singleSpaNavigate){
    
    
  delete appOptions.el; // 子应用中没有#vue,所以需要手动删除,挂载到 #app 中
  new Vue(appOptions).$mount('#app');
}

singleSpa defect

  1. JS files cannot be loaded dynamically
  2. Styles are not isolated
  3. Global object, no JS sandbox mechanism

3. Qiankun actual combat

qiankun official website

Features

1. 简单:任意 js 框架均可使用。微应用接入像使用接入一个 iframe 系统一样简单,但实际不是 iframe。
2. 完备:几乎包含所有构建微前端系统时所需要的基本能力,如 样式隔离、js 沙箱、预加载等。
3. 生产可用:已在蚂蚁内外经受过足够大量的线上系统的考验及打磨,健壮性值得信赖。

Project build

  1. Main application constructionqiankun-base
vue create qiankun-base
npm i --save qiankun
// 配置主项目的加载 main.js
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';

import {
    
    registerMicroApps, start} from 'qiankun';

Vue.config.productionTip = false
Vue.use(ElementUI);

const apps = [
  {
    
    
    name: 'vueApp', // 应用的名字
    entry: 'http://localhost:10000/', // 默认加载这个html,解析里面的js动态的执行(子应用必须支持跨域,内部使用的是 fetch)
    container: '#vue', // 要渲染到的容器名id
    activeRule: '/vue' // 通过哪一个路由来激活
  },
  {
    
    
    name: 'reactApp',
    entry: 'http://localhost:20000/',
    container: '#react',
    activeRule: '/react'
  }
];

registerMicroApps(apps); // 注册应用
start(); // 开启应用

new Vue({
    
    
  router,
  render: h => h(App)
}).$mount('#app')
<!-- 设置容器 -->
<template>
  <div>
    <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 id="vue"></div>
    <div id="react"></div>
  </div>
</template>
  1. Build Vue subproject
vue create qiankun-vue
// 子项目中不需要安装任何依赖,父组件会给window设置一些环境变量
// mian.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false

/*
new Vue({
  router,
  render: h => h(App)
}).$mount('#app')
*/
let instance = null;
function render(props) {
    
    
  // props 组件通信
  instance = new Vue({
    
    
    router,
    render: h => h(App)
  }).$mount('#app') // 这里是挂载到自己的HTML中,基座会拿到这个挂载后的HTML,将其插入进去
}

if (!window.__POWERED_BY_QIANKUN__) {
    
     // 如果是独立运行,则手动调用渲染
  render();
}
if(window.__POWERED_BY_QIANKUN__){
    
     // 如果是qiankun使用到了,则会动态注入路径
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

// 根据 qiankun 的协议需要导出 bootstrap/mount/unmount
export async function bootstrap(props) {
    
    

};
export async function mount(props) {
    
    
  render(props);
};
export async function unmount(props) {
    
    
  instance.$destroy();
};
// 设置router路径
const router = new VueRouter({
    
    
  mode: 'history',
  base: '/vue',
  routes
})
// 配置打包 vue.config.js
module.exports = {
    
    
  devServer: {
    
    
    port: 10000,
    headers:{
    
    
      'Access-Control-Allow-Origin': '*' // 允许跨域
    }
  },
  configureWebpack: {
    
    
    output: {
    
    
      library: 'vueApp',
      libraryTarget: 'umd'
    }
  }
};
  1. Build a React project
npx create-react-app qiankun-react
npm i --save-dev react-app-rewired
// 入口配置 /src/index.js
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'));
}
// 配置启动 config-overrides.js
module.exports = {
    
    
  webpack:(config)=>{
    
    
    config.output.library = 'reactApp';
    config.output.libraryTarget = 'umd';
    config.output.publicPath = 'http://localhost:20000/';
    return config;
  },
  devServer:(configFunction)=>{
    
    
    return function (proxy,allowedHost){
    
    
      const config = configFunction(proxy,allowedHost);
      config.headers = {
    
    
        "Access-Control-Allow-Origin":'*'
      }
      return config
    }
  }
}
添加react环境变量 .env

PORT=20000
WDS_SOCKET_PORT=20000
// 配置react路由
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>
  );
}

Guess you like

Origin blog.csdn.net/Big_YiFeng/article/details/127005631