⚡️ cache for loadMicroApp (#986)
This commit is contained in:
parent
f127251884
commit
efe5d2bf87
|
|
@ -14,4 +14,9 @@ module.exports = {
|
|||
'no-underscore-dangle': 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(
|
||||
{ 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]', () => {
|
||||
const id = 'REACT16';
|
||||
|
|
@ -86,3 +93,22 @@ test('Deferred should worked [2]', async () => {
|
|||
|
||||
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 { 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 { loadApp } from './loader';
|
||||
import { doPrefetchStrategy } from './prefetch';
|
||||
import { Deferred, toArray } from './utils';
|
||||
import { Deferred, getXPathForElement, toArray } from './utils';
|
||||
|
||||
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 = {}>(
|
||||
app: LoadableApp<T>,
|
||||
configuration?: FrameworkConfiguration,
|
||||
lifeCycles?: FrameworkLifeCycles<T>,
|
||||
): MicroApp {
|
||||
const { props } = app;
|
||||
return mountRootParcel(() => loadApp(app, configuration ?? frameworkConfiguration, lifeCycles), {
|
||||
domElement: document.createElement('div'),
|
||||
...props,
|
||||
});
|
||||
const { props, name } = app;
|
||||
|
||||
const getContainerXpath = (container: string | HTMLElement): string | void => {
|
||||
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 = {}) {
|
||||
|
|
|
|||
37
src/utils.ts
37
src/utils.ts
|
|
@ -126,3 +126,40 @@ export function isEnableScopedCSS(opt: FrameworkConfiguration) {
|
|||
|
||||
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