diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts index 777f9677d04..93d8a2fdad3 100644 --- a/packages/runtime-dom/__tests__/customElement.spec.ts +++ b/packages/runtime-dom/__tests__/customElement.spec.ts @@ -210,6 +210,29 @@ describe('defineCustomElement', () => { customElements.define('my-el-upgrade', E) expect(el.shadowRoot.innerHTML).toBe(`foo: hello`) }) + + test('handle properties set before connecting', () => { + const obj = {} + const E = defineCustomElement({ + props: { + foo: String, + post: Object + }, + setup(props) { + expect(props.foo).toBe('hello') + expect(props.post).toBe(obj) + }, + render() { + return `foo: ${this.foo}` + } + }) + customElements.define('my-el-preconnect', E) + const el = document.createElement('my-el-preconnect') as any + el.foo = 'hello' + el.post = obj + + container.appendChild(el) + }) }) describe('emits', () => { diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index 059fcac16f1..4adf8f1f007 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -173,6 +173,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) + } } } @@ -214,10 +218,9 @@ export class VueElement extends BaseClass { } }).observe(this, { attributes: true }) - const resolve = (def: InnerComponentDef) => { - const { props, styles } = def + const resolve = (def: InnerComponentDef, isAsync = false) => { + const { props = [], styles } = def const hasOptions = !isArray(props) - const rawKeys = props ? (hasOptions ? Object.keys(props) : props) : [] // cast Number-type props set before resolve let numberProps @@ -232,23 +235,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] !== '_') { - this._setProp(key, this[key as keyof this], true, false) - } - } - - // defining getter/setters on prototype - for (const key of rawKeys.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 @@ -260,12 +250,37 @@ 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) { + // check if there are props set pre-upgrade or connect + for (const key of Object.keys(this)) { + if (key[0] !== '_') { + this._setProp(key, this[key as keyof this], true, false) + } + } + + const { props } = def + const hasOptions = !isArray(props) + const rawKeys = props ? (hasOptions ? Object.keys(props) : props) : [] + + // defining getter/setters on prototype + for (const key of rawKeys.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) if (this._numberProps && this._numberProps[key]) {