qiankun(乾坤)
In Chinese traditional culture
qian
means heaven andkun
stands for earth, soqiankun
is the universe.
An implementation of Micro Frontends, based on single-spa, but made it production-ready.
🤔 Motivation
As we know what micro-frontends aims for:
Techniques, strategies and recipes for building a modern web app with multiple teams using different JavaScript frameworks. — Micro Frontends
An independent development experience is very important for a large system, especially with an enterprise application. But if you've tried to implement a micro-frontends architecture in such a system, you'll usually hurt your brain with such problems:
- How to compose your independent sub apps into your main system?
- How to guarantee your sub apps to be isolated by each other?
- and so on...
We built an library to help you solve these glitch problems automatically without any mental burden of yours, then named it qiankun
.
Probably the most complete micro-frontends solution you ever met🧐.
📦 Installation
$ yarn add qiankun # or npm i qiankun -S
🔨 Getting Started
1. Create master framework with qiankun
import { registerMicroApps, start } from 'qiankun';
function render({ appContent, loading }) {
const container = document.getElementById('container');
ReactDOM.render(<Framework loading={loading} content={appContent} />, container);
}
function genActiveRule(routerPrefix) {
return location => location.pathname.startsWith(routerPrefix);
}
registerMicroApps([
{
name: 'react app', // app name registered
entry: '//localhost:7100',
render,
activeRule: genActiveRule('/react'),
},
{
name: 'vue app',
entry: { scripts: ['//localhost:7100/main.js'] },
render,
activeRule: genActiveRule('/vue'),
},
]);
start();
2. Export the lifecycles from your sub app entry
export async function bootstrap() {
console.log('react app bootstraped');
}
export async function mount(props) {
console.log(props);
ReactDOM.render(<App />, document.getElementById('react15Root'));
}
export async function unmount() {
ReactDOM.unmountComponentAtNode(document.getElementById('react15Root'));
}
For more lifecycle information, see single-spa lifecycles
3. Config your sub app bundler
While you wanna build a sub app to integrate to qiankun, pls make sure your bundler have the required configuration below:
webpack:
const packageName = require('./package.json').name;
module.exports = {
output: {
library: `${packageName}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
},
};
see https://webpack.js.org/configuration/output/#outputlibrary
parcel:
parcel serve entry.js --global myvariable
see https://en.parceljs.org/cli.html#expose-modules-as-umd
💿 Examples
$yarn
$yarn install:examples
$yarn start
Visit http://localhost:7099
✨ Features
- Based on single-spa
- HTML Entry
- Config Entry
- Isolated styles
- JS Sandbox
- Assets Prefetch
- @umijs/plugin-qiankun integration
📖 API
registerMicroApps(apps: Array<RegistrableApp<T>>, lifeCycles?: LifeCycles<T>, opts?: RegisterMicroAppsOpts)
function registerMicroApps<T extends object = {}>(
apps: Array<RegistrableApp<T>>,
lifeCycles?: LifeCycles<T>,
opts?: RegisterMicroAppsOpts,
): void;
type RegistrableApp = {
// name to identify your app
name: string;
// where your sub app served from, supported html entry and config entry
entry: string | { scripts?: string[]; styles?: string[]; html?: string };
// render function called around sub app lifecycle
render: (props?: { appContent: string; loading: boolean }) => any;
// when sub app active
activeRule: (location: Location) => boolean;
// props pass through to sub app
props?: object;
};
type Lifecycle<T extends object> = (app: RegistrableApp<T>) => Promise<any>;
type LifeCycles<T extends object> = {
beforeLoad?: Lifecycle<T> | Array<Lifecycle<T>>;
beforeMount?: Lifecycle<T> | Array<Lifecycle<T>>;
afterMount?: Lifecycle<T> | Array<Lifecycle<T>>;
beforeUnmount?: Lifecycle<T> | Array<Lifecycle<T>>;
afterUnmount?: Lifecycle<T> | Array<Lifecycle<T>>;
};
start(options: Options): void
function start(options: Options): void;
Options
param | description | default |
---|---|---|
prefetch | Whether to prefetch assets of sub apps after first sub app mounted | true |
jsSandbox | While sandbox enabled, we could guarantee that sub apps is isolated with each others | true |
singular | Only one sub app display at one runtime, that means a sub app will wait to mount until the before one unmounted | true |
fetch | Set a custom fetch function | window.fetch |
setDefaultMountApp
Set which sub app shoule be active by default after master loaded.
function setDefaultMountApp(defaultAppLink: string): void;
runAfterFirstMounted
function runAfterFirstMounted(effect: () => void): void;
🎯 Roadmap
- Parcel apps integration (multiple sub apps displayed at the same time, but only one uses router at most)
- Communication development kits between master and sub apps
- Custom side effects hijacker
- Nested Microfrontends
❓ FAQ
https://github.com/umijs/qiankun/wiki/FAQ
👬 Community
https://github.com/umijs/umi#community
or
🎁 Acknowledgements
- single-spa What an awesome meta-framework for micro-frontends!