Micro front-end introduction, we have seen a lot of, here is not much to introduce, directly start demo, quick start, even grandma will learn cough up! (Project code at the end of the article) ~

Initialize the project

  1. Initialize the primary application

Initialize the project (check vue Router and VUex during installation)

vue create main-app
Copy the code

Install UI frame Element-UI, micro front end frame Qiankun

npm i element-ui qiankun --save
Copy the code
  1. Initializer application

Initialize the project (check vue Router and VUex during installation)

vue create child-app
Copy the code

Install the UI framework Element-UI

npm i element-ui --save
Copy the code

Basic version

Write the main application code

  1. /src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.config.productionTip = false

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);

import { registerMicroApps, start } from 'qiankun';

registerMicroApps([
  {
    name: 'vue'.// Name of the child application
    entry: '//localhost:10000'.// Subapplication entry
    container: '#vue'.// The location of the child application in the main application
    activeRule: '/vue'.// The routing rule activated by the subapplication}]); start();new Vue({
  router,
  store,
  render: h= > h(App)
}).$mount('#app')
Copy the code

Code details:

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
Copy the code

Introducing the element – the UI

import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
  {
    name: 'vue'.// Name of the child application
    entry: '//localhost:10000'.// Subapplication entry
    container: '#vue'.// The location of the child application in the main application
    activeRule: '/vue'.// The routing rule activated by the subapplication}]); start();Copy the code

There are two loading methods for microapplications, one is based on routing and the other is based on manual loading. We use the first method here.

Use registerMicroApps for configuration. registerMicroApps(apps, lifeCycles?) Two parameters are received. The first required parameter is the registration information for the micro application. BeforeLoad, beforeMount, afterMount, beforeUnmount, afterUnmount, afterUnmount, afterUnmount. In order to make the demo easier to understand, this parameter is left out and only the first parameter is used.

Use Start to open Qiankun

  1. /src/App.vue
<template>
  <div>
    <el-menu :router="true" mode="horizontal" default-active="/">
      <el-menu-item index="/">Home</el-menu-item>
      <el-menu-item index="/about">About</el-menu-item>
      <el-menu-item index="/vue">Vue</el-menu-item>
    </el-menu>
    <router-view></router-view>
    <div id="vue"></div>
  </div>
</template>
Copy the code

Code details:

<el-menu :router="true" mode="horizontal" default-active="/">
      <el-menu-item index="/">Home</el-menu-item>
      <el-menu-item index="/about">About</el-menu-item>
      <el-menu-item index="/vue">Vue</el-menu-item>
</el-menu>  
Copy the code

The menu bar provided by Element-UI

<router-view></router-view>
Copy the code

Acts as a routing component container for the host application itself

<div id="vue"></div>
Copy the code

The child application container must be the same as the container registered by registerMicroApps in main.js.

  1. /src/views/Home.vue
<template>
  <div class="home">
    <h1>The main application of Home</h1>
  </div>
</template>
Copy the code
  1. /src/views/About.vue
<template>
  <div class="about">
    <h1>The main application About</h1>
  </div>
</template>
Copy the code

Write child application code

  1. /src/main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
import router from './router'

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);

Vue.config.productionTip = false

let instance;

function render() {
  // Load the vue instance
  instance = new Vue({
    router,
    store,
    render: h= > h(App)
  }).$mount('#app')}// Run the microapplication independently
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

// When used by the main application
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}


export async function bootstrap() {}

export async function mount(props) {
  render(props)
}

export async function unmount(props) {
  instance.$destroy()
  instance = null
}
Copy the code

Code details:

let instance;

function render() {
  // Load the vue instance
  instance = new Vue({
    router,
    store,
    render: h= > h(App)
  }).$mount('#app')}Copy the code

There are two reasons for using a render function to wrap the process of the original instance Vue. One is that we need to render the child application when the main application needs to render the child application, and the other is that we need to support running the child application independently, so we need to make the rendering process controllable. Call the render function when you need to render

// Run the microapplication independently
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}
Copy the code

The global variable window.__powered_by_QIANkun__ can be used to distinguish whether the sub-application is currently running in the context of the main application of Qiankun. If the global variable does not exist, it is shown that the sub-application should be running independently, so the render function is called

// When used by the main application
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
Copy the code

Our main application uses port 8080, and our child application uses port 10000. If this were not set, our requests to the child application route would start with http://localhost:8080 instead of the correct http://localhost:10000.

Runtime publicPath resolves the problem that scripts, styles, and images loaded dynamically by microapplications are not correctly addressed.

export async function bootstrap() {}

export async function mount() {
  render()
}

export async function unmount() {
  instance.$destroy()
}
Copy the code

According to the regulations, microapplications need to export the bootstrap, mount, and unmount lifecycle hooks in their own entry JS files for the main application to call at the appropriate time.

  • bootstrapIt is called only once when the microapplication is initialized and directly the next time the microapplication re-entersmountHook that will not trigger againbootstrap. Usually we can do some initialization of global variables here, such as not inunmountPhase destroyed application level cache, etc.
  • The application is called every time it entersmountMethod, where we usually trigger the application’s render method
  • Every time the applicationCut/unloadThis is where we normally unload the application instance of the microapplication

Therefore, we render the child application in mount and unmount the child application in unmount.

  1. /src/App.vue
<template>
  <div class="child-app">
    <el-menu :router="true" default-active="/" class="child-menu">
      <el-menu-item index="/">Home</el-menu-item>
      <el-menu-item index="about">About</el-menu-item>
    </el-menu>
    <router-view />
  </div>
</template>

<style scoped>
.child-app {
  width: 400px;
  display: flex;
  border: 1px solid black;
  margin-top: 20px;
  padding: 10px;
}
.child-menu {
  margin-right: 20px;
}
</style>
Copy the code
  1. /src/view/Home.vue
<template>
  <div class="home">
    <h2>Son Home application</h2>
  </div>
</template>
Copy the code
  1. /src/views/About.vue
<template>
  <div class="about">
    <h2>The child used the About</h2>
  </div>
</template>
Copy the code
  1. /src/router/index.js
/ /... const router = new VueRouter({ mode: 'history', base: window.__POWERED_BY_QIANKUN__ ? '/vue' : process.env.BASE_URL, routes }) //...Copy the code

When running as a child application, the child application should have the same routing rules as the activeRule registered by registerMicroApps in main.js, so set base to ‘/vue’.

  1. /vue.config.js
module.exports = {
    devServer: {
        port: 10000.headers: {
            'Access-Control-Allow-Origin': The '*' // Cross-domain response header when the master application gets the child application}},configureWebpack: {
        output: {
            library: `vue`.libraryTarget: 'umd',}}}Copy the code

Since the access is from port 8080 to port 10000, we need to configure cross-domain permission. In addition, we need to add more webPack configuration so that the main application can correctly identify some information exposed by the microapplication.

Run away

At this point, the master and child apps are ready to run!

  1. Test subapplication

Open http://localhost:10000

If you can also see the following effect, then the child application can run independently

  1. Test master application

Open http://localhost:8080

If you see something like this, then both the main app and its children are working perfectly

Communication version

From the above results, we have realized the basic use of micro-front-end and successfully embedded the sub-application into the main application. However, in our project, we often involve some scenes that require the communication between the main application and the sub-application. Since both of them are VUE projects, we use VUEX to realize the communication.

Write the main application

  1. /src/main.js
// ...
registerMicroApps([
  {
    name: 'vue'.// app name registered
    entry: '//localhost:10000'.container: '#vue'.activeRule: '/vue'.// Add the following paragraph
    props: {
      initState: store.state  
    }
  }
]);
// ...
Copy the code

Pass the state of the main application, using props, to the child application, which can receive it in the exported mount function

  1. /src/store/index.js
// ...
mutations: {
  addNum(state, n) {
    state.num += n
  }
}
// ...
Copy the code
  1. /src/App.vue
<template>
  <div>
    <el-menu :router="true" mode="horizontal" default-active="/">
      <el-menu-item index="/">Home</el-menu-item>
      <el-menu-item index="/about">About</el-menu-item>
      <el-menu-item index="/vue">Vue</el-menu-item>
    </el-menu>
    <h1>Num: {{$store.state.num}}</h1>
    <button @click="addNum(10)">Add 10 to the main application</button>
    <router-view></router-view>
    <div id="vue"></div>
  </div>
</template>

<script>
export default {
  methods: {
    addNum(n) {
      this.$store.commit('addNum'.10)}}}</script>
Copy the code

Writing subapplications

  1. /src/main.js
// ...
function render(props = {}) {
  if(! store.hasModule('global')) {
    // State needs to be initialized when running the microapplication independently, otherwise an error will be reported
    const initState = props.initState || {
      num: 0
    }
    // Store the parent application's data in the child application with the namespace fixed as global
    const globalModule = {
      namespaced: true.state: initState,
      mutations: {
        addNum(state, n) {
          state.num += n
        }
      },
    };
    // Register a dynamic module
    store.registerModule('global', globalModule);
  }
  // Load the vue instance
  instance = new Vue({
    router,
    store,
    render: h= > h(App)
  }).$mount('#app')}// ...
export async function mount(props) {
  render(props)
}
// ...
Copy the code
  • mountReceives data from the master application and passes it toRender function
  • Render functionJudge the subapplications firststoreIs it registered in thereglobalModule, if not available, usepropsReceived from the main applicationstateAnd treat it asglobalThe modulestateData source, dynamic registration. After that, we useglobalThe module, in fact, is to use the data passed by the parent, so as to achieve data communication.
  1. /src/views/Home.vue
<template>
  <div class="home">
    <h2>Son Home application</h2>
    <h2>Num: {{$store.state.global.num}}</h2>
    <button @click="addNum(10)">Main application increased by 20</button>
  </div>
</template>

<script>
export default {
  methods: {
    addNum() {
      this.$store.commit('global/addNum'.20)}}}</script>
Copy the code

Run away

At this point, the master and child apps are ready to run!

  1. Test subapplication

Open http://localhost:10000

As you can see, the button in the child app is also clicking normally because we gave it an initial value in the Render function

const initState = props.initState || {
  num: 0
}
Copy the code
  1. Test master application

Open http://localhost:8080

You can see that whether you click the button in the main app or the button in the sub-app, the data can be updated synchronously and responsively, which proves that our data communication has been successfully realized!

Project code

Github.com/xiezijie439… If it helps you, please give me a little star