Hello everyone, I am Xiao Hei.
The company, which uses vUE as the technology stack, recently encountered a requirement to move the functional modules of the original backend management system to the new backend management system. Originally, this is not much complicated, just copy and paste to change, but there are several pits, I suddenly fell into a meditation:
1. The new background uses VUe3, and the original background uses VUe2
2. The new background has its own login role permission management scheme, as does the old background
3. Due to the big difference between VUe3 and VUe2, vue3 is equivalent to the whole Vue being rewritten. Although there is a downward compatibility, it is not practical to directly copy and paste the past (mainly BECAUSE I tried to copy a module and devTools was in a terrible red color).
What to do, write vue2 module again with vue3 (suffocating)? Is ready to hit the keyboard in tears, I thought of the micro front end of the related articles, it is better to try this thing, and then, the micro front end officially stepped on the pit
What is a micro front end?
According to the Internet and the understanding of the little black, the front end application is split, independent operation, independent deployment, will put all the functions to focus on a project originally the way into the functions are divided into a main project of the business and multiple components, each component is responsible for its own function, at the same time and other components and main project’s ability to communicate, Achieve more detailed and easier to manage the purpose. So basically the micro front end is
A complete application is divided into a master application and one or more micro applications, which are independent and can communicate with each other
How to implement a micro front end?
The easiest thing to think of is an IFrame. Two simple iframes and their communication codes are posted below
<button id="parentBtn">parent BTN </button> <iframe SRC ="./child.html" ParentFunc (MSG) {console.log("parent "); ", msg) } var btn = document.querySelector("#parentBtn") btn.addEventListener('click', Function () {console.log(" I am the parent's button") console.log(" I called: ") document.getElementById('frame').contentWindow.childFunc('parent'); }) </script> // child.html <div> <button id="childBtn">child BTN </button> <script> function childFunc(MSG) {console.log("child's method: ", msg) } var btn = document.querySelector("#childBtn") btn.addEventListener('click', Function () {console.log(" I am child's button") console.log(" I called: ") parent.window.parentfunc ('child'); }) </script>Copy the code
This is what the above two pieces of code look like on a local server
Then click two buttons, you can communicate with each other
The above two HTML files must be run in an environment with a domain name, otherwise an error will be reported
Of course, I did not directly transform the project with IFrame, but stood on the shoulders of giants. I used a micro front-end framework called Qiankun, because I could not post the code of the company. Next, I will build a VUe3 project and a VUE2 project to roughly restore how I transformed the project. And how do I fill the holes I encounter
Micro front end framework Qiankun
First of all, I will post the website of Qiankun, and you can have a look at its introduction and related API
First of all, a basic background interface of VUe3 and a basic background interface of VUe2 were established with the official scaffolding of VUE. Note that vue3 could not be used as a micro application for the Qiankun framework as vue3 was packaged with Vite. Here, the main application was VUe3 and the micro application was VUe2. This is also consistent with my transformation. The structure of the two projects is roughly the same, as follows
For your convenience, paste the template warehouse address I built
Vue3 Template: gitee.com/jimpp/vue3-… (Master app, master app must install Qiankun)
Vue2 template: gitee.com/jimpp/vue2-… (Microapplications)
The above master branch is the project that can run independently before the transformation, and the dev branch is the project after the final transformation. Of course, it is also possible to build from the beginning to the end, but it is necessary to ensure that both warehouses have router, store and login interception functions
Both templates have this interface
1. Login page
Cough cough, simple point, in order to show please don’t hit me ha ha
2. The left menu and router-view interface are displayed
Ok, now we begin to transform two projects based on the Qiankun framework
Main application launch Qiankun
Here I used registerMicroApps on qiankun’s official website to register microapplications
Create a micros folder under the SRC folder of the main app. In the micros folder, create index.js and app.js
// index.js import NProgress from "nprogress"; import "nprogress/nprogress.css"; import { registerMicroApps, addGlobalUncaughtErrorHandler, start, } from "qiankun"; Import apps from "./app"; RegisterMicroApps (apps, {beforeLoad: (app) => {// Load the progress bar nprogress.start () before loading the microapp; console.log("before load", app.name); return Promise.resolve(); }, afterMount: (app) => {// Before loading the microapp, the progress bar is finished loading nprogress.done (); console.log("after mount", app.name); return Promise.resolve(); }}); addGlobalUncaughtErrorHandler((event) => { console.error(event); const { message: MSG} = event if (MSG && msg.includes("died in status LOADING_SOURCE_CODE")) {console.error(" died in status LOADING_SOURCE_CODE"); }}); export default start;Copy the code
Yarn Add nProgress install the nprogress library so that the progress bar will be displayed when loading the micro application
-
RegisterMicroApps: Contains two parameters, the first parameter is some registration information for the microapplication, and the second parameter is the global microapplication lifecycle hook
-
AddGlobalUncaughtErrorHandler: global uncaught exception handler, the application of error when also can use this API to capture
-
Start: The method we used to start Qiankun contains one parameter. The specific purpose of the parameter will not be detailed
For more details on the API, please click here
// app.js
const apps = [
{
name: "vue-micro-app",
entry: "//localhost:8081",
container: "#micro-container",
activeRule: "#/vue2-micro-app",
},
];
export default apps;
Copy the code
App.js exports the first parameter to registerMicroApps above, which is an array of objects, where each field of the array does the following:
-
Name: indicates the name of the micro application. You must use this name when modifying the micro application
-
Entry: Domain name and port of the microapplication. I use the local port 8081
-
Container: A dom container is required to launch the microapplication. The ID of the DOM container is contained in the container
-
ActiveRule: The rule that triggers the launch of the microapplication. When the value of activeRule is detected in the URL, the microapplication is launched
After adding the above two js, we go back to main.js, which should look like this for now
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import '@/assets/main.css'
createApp(App).use(store).use(router).mount('#app')
Copy the code
The transformation is also very simple, just add index.js from micros above and run the start function
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import '@/assets/main.css'
import start from '@/micros'
createApp(App).use(store).use(router).mount('#app')
start()
Copy the code
Refresh the browser and see that the main app is the same as before!
The main application adds the micro-application container and the micro-application menu
The current menu code structure of the main app is as follows
<div class="nav" v-if="token">
<div class="menu">
<router-link to="/">Parent Home</router-link>
</div>
<div class="menu">
<router-link to="/about">Parent About</router-link>
</div>
</div>
Copy the code
Now let’s add two menus that correspond to the home and About subapplications
<div class="nav" v-if="token"> <div class="menu"> <router-link to="/">Parent Home</router-link> </div> <div class="menu"> <router-link to="/about">Parent About</router-link> </div> <! <div class="menu"> <router-link to="/vue2-micro-app">Child Home</router-link> </div> <div class="menu"> <router-link to="/vue2-micro-app/about">Child About</router-link> </div> </div> <div class="container"> <div class="header" v-if="token">Child Header</div> <div class="router-view"> <router-view /> <! - the newly added, the application of micro vessel -- -- > < div id = "micro - container" > < / div > < / div > < / div >Copy the code
As you may have noticed, the value in the activeRule field of app.js above is too much (# is removed), because #/vue2-micro-app is the condition that triggers the launch of the micro-app
This is to refresh our micro app, and then click on the Child Home menu, and you’ll see two errors
The first is a cross-domain error, because our main application is running on port 8080, micro application is running on port 8081, then use nginx to do the proxy
The second error is that our micro application has not been changed yet, so why wait for it to be changed
Microapplication modification
The bootstrap, mount, and unmount hooks must be exported to the microapplication portal for the host application to call at the appropriate time
This is main.js before the microapp transformation
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import '@/assets/main.css'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
Copy the code
Let’s change main.js
import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import '@/assets/main.css' vue.config. productionTip = false // New: Used to save Vue instance let instance = null; / / feature: Dynamically set webPack publicPath, If (window.__powered_by_QIANkun__) {// eslint-disable-next-line no-undef __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; } /** * new: * render function * two cases: */ function render() {// Render instance = new Vue({router, store, render: (h) => h(App), }).$mount("#micro-app"); } /** * New: * Bootstrap will only be called once during microapplication initialization. The next time the microapplication re-enters, it will call the mount hook directly. * This is usually where we can initialize global variables, such as application-level caches that will not be destroyed during the unmount phase. */ export async function bootstrap() { console.log("VueMicroApp bootstraped"); } /** * New: * The application calls the mount method every time it enters, */ export async function mount(props) {console.log("VueMicroApp mount", props); render(props); } /** * New: */ export async function unmount() {console.log("VueMicroApp unmount"); instance.$destroy(); instance = null; } // New: directly mount the application if (! window.__POWERED_BY_QIANKUN__) { render(); } / / this is the original startup code / / new Vue ({/ / router, / / store, / / render: h = > h (App) / /}). $mount (' # App)Copy the code
Note that in the render method I changed the parameter after $mount to #micro-app. This is to distinguish the root ID of the main app from the root id of the index.html in the micro app, so the index.html of the public folder in the micro app should also be changed to micro-app
The webPack configuration should then be modified by adding the vue.config.js file to the microapplication root
const path = require("path"); Module. exports = {devServer: {// listener port: 8081, // disableHostCheck so that micro applications can be fetched disableHostCheck: Headers: {" access-Control-allow-origin ": "*",},}, configureWebpack: {resolve: {alias: {"@": path.resolve(__dirname, "SRC "),},}, output: { "Vue-micro-app ", // Expose your library in a way that works for all module definitions: JsonpFunction: 'webpackJsonp_VueMicroApp',},},};Copy the code
And then we have to modify our routing
if (window.__POWERED_BY_QIANKUN__) { microPath = '/vue2-micro-app' } const routes = [ { path: microPath + '/login', name: 'login', component: Login }, { path: microPath + '/', redirect: microPath + '/home' }, { path: microPath + '/home', name: 'Home', component: Home }, { path: microPath + '/about', name: 'About', component: () => import( /* webpackChunkName: "about" */ '../views/About.vue') } ] router.beforeEach((to, from, next) => { if (to.path ! == (microPath + '/login')) { if (store.state.token) { next() } else { next(microPath + '/login') } } else { next() } })Copy the code
The main change of the route is to add a microPath variable to each path, which is used to detect whether it is changed by the micro front end. The microPath variable should also be added to the corresponding route guard. In addition, microPath judgment should be added to the login jump of the micro application
Finally, reboot our micro app, then go to our main app and click on the Child Home menu. If not, you will get the same interface as I showed in the screenshot below
That’s right, you’ve done it! The vue2 project has been successfully embedded in VUe3
However, careful you also found that I have logged in once, why to log in again ah, so, next we should use communication to solve this problem
Main application and micro application communication
For communication between applications, we will use initGlobalState and MicroAppStateActions apis of the Qiankun framework. The related apis are described as follows:
SetGlobalState: setGlobalState – when a new value is set, a shallow check is performed internally, and if a globalState change is detected, a notification is triggered to inform all observer functions.
OnGlobalStateChange: Registers the observer function – in response to globalState changes, the observer function is triggered when globalState changes.
OffGlobalStateChange: Cancel the observer function – the instance no longer responds to globalState changes.
So let’s revamp two projects again, starting with micros/index.js for the main application
The import {registerMicroApps addGlobalUncaughtErrorHandler, start, initGlobalState / / new} from "qiankun"; const state = {} const actions = initGlobalState(state); export { actions }Copy the code
Add and export actions above, then go to login.vue
import { actions } from "@/micros"; // Add const login = () => {if (username. Value && password.value) {store.com MIT ("setToken", "123456"); // add actions.setGlobalState({globalToken: "123456"}); router.push({path: "/"}); }};Copy the code
Actions was introduced and the actions.setGlobalState method was added
Then there is main.js for the child application
Function render (props) {the console. The log (" child application render parameters ", props). / / the new props onGlobalStateChange ((state, PrevState) => {// state: changed state; Prev Status before change console.log(" Communication status changed: ", state, prevState); MIT ('setToken', '123456')}, true); Instance = new Vue({router, store, render: (h) => h(App),}).$mount("#micro-app"); }Copy the code
In the Render method we add onGlobalStateChange and set the second parameter to true so that when the microapplication starts we can immediately see the globalToken we just set: 123456
Ok, the transformation has been completed. Let’s refresh and log in to the main application again and then click the menu of micro-application. We can see that micro-application does not need to log in again, as shown in the picture below
There seems to be some problem, how to display the menu of micro application?
__POWERED_BY_QIANKUN__ to determine whether it was started by qiankun. If so, we write a variable and use V-IF to hide the menu and head of the micro application.
Above is qiankun framework of actual combat the first article in the entire content of the local development, the overall structure of the project migration is similar with me to give, and other small details don’t affect, in fact, this chapter has a huge pit, the next combat (2) of the micro front-end framework qiankun projects – on pit and deployment “will take you to deploy package after the project, And tell everyone where the pit is
reference
Ming Yuan Cloud Qiankun tutorial
Qinkun website