Опыт работы с микроинтерфейсом (qiankun)

внешний интерфейс

предисловие

Недавно я увидел, как маленький друг в группе UED компании болтает о микро-фронтенде qiankun, и я тоже попал в яму от любопытства.

Что такое микрофронтенд?

Микро-интерфейс — это технические средства и стратегия методов, позволяющая нескольким командам совместно создавать современные веб-приложения путем независимой публикации функций.

qiankun

Микро-интерфейсное решение qiankun Ant Financial на основе единого спа-центра, доступное в производстве.

характеристика
  • Основанный на пакете single-spa, он предоставляет более готовый API.
  • Независимо от стека технологий, приложения любого стека технологий могут использоваться/доступны, будь то React/Vue/Angular/JQuery или другие фреймворки.
  • Метод доступа HTML Entry позволяет получить доступ к микроприложениям так же легко, как и с помощью iframe.
  • Изоляция стилей гарантирует, что стили между микроприложениями не будут мешать друг другу.
  • Песочница JS, чтобы глобальные переменные/события не конфликтовали между микроприложениями.
  • Предварительная загрузка ресурсов, которая предварительно загружает неоткрытые ресурсы микро-приложений во время простоя браузера, чтобы ускорить открытие микро-приложений.

Основная конструкция приложения

выбрать использованиеvue-cliОсновное приложение инициализировано, если вы не понимаете, то можете сами прочитать официальную документацию
Представлено в проектеqiankun:

$ yarn add qiankun # 或者 npm i qiankun -S
Зарегистрировать микроприложение

Определите микроприложения, которые необходимо загрузить

// src/micro/apps.ts
//此时我们还没有微应用,所以暂时为空
const apps: any = [
    
];

export default apps;

Зарегистрируйте микроприложение и выставьте метод

// src/micro/index.ts
import {
    registerMicroApps,
    addGlobalUncaughtErrorHandler,
    start,
} from "qiankun";
import NProgress from "nprogress";
import { Message } from 'element-ui';
import 'nprogress/nprogress.css';

NProgress.configure({ parent: '.scrollbar.scroll' });

export default function (apps: []) {
    registerMicroApps(apps, {
        beforeLoad: () => {
            // 加载微应用前,加载进度条
            NProgress.start();
            return Promise.resolve();
        },
        afterMount: () => {
            NProgress.done();
            return Promise.resolve();
        },
    });

    addGlobalUncaughtErrorHandler((event: any) => {
        const { msg } = event as any;
        NProgress.done();
        // 加载失败时提示
        if (msg && msg.includes("died in status LOADING_SOURCE_CODE")) {
            Message.error('微应用加载失败,请检查应用是否可运行');
        }
    });

    start();
}

Поскольку наше микро-приложение может войти в систему, микро-приложение пользователя создается в соответствии с разрешениями левого меню пользователя. Таким образом, открытые методы и входные параметры удобны для входа в систему и вызова зарегистрированного микроприложения.

Запустить микроприложение
import startQiankun from "@/micro";

startQiankun(...);  //在需要启动的地方调用传入数据就行

Вот что я начал с глобальной защиты маршрутизации для справки.


//router
import Vue from 'vue';
import VueRouter, { RouteConfig } from 'vue-router';
import store from "@/store";
import { getToken } from "@/utils/auth";
import startQiankun from "@/micro";
import apps from "@/micro/apps";

Vue.use(VueRouter);

const routes: Array<RouteConfig> = [
  {
    path: '/login',
    name: 'login',
    component: () => import('@/views/login/index.vue')
  },
  {
    path: '/',
    name: 'main',
    component: () => import('@/views/Layout/index.vue'),
    children: [
      {
        path: '',
        name: 'Home',
        component: () => import('@/views/Home.vue')
      }
    ]
  },
  {
    path: '*',
    name: 'redirect',
    redirect: '/'
  }
];

const createRouter: any = () => new VueRouter({
  mode: "history",
  routes,
});

const router: any = createRouter()

/**
 * 重置路由
 */
export function restRouter() {
  router.matcher = createRouter().matcher;
}

const whiteList = ['login'];
router.beforeEach((to: any, from: any, next: any) => {
  const token = getToken('token');
  if (token) {   //token存在
    if (to.name === 'login') {     //如果login直接跳转首页
      return next({ path: '/' });
    }
    if (!store.state.hasInited) {   //防止反复addRoutes预设的值
      store.dispatch('addRouters').then((res) => {
        router.addRoutes(res);
        startQiankun(apps);
        store.state.hasInited = true;
        next({ ...to, replace: true });
      })
      return;
    }
    next();
  } else if (whiteList.includes(to.name)) {   //白名单直接放行
    next();
  } else {     //token不存在
    next({ path: '/login', query: { redirect: to.path } });
  }
});

export default router;

Создание подприложения Vue

Настройте вспомогательные приложения, к которым необходимо получить доступ в основном приложении.

// micro/apps.ts
import app from "./shared";  //分享给子应用的数据

/*
 * name: 微应用名称 - 具有唯一性
 * entry: 微应用入口 - 通过该地址加载微应用
 * container: 微应用挂载节点 - 微应用加载完成后将挂载在该节点上
 * activeRule: 微应用触发的路由规则 - 触发路由规则后将加载该微应用
 * props: 共享给微应用的数据
 */
const apps: any = [
    {
        name: "vue-project",
        entry: "//localhost:10300",
        container: "#app-qiankun",
        activeRule: "/vue",
        props: { app }
    }
];

export default apps;

Настройка дополнительных приложений

После того, как основное приложение настроит зарегистрированное микроприложение, нам нужно настроить подприложение, чтобы подприложение могло получить доступ к основному приложению.
1. Конфигурация входа main.js подприложения vue

// public-path.js
if (window.__POWERED_BY_QIANKUN__) {
    // 动态设置 webpack publicPath,防止资源加载出错
    // eslint-disable-next-line no-undef
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
// main.js
import Vue from 'vue';
import App from './App.vue';
import VueRouter from "vue-router";
import './registerServiceWorker';
import routes from './router';
import store from './store';
import './public-path'

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

let instance = null;
let router = null;


function render() {
  router = new VueRouter({
    // 运行在主应用中时,基础路由地址配置为 /vue
    base: window.__POWERED_BY_QIANKUN__ ? "/vue" : "/",
    mode: "history",
    routes,
  });

  instance = new Vue({
    router,
    store,
    render: (h) => h(App),
  }).$mount("#app");
}

/**
 * 不存在主应用时可直接单独运行
 */
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap() {
  
}

/**
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
export async function mount(props) {
  console.log(props);
  render(props);
}

/**
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async function unmount() {
  instance.$destroy();
  instance = null;
  router = null;
}

2. Настройте стратегию упаковки webpack


// vue.config.js
const path = require("path");

module.exports = {
    //配置静态文件host路径
    publicPath: 'http://localhost:10300',
    devServer: {
        // 监听端口
        port: 10300,
        overlay: {
            warnings: false,
            errors: false
        },
        // 关闭主机检查,使微应用可以被 fetch
        disableHostCheck: true,
        // 配置跨域请求头,解决开发环境的跨域问题
        headers: {
            "Access-Control-Allow-Origin": "*",
        }
    },
    configureWebpack: {
        resolve: {
            alias: {
                "@": path.resolve(__dirname, "src"),
            },
        },
        output: {
            // 微应用的包名,这里与主应用中注册的微应用名称一致
            library: "vue-project",
            // 将你的 library 暴露为所有的模块定义下都可运行的方式
            libraryTarget: "umd",
            // 按需加载相关,设置为 webpackJsonp_vue-projec 即可
            jsonpFunction: `webpackJsonp_vue-project`,
        },
    },
};

На данный момент наш микро-интерфейс полностью настроен, но на данный момент подключено только одно суб-приложение. По вышеприведенному коду можно сделать вывод и несколько ключевых моментов:
1. Зарегистрируйте субприложения в основном приложении:registerMicroApps addGlobalUncaughtErrorHandler startТри важных API в qiankun используются вместе.
2. Запись подприложения зарезервирована для вызова основным приложением.bootstrap mount unmountзаявление о. а такжеwindow.__POWERED_BY_QIANKUN__Определение.
3. Перенастройте стратегию упаковки подприложения.
Запустите эффекты основного приложения и дополнительных приложений:

При совпадении пустого маршрута сопоставляется страница основного приложения При переключении маршрута на/vueКогда загружаются дополнительные приложения, включая/vue/aboutтакже загрузил вспомогательное приложение/aboutмаршрутизация. Откройте консоль, и вы увидите функцию хука жизненного цикла, которую мы выполнили.
Вы также можете видеть, что вспомогательное приложение отображается в указанном узле основного приложения.

Создание подприложения React

Добавьте подприложения, к которым необходимо получить доступ в основном приложении.

// micro/apps.ts
import app from "./shared";  //分享给子应用的数据

/*
 * name: 微应用名称 - 具有唯一性
 * entry: 微应用入口 - 通过该地址加载微应用
 * container: 微应用挂载节点 - 微应用加载完成后将挂载在该节点上
 * activeRule: 微应用触发的路由规则 - 触发路由规则后将加载该微应用
 * props: 共享给微应用的数据
 */
const apps: any = [
    {
        name: "vue-project",
        entry: "//localhost:10300",
        container: "#app-qiankun",
        activeRule: "/vue",
        props: { app }
    },
    {
        name: "react-project",
        entry: "//localhost:10100",
        container: "#app-qiankun",
        activeRule: "/react",
        props: { app }
    }
];

export default apps;

Конфигурация вспомогательного приложения

использоватьcreate-react-appИнициализировано приложение реакции
добавить в корневой каталог.envфайл, добавьте следующую конфигурацию

PORT=10100
BROWSER=none

1. Конфигурация входа в реакцию

// public-path.js
if (window.__POWERED_BY_QIANKUN__) {
    // 动态设置 webpack publicPath,防止资源加载出错
    // eslint-disable-next-line no-undef
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import "./public-path";

let root = document.getElementById("root");

function render() {
  ReactDOM.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>,
    root
  );
}

if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap() {
  console.log("ReactMicroApp bootstraped");
}

/**
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
export async function mount(props) {
  console.log("ReactMicroApp mount", props);
  root = document.getElementById("root");
  render(props);
}

/**
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async function unmount() {
  console.log("ReactMicroApp unmount");
  //console.log(ReactDOM);
  ReactDOM.unmountComponentAtNode(root);
  root = null;
}

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

2. Настройте стратегию упаковки webpack
использоватьreact-app-rewiredИзмените конфигурацию упаковки.

//config-overrides.js
const path = require("path");

module.exports = {
  webpack: (config) => {
    // 微应用的包名,这里与主应用中注册的微应用名称一致
    config.output.library = `react-project`;
    // 将你的 library 暴露为所有的模块定义下都可运行的方式
    config.output.libraryTarget = "umd";
    // 按需加载相关,设置为 webpackJsonp_react-project 即可
    config.output.jsonpFunction = `webpackJsonp_react-project`;

    config.resolve.alias = {
      ...config.resolve.alias,
      "@": path.resolve(__dirname, "src"),
    };
    return config;
  },

  devServer: function (configFunction) {
    return function (proxy, allowedHost) {
      const config = configFunction(proxy, allowedHost);
      // 关闭主机检查,使微应用可以被 fetch
      config.disableHostCheck = true;
      // 配置跨域请求头,解决开发环境的跨域问题
      config.headers = {
        "Access-Control-Allow-Origin": "*",
      };
      // 配置 history 模式
      config.historyApiFallback = true;

      return config;
    };
  },
};

На данный момент также подключено подприложение react, вы можете запустить его, чтобы увидеть эффект

О доступе к приложениям без использования приложений webpack, Angular и др. говорить особо нечего, если вам интересно, вы можете найти информацию самостоятельно.

упакованные файлы развертывания nginx

Установка и использование nginx не представлены, если вы не знакомы с ним, можете самостоятельно использовать Baidu.

конфигурация nginx
#user  nobody;
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html/dist;
            index  index.html index.htm;
            try_files $uri $uri/ /index.html;
            error_page 404 /index.html;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

    server {
        listen       10300;
        server_name  localhost;

        location / {
            root   html/vue;
            index  index.html index.htm;
            try_files $uri $uri/ /index.html;
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Allow-Credentials' 'true';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

    server {
        listen       10100;nginx
        server_name  localhost;

        location / {
            root   html/react;
            index  index.html index.htm;
            try_files $uri $uri/ /index.html;
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Allow-Credentials' 'true';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

    server {
        listen       10400;
        server_name  localhost;

        location / {
            root   html/static;
            index  index.html index.htm;
            try_files $uri $uri/ /index.html;
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Allow-Credentials' 'true';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }


}

онлайн из соображений безопасности,Access-Control-Allow-OriginНе следует настраивать как *, нужно настроить указанное доменное имя. Краткое описание картинки вышеnginxКонфигурация

  • html/distДля нашего основного приложения настроен порт 80,html/staticПростой проект без упаковки webpack.
  • Каждый сервер настроен с интерфейсными пакетами разных фреймворков.
  • Обратите внимание, что настроенный адрес доступа должен быть таким же, как и в основных приложениях приложения.

прошлый обзор

демонстрационный адрес

GitHub.com/F one Q in RF/Пожалуйста…

Суммировать

Режимы маршрутизации всех вышеперечисленных приложений используются единообразно.historyрежим, пробовал унифицированныйhashРежим не удался, что делать, если два режима смешаны? попробовал унифицированныйhashРежим или комбинация двух режимов - успешный копающий друг? Мой уровень ограничен, передвигать кирпичи нелегко, пожалуйста, дайте мне еще совет!