Skip to content

Commit

Permalink
⚡️ cache for loadMicroApp (#986)
Browse files Browse the repository at this point in the history
  • Loading branch information
kuitos authored Oct 12, 2020
1 parent f127251 commit efe5d2b
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,9 @@ module.exports = {
'no-underscore-dangle': 0,
'no-plusplus': 0,
},
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json',
createDefaultProgram: true,
},
};
9 changes: 9 additions & 0 deletions examples/main/multiple.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
{
Expand Down
28 changes: 27 additions & 1 deletion src/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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 changes: 46 additions & 7 deletions src/apis.ts
Original file line number Diff line number Diff line change
@@ -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[] = [];

Expand Down Expand Up @@ -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 = {}) {
Expand Down
37 changes: 37 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

0 comments on commit efe5d2b

Please sign in to comment.