preface

The convenient point is to use qiankun’s micro front end solution

Dependent version:

"Single - spa" : "^ 5.5.2", "single - spa - vue" : "^ 1.8.2",Copy the code

process

Main Application flow

  • Set out.libraryTarget under webpack to system

  • In the HTML entry, use importMap to set the name and address of the current application and sub-application

  • General usage (if DOM nodes are always present) : registerApplication registers the child application, is introduced through System.js, sets the render route activeWhen, and passes the parameter customProps to the child application

  • Use a Parcel if the DOM node is not always present: Main application also need package singleSpaVue/singleSpaReact etc, then registerApplication yourself, MountParcel exported by main.js/ts with bootstraps/mount was used in A component (A) and after A component (A) had been mounted, the child application (as A component) was manually mounted to A DOM node of that component (see 1.3)

Sub-application Flow (Vue)

  • The startup mode is taken over by single-SPa-vue, so windod. singleSpaNavigate can be set to false

  • Set the mount point in the main application, el Settings under appOptions, default mount to body

  • Export at least three life cycle events: bootstrap/mount/unmount. You can receive parameters from the main application under the mount

  • Set the publicPath of the child application:

    • systemjs-webpack-interopSet up thesetPublicPath; Remember that the name is the same as the one introduced by the main application
    1. webpackConfiguration:config.output.jsonpFunction = 'wpJsonpFlightsWidget';

1. Main project configuration

  • Example 1: Vue:json-utilThe routing/sub-app
  • Example 2: React:md-note.onlineThe homepage of,The clock/The calendarisvueWritten, usedsingle-spaThe loaded

1.1 Download Dependency

Download the single – spa

yarn add single-spa
Copy the code

1.2 configuration

inHTMLThe entrance

The system.js package is finally downloaded and placed in the project to prevent the referenced CDN from sometimes blowing up

Systemjs-importmap can also be generated automatically through configuration files, so as to distinguish between the entry of the development environment and the entry of the build environment. Note the cross-domain problem of the entry of the packaged child application

  • usewebpackAutomatic insertionHTML
// systemJs-Importmap.js
const isEnvDev = process.env.NODE_ENV === 'development';

// Systemjs-importMap configuration, via webpack for HTML use
module.exports = [
  {
    name: 'root-config'.entry: './js/app.js'}, {name: '@vue-mf/calendar'.entry: isEnvDev
      ? '//localhost:2333/js/app.js'
      : 'https://zero9527.github.io/vue-calendar/js/app.js',},];// vue.config.js
chainWebpack: config= > {
  config.plugin('html').tap(args= > {
    const importMap = { imports: {}}; systemJsImportmap.forEach(item= > (importMap.imports[item.name] = item.entry));
    args[0].systemJsImportmap = JSON.stringify(importMap, null.2);
    return args;
  });
},

// public\index.html
<meta name="importmap-type" content="systemjs-importmap" />
<script type="systemjs-importmap">
  <% = htmlWebpackPlugin.options.systemJsImportmap% >
</script>
<script src="./libs/systemjs/system.min.js"></script>
<script src="./libs/systemjs/extras/amd.min.js"></script>
<script src="./libs/systemjs/extras/named-exports.min.js"></script>
<script src="./libs/systemjs/extras/named-register.min.js"></script>
<script src="./libs/systemjs/extras/use-default.min.js"></script>
<script>
  System.import('root-config');
</script>
Copy the code
  • inpublic/index.htmlManually add
<meta name="importmap-type" content="systemjs-importmap" />
<script type="systemjs-importmap">
  {
    "imports": {
      "root-config": "//localhost:666/js/app.js"."@vue-mf/calendar": "//localhost:2333/js/app.js"}}</script>
<script src="./libs/systemjs/system.min.js"></script>
<script src="./libs/systemjs/extras/amd.min.js"></script>
<script src="./libs/systemjs/extras/named-exports.min.js"></script>
<script src="./libs/systemjs/extras/named-register.min.js"></script>
<script src="./libs/systemjs/extras/use-default.min.js"></script>
<script>
  System.import('root-config');
</script>
Copy the code
  • What’s inside is aJSON, pay attention to the format!

Set the name and address of the current application and the name and address of the sub-application

<script type="systemjs-importmap">
  {
    "imports": {
      "root-config": "//localhost:666/js/app.js"."@vue-mf/calendar": "//localhost:2333/js/app.js"}}</script>
Copy the code
  • Child application name@vue-mf/calendarIn theregisterApplication, the correspondingapp: import('@vue-mf/calendar')“, such as
registerApplication({
  name: '@vue-mf/calendar'.app: (a)= > (window as any).System.import('@vue-mf/calendar'),
  activeWhen: ' '.customProps: {
    root: 'json-util',}});Copy the code

System startup bysystemJSTo take over

  • html
<script>
  System.import('root-config');
</script>
Copy the code
  • The correspondingwebpackconfiguration

Remove file hash to import file names

// vue.config.js
module.exports = {
  outputDir: 'docs'.publicPath: '/'.filenameHashing: false.productionSourceMap: false.configureWebpack: config= > {
    config.output.libraryTarget = 'system';

    config.devServer = {
      port: Awesome!.headers: {
        'Access-Control-Allow-Origin': The '*',},disableHostCheck: true.historyApiFallback: true}; }};Copy the code

Register child applications

  • single-spa.config.js
// src\single-spa-config.ts
import { registerApplication, start } from 'single-spa';

registerApplication({
  name: '@vue-mf/calendar'.app: (a)= > (window as any).System.import('@vue-mf/calendar'),
  activeWhen: ' '.customProps: {
    root: 'json-util',}}); start();Copy the code
  • inmain.tsThe introduction of

In fact, it can be introduced anywhere, just make sure that the DOM node exists, if it is created dynamically, it can be loaded for the first time, but when it is restored, it will tell you that the DOM node cannot be found. Okay

// src\main.ts
import './single-spa-config';
Copy the code

1.3 Parcel configuration

The official documentation

Webpack 5 has a Module Federation that allows you to use components across projects

When to use it

DOM nodes are not always present when the child application is used as A component under A component (A) of the main application

  • Main application: Using singleSpaVue/singleSpaReact package, then registerApplication myself, A mountParcel exported by main.js/ts during bootstraps/mount was used in A component (A), and after A component (A) had been mounted, the child application (as A component) was manually mounted to A DOM node of that component

  • Child applications: Do not register with the main application registerAppliaction, but manually mount to A DOM node within A component (A)

Main application modification

// src\main.ts
/ /...

// **************** main application is generally written as ****************
// // child application registerAppliaction register
// new Vue({
// router,
// render: (h: any) => h(App),
// }).$mount('#json-util');

/ / * * * * * * * * * * * * * * * * main applications use Parcel write * * * * * * * * * * * * * * * *
// Use Parcel to mount a child application (under a component)
// The current application needs to be treated as a child application and registerAppliaction is called
const singleSpa = singleSpaVue({
  Vue,
  appOptions: {
    el: '#json-util'.render: (h: any) = > h(App),
    router,
  },
});

// eslint-disable-next-line
export let mountParcel: any;

export const bootstrap = (props: any) = > {
  mountParcel = props.mountParcel;
  return singleSpa.bootstrap(props);
};

export const { mount, unmount } = singleSpa;
Copy the code

Register child components

import { registerApplication, start } from 'single-spa';

// use singleVue to add a registerApplication to mount the child application
registerApplication({
  name: 'root-config'.app: (a)= > (window as any).System.import('root-config'),
  activeWhen: (a)= > true}); registerApplication({name: '@vue-mf/calendar'.app: (a)= > (window as any).System.import('@vue-mf/calendar'),
  activeWhen: location= > {
    return location.href.includes('/sub-app');
  },
  customProps: {
    root: 'json-util',}});// Parcel is mounted manually
// registerApplication({
// name: '@vue-mf/clock',
// app: () => (window as any).System.import('@vue-mf/clock'),
// activeWhen: location => {
// return location.href.includes('/sub-app');
/ /},
// customProps: {
// root: 'json-util',
/ /},
// });

start();
Copy the code

Manually mount

A component (A) manually mounts the child application to A DOM node after mount

Use the composition – API

import { mountParcel } from '@/main';

const parcel = ref<any>(null);

const mountClockParcel = (a)= > {
  const routePath = ctx.root.$route.path;
  const domElement = document.getElementById('app-clock');
  if (routePath === '/sub-app' && domElement) {
    const parcelConfig = (window as any).System.import('@vue-mf/clock');
    parcel.value = mountParcel(parcelConfig, { domElement });
  } else if(parcel.value) { parcel.value.unmount(); }}; onMounted((a)= > {
  mountClockParcel();
});

watch(
  (a)= >ctx.root.$route.path, () => { mountClockParcel(); });Copy the code

2. Configuration of sub-project (Vue)

Example: the vue – calendar

2.1 Download Dependencies

  • downloadsingle-spa-vue
yarn add single-spa-vue
Copy the code
  • downloadvue-cli-plugin-single-spa

Solve the problem

single-spa.min.js? 25A2:2 single-SPA Minified Message #37: See single-spa.js.org/error/?code…

yarn add -D vue-cli-plugin-single-spa
Copy the code

2.2 configuration

  • Application gatewaysmain.js/ts

Note: Under appOptions, EL can configure the current app to mount the DOM node in the main app. This node needs to be set in advance. If no EL is provided, it is mounted under body by default

// Other code omitted
import Vue from 'vue';
import singleSpaVue from 'single-spa-vue';
// ...

// ============= non-single-SPA independently launched =============
if(! (window as any).singleSpaNavigate) {
  new Vue({
    render: (h: any) = > h(App),
  }).$mount('#app-calendar');
}

// ============= single-SPA mode startup =============
const vueLifeCycles = singleSpaVue({
  Vue,
  appOptions: {
    // el: mount the DOM node, which is required in the main project; If there is no EL, it will be added under the body
    el: '#app-calendar'.render: (h: any) = > h(App),
  },
});

export function bootstrap(props: object) {
  return vueLifeCycles.bootstrap(props);
}

export function mount(props: object) {
  console.log('mount: ', props);
  return vueLifeCycles.mount(props);
}

export function unmount(props: object) {
  return vueLifeCycles.unmount(props);
}
Copy the code

2.3 the problem

Problem description

Subprojects that use the asynchronous import() component have no problem running alone!! In the main application, however, an error will be reported.

Child application uses asynchronous component, error reported in main application

Uncaught TypeError: application '@vue-mf/calendar' died in status BOOTSTRAPPING: Object(...) is not a function
Copy the code
<template>
  <div id="app-calendar">
    <div class="title">Vue-Calendar</div>
    <Calendar />
  </div>
</template>

<script lang="ts">
/ / normal
import Calendar from '@/components/Calendar/index.vue';

// Single-SPA loading in main app: no
// const Calendar = () => import(@/components/Calendar/index.vue);

// Single-SPA loading in main app: no
// import AsyncComponent from '@/components/AsyncComponent/index';

// Asynchronous components used in single-SPA have loading problems in main application
// const Calendar = AsyncComponent(() =>
// import(
// /* webpackPrefetch: true */
// /* webpackChunkName: 'calendar' */
// '@/components/Calendar/index.vue'
/ /),
// );

export default {
  name: 'App'.components: {
    Calendar,
  },
};
</script>
Copy the code

Asynchronous component problem resolved

Add the following Settings to the subproject

// src\set-public-path.ts
import { setPublicPath } from 'systemjs-webpack-interop';

if ((window as any).singleSpaNavigate) {
  setPublicPath('@vue-mf/calendar'.2);
}
Copy the code
// vue.config.js
config.output.jsonpFunction = 'wpJsonpFlightsWidget';
Copy the code

Single-spa.js.org/docs/recomm…

reference

  • single-spa
  • , etc.