diff --git a/dev/main-react16/src/pages/react16/react16.js b/dev/main-react16/src/pages/react16/react16.js
index dd788aeb4..4f6cd20a9 100644
--- a/dev/main-react16/src/pages/react16/react16.js
+++ b/dev/main-react16/src/pages/react16/react16.js
@@ -180,7 +180,7 @@ export default class App extends React.Component {
onAfterhidden={this.handleAfterhidden}
onDataChange={this.handleDataChange}
baseRoute='/micro-app/demo/react16'
- keep-alive
+ // keep-alive
// destroy
// inline
// disableSandbox
diff --git a/package.json b/package.json
index 4721eafe8..bf449dea7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@micro-zoe/micro-app",
- "version": "0.6.2",
+ "version": "0.7.0",
"description": "A lightweight, efficient and powerful micro front-end framework",
"private": false,
"main": "lib/index.min.js",
diff --git a/src/__tests__/create_app.test.ts b/src/__tests__/create_app.test.ts
index face963eb..310542581 100644
--- a/src/__tests__/create_app.test.ts
+++ b/src/__tests__/create_app.test.ts
@@ -2,7 +2,7 @@
import { commonStartEffect, releaseAllEffect, ports } from './common/initial'
import { appInstanceMap } from '../create_app'
import { appStates, keepAliveStates } from '../constants'
-import microApp, { unmountApp, unmountAllApps } from '..'
+import microApp, { unmountApp, unmountAllApps, getActiveApps } from '..'
describe('create_app', () => {
let appCon: Element
@@ -457,4 +457,10 @@ describe('create_app', () => {
})
})
})
+
+ // 测试getActiveApps方法
+ test('test getActiveApps method', async () => {
+ // 因为上面已经执行unmountAllApps卸载了所有应用,所以这里长度为0
+ expect(getActiveApps(true).length).toBe(0)
+ })
})
diff --git a/src/__tests__/demos/common/defer.js b/src/__tests__/demos/common/defer.js
index e69de29bb..3684963e5 100644
--- a/src/__tests__/demos/common/defer.js
+++ b/src/__tests__/demos/common/defer.js
@@ -0,0 +1 @@
+console.error('my defer')
diff --git a/src/__tests__/demos/dynamic/errorjs.html b/src/__tests__/demos/dynamic/errorjs.html
new file mode 100644
index 000000000..603e0ccb9
--- /dev/null
+++ b/src/__tests__/demos/dynamic/errorjs.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ dynamic
+
+
+
+
+
+
diff --git a/src/__tests__/demos/special-html/notexistdefer.html b/src/__tests__/demos/special-html/notexistdefer.html
new file mode 100644
index 000000000..40e4e9e34
--- /dev/null
+++ b/src/__tests__/demos/special-html/notexistdefer.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ Document
+
+
+
+
+
+
+
diff --git a/src/__tests__/demos/special-html/onlydefer.html b/src/__tests__/demos/special-html/onlydefer.html
new file mode 100644
index 000000000..75252fb00
--- /dev/null
+++ b/src/__tests__/demos/special-html/onlydefer.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Document
+
+
+
+
+
+
diff --git a/src/__tests__/sandbox/index.test.ts b/src/__tests__/sandbox/index.test.ts
index 7227ae473..0aade2880 100644
--- a/src/__tests__/sandbox/index.test.ts
+++ b/src/__tests__/sandbox/index.test.ts
@@ -125,6 +125,13 @@ describe('sandbox', () => {
}
})
+ Object.defineProperty(window, 'qqqqqqqqqq', {
+ value: 11,
+ configurable: false,
+ enumerable: true,
+ writable: true,
+ })
+
// proxyWindow 无法继承原window上被隔离的变量
expect(proxyWindow.scopeProperty1).toBeUndefined()
expect(proxyWindow.scopeProperty2).toBeUndefined()
@@ -136,7 +143,8 @@ describe('sandbox', () => {
const proxyScopedPro1Desc = Object.getOwnPropertyDescriptor(proxyWindow, 'scopeProperty1')
expect(proxyScopedPro1Desc?.configurable).toBeTruthy()
- proxyWindow.notScopedProperty = 'not-ScopedProperty'
+ proxyWindow.notScopedProperty = 'not-ScopedProperty-value'
+ proxyWindow.qqqqqqqqqq = 11
const proxyNotScopedProDesc = Object.getOwnPropertyDescriptor(proxyWindow, 'notScopedProperty')
expect(proxyNotScopedProDesc?.configurable).toBeFalsy()
})
@@ -228,6 +236,7 @@ describe('sandbox', () => {
value: 'value3',
configurable: true,
writable: true,
+ enumerable: true,
},
})
@@ -342,8 +351,8 @@ describe('sandbox', () => {
proxyWindow['descriptor-key2'] = 'new-descriptor-key2'
expect(proxyWindow['descriptor-key1']).toBe('new-descriptor-key1')
- // writable为false,赋值无效
- expect(proxyWindow['descriptor-key2']).toBe('descriptor-key2')
+ // 原生window有相同值,且writable为false,vlaue依然可以定义到proxyWindow上
+ expect(proxyWindow['descriptor-key2']).toBe('new-descriptor-key2')
})
// 将原window上的_babelPolyfill设置为false
@@ -368,4 +377,47 @@ describe('sandbox', () => {
expect(proxyWindow.boundFunction.name).toBe('bound willbind')
expect(proxyWindow.boundFunction).toBe(boundFunction)
})
+
+ // 测试重写eval、Image方法
+ test('test set eval & Image', () => {
+ const sandbox = new Sandbox('test-set-eval&Image', `http://127.0.0.1:${ports.sandbox}/common/`)
+ sandbox.start('')
+ const proxyWindow: any = sandbox.proxyWindow
+
+ proxyWindow.eval = 'neweval'
+ expect(proxyWindow.eval).toBe('neweval')
+
+ proxyWindow.Image = 'newimage'
+ expect(proxyWindow.Image).toBe('newimage')
+ })
+
+ // 分支覆盖 proxyWindow getter方法
+ test('coverage: proxyWindow getter', () => {
+ const sandbox = new Sandbox('test-coverage-proxy-getter', `http://127.0.0.1:${ports.sandbox}/common/`)
+ sandbox.start('')
+ const proxyWindow: any = sandbox.proxyWindow
+
+ Object.defineProperties(window, {
+ 'key1-for-proxy-getter': {
+ get () {
+ return 'key1-proxy-getter-value'
+ },
+ set () {},
+ configurable: true,
+ enumerable: true,
+ },
+ })
+
+ proxyWindow['key1-for-proxy-getter'] = 'set value to proxyWindow'
+ expect(Object.getOwnPropertyDescriptor(proxyWindow, 'key1-for-proxy-getter')?.writable).toBeTruthy()
+ })
+
+ // 分支覆盖: 在createDescriptorFormicroAppWindow 获取descriptor为空
+ test('coverage: empty descriptor in createDescriptorFormicroAppWindow ', () => {
+ // @ts-ignore
+ delete window.parent
+ const sandbox = new Sandbox('empty-descriptor-createDescriptorFormicroAppWindow', `http://127.0.0.1:${ports.sandbox}/common/`)
+ sandbox.start('')
+ expect(Object.getOwnPropertyDescriptor(window, 'parent')).toBeUndefined()
+ })
})
diff --git a/src/__tests__/source/scripts.test.ts b/src/__tests__/source/scripts.test.ts
index 368c711cc..30d1c7006 100644
--- a/src/__tests__/source/scripts.test.ts
+++ b/src/__tests__/source/scripts.test.ts
@@ -10,12 +10,18 @@ describe('source scripts', () => {
commonStartEffect(ports.source_scripts)
microApp.start({
plugins: {
- global: [{
- loader (code, _url, _options) {
- // console.log('全局插件', _url)
- return code
- }
- }],
+ global: [
+ {
+ loader (code, _url, _options) {
+ // console.log('全局插件', _url)
+ return code
+ }
+ },
+ {
+ loader: 'invalid loader' as any,
+ },
+ 'invalid plugin' as any,
+ ],
modules: {
'test-app1': [
{
@@ -190,7 +196,7 @@ describe('source scripts', () => {
await new Promise((reslove) => {
microappElement9.addEventListener('mounted', () => {
setTimeout(() => {
- expect(console.error).toHaveBeenLastCalledWith('[micro-app] app test-app9:', expect.any(Error))
+ expect(console.error).toHaveBeenLastCalledWith('[micro-app] app test-app9:', expect.any(Object))
reslove(true)
}, 100)
}, false)
@@ -302,4 +308,34 @@ describe('source scripts', () => {
}, false)
})
})
+
+ // 初始化渲染时,某个js文件报错
+ test('an error occurs at the first rendering', async () => {
+ const microappElement15 = document.createElement('micro-app')
+ microappElement15.setAttribute('name', 'test-app15')
+ microappElement15.setAttribute('url', `http://127.0.0.1:${ports.source_scripts}/dynamic/errorjs.html`)
+
+ appCon.appendChild(microappElement15)
+ await new Promise((reslove) => {
+ microappElement15.addEventListener('mounted', () => {
+ expect(console.error).toHaveBeenLastCalledWith('[micro-app from runScript] app test-app15: ', expect.any(Error))
+ reslove(true)
+ }, false)
+ })
+ })
+
+ // 分支覆盖:初始化获取 defer js 文件失败
+ test('coverage: failed to get defer js file', async () => {
+ const microappElement16 = document.createElement('micro-app')
+ microappElement16.setAttribute('name', 'test-app16')
+ microappElement16.setAttribute('url', `http://127.0.0.1:${ports.source_scripts}/special-html/notexistdefer.html`)
+
+ appCon.appendChild(microappElement16)
+ await new Promise((reslove) => {
+ microappElement16.addEventListener('mounted', () => {
+ expect(console.error).toHaveBeenLastCalledWith('[micro-app] app test-app16:', expect.any(Object))
+ reslove(true)
+ }, false)
+ })
+ })
})
diff --git a/src/libs/utils.ts b/src/libs/utils.ts
index c248c0725..c0970ee53 100644
--- a/src/libs/utils.ts
+++ b/src/libs/utils.ts
@@ -68,6 +68,7 @@ export function isShadowRoot (target: unknown): boolean {
export const rawDefineProperty = Object.defineProperty
export const rawDefineProperties = Object.defineProperties
+export const rawHasOwnProperty = Object.prototype.hasOwnProperty
/**
* format error log
diff --git a/src/sandbox/index.ts b/src/sandbox/index.ts
index 9c97c2a99..ad9a09d52 100644
--- a/src/sandbox/index.ts
+++ b/src/sandbox/index.ts
@@ -14,6 +14,7 @@ import {
rawDefineProperty,
rawDefineProperties,
isFunction,
+ rawHasOwnProperty,
} from '../libs/utils'
import microApp from '../micro_app'
import bindFunctionToRawWindow from './bind_function'
@@ -74,12 +75,12 @@ export default class SandBox implements SandBoxInterface {
constructor (appName: string, url: string) {
// get scopeProperties and escapeProperties from plugins
this.getScopeProperties(appName)
- // Rewrite global event listener & timeout
- Object.assign(this, effect(this.microAppWindow))
// create proxyWindow with Proxy(microAppWindow)
this.proxyWindow = this.createProxyWindow()
// inject global properties
this.initMicroAppWindow(this.microAppWindow, appName, url)
+ // Rewrite global event listener & timeout
+ Object.assign(this, effect(this.microAppWindow))
}
start (baseroute: string): void {
@@ -175,6 +176,7 @@ export default class SandBox implements SandBoxInterface {
// create proxyWindow with Proxy(microAppWindow)
private createProxyWindow () {
const rawWindow = globalEnv.rawWindow
+ const descriptorTargetMap = new Map()
// window.xxx will trigger proxy
return new Proxy(this.microAppWindow, {
get: (target: microAppWindowType, key: PropertyKey): unknown => {
@@ -193,8 +195,9 @@ export default class SandBox implements SandBoxInterface {
if (escapeSetterKeyList.includes(key)) {
Reflect.set(rawWindow, key, value)
} else if (
- !target.hasOwnProperty(key) &&
- rawWindow.hasOwnProperty(key) &&
+ // target.hasOwnProperty has been rewritten
+ !rawHasOwnProperty.call(target, key) &&
+ rawHasOwnProperty.call(rawWindow, key) &&
!this.scopeProperties.includes(key)
) {
const descriptor = Object.getOwnPropertyDescriptor(rawWindow, key)
@@ -233,23 +236,36 @@ export default class SandBox implements SandBoxInterface {
},
// Object.getOwnPropertyDescriptor(window, key)
getOwnPropertyDescriptor: (target: microAppWindowType, key: PropertyKey): PropertyDescriptor|undefined => {
- if (target.hasOwnProperty(key)) {
+ if (rawHasOwnProperty.call(target, key)) {
+ descriptorTargetMap.set(key, 'target')
return Object.getOwnPropertyDescriptor(target, key)
}
- if (rawWindow.hasOwnProperty(key)) {
- // console, alert ...
- return Object.getOwnPropertyDescriptor(rawWindow, key)
+ if (rawHasOwnProperty.call(rawWindow, key)) {
+ descriptorTargetMap.set(key, 'rawWindow')
+ const descriptor = Object.getOwnPropertyDescriptor(rawWindow, key)
+ if (descriptor && !descriptor.configurable) {
+ descriptor.configurable = true
+ }
+ return descriptor
}
return undefined
},
+ // Object.defineProperty(window, key, Descriptor)
+ defineProperty: (target: microAppWindowType, key: PropertyKey, value: PropertyDescriptor): boolean => {
+ const from = descriptorTargetMap.get(key)
+ if (from === 'rawWindow') {
+ return Reflect.defineProperty(rawWindow, key, value)
+ }
+ return Reflect.defineProperty(target, key, value)
+ },
// Object.getOwnPropertyNames(window)
ownKeys: (target: microAppWindowType): Array => {
return unique(Reflect.ownKeys(rawWindow).concat(Reflect.ownKeys(target)))
},
deleteProperty: (target: microAppWindowType, key: PropertyKey): boolean => {
- if (target.hasOwnProperty(key)) {
+ if (rawHasOwnProperty.call(target, key)) {
this.injectedKeys.has(key) && this.injectedKeys.delete(key)
this.escapeKeys.has(key) && Reflect.deleteProperty(rawWindow, key)
return Reflect.deleteProperty(target, key)
@@ -274,7 +290,7 @@ export default class SandBox implements SandBoxInterface {
microAppWindow.rawWindow = globalEnv.rawWindow
microAppWindow.rawDocument = globalEnv.rawDocument
microAppWindow.removeDomScope = removeDomScope
- microAppWindow.hasOwnProperty = (key: PropertyKey) => Object.prototype.hasOwnProperty.call(microAppWindow, key) || Object.prototype.hasOwnProperty.call(globalEnv.rawWindow, key)
+ microAppWindow.hasOwnProperty = (key: PropertyKey) => rawHasOwnProperty.call(microAppWindow, key) || rawHasOwnProperty.call(globalEnv.rawWindow, key)
this.setMappingPropertiesWithRawDescriptor(microAppWindow)
this.setHijackProperties(microAppWindow, appName)
}
diff --git a/src/source/scripts.ts b/src/source/scripts.ts
index d5534bb17..0cd1f3e1b 100644
--- a/src/source/scripts.ts
+++ b/src/source/scripts.ts
@@ -217,18 +217,6 @@ export function execScripts (
initedHook.errorCount === deferScriptPromise.length
)
})
- // Promise.all(deferScriptPromise).then((res: string[]) => {
- // res.forEach((code, index) => {
- // const [url, info] = deferScriptInfo[index]
- // info.code = info.code || code
- // runScript(url, app, info, false, initedHook)
- // !info.module && initedHook(false)
- // })
- // initedHook(isUndefined(initedHook.moduleCount))
- // }).catch((err) => {
- // logError(err, app.name)
- // initedHook(true)
- // })
} else {
initedHook(true)
}