⚡️ cache for loadMicroApp (#986)
This commit is contained in:
parent
f127251884
commit
efe5d2bf87
|
|
@ -14,4 +14,9 @@ module.exports = {
|
||||||
'no-underscore-dangle': 0,
|
'no-underscore-dangle': 0,
|
||||||
'no-plusplus': 0,
|
'no-plusplus': 0,
|
||||||
},
|
},
|
||||||
|
parserOptions: {
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
project: './tsconfig.json',
|
||||||
|
createDefaultProgram: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,15 @@ const app1 = loadMicroApp(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// for cached scenario
|
||||||
|
setTimeout(() => {
|
||||||
|
app1.unmount();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
loadMicroApp({ name: 'react15', entry: '//localhost:7102', container: '#react15' });
|
||||||
|
}, 1000 * 5);
|
||||||
|
}, 1000 * 5);
|
||||||
|
|
||||||
const app2 = loadMicroApp(
|
const app2 = loadMicroApp(
|
||||||
{ name: 'vue', entry: '//localhost:7101', container: '#vue' },
|
{ name: 'vue', entry: '//localhost:7101', container: '#vue' },
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,11 @@
|
||||||
import { getWrapperId, getDefaultTplWrapper, validateExportLifecycle, sleep, Deferred } from '../utils';
|
import {
|
||||||
|
Deferred,
|
||||||
|
getDefaultTplWrapper,
|
||||||
|
getWrapperId,
|
||||||
|
getXPathForElement,
|
||||||
|
sleep,
|
||||||
|
validateExportLifecycle,
|
||||||
|
} from '../utils';
|
||||||
|
|
||||||
test('should wrap the id [1]', () => {
|
test('should wrap the id [1]', () => {
|
||||||
const id = 'REACT16';
|
const id = 'REACT16';
|
||||||
|
|
@ -86,3 +93,22 @@ test('Deferred should worked [2]', async () => {
|
||||||
|
|
||||||
expect(err).toBeInstanceOf(Error);
|
expect(err).toBeInstanceOf(Error);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should getXPathForElement work well', () => {
|
||||||
|
const article = document.createElement('article');
|
||||||
|
article.innerHTML = `
|
||||||
|
<div>
|
||||||
|
<div></div>
|
||||||
|
<div id="testNode"></div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(article);
|
||||||
|
const testNode = document.querySelector('#testNode');
|
||||||
|
const xpath = getXPathForElement(testNode!, document);
|
||||||
|
expect(xpath).toEqual(
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
`/*[name()='HTML' and namespace-uri()='http://www.w3.org/1999/xhtml']/*[name()='BODY' and namespace-uri()='http://www.w3.org/1999/xhtml'][1]/*[name()='ARTICLE' and namespace-uri()='http://www.w3.org/1999/xhtml'][1]/*[name()='DIV' and namespace-uri()='http://www.w3.org/1999/xhtml'][1]/*[name()='DIV' and namespace-uri()='http://www.w3.org/1999/xhtml'][2]`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
||||||
53
src/apis.ts
53
src/apis.ts
|
|
@ -1,9 +1,9 @@
|
||||||
import { noop } from 'lodash';
|
import { noop } from 'lodash';
|
||||||
import { mountRootParcel, registerApplication, start as startSingleSpa } from 'single-spa';
|
import { mountRootParcel, ParcelConfigObject, registerApplication, start as startSingleSpa } from 'single-spa';
|
||||||
import { FrameworkConfiguration, FrameworkLifeCycles, LoadableApp, MicroApp, RegistrableApp } from './interfaces';
|
import { FrameworkConfiguration, FrameworkLifeCycles, LoadableApp, MicroApp, RegistrableApp } from './interfaces';
|
||||||
import { loadApp } from './loader';
|
import { loadApp } from './loader';
|
||||||
import { doPrefetchStrategy } from './prefetch';
|
import { doPrefetchStrategy } from './prefetch';
|
||||||
import { Deferred, toArray } from './utils';
|
import { Deferred, getXPathForElement, toArray } from './utils';
|
||||||
|
|
||||||
let microApps: RegistrableApp[] = [];
|
let microApps: RegistrableApp[] = [];
|
||||||
|
|
||||||
|
|
@ -46,16 +46,55 @@ export function registerMicroApps<T extends object = {}>(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const appConfigMap = new Map<string, Promise<ParcelConfigObject>>();
|
||||||
|
|
||||||
export function loadMicroApp<T extends object = {}>(
|
export function loadMicroApp<T extends object = {}>(
|
||||||
app: LoadableApp<T>,
|
app: LoadableApp<T>,
|
||||||
configuration?: FrameworkConfiguration,
|
configuration?: FrameworkConfiguration,
|
||||||
lifeCycles?: FrameworkLifeCycles<T>,
|
lifeCycles?: FrameworkLifeCycles<T>,
|
||||||
): MicroApp {
|
): MicroApp {
|
||||||
const { props } = app;
|
const { props, name } = app;
|
||||||
return mountRootParcel(() => loadApp(app, configuration ?? frameworkConfiguration, lifeCycles), {
|
|
||||||
domElement: document.createElement('div'),
|
const getContainerXpath = (container: string | HTMLElement): string | void => {
|
||||||
...props,
|
const containerElement = typeof container === 'string' ? document.querySelector(container) : container;
|
||||||
});
|
if (containerElement) {
|
||||||
|
return getXPathForElement(containerElement, document);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* using name + container xpath as the micro app instance id,
|
||||||
|
* it means if you rendering a micro app to a dom which have been rendered before,
|
||||||
|
* the micro app would not load and evaluate its lifecycles again
|
||||||
|
*/
|
||||||
|
const memorizedLoadingFn = async (): Promise<ParcelConfigObject> => {
|
||||||
|
const container = 'container' in app ? app.container : undefined;
|
||||||
|
if (container) {
|
||||||
|
const xpath = getContainerXpath(container);
|
||||||
|
if (xpath) {
|
||||||
|
const parcelConfig = appConfigMap.get(`${name}-${xpath}`);
|
||||||
|
if (parcelConfig) return parcelConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parcelConfig = loadApp(app, configuration ?? frameworkConfiguration, lifeCycles);
|
||||||
|
|
||||||
|
if (container) {
|
||||||
|
const xpath = getContainerXpath(container);
|
||||||
|
if (xpath)
|
||||||
|
appConfigMap.set(
|
||||||
|
`${name}-${xpath}`,
|
||||||
|
// empty bootstrap hook which should not run twice while it calling from cached micro app
|
||||||
|
parcelConfig.then(config => ({ ...config, bootstrap: () => Promise.resolve() })),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parcelConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
return mountRootParcel(memorizedLoadingFn, { domElement: document.createElement('div'), ...props });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function start(opts: FrameworkConfiguration = {}) {
|
export function start(opts: FrameworkConfiguration = {}) {
|
||||||
|
|
|
||||||
37
src/utils.ts
37
src/utils.ts
|
|
@ -126,3 +126,40 @@ export function isEnableScopedCSS(opt: FrameworkConfiguration) {
|
||||||
|
|
||||||
return !!opt.sandbox.experimentalStyleIsolation;
|
return !!opt.sandbox.experimentalStyleIsolation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* copy from https://developer.mozilla.org/zh-CN/docs/Using_XPath
|
||||||
|
* @param el
|
||||||
|
* @param xml
|
||||||
|
*/
|
||||||
|
export function getXPathForElement(el: Node, xml: Document) {
|
||||||
|
let xpath = '';
|
||||||
|
let pos;
|
||||||
|
let tmpEle;
|
||||||
|
let element = el;
|
||||||
|
|
||||||
|
while (element !== xml.documentElement) {
|
||||||
|
pos = 0;
|
||||||
|
tmpEle = element;
|
||||||
|
while (tmpEle) {
|
||||||
|
if (tmpEle.nodeType === 1 && tmpEle.nodeName === element.nodeName) {
|
||||||
|
// If it is ELEMENT_NODE of the same name
|
||||||
|
pos += 1;
|
||||||
|
}
|
||||||
|
tmpEle = tmpEle.previousSibling;
|
||||||
|
}
|
||||||
|
|
||||||
|
xpath = `*[name()='${element.nodeName}' and namespace-uri()='${
|
||||||
|
element.namespaceURI === null ? '' : element.namespaceURI
|
||||||
|
}'][${pos}]/${xpath}`;
|
||||||
|
|
||||||
|
element = element.parentNode!;
|
||||||
|
}
|
||||||
|
|
||||||
|
xpath = `/*[name()='${xml.documentElement.nodeName}' and namespace-uri()='${
|
||||||
|
element.namespaceURI === null ? '' : element.namespaceURI
|
||||||
|
}']/${xpath}`;
|
||||||
|
xpath = xpath.replace(/\/$/, '');
|
||||||
|
|
||||||
|
return xpath;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user