preface
Recently, I saw some friends in the COMPANY’s UED group talking about micro front-end Qiankun, and I was also curious to get into the pit.
What is a micro front end?
The micro front end is a technical approach and method strategy for multiple teams to jointly build modern Web applications by releasing functions independently, as shown in the figure below.
qiankun
Ant Financial’s single-SPa-based micro front end solution is available for production.
features
- Based on the single-SPA package, provides a more out-of-the-box API.
- Technology stack independent, any technology stack applications can use/access, be it React/Vue/Angular/JQuery or other frameworks.
- HTML Entry access allows you to access micro applications as easily as using iframe.
- Style isolation ensures that styles do not interfere with each other between microapplications.
- JS sandbox to ensure no global variable/event conflicts between microapplications.
- Resource preloading: Preloads unopened micro-application resources in idle time of the browser to speed up the opening of micro-applications.
Main application construction
Choose to initialize the main application with vue-CLI.
$YARN add Qiankun # or NPM I Qiankun -SCopy the code
Sign up for microapps
Define the microapplications that need to be loaded
// src/micro/apps.ts
// We have no microapplications at this point, so it is null for now
const apps: any = [
];
export default apps;
Copy the code
Register microapplications and expose methods
// 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: (a)= > {
// Load the progress bar before loading the microapplication
NProgress.start();
return Promise.resolve();
},
afterMount: (a)= > {
NProgress.done();
return Promise.resolve(); }}); addGlobalUncaughtErrorHandler((event: any) = > {
const { msg } = event as any;
NProgress.done();
// Load failed
if (msg && msg.includes("died in status LOADING_SOURCE_CODE")) {
Message.error('Micro application failed to load, please check whether the application is running'); }}); start(); }Copy the code
Our microapplication may be generated according to the user’s left menu permission after login. Therefore, exposed methods and input parameters facilitate login to complete the call to register the microapplication.
Launch microapps
import startQiankun from "@/micro"; startQiankun(...) ;// Just call the incoming data where it needs to be started
Copy the code
Here I start at the global routing guard for your reference.
//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: (a)= > import('@/views/login/index.vue')}, {path: '/'.name: 'main'.component: (a)= > import('@/views/Layout/index.vue'),
children: [{path: ' '.name: 'Home'.component: (a)= > import('@/views/Home.vue'}]}, {path: The '*'.name: 'redirect'.redirect: '/'}];const createRouter: any = (a)= > new VueRouter({
mode: "history",
routes,
});
const router: any = createRouter()
/** * resets the route */
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') { // If login directly jumps to the home page
return next({ path: '/' });
}
if(! store.state.hasInited) {// Prevent repeated addRoutes from preset values
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)) { // Whitelist is allowed directly
next();
} else { / / token does not exist
next({ path: '/login'.query: { redirect: to.path } }); }});export default router;
Copy the code
Vue sub-application construction
Configure the sub-applications to be added to the primary application
// micro/apps.ts
import app from "./shared"; // Data to share with child applications
/* * name: micro app name - Unique * entry: micro app entry - Load the micro app through this address * container: mount the micro app to the node - After the micro app is loaded, it will be mounted to the node * activeRule: Routing rules triggered by the micro-application - After the routing rule is triggered, the micro-application * props: data shared to the micro-application */
const apps: any = [
{
name: "vue-project".entry: "//localhost:10300".container: "#app-qiankun".activeRule: "/vue".props: { app }
}
];
export default apps;
Copy the code
Configuring subapplications
After the master application has configured the registered micro-application, we need to configure the sub-application so that the sub-application can access the master application. 1, vUE sub-application entry main.js configuration
// public-path.js
if (window.__POWERED_BY_QIANKUN__) {
// Dynamically set webpack publicPath to prevent resource loading errors
// 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({
// In the main application, the base routing address is set to /vue
base: window.__POWERED_BY_QIANKUN__ ? "/vue" : "/".mode: "history",
routes,
});
instance = new Vue({
router,
store,
render: (h) = > h(App),
}).$mount("#app");
}
/** * Can be run separately when there is no main application */
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {}/** * The application calls the mount method every time it enters, and usually we trigger the application's render method here */
export async function mount(props) {
console.log(props);
render(props);
}
/** * The method that is called each time the application is cut/unloaded, usually here we unload the application instance of the microapplication */
export async function unmount() {
instance.$destroy();
instance = null;
router = null;
}
Copy the code
2. Configure the WebPack packaging policy
// vue.config.js
const path = require("path");
module.exports = {
// Set the static file host path
publicPath: 'http://localhost:10300'.devServer: {
// Listen on the port
port: 10300.overlay: {
warnings: false.errors: false
},
// Turn off host checking so that microapplications can be fetched
disableHostCheck: true.// Configure cross-domain request headers to solve cross-domain problems in the development environment
headers: {
"Access-Control-Allow-Origin": "*",}},configureWebpack: {
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),}},output: {
// The package name of the micro-application, which is the same as the registered micro-application name in the main application
library: "vue-project".// Expose your library in a way that all module definitions work
libraryTarget: "umd".// Load as needed, set to webPackJsonp_vue-projec
jsonpFunction: `webpackJsonp_vue-project`,}}};Copy the code
At this point, our micro front end is fully configured, but only one child application is currently connected. According to the code above can be summarized and several key points: 1, the main application of registration application: registerMicroApps addGlobalUncaughtErrorHandler startqiankun three important API collocation in use. 2. The child entry is left to the main application to call the bootstrap mount unmount declaration. And window.__powered_by_qiankun__. 3. Reconfigure the packaging policy for subapplications. Effects of launching the main app and child app:
/vue
/vue/about
/about
Build the React sub-application
Add sub-applications to the primary application
// micro/apps.ts
import app from "./shared"; // Data to share with child applications
/* * name: micro app name - Unique * entry: micro app entry - Load the micro app through this address * container: mount the micro app to the node - After the micro app is loaded, it will be mounted to the node * activeRule: Routing rules triggered by the micro-application - After the routing rule is triggered, the micro-application * props: data shared to the micro-application */
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;
Copy the code
Sub-application Configuration
Create a. Env file in the root directory and add the following configuration
PORT=10100
BROWSER=none
Copy the code
1. Configure the React interface
// public-path.js
if (window.__POWERED_BY_QIANKUN__) {
// Dynamically set webpack publicPath to prevent resource loading errors
// 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"); } /** * the application calls the mount method every time it enters, */ 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();Copy the code
React-app-rewired is used to modify the webpack configuration.
//config-overrides.js
const path = require("path");
module.exports = {
webpack: (config) = > {
// The package name of the micro-application, which is the same as the registered micro-application name in the main application
config.output.library = `react-project`;
// Expose your library in a way that all module definitions work
config.output.libraryTarget = "umd";
// Load related files as needed, set it to 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);
// Turn off host checking so that microapplications can be fetched
config.disableHostCheck = true;
// Configure cross-domain request headers to solve cross-domain problems in the development environment
config.headers = {
"Access-Control-Allow-Origin": "*"};// Configure the history mode
config.historyApiFallback = true;
returnconfig; }; }};Copy the code
The React subapplication is now ready to use
Access to apps that do not use WebPack, Angular, etc., will not be mentioned, if you are interested, you can find information.
Nginx deploys packaged files
Nginx installation and use will not be introduced, not familiar with the search engine.
Nginx configuration
#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' The '*';
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' The '*';
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' The '*';
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; }}}Copy the code
For online security, access-Control-allow-origin should not be set to * and should be configured with the specified domain name. Note the configuration of nginx in the figure above
html/dist
Port 80 configured for our main application,html/static
Simple written project without Webpack.- Each server is configured with a front-end package for a different framework.
- Note that the configured access address must be the same as that in the main application app.
Review past
- Write a Vue version of the Upload component
- On the basis of menu permissions, the operation platform system further refined to button permissions Management (Vue).
- Vue version Backtop component
The demo address
Github.com/FoneQinrf/q…
conclusion
All the routing modes used in the above applications are history mode. I have tried the hash mode but failed. What if the two modes are mixed? Have you tried a unified hash model or a successful mixture of the two? Oneself level is limited, move brick not easy, inadequacy place asks for advice!