Preface ✨

This paper introduces the basic scheme of integrating VUE applications with the Qiankun micro-front-end framework and some common problems encountered in the process of integration.

First, basic use

The main application

1. Install qiankun

$YARN add Qiankun # or NPM I Qiankun -SCopy the code

2. Register the microapplication and start it:

Import {registerMicroApps, start} from 'qiankun' /** * 'child-vue2', // Register application name entry: '//localhost:7012',// Register service container: '#sub-container', // Mount container activeRule: '/vue2', // Route matching rule}, {name: 'child-vue3', entry: '//localhost:7013', container: '#sub-container', activeRule: '/vue3',}]) /** * Step2 Set the default sub-app (optional) */ setDefaultMountAppCopy the code

Micro application

Vue micro application

The microapplication does not require any additional dependencies to be installed to access the Qiankun master application.

Take the Vue 2.x project as an example

  1. Add public-path.js to the SRC directory and add public-path.js to the top of main.js

    if (window.__POWERED_BY_QIANKUN__) {
      __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    }
    Copy the code
  2. Microapplications recommend using the route in history mode. The route base should be set to the same value as its activeRule

const router = new VueRouter({
  mode: 'history',
  base: window.__POWERED_BY_QIANKUN__ ? '/vue2' : '/',
  routes
})
Copy the code
  1. The bootstrap, mount and unmount life cycle functions were modified and exported in the entry file main.js to ensure that the microapplication could run independently and in the Qiankun environment. To prevent root ID #app from colliding with the rest of the DOM, you need to limit the search scope.

    import './public-path' import Vue from 'vue' import App from './App.vue' import routes from './router' import store from  './store' Vue.config.productionTip = false let instance = null function render(props = {}) { const { container } = props instance = new Vue({ router, store, render: (h) => h(App) }).$mount(container ? Container. QuerySelector ('#app') : '#app')} // If (! window.__POWERED_BY_QIANKUN__) { render() } export async function bootstrap() { console.log('[vue] vue app bootstraped') } export async function mount(props) {render(props)} export async function unmount() { instance.$destroy() instance.$el.innerHTML = '' instance = null }Copy the code
  2. Modified webPack packaging to allow development environments to be packaged across domains and umD (vue.config.js) :

    const { name } = require('./package'); module.exports = { devServer: { headers: { 'Access-Control-Allow-Origin': '*', }, }, configureWebpack: { output: Library: '${name}-[name] ', library:' ${name}-[name] ', library: 'umd', library: '${name}-[name] ', library:' umD ', library: '${name}-[name] ', library:' umD ', library: 'webpackJsonp_${name} ',},},};Copy the code

Second, the details

1. Life cycle

2. Style isolation

Style isolation, you need to add the following configuration when starting the main application:

start({sandbox: {strictStyleIsolation: true}})
Copy the code

In this case, the parent app’s body style will be inherited by the parent app. To make the child app’s original body style effective, copy the child app’s body style and hang it under the #app node (which can be extracted as a separate CSS file), which will be loaded when the child app goes to the logic of qiankun

export async function mount(props) {
  require('./style/config.css')
}
Copy the code

Strict style isolation based on ShadowDOM is not a solution that can be used mindlessly. In most cases, it is necessary to access the application to make some adaptation to run properly in ShadowDOM.

fix shadow dom

  • getComputedStyle

An error is reported when the element passed in is a DocumentFragment while fetching the computed style of the Shadow DOM.

Const getComputedStyle = window.getcomputedstyle; window.getComputedStyle = (el, ... Args) => {// If (el instanceof DocumentFragment) {return {}; } return Reflect.apply(getComputedStyle, window, [el, ...args]); };Copy the code
  • elementFromPoint

When fetching an element of a child application based on coordinates (x, y), shadow root is returned, not the actual element.

const elementFromPoint = document.elementFromPoint; document.elementFromPoint = function (x, y) { const result = Reflect.apply(elementFromPoint, this, [x, y]); / / if the coordinates of elements for the shadow is the shadow again to obtain the if (result && result. ShadowRoot) {return result. ShadowRoot. ElementFromPoint (x, y); } return result; };Copy the code
  • Document Event target is shadow

When we add click, mouseDown, mouseup, etc., to the document, the event. Target in the callback is not the actual target element, but the shadow root element.

Const {addEventListener: oldAddEventListener, removeEventListener: const {addEventListener: oldAddEventListener, removeEventListener: oldRemoveEventListener} = document; const fixEvents = ['click', 'mousedown', 'mouseup']; const overrideEventFnMap = {}; const setOverrideEvent = (eventName, fn, overrideFn) => { if (fn === overrideFn) { return; } if (! overrideEventFnMap[eventName]) { overrideEventFnMap[eventName] = new Map(); } overrideEventFnMap[eventName].set(fn, overrideFn); }; const resetOverrideEvent = (eventName, fn) => { const eventFn = overrideEventFnMap[eventName]? .get(fn); if (eventFn) { overrideEventFnMap[eventName].delete(fn); } return eventFn || fn; }; Document. The addEventListener = (event, fn, options) = > {const callback = (e) = > {/ / the current event object for qiankun box, and the current object has a shadowRoot elements, The fix event object is the real element if (e.target.id? .startsWith('__qiankun_microapp_wrapper') && e.target? .shadowRoot) { fn({... e, target: e.path[0]}); return; } fn(e); }; const eventFn = fixEvents.includes(event) ? callback : fn; setOverrideEvent(event, fn, eventFn); Reflect.apply(oldAddEventListener, document, [event, eventFn, options]); }; document.removeEventListener = (event, fn, options) => { const eventFn = resetOverrideEvent(event, fn); Reflect.apply(oldRemoveEventListener, document, [event, eventFn, options]); };Copy the code

3. The history modepublicPathUse an absolute path instead of a relative path

The active application needs to be configured

publicPath:'/'
Copy the code

4. Communication between applications

The Qiankun provides initGlobalState onGlobalStateChange setGlobalState for communication.

The main application we can import directly

Import {initGlobalState} from 'qiankun' const {onGlobalStateChange, setGlobalState } = initGlobalState({ user: Prototype.$onGlobalStateChange = onGlobalStateChange $setGlobalState = setGlobalState onGlobalStateChange((value, SetGlobalState ({user: 'master'}) => console.log('[onglobalStatechange-master]:', value, prev),true)Copy the code

Child application we need to mount onGlobalStateChange setGlobalState to the child application’s vue object during its lifecycle:

export async function mount(props) {
    Vue.prototype.$onGlobalStateChange = props.onGlobalStateChange
    Vue.prototype.$setGlobalState = props.setGlobalState
}
Copy the code

5. How to load the micro-app from the page in the multi-level nested routine of the main app

When the primary application registers this route, it adds an * to the path so that it can load the primary application’s outer box even if it does not match a page of the primary application, ensuring that the child application to be mounted can easily exist.

import empty from "./.. /empty" export default { path: '/app', name: 'AppLayout', component: AppLayout, children: [{ path: "manage", name: 'AppManage', Component: Manage,},{path: "*", name: 'empty', Component: empty div tag}]}Copy the code

In addition, activeRule needs to include this route path for the main application when registering a microapplication

RegisterMicroApps ([{name: 'website', // register application name entry: '//localhost:8888',// Register service container: '#con', // mount container activeRule: '/app/website', // route matching rule}])Copy the code

The path also needs to be added to base during route registration of subapplications

function render(props = {}) {
  const {container} = props
  router = new VueRouter({
    mode: 'history',
    base: container ? /app/website : '/',
    routes
  })
  instance = new Vue({
    router,
    render: h => h(App)
  }).$mount(container ? container.querySelector('#app') : '#app')
}
Copy the code