微前端 从 0到 1搭建

全栈小刘

共 9534字,需浏览 20分钟

 · 2022-04-08

在这里插入图片描述

微前端

微前端

Single-SPA

微服务是面向服务架构(SOA)的一种变体,把应用程序设计成一系列松耦合的细粒度服务,并通过轻量级的通信协议组织起来 具体地,将应用构建成一组小型服务。这些服务都能够独立部署、独立扩展,每个服务都具有稳固的模块边界,甚至允许使用不同的编程语言来编写不同服务,也可以由不同的团队来管理

image-20220405155852230

概念

官网 :

2018年 Single-SPA诞生了, single-spa是一个用于前端微服务化的JavaScript前端解决方案  (本身没有处理样式隔离、js执行隔离)  实现了路由劫持和应用加载;

Alibaba -

springboot

- sofaboot

Single-SPA 搞了个入口  -->  qiankun

2019年 qiankun基于Single-SPA, 提供了更加开箱即用的 API  (single-spa + sandbox + import-html-entry),它 做到了技术栈无关,并且接入简单(有多简单呢,像iframe一样简单)。

总结:子应用可以独立构建,运行时动态加载,主子应用完全解耦,并且技术栈无关,靠的是协议接入(这里提前强调一下:子应用必须导出 bootstrap、mount、unmount三个方法)。

micro front ends single spot

应用量庞大,

实现上,关键问题在于:

  • 多个 Bundle 如何集成?

  • 子应用之间怎样隔离影响?

  • 公共资源如何复用?

  • 子应用间怎样通信?

  • 如何测试?

  • 当然,这种架构模式并非百益而无一害,一些问题也随之而来:

    • 导致依赖项冗余,增加用户的流量负担

    • 团队自治程度的增加,可能会破坏协作

      「.....」

简单来讲,微前端的理念类似于微服务:

In short, micro frontends are all about slicing up big and scary things into smaller, more manageable pieces, and then being explicit about the dependencies between them.

将庞大的整体拆成可控的小块,并明确它们之间的依赖关系。关键优势在于:

  • 代码库更小,更内聚、可维护性更高
  • 松耦合、自治的团队可扩展性更好
  • 「渐进地升级、更新甚至重写部分前端功能成为了可能」

微前端

微前端到底是什么?
img
微前端架构实战中-single-spa 篇
微前端

微前端就是将不同的功能按照不同的维度拆分成多个子应用。通过主应用来加载这些子应用。

image-20220403073941748

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

今天来一块聊聊微前端 技术

img
  • 一门前端语言的基础 Vue React

SingleSpa 实战

  • 构建子应用

首先创建一个vue子应用,并通过single-spa-vue来导出必要的生命周期:

vue create spa-vue  
npm install single-spa-vue  
import singleSpaVue from 'single-spa-vue';
const appOptions = {
   el'#vue',
   router,
   renderh => h(App)
}
// 在非子应用中正常挂载应用
if(!window.singleSpaNavigate){
 delete appOptions.el;
 new Vue(appOptions).$mount('#app');
}
const vueLifeCycle = singleSpaVue({
   Vue,
   appOptions
});
// 子应用必须导出以下生命周期:bootstrap、mount、unmount
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
})
  • 配置库打包

    //vue.config.js
    module.exports = {
        configureWebpack: {
            output: {
                library'singleVue',
                libraryTarget'umd'
            },
            devServer:{
                port:10000
            }
        }
    }
  • 主应用搭建

将子应用挂载到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://localhost:10000/js/chunk-vendors.js');
        await loadScript('http://localhost:10000/js/app.js');
        return window.singleVue
    },
    location => location.pathname.startsWith('/vue')
)
start();
new Vue({
  router,
  renderh => h(App)
}).$mount('#app')
 
  • 动态设置子应用
if(window.singleSpaNavigate){
  __webpack_public_path__ = 'http://localhost:10000/'
}

前置条件

npm install -g yarn
yarn init
  • 安装 官方 React

Create React App是FaceBook的React团队官方出的一个构建React单页面应用的脚手架工具。它本身集成了Webpack,并配置了一系列内置的loader和默认的npm的脚本,可以很轻松的实现零配置就可以快速开发React的应用。

# 全局安装
npm install -g create-react-app
# 构建一个my-app的项目
npx create-react-app my-app
cd my-app

# 启动编译当前的React项目,并自动打开 http://localhost:3000/
npm start
  • 构建 React项目

    • npm

      npm init react-app my-app
    • Yarn

      # yarn create is available in Yarn 0.25+
      yarn create react-app my-app

使用 qiankun 微前端构建

官方文档: https://qiankun.umijs.org/zh

首先我们需要创建 3个 前端应用, 微前端 ,就是 代表 一个小型应用的独立部署,独立交互,需要 应用之间进行通信,这里我们使用qiankun来完成 微前端 应用

  • 创建  3 个 react app

    yarn create react-app qiankun-base  --template typescript
    yarn create react-app qiankun-micro-app1  --template typescript
    yarn create react-app qiankun-micro-app2  --template typescript
    • app2
    • app1
    • 基座
  • 在 react app 应用中  安装 qiankun  依赖

    $ yarn add qiankun  # or npm i qiankun -S
  • 分别创建 .env 文件来指定 项目 运行的端口号

PORT=3010
PORT=3011
PORT=3012
image-20220405201705407

在主应用中index.tsx   注册子应用

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';


import { registerMicroApps, start } from 'qiankun';

registerMicroApps([
{
name: 'react app one', // app name registered
entry: '//localhost:3011',
container: '#micro-app2',
activeRule: '/micro-app2',
props:{
nickname: "全栈小刘",
age:19
}
},
{
name: 'react app two', // app name registered
entry: '//localhost:3012',
container: '#micro-app1',
activeRule: '/micro-app1',
props:{
nickname: "全栈小刘",
age:18
}
},
]);

start();

ReactDOM.render(


,
document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

:子应用加载进来 ,需要有主应用进行挂载,现在我们已经将 子应用注册在 了 主应用当中

  • name 应用名称
  • entry  端口号
  • container 挂载容器
  • activeRule 激活的规则
  • props: 父子属性之间传参

api文档 : https://qiankun.umijs.org/zh/api

  • 在 App.tsx 中创建挂载点
import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
return (

);
}

export default App;

「所有」应用中 添加 public-path.js 用于 加载静态 资源


if (window.__POWERED_BY_QIANKUN__) {
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
  }

在子应用中 添加 webpack 重写项

  • 添加
yarn add react-app-rewired  -D
  • 「设置 子应用」启动,在 scripts
  "scripts": {
    "start""react-app-rewired start",
    "build""react-scripts build",
    "test""react-scripts test",
    "eject""react-scripts eject"
  },

在webpack 中 进行 overrides 重写,重写的 目的是 允许跨域

  • config-overrides.js
const { name } = require('./package');

module.exports = {
  webpack(config) => {
    config.output.library = `${name}-[name]`;
    config.output.libraryTarget = 'umd';
    // config.output.jsonpFunction = `webpackJsonp_${name}`;
    config.output.globalObject = 'window';

    return config;
  },

  devServer(_) => {
    const config = _;

    config.headers = {
      'Access-Control-Allow-Origin''*',
    };
    config.historyApiFallback = true;
    config.hot = false;
    config.watchContentBase = false;
    config.liveReload = false;

    return config;
  },
};

在不同的子应用当中 去加载 tsx 生命周期

app1  、app2 、 index.tsx

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

export async function bootstrap() {
console.log('[react] react app bootstraped');
}

// @ts-ignore
export async function mount(props) {
console.log(props)
ReactDOM.render(
,
props.container
? props.container.querySelector('#root')
: document.getElementById('root'));
}
// @ts-ignore
export async function update(props){
console.log("update props",props)
}

// @ts-ignore
export async function unmount(props) {
ReactDOM.unmountComponentAtNode(
props.container
? props.container.querySelector('#root')
: document.getElementById('root'));
}
// @ts-ignore
if (!window.__POWERED_BY_QIANKUN__) {
ReactDOM.render(,document.getElementById("root"))
}




// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

在 index.tsx 引入 public-path.js 解决静态资源

import './public-path.js'

在 主 应用 添加 访问

import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
return (

);
}

export default App;

  • 在 主 应用 index.tsx 中 传递 数据
 props:{

nickname: "全栈小刘",

age:19

}


  • 在 子应用  周期中进行打印
export async function mount(props) {
console.log(props)
ReactDOM.render(
,
props.container
? props.container.querySelector('#root')
: document.getElementById('root'));
}
image-20220402075342284
  • 在 主应用中 监听事件改变
import { initGlobalState, MicroAppStateActions } from 'qiankun';

const state ={
nickname: "全栈小刘"
}

// 初始化

const actions: MicroAppStateActions = initGlobalState(state);

actions.onGlobalStateChange((state,prev)=>{
console.log(state,prev)
})
// 2秒钟后 改变
setTimeout(()=>{
actions.setGlobalState({...state,age:19})
},2000)

  • 在子应用中 监听改变
export async function mount(props) {


console.log(props)
// @ts-ignore

props.onGlobalStateChange((state,prev)=>{
console.log(state,prev)
setTimeout(()=>{
props.setGlobalState({ ...state, age:20 });
},2000)

})
// @ts-ignore


ReactDOM.render(
,
props.container
? props.container.querySelector('#root')
: document.getElementById('root'));
}
  • 安装 NPM SCRIPT 插件 ,分别 启动 运行
image-20220402073016755

接入 Vue3

通用 vue3

  • 安装最新的脚手架
npm install -g @vue/cli
  • 创建 项目  es6 js 模块
vue create qiankun-vue-micro-app3
  • 添加typescript ,转换 ts    -Y
  • yes
vue add typescript
  • 依次加入 public-path.js
/* eslint-disable */ 
if (window.__POWERED_BY_QIANKUN__) {
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
  }
  • 安装 qiankun
 yarn add qiankun
  • 参考qiankun官网的示例main.js ,完成生命周期的钩子函数

  • base 中 进行注册  index.tsx

  • vue-config.js 设置启动端口

  • vue-config.js

/* eslint-disable */ 
const { name } = require('./package');

module.exports = {
    devServer:{
       port: 3013,
       headers:{
        'Access-Control-Allow-Origin''*',
       }
    },
    configureWebpack:{
      output: {
        library: `${name}-[name]`,
        libraryTarget: 'umd'
      }
    }
  
};

微前端项目 实战

https://github.com/a1029563229/micro-front-template

效果图
  • Reference Document :
    • https://juejin.cn/post/6844903943873675271
    • https://zhuanlan.zhihu.com/p/96464401
    • https://single-spa.js.org/


浏览 44
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报