Skip to content

Commit 8905f8c

Browse files
authored
fix: avoid to overwrite component options on IE <= 10 (#203)
* fix: avoid to overwrite component options on IE <= 10 * fix: prevent reconfiguration if native properties does not allow it
1 parent b6a12a8 commit 8905f8c

File tree

3 files changed

+108
-6
lines changed

3 files changed

+108
-6
lines changed

src/component.ts

+80-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Vue, { ComponentOptions } from 'vue'
22
import { VueClass, DecoratedClass } from './declarations'
33
import { collectDataFromConstructor } from './data'
4+
import { hasProto, isPrimitive, warn } from './util'
45

56
export const $internalHooks = [
67
'data',
@@ -68,12 +69,85 @@ export function componentFactory (
6869
: Vue
6970
const Extended = Super.extend(options)
7071

71-
Object.getOwnPropertyNames(Component).forEach(key => {
72-
if (key !== 'prototype') {
73-
const descriptor = Object.getOwnPropertyDescriptor(Component, key)!
74-
Object.defineProperty(Extended, key, descriptor)
75-
}
76-
})
72+
forwardStaticMembers(Extended, Component, Super)
7773

7874
return Extended
7975
}
76+
77+
const reservedPropertyNames = [
78+
// Unique id
79+
'cid',
80+
81+
// Super Vue constructor
82+
'super',
83+
84+
// Component options that will be used by the component
85+
'options',
86+
'superOptions',
87+
'extendOptions',
88+
'sealedOptions',
89+
90+
// Private assets
91+
'component',
92+
'directive',
93+
'filter'
94+
]
95+
96+
function forwardStaticMembers (Extended: typeof Vue, Original: typeof Vue, Super: typeof Vue): void {
97+
// We have to use getOwnPropertyNames since Babel registers methods as non-enumerable
98+
Object.getOwnPropertyNames(Original).forEach(key => {
99+
// `prototype` should not be overwritten
100+
if (key === 'prototype') {
101+
return
102+
}
103+
104+
// Some browsers does not allow reconfigure built-in properties
105+
const extendedDescriptor = Object.getOwnPropertyDescriptor(Extended, key)
106+
if (extendedDescriptor && !extendedDescriptor.configurable) {
107+
return
108+
}
109+
110+
const descriptor = Object.getOwnPropertyDescriptor(Original, key)!
111+
112+
// If the user agent does not support `__proto__` or its family (IE <= 10),
113+
// the sub class properties may be inherited properties from the super class in TypeScript.
114+
// We need to exclude such properties to prevent to overwrite
115+
// the component options object which stored on the extended constructor (See #192).
116+
// If the value is a referenced value (object or function),
117+
// we can check equality of them and exclude it if they have the same reference.
118+
// If it is a primitive value, it will be forwarded for safety.
119+
if (!hasProto) {
120+
121+
// Only `cid` is explicitly exluded from property forwarding
122+
// because we cannot detect whether it is a inherited property or not
123+
// on the no `__proto__` environment even though the property is reserved.
124+
if (key === 'cid') {
125+
return
126+
}
127+
128+
const superDescriptor = Object.getOwnPropertyDescriptor(Super, key)
129+
130+
if (
131+
!isPrimitive(descriptor.value)
132+
&& superDescriptor
133+
&& superDescriptor.value === descriptor.value
134+
) {
135+
return
136+
}
137+
}
138+
139+
// Warn if the users manually declare reserved properties
140+
if (
141+
process.env.NODE_ENV !== 'production'
142+
&& reservedPropertyNames.indexOf(key) >= 0
143+
) {
144+
warn(
145+
`Static property name '${key}' declared on class '${Original.name}' ` +
146+
'conflicts with reserved property name of Vue internal. ' +
147+
'It may cause unexpected behavior of the component. Consider renaming the property.'
148+
)
149+
}
150+
151+
Object.defineProperty(Extended, key, descriptor)
152+
})
153+
}

src/util.ts

+7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { DecoratedClass } from './declarations'
33

44
export const noop = () => {}
55

6+
export const hasProto = { __proto__: [] } instanceof Array
7+
68
export interface VueDecorator {
79
// Class decorator
810
(Ctor: typeof Vue): void
@@ -29,6 +31,11 @@ export function createDecorator (factory: (options: ComponentOptions<Vue>, key:
2931
}
3032
}
3133

34+
export function isPrimitive (value: any): boolean {
35+
const type = typeof value
36+
return value == null || (type !== "object" && type !== "function")
37+
}
38+
3239
export function warn (message: string): void {
3340
if (typeof console !== 'undefined') {
3441
console.warn('[vue-class-component] ' + message)

test/test.ts

+21
Original file line numberDiff line numberDiff line change
@@ -321,4 +321,25 @@ describe('vue-class-component', () => {
321321
expect(MyComp.myValue).to.equal(52)
322322
expect(MyComp.myFunc()).to.equal(42)
323323
})
324+
325+
it('should warn if declared static property uses a reserved name but not prevent forwarding', function () {
326+
const originalWarn = console.warn
327+
console.warn = td.function('warn') as any
328+
329+
@Component
330+
class MyComp extends Vue {
331+
static options = 'test'
332+
}
333+
334+
const message = '[vue-class-component] ' +
335+
'Static property name \'options\' declared on class \'MyComp\' conflicts with ' +
336+
'reserved property name of Vue internal. It may cause unexpected behavior of the component. Consider renaming the property.'
337+
338+
expect(MyComp.options).to.equal('test')
339+
try {
340+
td.verify(console.warn(message))
341+
} finally {
342+
console.warn = originalWarn
343+
}
344+
})
324345
})

0 commit comments

Comments
 (0)