preface

Heaven and earth were in chaos. To meet the needs of business, a preliminary study of Qiankun.

What am I going to do

React and Angular frameworks are adopted in the company’s projects, which have complex and diverse functions, resulting in complex project structures and huge amounts of code. In order to further verify the feasibility of qiankun scheme, the research objectives are determined as follows:

  • React and Angular stack projects are compatible
  • Child applications are nested
  • Subapplication parallelism
  • Whether localStrage and cookie are shared
  • Availability of the props and initGlobalState communication mechanism

Project structures,

The react projects:

Sub-main was built for the main base, and react-sub and react-sub2 were built for the sub-projects. Creation-react-app was used for the official website, and scaffolding was quickly configured with @rescripts/ CLI for relevant configuration.

Sub-main:

* Subproject react-sub:Copy the code

Subproject React-sub2:

Presents the project:

Use the @angular/cli scaffolding to build angular-sub

Use of qiankun frame

The main project installs dependencies

Yarn Add Qiankun # or NPM I Qiankun-sCopy the code

Register microapplications in the main application

import { registerMicroApps, start } from "qiankun";
registerMicroApps([
  {
    name"react-sub".// Application name
    entry"//localhost:3000".// Application address
    container"#subContainer".// Embed the main application location
    activeRule"/sub1".// Match rules
  },
  {
    name"react-sub2".entry"//localhost:3002".container"#subContainer".activeRule"/sub2"}, {name"angular-sub".entry"//localhost:4200".container"#subContainer".activeRule"/sub3",}]); start();Copy the code

Sub-application Configuration

The child application needs to export the bootstrap, mount, and unmount lifecycle hooks in its own entry JS (usually the entry JS of your webpack configuration) for the main application to call when appropriate.

The react projects:

// Project mount
function render(props{
  ReactDOM.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>.document.getElementById("sub2root"));console.log(props);
}

// The child application runs independently
if (!window.__POWERED_BY_QIANKUN_PARENT__) {
  render();
}

// It is called only once when the child application is initialized
export async function bootstrap({
  console.log("react-sub2 bootstraped");
}

// The mount method is called every time the application is entered. This is where we usually trigger the application's render method
export async function mount(props{
  console.log("react-sub2 props from main framework", props);
  render(props);
}

// The method that is called each time the application is cut/unloaded. This is where we usually unload the application instance of the microapplication
export async function unmount(props{
  const { container } = props;
  ReactDOM.unmountComponentAtNode(
    container
      ? container.querySelector("#sub2root")
      : document.querySelector("#sub2root")); }Copy the code

Add the public-path.js file and introduce the child application index.js

if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
Copy the code

Presents the project:

The current compatibility issues between Angular and Qiankun require special treatment.

Generate a configuration using single-spa-Angular

ng add single-spa-angular 
Copy the code

After generating the single-SPA configuration, we needed to do some qiankun access configuration. We exported the three lifecycle hook functions required by the Qiankun main application in the Entry file main.single-spa.ts. The code is as follows:

import "zone.js/dist/zone"; // Need additional introduction to ensure independent operation of subprojects
import { enableProdMode, NgZone } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { Router } from '@angular/router';
import { singleSpaAngular, getSingleSpaExtraProviders } from 'single-spa-angular';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { singleSpaPropsSubject } from './single-spa/single-spa-props';

if (environment.production) {
  enableProdMode();
}

// Microapplications run when they are started alone
if(! (window as any).__POWERED_BY_QIANKUN__) {
  platformBrowserDynamic()
    .bootstrapModule(AppModule)
    .catch((err) = > console.error(err));
}

const lifecycles = singleSpaAngular({
  bootstrapFunctionsingleSpaProps= > {
    singleSpaPropsSubject.next(singleSpaProps);
    return platformBrowserDynamic(getSingleSpaExtraProviders()).bootstrapModule(AppModule);
  },
  template'<app-root />',
  Router,
  NgZone,
});

export const bootstrap = lifecycles.bootstrap;
export const mount = lifecycles.mount;
export const unmount = lifecycles.unmount;

Copy the code

You need to install zone.js separately and import the above files

npm i zone.js -S
Copy the code

Resolve Angular project compatibility issues by installing zone.js and importing the index.js file in the main application

npm i zone.js -S
Copy the code

Note: Compatible reasons

  1. Before loading the sub-application, Qiankun used es6 Proxy method to create a Proxy on Window (the so-called jsSandbox), so that the sub-application could actually run in the Proxy, so as to monitor the modification of Window made by the sub-application and restore it when uninstalling the application. Simply put, the current proxy is incompatible with the Angular dependent zone.js, causing an error when loading an Angular child application.
  2. When currently importing modules for sub-applications, QianKun will look for the last global variable added to the window as the umD exported variable. Angular 6.x and later creates global variables such as ngDevMode globally, causing the export variable to be ngDevMode. Finally, errors occurred when Qiankun searched for modules.

Package command modification, support the specified path application

ng serve --disable-host-check --port 2000 --base-href /sub3 --live-reload false
Copy the code

Build tool Configuration

The react application webpack:

const { name } = require('./package');

module.exports = {
  webpackconfig= > {
    config.output.library = `${name}-[name]`;
    config.output.libraryTarget = 'umd';
    config.output.jsonpFunction = `webpackJsonp_${name}`;
    config.output.globalObject = 'window';

    return config;
  },

  devServer_= > {
    const config = _;

    config.headers = {
      'Access-Control-Allow-Origin'The '*'}; config.historyApiFallback =true;

    config.hot = false;
    config.watchContentBase = false;
    config.liveReload = false;

    returnconfig; }};Copy the code

Ng configuration file extra-webpack.config.js

const singleSpaAngularWebpack = require("single-spa-angular/lib/webpack")
  .default;
const webpackMerge = require("webpack-merge");
const { name } = require("./package");

module.exports = (angularWebpackConfig, options) = > {
  const singleSpaWebpackConfig = singleSpaAngularWebpack(
    angularWebpackConfig,
    options
  );

  const singleSpaConfig = {
    output: {
      library`${name}-[name]`.libraryTarget"umd",},externals: {
      "zone.js""Zone",}};const mergedConfig = webpackMerge.smart(
    singleSpaWebpackConfig,
    singleSpaConfig
  );
  return mergedConfig;
};

Copy the code

Note: When the main application loads a child application, the child application must support cross-domain loading

Enable applications to run independently

As can be seen from the actual results, Qiankun is compatible with React and Angular sub-applications. Angular projects have some compatibility problems.

Load the child app React-Sub

Load the child app React-sub2

Load the child app Angular-sub

Child applications are nested

The sub-project itself runs an Qiankun

Existing problems:

1. Subprojects cannot be judged whether to run independently or be integrated according to existing information

if (!window.__POWERED_BY_QIANKUN__) {
 render();
}
Copy the code

Since the sub-project itself is also an Qiankun project, window.powered_by_QIANkun will be true when it is independently run and true when it is integrated.

Define another global variable window.powered_BY_QIANkun_parent = true in the main project entry file. Use this variable to distinguish between being integrated and running independently

2. Modify the entry file of subproject

Note the following: When switching subprojects, avoid duplicate registration of the grandchild project. Since the subproject will be injected with a prefix, the route of the grandchild project should also be added with this prefix. Note the container conflict, and the subproject and the grandchild project use different containers

3. Package configuration modification

After the above operation is complete, the sub-project of Qiankun can be loaded in the main project, but click on the grandson project, an error is reported, and the life cycle can not be found.

Library: '${name}',Copy the code

Cause: Qiankun took the life cycle of the sub-project, and took the last variable mounted to the window when the sub-project was running first. If it was not a life cycle function, the variable was taken according to appName. Make webpack’s Library value correspond to appName.

Running results:

Subapplication parallelism

Start Enable multiple instances. If multiple applications are configured with the route matching rule activeRule, they will be activated concurrently. Note that sub-applications must be connected to different ids of the active application in parallel.

registerMicroApps([
  {
    name"react-sub".// Application name
    entry"//localhost:3000".// Application address
    container"#subContainer".// Embed the main application location
    activeRule"/sub1".// Match rules
  },
  {
    name"react-sub2".entry"//localhost:3002".container"#subContainer2".activeRule"/sub1",}]); start({singularfalse});
Copy the code

communication

The browser API

As Qiankunshi uses HTML Entry, localStrage and cookies can be shared.

qiankun api

props

The primary application is passed to the child application through props

{
    name"react-sub".// Application name
    entry"//localhost:3000".// Application address
    container"#subContainer".// Embed the main application location
    activeRule"/sub1".// Match rules
    props: {
      aa11,}},Copy the code

Child applications are acquired in the lifecycle

export async function bootstrap(props{}export async function mount(props{}export async function unmount(props{}Copy the code

initGlobalState

The main application is passed through initGlobalState and listens

// Initialize state
const actions = initGlobalState(state);
actions.onGlobalStateChange((state, prev) = > {
  // state: state after the change; Prev Status before change
  console.log("main-onGlobalStateChange", state, prev);
});
actions.setGlobalState(state);
actions.offGlobalStateChange();
Copy the code

Child applications receive in the life cycle

// Get the communication method from lifecycle mount, Use and master the export function mount (props) {props. OnGlobalStateChange ((state, prev) = > {/ / state: after the change of state; Prev Status before change console.log(state, prev); }); props.setGlobalState(state); }Copy the code

Pay attention to the pit point

  1. Child applications must support cross-domain
  2. React needs to be setpublic-pathTo ensure the normal loading of sub-applications
  3. Angluar sub application note compatibility issues between Zone. js and the Universe framework
  4. Once single-spa-Angular is installed for the Anluar child application, the zone.js dependency needs to be installed separately and introduced
  5. The library configuration of chrome is changed to packageNmae
  6. The root element IDS of the master and child applications should be unique. You are advised to use their respective project names

Github project address