{appLoading === this.appKey ? renderComponent(LoadingComponent, {}) : null}
{React.cloneElement(element, {
key: this.appKey,
diff --git a/packages/icestark/src/apps.ts b/packages/icestark/src/apps.ts
index d39f153c..b61ab435 100644
--- a/packages/icestark/src/apps.ts
+++ b/packages/icestark/src/apps.ts
@@ -1,3 +1,4 @@
+/* eslint-disable max-lines */
import Sandbox, { SandboxConstructor, SandboxProps } from '@ice/sandbox';
import isEmpty from 'lodash.isempty';
import { NOT_LOADED, NOT_MOUNTED, LOADING_ASSETS, UNMOUNTED, LOAD_ERROR, MOUNTED } from './util/constant';
@@ -167,6 +168,8 @@ export function updateAppConfig(appName: string, config) {
}
return microApp;
});
+ // Update global variable of microApps.
+ (window as any).microApps = microApps;
}
/**
@@ -179,7 +182,8 @@ export async function loadAppModule(appConfig: AppConfig) {
let lifecycle: ModuleLifeCycle = {};
onLoadingApp(appConfig);
- const { url, container, entry, entryContent, name, scriptAttributes = [], loadScriptMode, appSandbox } = appConfig;
+
+ const { url, container, entry, entryContent, name, scriptAttributes = [], loadScriptMode, appSandbox, cached } = appConfig;
const appAssets = url ? getUrlAssets(url) : await getEntryAssets({
root: container,
entry,
@@ -189,6 +193,8 @@ export async function loadAppModule(appConfig: AppConfig) {
fetch,
});
+ const cacheId = cached ? name : undefined;
+
updateAppConfig(appConfig.name, { appAssets });
const cacheCss = shouldCacheCss(loadScriptMode);
@@ -201,6 +207,7 @@ export async function loadAppModule(appConfig: AppConfig) {
], {
cacheCss,
fetch,
+ cacheId,
});
lifecycle = await loadScriptByImport(appAssets.jsList);
// Not to handle script element temporarily.
@@ -209,6 +216,7 @@ export async function loadAppModule(appConfig: AppConfig) {
await loadAndAppendCssAssets(appAssets.cssList, {
cacheCss,
fetch,
+ cacheId,
});
lifecycle = await loadScriptByFetch(appAssets.jsList, appSandbox, fetch);
break;
@@ -217,13 +225,14 @@ export async function loadAppModule(appConfig: AppConfig) {
loadAndAppendCssAssets(appAssets.cssList, {
cacheCss,
fetch,
+ cacheId,
}),
- loadAndAppendJsAssets(appAssets, { scriptAttributes }),
+ loadAndAppendJsAssets(appAssets, { scriptAttributes, cacheId }),
]);
lifecycle =
- getLifecyleByLibrary() ||
- getLifecyleByRegister() ||
- {};
+ getLifecyleByLibrary() ||
+ getLifecyleByRegister() ||
+ {};
}
if (isEmpty(lifecycle)) {
@@ -446,14 +455,29 @@ export async function unmountMicroApp(appName: string) {
}
}
-// unload micro app, load app bundles when create micro app
+/**
+ * uninstall micro app thoroughly
+ * @param appName
+ */
export async function unloadMicroApp(appName: string) {
const appConfig = getAppConfig(appName);
if (appConfig) {
+ if (appConfig.cached) {
+ log.warn(
+ formatErrMessage(
+ ErrorCode.CACHED_APP_USE_UNLOAD,
+ isDev && 'Use unmountMicroApp instead of unloadMicroApp to uninstall app {0}. This will not break the whole app but invalidate caching',
+ appName,
+ ),
+ );
+ }
+
unmountMicroApp(appName);
+
delete appConfig.mount;
delete appConfig.unmount;
delete appConfig.appAssets;
+
updateAppConfig(appName, { status: NOT_LOADED });
} else {
log.error(
diff --git a/packages/icestark/src/start.ts b/packages/icestark/src/start.ts
index b45abbc0..e621d981 100644
--- a/packages/icestark/src/start.ts
+++ b/packages/icestark/src/start.ts
@@ -197,5 +197,5 @@ function unload() {
clearMicroApps();
}
-export { unload, globalConfiguration };
+export { unload, globalConfiguration, started };
export default start;
diff --git a/packages/icestark/src/util/appLifeCycle.ts b/packages/icestark/src/util/appLifeCycle.ts
deleted file mode 100644
index f651efb1..00000000
--- a/packages/icestark/src/util/appLifeCycle.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { setCache, getCache } from './cache';
-import { resetCapturedEventListeners } from './capturedListeners';
-
-export enum AppLifeCycleEnum {
- AppEnter = 'appEnter',
- AppLeave = 'appLeave',
-}
-
-export function cacheApp(cacheKey: string) {
- [AppLifeCycleEnum.AppEnter, AppLifeCycleEnum.AppLeave].forEach((lifeCycle) => {
- const lifeCycleCacheKey = `cache_${cacheKey}_${lifeCycle}`;
- if (getCache(lifeCycle)) {
- setCache(lifeCycleCacheKey, getCache(lifeCycle));
- } else if (getCache(lifeCycleCacheKey)) {
- // set cache to current lifeCycle
- setCache(lifeCycle, getCache(lifeCycleCacheKey));
- }
- });
-}
-
-export function deleteCache(cacheKey: string) {
- [AppLifeCycleEnum.AppEnter, AppLifeCycleEnum.AppLeave].forEach((lifeCycle) => {
- setCache(`cache_${cacheKey}_${lifeCycle}`, null);
- });
-}
-
-export function isCached(cacheKey: string) {
- return !!getCache(`cache_${cacheKey}_${AppLifeCycleEnum.AppEnter}`);
-}
-
-export function callAppEnter() {
- const appEnterKey = AppLifeCycleEnum.AppEnter;
- const registerAppEnterCallback = getCache(appEnterKey);
-
- if (registerAppEnterCallback) {
- registerAppEnterCallback();
- setCache(appEnterKey, null);
- }
-}
-
-export function callAppLeave() {
- // resetCapturedEventListeners when app change, remove react-router/vue-router listeners
- resetCapturedEventListeners();
-
- const appLeaveKey = AppLifeCycleEnum.AppLeave;
- const registerAppLeaveCallback = getCache(appLeaveKey);
-
- if (registerAppLeaveCallback) {
- registerAppLeaveCallback();
- setCache(appLeaveKey, null);
- }
-}
diff --git a/packages/icestark/src/util/capturedListeners.ts b/packages/icestark/src/util/capturedListeners.ts
index 47aab6e6..c212b0ce 100644
--- a/packages/icestark/src/util/capturedListeners.ts
+++ b/packages/icestark/src/util/capturedListeners.ts
@@ -84,3 +84,4 @@ export function resetCapturedEventListeners() {
capturedEventListeners[CapturedEventNameEnum.POPSTATE] = [];
capturedEventListeners[CapturedEventNameEnum.HASHCHANGE] = [];
}
+
diff --git a/packages/icestark/src/util/constant.ts b/packages/icestark/src/util/constant.ts
index 27613d0e..13ee6e0f 100644
--- a/packages/icestark/src/util/constant.ts
+++ b/packages/icestark/src/util/constant.ts
@@ -23,3 +23,8 @@ export const NOT_MOUNTED = 'NOT_MOUNTED';
export const MOUNTED = 'MOUNTED';
export const UNMOUNTED = 'UNMOUNTED';
+
+export enum AppLifeCycleEnum {
+ AppEnter = 'appEnter',
+ AppLeave = 'appLeave',
+}
diff --git a/packages/icestark/src/util/error.ts b/packages/icestark/src/util/error.ts
index c8b19c9a..6526d8d9 100644
--- a/packages/icestark/src/util/error.ts
+++ b/packages/icestark/src/util/error.ts
@@ -6,6 +6,7 @@ export enum ErrorCode {
'JS_LOAD_ERROR' = 5,
'CSS_LOAD_ERROR' = 6,
'ACTIVE_PATH_ITEM_CAN_NOT_BE_EMPTY' = 7,
+ 'CACHED_APP_USE_UNLOAD' = 8,
}
export function normalizeMsg(msg: string, args: string[]) {
diff --git a/packages/icestark/src/util/getLifecycle.ts b/packages/icestark/src/util/getLifecycle.ts
index fe578212..bc2b9850 100644
--- a/packages/icestark/src/util/getLifecycle.ts
+++ b/packages/icestark/src/util/getLifecycle.ts
@@ -1,5 +1,5 @@
import { getCache, setCache } from './cache';
-import { AppLifeCycleEnum } from './appLifeCycle';
+import { AppLifeCycleEnum } from './constant';
import type { ModuleLifeCycle } from '../apps';
export function getLifecyleByLibrary() {
diff --git a/packages/icestark/src/util/handleAssets.ts b/packages/icestark/src/util/handleAssets.ts
index 41c9ec59..cc31e92c 100644
--- a/packages/icestark/src/util/handleAssets.ts
+++ b/packages/icestark/src/util/handleAssets.ts
@@ -75,11 +75,16 @@ function isAssetExist(element: HTMLScriptElement | HTMLLinkElement, type: 'scrip
/**
* Create link/style element and append to root
*/
-export function appendCSS(
- root: HTMLElement | ShadowRoot,
- asset: Asset | HTMLElement,
- id: string,
-): Promise
{
+export function appendCSS(asset: Asset | HTMLElement,
+ {
+ root,
+ id,
+ cacheId,
+ }: {
+ root?: HTMLElement | ShadowRoot;
+ id?: string;
+ cacheId?: string;
+ }): Promise {
return new Promise(async (resolve, reject) => {
if (!root) reject(new Error('no root element for css asset'));
@@ -95,6 +100,7 @@ export function appendCSS(
const styleElement: HTMLStyleElement = document.createElement('style');
styleElement.id = id;
styleElement.setAttribute(PREFIX, DYNAMIC);
+ cacheId && styleElement.setAttribute('data-cache', cacheId);
styleElement.innerHTML = content;
root.appendChild(styleElement);
resolve();
@@ -112,6 +118,7 @@ export function appendCSS(
styleElement.innerHTML = await cachedStyleContent[content];
styleElement.id = id;
styleElement.setAttribute(PREFIX, DYNAMIC);
+ cacheId && styleElement.setAttribute('data-cache', cacheId);
root.appendChild(styleElement);
useExternalLink = false;
resolve();
@@ -126,6 +133,7 @@ export function appendCSS(
element.id = id;
element.rel = 'stylesheet';
element.href = content;
+ cacheId && element.setAttribute('data-cache', cacheId);
element.addEventListener(
'error',
@@ -156,19 +164,21 @@ function setAttributeForScriptNode(element: HTMLScriptElement, {
id,
src,
scriptAttributes,
+ cacheId,
}: {
module: boolean;
id: string;
src: string;
scriptAttributes: ScriptAttributes;
+ cacheId?: string;
}) {
/*
* stamped by icestark for recycle when needed.
*/
element.setAttribute(PREFIX, DYNAMIC);
+ cacheId && element.setAttribute('data-cache', cacheId);
element.id = id;
-
element.type = module ? 'module' : 'text/javascript';
element.src = src;
@@ -225,10 +235,12 @@ export function appendExternalScript(asset: string | Asset,
id,
root = document.getElementsByTagName('head')[0],
scriptAttributes = [],
+ cacheId,
}: {
id: string;
root?: HTMLElement | ShadowRoot;
scriptAttributes?: ScriptAttributes;
+ cacheId?: string;
}): Promise {
return new Promise((resolve, reject) => {
const { type, content, module } = (asset as Asset);
@@ -239,6 +251,7 @@ export function appendExternalScript(asset: string | Asset,
element.innerHTML = content;
element.id = id;
element.setAttribute(PREFIX, DYNAMIC);
+ cacheId && element.setAttribute('data-cache', cacheId);
module && (element.type = 'module');
root.appendChild(element);
@@ -254,6 +267,7 @@ export function appendExternalScript(asset: string | Asset,
id,
src: content || (asset as string),
scriptAttributes,
+ cacheId,
});
if (isAssetExist(element, 'script')) {
@@ -653,21 +667,8 @@ export function emptyAssets(
export function checkCacheKey(node: HTMLElement | HTMLLinkElement | HTMLStyleElement | HTMLScriptElement, cacheKey: string|boolean) {
return (typeof cacheKey === 'boolean' && cacheKey)
- || !node.getAttribute('cache')
- || node.getAttribute('cache') === cacheKey;
-}
-
-/**
- * cache all assets loaded by current sub-application
- */
-export function cacheAssets(cacheKey: string): void {
- const assetsList = getAssetsNode();
- assetsList.forEach((assetsNode) => {
- // set cache key if asset attributes without prefix=static and cache
- if (assetsNode.getAttribute(PREFIX) !== STATIC && !assetsNode.getAttribute('cache')) {
- assetsNode.setAttribute('cache', cacheKey);
- }
- });
+ || !node.getAttribute('data-cache')
+ || node.getAttribute('data-cache') === cacheKey;
}
/**
@@ -679,9 +680,11 @@ export function cacheAssets(cacheKey: string): void {
export async function loadAndAppendCssAssets(cssList: Array, {
cacheCss = false,
fetch = defaultFetch,
+ cacheId,
}: {
cacheCss?: boolean;
fetch?: Fetch;
+ cacheId?: string;
}) {
const cssRoot: HTMLElement = document.getElementsByTagName('head')[0];
@@ -704,18 +707,28 @@ export async function loadAndAppendCssAssets(cssList: Array
// And supposed to be remove from 3.x
if (!useLinks) {
return await Promise.all([
- ...cssContents.map((content, index) => appendCSS(
- cssRoot,
- { content, type: AssetTypeEnum.INLINE }, `${PREFIX}-css-${index}`,
- )),
- ...cssList.filter((css) => isElement(css)).map((asset, index) => appendCSS(cssRoot, asset, `${PREFIX}-css-${index}`)),
+ ...cssContents.map((content, index) => appendCSS({ content, type: AssetTypeEnum.INLINE },
+ {
+ root: cssRoot,
+ id: `${PREFIX}-css-${index}`,
+ cacheId,
+ })),
+ ...cssList.filter((css) => isElement(css)).map((asset, index) => appendCSS(asset, {
+ root: cssRoot,
+ id: `${PREFIX}-css-${index}`,
+ cacheId,
+ })),
]);
}
}
// load css content
- return await Promise.all(
- cssList.map((asset, index) => appendCSS(cssRoot, asset, `${PREFIX}-css-${index}`)),
+ await Promise.all(
+ cssList.map((asset, index) => appendCSS(asset, {
+ root: cssRoot,
+ id: `${PREFIX}-css-${index}`,
+ cacheId,
+ })),
);
}
@@ -731,8 +744,10 @@ export function loadAndAppendJsAssets(
assets: Assets,
{
scriptAttributes = [],
+ cacheId,
}: {
scriptAttributes?: ScriptAttributes;
+ cacheId?: string;
},
) {
const jsRoot: HTMLElement = document.getElementsByTagName('head')[0];
@@ -748,6 +763,7 @@ export function loadAndAppendJsAssets(
root: jsRoot,
scriptAttributes,
id: `${PREFIX}-js-${index}`,
+ cacheId,
}));
}, Promise.resolve());
}
@@ -757,6 +773,7 @@ export function loadAndAppendJsAssets(
root: jsRoot,
scriptAttributes,
id: `${PREFIX}-js-${index}`,
+ cacheId,
})),
);
}
diff --git a/packages/sandbox/CHANGELOG.md b/packages/sandbox/CHANGELOG.md
index 0526bbe6..6fc17359 100644
--- a/packages/sandbox/CHANGELOG.md
+++ b/packages/sandbox/CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog
+## 1.1.5
+
+- [fix] exposes `injection` through constructor.
+
## 1.1.4
- [fix] simply copy callable funtions's extra properties.
diff --git a/packages/sandbox/__tests__/index.spec.ts b/packages/sandbox/__tests__/index.spec.ts
index fea1ee99..06d6c6a0 100644
--- a/packages/sandbox/__tests__/index.spec.ts
+++ b/packages/sandbox/__tests__/index.spec.ts
@@ -151,3 +151,31 @@ describe('callable functions in sandbox', () => {
expect(error).toBe(null);
});
});
+
+describe('inject value to sandbox', () => {
+ const testObject = { a: 1 };
+ const testProxy = new Proxy(testObject, {
+ get(target, p, receiver) {
+ if(p === 'a') {
+ return 2;
+ }
+ return Reflect.get(target, p);
+ }
+ })
+ // in order to pass value outof sandbox to validate, set multiMode false
+ const sandbox = new Sandbox({multiMode: false, injection: {testObject: testProxy}});
+ test('inject proxy object in sandbox', () => {
+ // @ts-ignore
+ window.testObject = testObject;
+ // @ts-ignore
+ window.result = undefined;
+ sandbox.execScriptInSandbox(
+ `
+ window.result = window.testObject.a;
+ `,
+ );
+
+ // @ts-ignore
+ expect(window.result).toBe(2);
+ })
+})
diff --git a/packages/sandbox/package.json b/packages/sandbox/package.json
index 9433e009..2fcf8084 100644
--- a/packages/sandbox/package.json
+++ b/packages/sandbox/package.json
@@ -1,6 +1,6 @@
{
"name": "@ice/sandbox",
- "version": "1.1.4",
+ "version": "1.1.5",
"description": "sandbox for execute scripts",
"main": "lib/index.js",
"scripts": {
diff --git a/packages/sandbox/src/index.ts b/packages/sandbox/src/index.ts
index b91cf98e..34a5fe88 100644
--- a/packages/sandbox/src/index.ts
+++ b/packages/sandbox/src/index.ts
@@ -1,5 +1,6 @@
export interface SandboxProps {
multiMode?: boolean;
+ injection?: Record;
}
export interface SandboxConstructor {
@@ -33,6 +34,8 @@ export default class Sandbox {
private multiMode = false;
+ private injection = {};
+
private eventListeners = {};
private timeoutIds: number[] = [];
@@ -46,7 +49,7 @@ export default class Sandbox {
public sandboxDisabled: boolean;
constructor(props: SandboxProps = {}) {
- const { multiMode } = props;
+ const { multiMode, injection } = props;
if (!window.Proxy) {
console.warn('proxy sandbox is not support by current browser');
this.sandboxDisabled = true;
@@ -54,8 +57,13 @@ export default class Sandbox {
// enable multiMode in case of create mulit sandbox in same time
this.multiMode = multiMode;
this.sandbox = null;
+ this.injection = injection;
}
+ /**
+ * create proxy sandbox
+ * @param injection @deprecated will be deprecated in the future
+ */
createProxySandbox(injection?: object) {
const { propertyAdded, originalValues, multiMode } = this;
const proxyWindow = Object.create(null) as Window;
@@ -65,6 +73,9 @@ export default class Sandbox {
const originalSetInterval = window.setInterval;
const originalSetTimeout = window.setTimeout;
+ // `this` in Proxy traps will retarget to the trap.
+ const _self = this;
+
// hijack addEventListener
proxyWindow.addEventListener = (eventName, fn, ...rest) => {
this.eventListeners[eventName] = (this.eventListeners[eventName] || []);
@@ -135,7 +146,8 @@ export default class Sandbox {
}
// search from injection
- const injectionValue = injection && injection[p];
+ const injectionValue = _self.injection?.[p] ?? injection?.[p];
+
if (injectionValue) {
return injectionValue;
}
@@ -159,6 +171,7 @@ export default class Sandbox {
// Axios, Moment, and other callable functions may have additional properties.
// Simply copy them into boundValue.
+ // eslint-disable-next-line guard-for-in
for (const key in value) {
boundValue[key] = value[key];
}