主要研究对象qiankun,探究内部原理css
$ yarn add qiankun # 或者 npm i qiankun -S
复制代码
registerMicroApps([
{
name: 'reactApp',
entry: '//localhost:3000',
container: '#container',
activeRule: '/views/app-react',
},
{
name: 'vueApp',
entry: '//localhost:8080',
container: '#container',
activeRule: '/views/app-vue',
},
{
name: 'reactApp2',
entry: 'http://localhost:9100/react-app2/template/index.html',
container: '#container',
activeRule: '/views/app-react2',
}
]);
// 启动 qiankun
start();
复制代码
当微应用信息注册完以后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,全部 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。若是子应用还存在子路由,则须要配置子应用路由的basenamehtml
微应用须要在本身的入口 js (一般就是 webpack 的 entry js) 导出 bootstrap、mount、unmount 三个生命周期钩子,以供主应用在适当的时机调用前端
/** * bootstrap 只会在微应用初始化的时候调用一次,下次微应用从新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。 * 一般咱们能够在这里作一些全局变量的初始化,好比不会在 unmount 阶段被销毁的应用级别的缓存等。 */
export async function bootstrap() {
console.log('react app bootstraped');
}
/** * 应用每次进入都会调用 mount 方法,一般咱们在这里触发应用的渲染方法 */
export async function mount(props) {
ReactDOM.render(<App />, props.container ? props.container.querySelector('#root') : document.getElementById('root'));
}
/** * 应用每次 切出/卸载 会调用的方法,一般在这里咱们会卸载微应用的应用实例 */
export async function unmount(props) {
ReactDOM.unmountComponentAtNode(props.container ? props.container.querySelector('#root') : document.getElementById('root'));
}
/** * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效 */
export async function update(props) {
console.log('update props', props);
}
复制代码
public-path.js
文件,并在入口文件最顶部引用,用于修改运行时的 publicPathconst packageName = require('./package.json').name;
module.exports = {
output: {
library: `${packageName}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
},
};
复制代码
关于
public-path
的描述能够参见此文档:webpack.docschina.org/guides/publ…vue关于
library
的描述能够参见此文档:webpack.docschina.org/guides/auth…react
single-spa主要功能是实现了路由劫持与应用加载,但未实现js、css隔离webpack
在研究qiankun的原理以前,咱们应该试着提出一些问题,而后在阅读源码的过程当中去寻找答案。对于qiankun,咱们比较关心的问题有:git
qiankun
与single-spa
是什么关系?qiankun
如何实现js沙箱?qiankun
如何实现css隔离?配置微应用的第一步就是在主应用中注册子应用,配置好子应用的名称、入口、dom容器以及唤醒条件,以后qiankun
会按照唤醒条件将子应用挂载到dom容器上。github
Array<RegistrableApp>
- 必选,微应用的一些注册信息LifeCycles
- 可选,全局的微应用生命周期钩子string
- 必选,微应用的名称,微应用之间必须确保惟一string | { scripts?: string[]; styles?: string[]; html?: string }
- 必选,微应用的入口string | HTMLElement
- 必选,微应用的容器节点的选择器或者 Element
实例。如container: '#root'
或 container: document.querySelector('#root')
string | (location: Location) => boolean | Array<string | (location: Location) => boolean>
- 必选,微应用的激活规则(loading: boolean) => void
- 可选,loading
状态发生变化时会调用的方法object
- 可选,主应用须要传递给微应用的数据Lifecycle | Array<Lifecycle>
- 可选Lifecycle | Array<Lifecycle>
- 可选Lifecycle | Array<Lifecycle>
- 可选Lifecycle | Array<Lifecycle>
- 可选Lifecycle | Array<Lifecycle>
- 可选import { registerMicroApps } from 'qiankun';
registerMicroApps([{
name: 'app1',
entry: '//localhost:8080',
container: '#container',
activeRule: '/react',
props: {
name: 'kuitos'
}
}], {
beforeLoad: app => console.log('before load', app.name),
beforeMount: [
app => console.log('before mount', app.name)
]
});
复制代码
single-spa
的registerApplication
方法,此处主要工做在registerApplication
的app
参数中处理:registerApplication({
name,
app: async () => {
loader(true);
await frameworkStartedDefer.promise;
const { mount, ...otherMicroAppConfigs } = (
await loadApp({ name, props, ...appConfig }, frameworkConfiguration, lifeCycles)
)();
return {
mount: [async () => loader(true), ...toArray(mount), async () => loader(false)],
...otherMicroAppConfigs,
};
},
activeWhen: activeRule,
customProps: props,
});
复制代码
其中loadApp
是核心,从新包装了single-spa
所需applicat
的mount
、unmount
方法,在这两个生命周期中分别加入了一些额外的操做。如在mount
的时候,会执行beforeMount
、afterMount
回调,以及mountSandbox
开启沙箱。在unmount
的时候,会执行beforeUnmount
、afterUnmount
回调,以及unmountSandbox
释放沙箱。web
qiankun是如何实现沙箱的?npm
经过调用createSandboxContainer
生成一个沙箱,若是当前浏览器环境支持Proxy
则采用Proxy
,反之则采用快照方式。对于Proxy
模式来讲,首先调用createFakeWindow(rawWindow)
生成一个原始window
的复制版fakeWindow
执行用户调用registerApplication
时传入的app
方法,加载应用
import-html-entry
模块的importEntry
函数,获取到对应子应用的html文件、可执行脚本文件以及publicpathgetDefaultTplWrapper
将子应用的html内容用div
标签包裹起来createElement
函数生成剔除html、body、head标签后的子应用html内容(经过innerHTML
达到过滤效果)getRender
函数获得render函数getAppWrapperGetter
函数,生成一个能够获取处理过的子应用dom元素的函数initialAppWrapperGetter
,以备后续使用子应用dom元素sandbox
为true,则调用createSandboxContainer
函数execScripts
函数,执行子应用脚本getMicroAppStateActions
函数,获取onGlobalStateChange
、setGlobalState
、offGlobalStateChange
,用于主子应用传递信息parcelConfigGetter
函数,包装mount
和unmount
{name,loadApp,activeWhen,customProps}
appName
是否重名,重名则抛出异常application
配置推入到全局数组apps
中保存jQuery
的前提早下window.jQuery
可以使用reroute
函数,并进行下列操做:
apps
数组,获取到NOT_MOUNTED
、MOUNTED
、LOADING_SOURCE_CODE
的application
start
方法,若是已经执行了(例如在页面上切换路由),则执行performAppChanges
函数,反之执行loadApps
函数(首次刷新页面时)利用Promise.resolve().then()
将函数内的所有代码异步执行
window.dispatchEvent
和CustomEvent
触发single-spa:before-no-app-change
或single-spa:before-app-change
以及single-spa:before-routing-event
事件(这两个接口IE不支持)app
的mount
和unmount
操做(具体操做有待进一步研究)启动 qiankun,能够传入一些资源获取方式以及是否采用沙箱等配置
single-spa
的start方法,执行reroute
或setUrlRerouteOnly
函数