From 6d53a3210755f582c15e4c52b92c60d9a562c9d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorsten=20L=C3=BCnborg?= Date: Fri, 11 Nov 2022 08:46:01 +0100 Subject: [PATCH] fix(custom-elements): define declared properties in constructor (#5328) --- .../__tests__/customElement.spec.ts | 24 +++++++++ packages/runtime-dom/src/apiCustomElement.ts | 54 ++++++++++++------- 2 files changed, 58 insertions(+), 20 deletions(-) diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts index 6c991b9f523..a3d35790f31 100644 --- a/packages/runtime-dom/__tests__/customElement.spec.ts +++ b/packages/runtime-dom/__tests__/customElement.spec.ts @@ -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({ diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index ecb558b17a4..4de753e38be 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -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) + } } } @@ -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 @@ -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 @@ -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)