This tutorial is suitable for people who are new to qiankun
, and introduces how to build a qiankun
project from 0.
The main app is not limited to the technical framework, it only needs to provide a container DOM, then register the micro apps and start it.
Install qiankun
first :
$ yarn add qiankun # or npm i qiankun -S
Register the micro apps and start:
import { registerMicroApps, start } from 'qiankun';registerMicroApps([{name: 'angularApp',entry: '//localhost:4200',container: '#container',activeRule: '/app-angular',},{name: 'reactApp',entry: '//localhost:3000',container: '#container',activeRule: '/app-react',},{name: 'vueApp',entry: '//localhost:8080',container: '#container',activeRule: '/app-vue',},]);// start qiankunstart();
Micro apps are divided into projects with webpack
and without webpack
. The things that need to be done for micro apps with webpack
(mainly refers to Vue, React, Angular) are:
public-path.js
file, used to modify the runtime publicPath
.What is publicPath at runtime?.history
mode for the micro app. The route base
needs to be set, and the value is the same as its activeRule
.public-path.js
at the top of the entry file, modify and export three lifecycles
functions.webpack
configuration to allow cross-domain in development environments and bundle with umd
.The main modifications are the above four, which may change according to different situations of the project. For example, if your project is deployed separately from all other files of index.html
, it means that you have set the publicPath
at build time to the full path, so you don’t need to modify the publicPath
at runtime (the first step can be omitted).
For micro app built without webpack
, just mount lifecycles
to window
.
Take the react 16
project generated by create react app
as an example, with react-router-dom
5.x.
Add public-path.js
in the src
directory:
if (window.__POWERED_BY_QIANKUN__) {__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;}
Set the base
of history
mode routing:
<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/app-react' : '/'}>
The entry file index.js
is modified. In order to avoid the root id #root
from conflicting with other DOMs, the search scope needs to be limited.
import './public-path';import React from 'react';import ReactDOM from 'react-dom';import App from './App';function render(props) {const { container } = props;ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));}if (!window.__POWERED_BY_QIANKUN__) {render({});}export async function bootstrap() {console.log('[react16] react app bootstraped');}export async function mount(props) {console.log('[react16] props from main framework', props);render(props);}export async function unmount(props) {const { container } = props;ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));}
It's important, When mount a sub-application through ReactDOM.render, need to ensure each sub-application load with a new router instance.
Modify webpack
configuration
Install the plugin @rescripts/cli
, of course, you can also choose other plugins, such as react-app-rewired
.
npm i -D @rescripts/cli
Add .rescriptsrc.js
to the root directory:
const { name } = require('./package');module.exports = {webpack: (config) => {config.output.library = `${name}-[name]`;config.output.libraryTarget = 'umd';// If you are using webpack 5, please replace jsonpFunction with chunkLoadingGlobalconfig.output.jsonpFunction = `webpackJsonp_${name}`;config.output.globalObject = 'window';return config;},devServer: (_) => {const config = _;config.headers = {'Access-Control-Allow-Origin': '*',};config.historyApiFallback = true;config.hot = false;config.watchContentBase = false;config.liveReload = false;return config;},};
Modify package.json
:
- "start": "react-scripts start",+ "start": "rescripts start",- "build": "react-scripts build",+ "build": "rescripts build",- "test": "react-scripts test",+ "test": "rescripts test",- "eject": "react-scripts eject"
Take the vue 2.x
project generated by vue-cli 3+
as an example, and add it after the vue 3
version becomes stable.
Add public-path.js
in the src
directory:
if (window.__POWERED_BY_QIANKUN__) {__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;}
The entry file main.js
is modified. In order to avoid the root id #app
from conflicting with other DOMs, the search scope needs to be limited.
import './public-path';import Vue from 'vue';import VueRouter from 'vue-router';import App from './App.vue';import routes from './router';import store from './store';Vue.config.productionTip = false;let router = null;let instance = null;function render(props = {}) {const { container } = props;router = new VueRouter({base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/',mode: 'history',routes,});instance = new Vue({router,store,render: (h) => h(App),}).$mount(container ? container.querySelector('#app') : '#app');}// when run independentlyif (!window.__POWERED_BY_QIANKUN__) {render();}export async function bootstrap() {console.log('[vue] vue app bootstraped');}export async function mount(props) {console.log('[vue] props from main framework', props);render(props);}export async function unmount() {instance.$destroy();instance.$el.innerHTML = '';instance = null;router = null;}
Modify webpack
configuration(vue.config.js
):
const { name } = require('./package');module.exports = {devServer: {headers: {'Access-Control-Allow-Origin': '*',},},configureWebpack: {output: {library: `${name}-[name]`,libraryTarget: 'umd', // bundle the micro app into umd library formatjsonpFunction: `webpackJsonp_${name}`, // // If you are using webpack 5, please replace jsonpFunction with chunkLoadingGlobal},},};
Take the angular 9
project generated by Angular-cli 9
as an example, other versions of angular
will be added later.
Add the file public-path.js
in the src
directory with the content:
if (window.__POWERED_BY_QIANKUN__) {// eslint-disable-next-line no-undef__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;}
Set the base
of history
mode routing, src/app/app-routing.module.ts
file:
+ import { APP_BASE_HREF } from '@angular/common';@NgModule({imports: [RouterModule.forRoot(routes)],exports: [RouterModule],// @ts-ignore+ providers: [{ provide: APP_BASE_HREF, useValue: window.__POWERED_BY_QIANKUN__ ? '/app-angular' : '/' }]})
Modify the entry file, src/main.ts
file:
import './public-path';import { enableProdMode, NgModuleRef } from '@angular/core';import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';import { AppModule } from './app/app.module';import { environment } from './environments/environment';if (environment.production) {enableProdMode();}let app: void | NgModuleRef<AppModule>;async function render() {app = await platformBrowserDynamic().bootstrapModule(AppModule).catch((err) => console.error(err));}if (!(window as any).__POWERED_BY_QIANKUN__) {render();}export async function bootstrap(props: Object) {console.log(props);}export async function mount(props: Object) {render();}export async function unmount(props: Object) {console.log(props);// @ts-ignoreapp.destroy();}
Modify webpack
bundling configuration
First install the @angular-builders/custom-webpack
plugin. Note: Angular 9
project can only install 9.x
version, angular 10
project can install the latest version.
npm i @angular-builders/custom-webpack@9.2.0 -D
Add custom-webpack.config.js
to the root directory with the content:
const appName = require('./package.json').name;module.exports = {devServer: {headers: {'Access-Control-Allow-Origin': '*',},},output: {library: `${appName}-[name]`,libraryTarget: 'umd',jsonpFunction: `webpackJsonp_${appName}`, // // If you are using webpack 5, please replace jsonpFunction with chunkLoadingGlobal},};
Modify angular.json
, change the values of [packageName]> architect> build> builder
and [packageName]> architect> serve> builder
to the plugins we installed, and add our webpack's configuration file to [ packageName]> architect> build> options
.
- "builder": "@angular-devkit/build-angular:browser",+ "builder": "@angular-builders/custom-webpack:browser","options": {+ "customWebpackConfig": {+ "path": "./custom-webpack.config.js"+ }}
- "builder": "@angular-devkit/build-angular:dev-server",+ "builder": "@angular-builders/custom-webpack:dev-server",
Solve the problem of zone.js
Import zone.js
in main app, it needs to be imported before import qiankun
.
Delete the code of import zone.js
in the src/polyfills.ts
of the micro app.
- import 'zone.js/dist/zone';
Add the following content to the <head>
tag in the src/index.html
of the micro app, which is used when the micro app is accessed independently.
<!-- Other CDN/local packages can also be used --><script src="https://unpkg.com/zone.js" ignore></script>
Fix ng build
comand's error report, modify tsconfig.json
file, referenceissues/431.
- "target": "es2015",+ "target": "es5",+ "typeRoots": [+ "node_modules/@types"+ ],
In order to prevent the conflict of <app-root></app-root>
when the main app or other micro apps are also angular
, it is recommended to add a unique id to <app-root>
, such as Say the current app name.
src/index.html :
- <app-root></app-root>+ <app-root id="angular9"></app-root>
src/app/app.component.ts :
- selector: 'app-root',+ selector: '#angular9 app-root',
Of course, you can also choose to use the single-spa-angular
plugin, refer to single-spa-angular official website 和 angular demo
(supplement)The angular7 has the same steps as angular9 except for step 4. The steps for angular7 to modify the webpack
configuration are as follows:
In addition to installing the 7.x version of angular-builders/custom-webpack
, you also need to install angular-builders/dev-server
.
npm i @angular-builders/custom-webpack@7 -Dnpm i @angular-builders/dev-server -D
Add custom-webpack.config.js
to the root directory, same as above.
Modify angular.json
, [packageName] > architect > build > builder
is the same as Angular9, and [packageName] > architect > serve > builder
is different from Angular9.
- "builder": "@angular-devkit/build-angular:browser",+ "builder": "@angular-builders/custom-webpack:browser","options": {+ "customWebpackConfig": {+ "path": "./custom-webpack.config.js"+ }}
- "builder": "@angular-devkit/build-angular:dev-server",+ "builder": "@angular-builders/dev-server:generic",
Some apps that are not built by webpack
, such as jQuery
app, jsp
app, can be handled according to this.
Before modify, please make sure that the resources such as pictures, audio and video in your project can be loaded normally. If the addresses of these resources are all full paths (for example, https://qiankun.umijs.org/logo.png
), there is no problem. If they are all relative paths, you need to upload these resources to the server first and reference the full path.
The only change is that we need to declare a script tag, to export the lifecycles
example:
declare entry script
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Purehtml Example</title></head><body><div>Purehtml Example</div></body>+ <script src="//yourhost/entry.js" entry></script></html>
export lifecycles in the entry
const render = ($) => {$('#purehtml-container').html('Hello, render with jQuery');return Promise.resolve();};((global) => {global['purehtml'] = {bootstrap: () => {console.log('purehtml bootstrap');return Promise.resolve();},mount: () => {console.log('purehtml mount');return render($);},unmount: () => {console.log('purehtml unmount');return Promise.resolve();},};})(window);
refer to the purehtml examples
At the same time, the subApp must support the CORS
For the tutorial of umi-qiankun
, please go to umi official website and umi-qiankun official demo