Repository: vuejs/vue-next
File: packages/runtime-dom/src/apiCustomElement.ts (TypeScript)

Code before:
        if (key === 'key' && this._app) {
          this._app._ceVNode!.key = val
        }
      }
      if (shouldUpdate && this._instance) {
        this._update()
      }
      // reflect
      if (shouldReflect) {
        const ob = this._ob
        ob && ob.disconnect()
        if (val === true) {
          this.setAttribute(hyphenate(key), '')
        } else if (typeof val === 'string' || typeof val === 'number') {
          this.setAttribute(hyphenate(key), val + '')
        } else if (!val) {
          this.removeAttribute(hyphenate(key))
        }
        ob && ob.observe(this, { attributes: true })
      }
    }
  }

  private _update() {
    const vnode = this._createVNode()
    if (this._app && !this._instance) vnode.appContext = this._app._context
    render(vnode, this._root)
  }

  private _createVNode(): VNode<any, any> {
    const baseProps: VNodeProps = {}
    if (!this.shadowRoot) {
      baseProps.onVnodeMounted = baseProps.onVnodeUpdated =
        this._renderSlots.bind(this)
    }
    const vnode = createVNode(this._def, extend(baseProps, this._props))
    if (!this._instance) {
      vnode.ce = instance => {
        this._instance = instance
        instance.ce = this
        instance.isCE = true // for vue-i18n backwards compat
        // HMR
        if (__DEV__) {
          instance.ceReload = newStyles => {
            // always reset styles
            if (this._styles) {
              this._styles.forEach(s => this._root.removeChild(s))
              this._styles.length = 0
            }
            this._applyStyles(newStyles)
            this._instance = null

Review comment (bug):
The reason for checking `this._app` here is that the test case will not pass. https://github.com/vuejs/core/blob/b7b093203775d292ede1ec5841452af9e2a7f3cc/packages/runtime-dom/__tests__/customElement.spec.ts#L137-L147
When re-inserting, `this._app` is `null` because we cleared it in `disconnectedCallback`.
For custom elements that remove and then re-insert (`this._resolved = true`), just calling `_update` is not enough. https://github.com/vuejs/core/blob/2235643a4e1c559fea25ae1cfb9f6058ac8becfc/packages/runtime-dom/src/apiCustomElement.ts#L300-L303
We must also reinitialize `this._ob` because it is cleared in `disconnectedCallback`. 
For more edge cases refer to #12412. Maybe once #12413 is merged, the check for `this._app` can be removed.

Code after:
   */
  _setProp(
    key: string,
    val: any,
    shouldReflect = true,
    shouldUpdate = false,
  ): void {
    if (val !== this._props[key]) {
      if (val === REMOVAL) {
        delete this._props[key]
      } else {
        this._props[key] = val
        // support set key on ceVNode
        if (key === 'key' && this._app) {
          this._app._ceVNode!.key = val
        }
      }
      if (shouldUpdate && this._instance) {
        this._update()
      }
      // reflect
      if (shouldReflect) {
        const ob = this._ob
        ob && ob.disconnect()
        if (val === true) {
          this.setAttribute(hyphenate(key), '')
        } else if (typeof val === 'string' || typeof val === 'number') {
          this.setAttribute(hyphenate(key), val + '')
        } else if (!val) {
          this.removeAttribute(hyphenate(key))
        }
        ob && ob.observe(this, { attributes: true })
      }
    }
  }

  private _update() {
    const vnode = this._createVNode()
    if (this._app) vnode.appContext = this._app._context
    render(vnode, this._root)
  }

  private _createVNode(): VNode<any, any> {
    const baseProps: VNodeProps = {}
    if (!this.shadowRoot) {
      baseProps.onVnodeMounted = baseProps.onVnodeUpdated =
        this._renderSlots.bind(this)
    }
    const vnode = createVNode(this._def, extend(baseProps, this._props))
    if (!this._instance) {
      vnode.ce = instance => {