Skip to content

Commit

Permalink
fix: 修复了hasOwnProperty和effect失效的问题
Browse files Browse the repository at this point in the history
  • Loading branch information
bailicangdu committed Dec 29, 2021
1 parent ae1f1fe commit 96f4760
Show file tree
Hide file tree
Showing 12 changed files with 175 additions and 35 deletions.
2 changes: 1 addition & 1 deletion dev/main-react16/src/pages/react16/react16.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
8 changes: 7 additions & 1 deletion src/__tests__/create_app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -457,4 +457,10 @@ describe('create_app', () => {
})
})
})

// 测试getActiveApps方法
test('test getActiveApps method', async () => {
// 因为上面已经执行unmountAllApps卸载了所有应用,所以这里长度为0
expect(getActiveApps(true).length).toBe(0)
})
})
1 change: 1 addition & 0 deletions src/__tests__/demos/common/defer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.error('my defer')
13 changes: 13 additions & 0 deletions src/__tests__/demos/dynamic/errorjs.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>dynamic</title>
<script src="./throw-error.js"></script>
</head>
<body>

</body>
</html>
14 changes: 14 additions & 0 deletions src/__tests__/demos/special-html/notexistdefer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="http://www.micro-app-test.com/not-exist1.js" defer></script>
<script src="http://www.micro-app-test.com/not-exist2.js" defer></script>
</head>
<body>

</body>
</html>
13 changes: 13 additions & 0 deletions src/__tests__/demos/special-html/onlydefer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../common/defer.js" defer></script>
</head>
<body>

</body>
</html>
58 changes: 55 additions & 3 deletions src/__tests__/sandbox/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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()
})
Expand Down Expand Up @@ -228,6 +236,7 @@ describe('sandbox', () => {
value: 'value3',
configurable: true,
writable: true,
enumerable: true,
},
})

Expand Down Expand Up @@ -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
Expand All @@ -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()
})
})
50 changes: 43 additions & 7 deletions src/__tests__/source/scripts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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': [
{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
})
})
})
1 change: 1 addition & 0 deletions src/libs/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
36 changes: 26 additions & 10 deletions src/sandbox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
rawDefineProperty,
rawDefineProperties,
isFunction,
rawHasOwnProperty,
} from '../libs/utils'
import microApp from '../micro_app'
import bindFunctionToRawWindow from './bind_function'
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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<PropertyKey, 'target' | 'rawWindow'>()
// window.xxx will trigger proxy
return new Proxy(this.microAppWindow, {
get: (target: microAppWindowType, key: PropertyKey): unknown => {
Expand All @@ -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)
Expand Down Expand Up @@ -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<string | symbol> => {
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)
Expand All @@ -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)
}
Expand Down
12 changes: 0 additions & 12 deletions src/source/scripts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down

0 comments on commit 96f4760

Please sign in to comment.