Skip to content

Commit

Permalink
fix(custom-elements): define declared properties in constructor (#5328)
Browse files Browse the repository at this point in the history
  • Loading branch information
LinusBorg authored Nov 11, 2022
1 parent 89f37ce commit 55382ae
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 20 deletions.
24 changes: 24 additions & 0 deletions packages/runtime-dom/__tests__/customElement.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,30 @@ describe('defineCustomElement', () => {
expect(el.hasAttribute('not-prop')).toBe(false)
})

test('handle properties set before connecting', () => {
const obj = { a: 1 }
const E = defineCustomElement({
props: {
foo: String,
post: Object
},
setup(props) {
expect(props.foo).toBe('hello')
expect(props.post).toBe(obj)
},
render() {
return JSON.stringify(this.post)
}
})
customElements.define('my-el-preconnect', E)
const el = document.createElement('my-el-preconnect') as any
el.foo = 'hello'
el.post = obj

container.appendChild(el)
expect(el.shadowRoot.innerHTML).toBe(JSON.stringify(obj))
})

// https://github.com/vuejs/core/issues/6163
test('handle components with no props', async () => {
const E = defineCustomElement({
Expand Down
54 changes: 34 additions & 20 deletions packages/runtime-dom/src/apiCustomElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ export class VueElement extends BaseClass {
)
}
this.attachShadow({ mode: 'open' })
if (!(this._def as ComponentOptions).__asyncLoader) {
// for sync component defs we can immediately resolve props
this._resolveProps(this._def)
}
}
}

Expand Down Expand Up @@ -227,9 +231,8 @@ export class VueElement extends BaseClass {
}
}).observe(this, { attributes: true })

const resolve = (def: InnerComponentDef) => {
const resolve = (def: InnerComponentDef, isAsync = false) => {
const { props, styles } = def
const declaredPropKeys = isArray(props) ? props : Object.keys(props || {})

// cast Number-type props set before resolve
let numberProps
Expand All @@ -248,23 +251,10 @@ export class VueElement extends BaseClass {
}
this._numberProps = numberProps

// check if there are props set pre-upgrade or connect
for (const key of Object.keys(this)) {
if (key[0] !== '_' && declaredPropKeys.includes(key)) {
this._setProp(key, this[key as keyof this], true, false)
}
}

// defining getter/setters on prototype
for (const key of declaredPropKeys.map(camelize)) {
Object.defineProperty(this, key, {
get() {
return this._getProp(key)
},
set(val) {
this._setProp(key, val)
}
})
if (isAsync) {
// defining getter/setters on prototype
// for sync defs, this already happened in the constructor
this._resolveProps(def)
}

// apply CSS
Expand All @@ -276,12 +266,36 @@ export class VueElement extends BaseClass {

const asyncDef = (this._def as ComponentOptions).__asyncLoader
if (asyncDef) {
asyncDef().then(resolve)
asyncDef().then(def => resolve(def, true))
} else {
resolve(this._def)
}
}

private _resolveProps(def: InnerComponentDef) {
const { props } = def
const declaredPropKeys = isArray(props) ? props : Object.keys(props || {})

// check if there are props set pre-upgrade or connect
for (const key of Object.keys(this)) {
if (key[0] !== '_' && declaredPropKeys.includes(key)) {
this._setProp(key, this[key as keyof this], true, false)
}
}

// defining getter/setters on prototype
for (const key of declaredPropKeys.map(camelize)) {
Object.defineProperty(this, key, {
get() {
return this._getProp(key)
},
set(val) {
this._setProp(key, val)
}
})
}
}

protected _setAttr(key: string) {
let value = this.getAttribute(key)
const camelKey = camelize(key)
Expand Down

0 comments on commit 55382ae

Please sign in to comment.