本教程适合刚接触 qiankun
的新人,介绍了如何从 0 构建一个 qiankun
项目。
主应用不限技术栈,只需要提供一个容器 DOM,然后注册微应用并 start
即可。
先安装 qiankun
:
$ yarn add qiankun # 或者 npm i qiankun -S
注册微应用并启动:
import { registerMicroApps, start } from 'qiankun';registerMicroApps([{name: 'reactApp',entry: '//localhost:3000',container: '#container',activeRule: '/app-react',},{name: 'vueApp',entry: '//localhost:8080',container: '#container',activeRule: '/app-vue',},{name: 'angularApp',entry: '//localhost:4200',container: '#container',activeRule: '/app-angular',},]);// 启动 qiankunstart();
微应用分为有 webpack
构建和无 webpack
构建项目,有 webpack
的微应用(主要是指 Vue、React、Angular)需要做的事情有:
public-path.js
文件,用于修改运行时的 publicPath
。什么是运行时的 publicPath ?。history
模式的路由,需要设置路由 base
,值和它的 activeRule
是一样的。public-path.js
,修改并导出三个生命周期函数。webpack
打包,允许开发环境跨域和 umd
打包。主要的修改就是以上四个,可能会根据项目的不同情况而改变。例如,你的项目是 index.html
和其他的所有文件分开部署的,说明你们已经将构建时的 publicPath
设置为了完整路径,则不用修改运行时的 publicPath
(第一步操作可省)。
无 webpack
构建的微应用直接将 lifecycles
挂载到 window
上即可。
以 create react app
生成的 react 16
项目为例,搭配 react-router-dom
5.x。
在 src
目录新增 public-path.js
:
if (window.__POWERED_BY_QIANKUN__) {__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;}
设置 history
模式路由的 base
:
<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/app-react' : '/'}>
入口文件 index.js
修改,为了避免根 id #root
与其他的 DOM 冲突,需要限制查找范围。
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'));}
这里需要特别注意的是,通过 ReactDOM.render 挂载子应用时,需要保证每次子应用加载都应使用一个新的路由实例。
修改 webpack
配置
安装插件 @rescripts/cli
,当然也可以选择其他的插件,例如 react-app-rewired
。
npm i -D @rescripts/cli
根目录新增 .rescriptsrc.js
:
const { name } = require('./package');module.exports = {webpack: (config) => {config.output.library = `${name}-[name]`;config.output.libraryTarget = 'umd';// webpack 5 需要把 jsonpFunction 替换成 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;},};
修改 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-cli 3+
生成的 vue 2.x
项目为例,vue 3
版本等稳定后再补充。
在 src
目录新增 public-path.js
:
if (window.__POWERED_BY_QIANKUN__) {__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;}
入口文件 main.js
修改,为了避免根 id #app
与其他的 DOM 冲突,需要限制查找范围。
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');}// 独立运行时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;}
打包配置修改(vue.config.js
):
const { name } = require('./package');module.exports = {devServer: {headers: {'Access-Control-Allow-Origin': '*',},},configureWebpack: {output: {library: `${name}-[name]`,libraryTarget: 'umd', // 把微应用打包成 umd 库格式jsonpFunction: `webpackJsonp_${name}`, // webpack 5 需要把 jsonpFunction 替换成 chunkLoadingGlobal},},};
以 Angular-cli 9
生成的 angular 9
项目为例,其他版本的 angular
后续会逐渐补充。
在 src
目录新增 public-path.js
文件,内容为:
if (window.__POWERED_BY_QIANKUN__) {// eslint-disable-next-line no-undef__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;}
设置 history
模式路由的 base
,src/app/app-routing.module.ts
文件:
+ 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' : '/' }]})
修改入口文件,src/main.ts
文件。
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();}
修改 webpack
打包配置
先安装 @angular-builders/custom-webpack
插件,注意:angular 9
项目只能安装 9.x
版本,angular 10
项目可以安装最新版。
npm i @angular-builders/custom-webpack@9.2.0 -D
在根目录增加 custom-webpack.config.js
,内容为:
const appName = require('./package.json').name;module.exports = {devServer: {headers: {'Access-Control-Allow-Origin': '*',},},output: {library: `${appName}-[name]`,libraryTarget: 'umd',jsonpFunction: `webpackJsonp_${appName}`, // webpack 5 需要把 jsonpFunction 替换成 chunkLoadingGlobal},};
修改 angular.json
,将 [packageName] > architect > build > builder
和 [packageName] > architect > serve > builder
的值改为我们安装的插件,将我们的打包配置文件加入到 [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",
解决 zone.js
的问题
在父应用引入 zone.js
,需要在 import qiankun
之前引入。
将微应用的 src/polyfills.ts
里面的引入 zone.js
代码删掉。
- import 'zone.js/dist/zone';
在微应用的 src/index.html
里面的 <head>
标签加上下面内容,微应用独立访问时使用。
<!-- 也可以使用其他的CDN/本地的包 --><script src="https://unpkg.com/zone.js" ignore></script>
修正 ng build
打包报错问题,修改 tsconfig.json
文件,参考issues/431
- "target": "es2015",+ "target": "es5",+ "typeRoots": [+ "node_modules/@types"+ ],
为了防止主应用或其他微应用也为 angular
时,<app-root></app-root>
会冲突的问题,建议给<app-root>
加上一个唯一的 id,比如说当前应用名称。
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',
当然,也可以选择使用 single-spa-angular
插件,参考 single-spa-angular 的官网 和 angular demo
(补充)angular7 项目除了第 4 步以外,其他的步骤和 angular9 一模一样。angular7 修改 webpack
打包配置的步骤如下:
除了安装 angular-builders/custom-webpack
插件的 7.x 版本外,还需要安装 angular-builders/dev-server
。
npm i @angular-builders/custom-webpack@7 -Dnpm i @angular-builders/dev-server -D
在根目录增加 custom-webpack.config.js
,内容同上。
修改 angular.json
, [packageName] > architect > build > builder
的修改和 angular9 一样, [packageName] > architect > serve > builder
的修改和 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",
一些非 webpack
构建的项目,例如 jQuery
项目、jsp
项目,都可以按照这个处理。
接入之前请确保你的项目里的图片、音视频等资源能正常加载,如果这些资源的地址都是完整路径(例如 https://qiankun.umijs.org/logo.png
),则没问题。如果都是相对路径,需要先将这些资源上传到服务器,使用完整路径。
接入非常简单,只需要额外声明一个 script
,用于 export
相对应的 lifecycles
。例如:
声明 entry 入口
<!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>
在 entry js 里声明 lifecycles
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);
你也可以直接参照 examples 中 purehtml 部分的代码
同时,你也需要开启相关资源的 CORS,具体请参照此处
umi-qiankun
的教程请移步 umi 官网 和 umi-qiankun 的官方 demo