project practise

This tutorial is suitable for people who are new to qiankun, and introduces how to build a qiankun project from 0.

main app

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 qiankun
start();

micro app

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:

  1. Added public-path.js file, used to modify the runtime publicPath.What is publicPath at runtime?.
Note: `publicPath` at runtime and `publicPath` at build time are different, and the two cannot be equivalently substituted.
  1. It is recommended to use the route of the history mode for the micro app. The route base needs to be set, and the value is the same as its activeRule.
  2. Import public-path.js at the top of the entry file, modify and export three lifecycles functions.
  3. Modify the 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.

React micro app

Take the react 16 project generated by create react app as an example, with react-router-dom 5.x.

  1. Add public-path.js in the src directory:

    if (window.__POWERED_BY_QIANKUN__) {
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    }
  2. Set the base of history mode routing:

    <BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/app-react' : '/'}>
  3. 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.

  1. 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 chunkLoadingGlobal
    config.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"

Vue micro app

Take the vue 2.x project generated by vue-cli 3+ as an example, and add it after the vue 3 version becomes stable.

  1. Add public-path.js in the src directory:

    if (window.__POWERED_BY_QIANKUN__) {
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    }
  2. 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 independently
    if (!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;
    }
  3. 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 format
    jsonpFunction: `webpackJsonp_${name}`, // // If you are using webpack 5, please replace jsonpFunction with chunkLoadingGlobal
    },
    },
    };

Angular micro app

Take the angular 9 project generated by Angular-cli 9 as an example, other versions of angular will be added later.

  1. 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__;
    }
  2. 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' : '/' }]
    })
  3. 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-ignore
    app.destroy();
    }
  4. 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",
  5. 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>
  6. Fix ng build comand's error report, modify tsconfig.json file, referenceissues/431.

    - "target": "es2015",
    + "target": "es5",
    + "typeRoots": [
    + "node_modules/@types"
    + ],
  7. 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 websiteangular 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 -D
npm 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",

Micro app built without webpack

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:

  1. 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>
  2. 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

umi-qiankun app

For the tutorial of umi-qiankun, please go to umi official website and umi-qiankun official demo