This is the first day of my participation in the August Text Challenge.More challenges in August
Because this development project needs to embed the previous old project, due to take into account the iframe speed is slow, CSS/JS need additional requests, blocking page loading, browser forward/back and other shortcomings, so I intend to step on the qiankun, in order to get earlier.
Introduction to the
Qiankun is a single-SPa-based microfront-end implementation library that aims to make it easier and painless to build a production-ready microfront-end architecture system.
Official resources:
- A sample code is provided
- An online demo of sample code
- Access the online VUE child application separately
- Access the React child app alone
According to theQiankun official fileIntroduction, mainly has the following seven features:
- 📦 provides a more out-of-the-box API based on a single-SPA package.
- 📱 technology stack independent, any technology stack applications can use/access, be it React/Vue/Angular/JQuery or other frameworks.
- 💪 HTML Entry access, let you access micro applications as easy as iframe.
- 🛡 style isolation to ensure that styles do not interfere with each other in microapplications.
- 🧳 JS sandbox to ensure that global variables/events do not conflict between microapplications.
- ⚡️ Resource preloading: Preloads unopened micro-application resources in idle time of the browser to speed up the opening of micro-applications.
- 🔌 UMI plug-in, providing @umiJS/plugin-Qiankun for UMI applications to switch to a micro front-end architecture system with one click.
Views and practices of other front-end teams in the industry on microfronds:
- Daily Youxian supply chain front end team micro front end transformation
- Micro front end in meituan takeout practice
- Polish and application of front-end microserver in Bytedance
- Practice of micro front end in Mi CRM system
- Implementation of standard microfront-end architecture in Ant
The API is introduced
This section only describes the basic functions of the API. For more information, please refer to the official documentation
registerMicroApps(apps, lifeCycles?)
Basic configuration information for registering microapplications. When the browser URL changes, it will automatically check the activeRule rule registered for each micro-application, and the application that meets the rule will be automatically activated.
import { registerMicroApps } from 'qiankun';
registerMicroApps(
[
{
// name-string - Specifies the name of the micro-application. Each micro-application must be unique
name: 'apass-micro'.// entry-string - Mandatory, the entry of the micro-application
entry: 'localhost:8080'./ / container - string | HTMLElement - will be chosen, the application of micro selector of the container node or Element instance
container: '#apassMicroTemplateConfig'.// activeRule -string - Mandatory, micro application activation rule
activeRule: '/index/config/template/edit'.// props - Object - Optional, the data that the main application needs to pass to the micro-application
props: {
name: 'kuitos'.routerPushFunc: (that) = > {
that.$router.push('/713/5f4f65fabcb7c173/fields')},data: {
// Reactive data communication
store: microAppStore.getGlobalState
},
}
}
],
{
beforeLoad: app= > console.log('before load', app.name),
beforeMount: [
app= > console.log('before mount', app.name),
],
afterMount: [
app= > console.log('after mount', app.name),
],
beforeUnmoun: [
app= > console.log('before unmount', app.name),
],
afterUnmount: [
app= > console.log('after unmount', app.name),
]
},
);
Copy the code
start(opts?)
Start the qiankun
import { start } from 'qiankun';
start();
Copy the code
setDefaultMountApp(appLink)
Set the microapps that enter by default after the main app starts.
import { setDefaultMountApp } from 'qiankun';
setDefaultMountApp('/homeApp');
Copy the code
runAfterFirstMounted(effect)
The first micro applies methods that need to be called after the mount, such as enabling some monitoring or burying scripts.
import { runAfterFirstMounted } from 'qiankun';
runAfterFirstMounted(() = > {
console.log('This method is called after the first child application loads.')
this.otherFunction()
})
Copy the code
loadMicroApp(app, configuration?)
This applies to scenarios where you need to manually load or uninstall a microapplication.
Typically, a microapplication in this scenario is a business component that can run independently without routing. You are advised to split microapplications based on service domains. Functional units with close business connection should be made into one micro-application, whereas those with weak business connection can be divided into multiple micro-applications. One criterion for determining whether the business is closely related is whether this micro-application has frequent communication requirements with other micro-applications. If it is possible to demonstrate that the two microapplications themselves serve the same business scenario, merging them into a single microapplication may be more appropriate.
import { loadMicroApp } from 'qiankun';
// Since loadMicroApp() returns an instance of the child application, take a global variable and then perform other operations such as manually unloading the child application
this.microApp = loadMicroApp(
{
name: 'sub-vue'.entry: 'http://localhost:7777/subapp/sub-vue'.container: '#apassMicroTemplateConfig'.props: {
routerBase: '/index/config/template/edit'.getGlobalState: microAppStore.getGlobalState,
sheetId: '2133123123'}}, {// sandbox - boolean | { strictStyleIsolation? : boolean, experimentalStyleIsolation? : Boolean} - Optional, whether to enable sandbox. Default is true
sandbox: { strictStyleIsolation: true },
// singular - boolean | ((app: RegistrableApp
) => Promise
); - Optional, whether it is a single instance scenario, which means that only one microapplication is rendered at a time. The default is false
singular: true})// Encapsulates the function that uninstalls the child application
private unmountMicroApp () {
if (this.microApp) {
this.microApp.mountPromise.then(() = > {
this.microApp.unmount()
})
}
}
Copy the code
prefetchApps(apps, importEntryOpts?)
Manually preloads the specified microapplication static resources. Manually loading the prefetch attribute is required only in micro-application scenarios. Configure the Prefetch attribute based on automatic route activation scenarios.
import { prefetchApps } from 'qiankun';
prefetchApps([ { name: 'app1'.entry: '//locahost:7001' }, { name: 'app2'.entry: '//locahost:7002'}])Copy the code
Active Application Configuration
Install qiankun
$ npm i qiankun -S # yarn Add Qiankun
Copy the code
Adjust the main. Js
If you need to load these subapps at project initialization, you’ll need to change some of the main.js configuration. You can skip this step if you manually load the file on the page.
import Vue from "vue"
import App from "./App.vue"
import router from "./router"
import { registerMicroApps, setDefaultMountApp, start } from "qiankun"
Vue.config.productionTip = false
let app = null;
/** * Render function * appContent child application HTML content * loading child application loading effect, optional */
function render({ appContent, loading } = {}) {
if(! app) { app =new Vue({
el: "#container",
router,
data() {
return {
content: appContent,
loading
};
},
render(h) {
return h(App, {
props: {
content: this.content,
loading: this.loading } }); }}); }else{ app.content = appContent; app.loading = loading; }}/** * Route listener *@param {*} RouterPrefix prefix * /
function genActiveRule(routerPrefix) {
return location= > location.pathname.startsWith(routerPrefix);
}
function initApp() {
render({ appContent: ' '.loading: true });
}
initApp();
// Pass data to the child application
let msg = {
data: {
auth: false
},
fns: [{name: "_LOGIN"._LOGIN(data) {
console.log('Parent application returns information${data}`); }}};// Register child applications
registerMicroApps(
[
{
name: "sub-app-1".entry: "//localhost:8091",
render,
activeRule: genActiveRule("/app1"),
props: msg
},
{
name: "sub-app-2".entry: "//localhost:8092",
render,
activeRule: genActiveRule("/app2"}, {),beforeLoad: [
app= > {
console.log("before load", app); }].// Pre-mount callback
beforeMount: [
app= > {
console.log("before mount", app); }].// Mount callback
afterUnmount: [
app= > {
console.log("after unload", app); }]// Callback after uninstallation});// Set the default child application to the same parameters as in genActiveRule
setDefaultMountApp("/app1");
/ / start
start();
Copy the code
Change the ID in app. vue or add render child App box
Since a main App can have multiple sub-apps nested, app.vue will inevitably have the same name, so it is best to add a prefix of your project name to distinguish it.
<template>
<div id="main-root">
<! -- loading -->
<div v-if="loading">loading</div>
<! -- App box -->
<div id="root-view" class="app-view-box" v-html="content"></div>
</div>
</template>
<script>
export default {
name: "App".props: {
loading: Boolean.content: String}};</script>
Copy the code
Configure vUE subapplications
As the sub-application itself was a separate application, it was not necessary to install Qiankun and only needed to expose the three life cycles required by qiankun when it was embedded as a sub-application.
Configuration maim. Js
In addition to supporting embedding as a child application, the project needs to be supported to run independently, compatible with the previous configuration
import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './App.vue';
import routes from './router';
import './public-path';
Vue.config.productionTip = false;
let router = null;
let instance = null;
function render() {
router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/app1' : '/'.mode: 'history',
routes,
});
instance = new Vue({
router,
render: h= > h(App),
beforeMount () {
if (window.__POWERED_BY_QIANKUN__) {
routerPushFunc(this)
AppModule.SET_CURRENT_ENV()
}
}
}).$mount(container ? container.querySelector('#templateConfig') : '#templateConfig');
}
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log('vue app bootstraped');
}
export async function mount(props) {
console.log('props from main app', props);
render();
}
export async function unmount() {
(instance as Vue).$destroy();
(instance as Vue).$el.innerHTML = ' '; // Prevent memory leaks and clear the DOM when subprojects are destroyed
instance = null;
router = null;
}
Copy the code
public-path.js
Static publicPath configuration with Webpack: You can set it either by importing the public-path.js file directly into mian.js, or by modifying vue.config.js directly in your development environment
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
Copy the code
Configure the vue. Config. Js
Sub-application must support cross-domain: Since Qiankun acquired static resources introduced by sub-application through FETCH, it must require these static resources to support cross-domain
const path = require('path');
const { name } = require('./package');
function resolve(dir) {
return path.join(__dirname, dir);
}
const pagesMicro = {
templateConfig: {
entry: 'src/microPage/templateConfig/main.ts'.template: 'src/microPage/templateConfig/index.html'.chunks: ['runtime~templateConfig'.'chunk-vendors'.'chunk-common'.'templateConfig']}}const pagesMain = {
index: {
entry: 'src/main.ts'.template: '/index.html'}}const pages = process.env.VUE_APP_ENTRY === 'main' ? pagesMain : pagesMicro
let config = {
/** * You will need to set publicPath if you plan to deploy your site under a sub path, * for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/, * then publicPath should be set to "/bar/". * In most cases please use '/' !!! * Detail: https://cli.vuejs.org/config/#publicpath */
outputDir: 'dist'.assetsDir: 'static'.filenameHashing: true.// tweak internal webpack configuration.
// see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md
devServer: {
/ / host: '0.0.0.0',
hot: true.disableHostCheck: true,
port,
overlay: {
warnings: false.errors: true,},headers: {
'Access-Control-Allow-Origin': The '*',}},// Customize the WebPack configuration
configureWebpack: {
resolve: {
alias: {
The '@': resolve('src'),}},output: {
// Package the child application into the UMD library format
library: `${name}-[name]`.libraryTarget: 'umd'.jsonpFunction: `webpackJsonp_${name}`,}}};if (process.env.VUE_APP_ENTRY === 'micro') {
config.pages = pagesMicro
}
module.exports = config
Copy the code
FAQ and Solutions in Qiankun
Avoid CSS contamination
Qiankun can only solve the style pollution among sub-projects, but not the style pollution of the main project. There are about 5 solutions in terms of technology and specifications:
- Vue comes with scope
- It can only solve some of the style contamination within the page, but it is generally not a problem
- BEM naming mode
- css-in-js
- High learning curve; Poor readability; Use the front-end stack to consume performance;
- css-loader
- Turn on CSS-modules, similar to lazy loading of images, instead of attr
- Cons: You need to write class in CSS-modules; After many styles, the form of hash is not readable;
- postcss-loader
- Convert classes in all CSS files to JSON objects using the getJson() function of postCSs-Modules; Render json objects back to the HTML page class using postCSS-HTML
- Disadvantages: It is not meaningful to use the new GULP. Compile every change, very slowly;
Take CSS-loader as an example to enable CSS-modules, refer to the following articles:
- Ruan Yifeng CSS Modules usage tutorial
- CSS Modules Basic usage
- Talk about CSS Modules and the use of CSS Modules in vue.js
- CSS names: BEM, Scoped CSS, CSS Modules and CSS-in-js
- Vue CLI CSS configuration
- CSS – lot of loader
- CSS – lot of modules
- CSS Modules are used in TypeScript
module.exports = {
/ /... Omit other configurations
css: {
// Whether to use CSS ExtractTextPlugin
extract: false.// Enable CSS source maps?
sourceMap: false.// CSS default configuration item
loaderOptions: {
css: {
// These properties are valid:
// object { url? , import? , modules? , sourceMap? , importLoaders? , localsConvention? , onlyLocals? , esModule? }
modules: {
// These properties are valid:
// object { auto? , mode? , exportGlobals? , localIdentName? , localIdentRegExp? , context? , hashPrefix? , getLocalIdent? }
exportGlobals: true.localIdentName: '[path][name]__[local]--[hash:base64:5]'
},
localsConvention: 'asIs' // asIs camelCase camelCaseOnly dashes dashesOnly}},// Enable CSS modules for all CSS/pre-processor files.
requireModuleExtension: true}},Copy the code
Use position: fixed with caution
In the sub-project this location will appear problems, basically appear in the modal box and drawer positioning, should try to avoid using, there is a relative to the browser window positioning requirements, you can use position: sticky, but there will be compatibility problems (IE does not support). If the location uses bottom and right, this is not a problem. Alternatively, positions can be written as dynamically bound styles:
<div :style="{ top: isQiankun ? '10px' : '0'}">
Copy the code
Events bound to body, document, etc., should be cleared during the unmount cycle
Js sandbox hijacked only window. The addEventListener, the use of the document. The body. The addEventListener or document. The body. Add the onClick event will not be remoed sandbox affect other pages, Clear it in an unmount cycle
Uncaught Error Application ‘XXX’ died in status LOADING_SOURCE_CODE: [qiankun] You need to export lifecycle functions in xxx entry
It is usually the wrong packaging posture, possible reasons: not packaged into UMD format; The required JS file is packaged as a whole but not loaded and needs to be packaged separately using runtimeChunk
Now refresh the page error, container can not be found
Solution 1: Register and start qiankun during component Mounted cycles
Solution 2: After new Vue(), wait for the DOM to load and then register and start Qiankun
const vueApp = new Vue({
router,
store,
render: h= > h(App)
}).$mount("#app");
vueApp.$nextTick(() = > {
// Register and launch Qiankun here
})
Copy the code
The history mode can be used for both the primary and sub-application routes
Because the history mode of vue-Router is fully matched, if the current sub-application is embedded by qiankun, all the routes of the master application except for http://ip+port/ should be added before the sub-application’s first-level routes, that is, the initial sub-application in the master application is the activeRule defined.
router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/templateConfig' : '/'.mode: 'history'.routes: [{...}]})Copy the code
In history mode, the route configuration of the primary and sub-applications failed
If the vue-router of both the primary and sub-applications is in history mode, that is, all routes match
- The path attribute of the route information in the primary application needs to be changed to ‘index/edit*’, i.e. fuzzy full match, and the sub-application’s route needs to be changed to ‘index/ Edit /’ (as mentioned above). Otherwise, if the primary application fails to match the current page after the sub-application changes the route, it will jump back to the login page and adjust to 404.
- Child application route information should not have ” or ‘*’ nulls. Otherwise, after the primary application (from the embedded page of the sub-application) redirects to another page, the route matching rule of the sub-application is triggered and the sub-application is redirected to the login page of the sub-application. As a result, the route redirecting of the primary application fails.
Jump from one subproject to another
Inside the subproject how to jump to another component/main project page, or write directly in the router. Push/router. Replace are not enough, the reason is the router is a subproject of routing, all the jump will be based on the base of sub-projects. Writing links can jump to the past, but will refresh the page, the user experience is not good.
The solution is also relatively simple. When the child project is registered, the main project route instance object is passed, and the child project is mounted to the global, using the parent project router jump can be done.
However, there is a drop is not perfect, so that only through JS to jump, jump link can not use the browser’s own right menu
Image resource error 404
It’s best to change to an absolute path
<img src="./img/logo.jpg">
<!-- 改为 -->
<img src="/img/logo.jpg">
Copy the code
Or configure the proxy for nginx static files in the main application (there is no background nginx configuration here, so take the proxyTable proxy that comes with WebPack as an example)
if (item === '/index/config/template/edit/static') { // Login page img
proxyObj[item] = {
target: 'http://localhost:8081'.ws: false.changeOrigin: true.pathRewrite: { '^/index/config/template/edit/static': '/static'}}}else if (item === '/static/home') { / img/front page
proxyObj[item] = {
target: 'http://localhost:8081'.ws: false.changeOrigin: true.pathRewrite: { '^/static/home': '/static/home'}}}Copy the code
If the js file of the child application is too large, the child application will block when loading the child application manually
If you are manually loading the child application, loadMicroApp(), it is recommended to preload the resource, prefetchApps(), when the page is initialized. Avoid request pending times that are too long and block loading
Ts project and JS project file loading problems
Since the main project is TS, ts files are loaded by default. But the subproject is JS. So when introducing js files into subprojects, make sure they have a clear suffix, for example
// Error Unknown custom element: <widget> - did you register the component correctly? For recursive components, make sure to provide the "name" option. import {widgetInRecord as widget} from '@ / views/sheetConfig/fieldConfig/widget/widgets' add suffix is not an error / / import {widgetInRecord as widget} the from '@/views/sheetConfig/fieldConfig/widget/widget.js'Copy the code
Load the same sub-app with different initialization data within a page (e.g. list on the left and detail on the right)
Reloading problem, data communication problem, request response problem
Copy the code
Data communication between main project and subproject
There should not be too much data dependence between projects, after all, projects should be run independently. Communication operations need to determine whether the Qiankun mode is compatible.
Pass Vuex from the parent project through props, which works well if the child project is a VUE technology stack. If the child project is jQuery/react/angular, it will not be able to monitor data changes very well.
Qiakun provides a global GlobalState to share data. After the master project is initialized, subprojects can listen for changes to this data and can also commit this data.
// The main project is initialized
import { initGlobalState } from 'qiankun';
const actions = initGlobalState(state);
// Main project project monitor and modify
actions.onGlobalStateChange((state, prev) = > {
// state: state after the change; Prev Status before change
console.log(state, prev);
});
actions.setGlobalState(state);
// Subprojects listen and modify
export function mount(props) {
props.onGlobalStateChange((state, prev) = > {
// state: state after the change; Prev Status before change
console.log(state, prev);
});
props.setGlobalState(state);
}
Copy the code
Vue subproject memory leak problem
This problem is very difficult to find in the Issue area of Qiankun: github.com/umijs/qiank… I’ll skip the screening process. The solution is simple.
Empty the DOM when the subproject is destroyed:
export async function unmount() {
instance.$destroy();
+ instance.$el.innerHTML = ""; // Add this line of code
instance = null;
router = null;
}
Copy the code
However, switching back and forth between subprojects does not increase memory. That is, even if the memory occupied by the subproject is not freed when the subproject is unloaded, the memory will be reused the next time it is loaded. Will the subproject load faster? (Not verified yet)
Safety and performance issues
Qiankun records the JS/CSS file contents of each subproject in a global variable. If there are too many subprojects or the file size is too large, it may take up too much memory and cause the page to lag.
In addition, qiankun runs the JS of the sub-project, not through script tags, but through eval functions, whose security and performance are controversial: Introduction to EVAL in MDN