import {
+ type App,
type Component,
type ComponentCustomElementInterface,
type ComponentInjectOptions,
type ComponentProvideOptions,
type ComputedOptions,
type ConcreteComponent,
+ type CreateAppFunction,
type CreateComponentPublicInstanceWithMixins,
type DefineComponent,
type Directive,
type ExtractPropTypes,
type MethodOptions,
type RenderFunction,
- type RootHydrateFunction,
type SetupContext,
type SlotsType,
type VNode,
isPlainObject,
toNumber,
} from '@vue/shared'
-import { hydrate, render } from '.'
+import { createApp, createSSRApp, render } from '.'
export type VueElementConstructor<P = {}> = {
new (initialProps?: Record<string, any>): VueElement & P
styles?: string[]
shadowRoot?: boolean
nonce?: string
+ configureApp?: (app: App) => void
}
// defineCustomElement provides the same type inference as defineComponent
/**
* @internal
*/
- hydrate?: RootHydrateFunction,
+ _createApp?: CreateAppFunction<Element>,
): VueElementConstructor {
const Comp = defineComponent(options, extraOptions) as any
if (isPlainObject(Comp)) extend(Comp, extraOptions)
class VueCustomElement extends VueElement {
static def = Comp
constructor(initialProps?: Record<string, any>) {
- super(Comp, initialProps, hydrate)
+ super(Comp, initialProps, _createApp)
}
}
extraOptions?: ComponentOptions,
) => {
// @ts-expect-error
- return defineCustomElement(options, extraOptions, hydrate)
+ return defineCustomElement(options, extraOptions, createSSRApp)
}) as typeof defineCustomElement
const BaseClass = (
* @internal
*/
_instance: ComponentInternalInstance | null = null
+ /**
+ * @internal
+ */
+ _app: App | null = null
+ /**
+ * @internal
+ */
+ _nonce = this._def.nonce
private _connected = false
private _resolved = false
private _slots?: Record<string, Node[]>
constructor(
+ /**
+ * Component def - note this may be an AsyncWrapper, and this._def will
+ * be overwritten by the inner component when resolved.
+ */
private _def: InnerComponentDef,
private _props: Record<string, any> = {},
- hydrate?: RootHydrateFunction,
+ private _createApp: CreateAppFunction<Element> = createApp,
) {
super()
- // TODO handle non-shadowRoot hydration
- if (this.shadowRoot && hydrate) {
- hydrate(this._createVNode(), this.shadowRoot)
+ if (this.shadowRoot && _createApp !== createApp) {
this._root = this.shadowRoot
+ // TODO hydration needs to be reworked
+ this._mount(_def)
} else {
if (__DEV__ && this.shadowRoot) {
warn(
this._ob.disconnect()
this._ob = null
}
- render(null, this._root)
+ // unmount
+ this._app && this._app.unmount()
this._instance!.ce = undefined
- this._instance = null
+ this._app = this._instance = null
}
})
}
)
}
- // initial render
- this._update()
-
- // apply expose
- this._applyExpose()
+ // initial mount
+ this._mount(def)
}
const asyncDef = (this._def as ComponentOptions).__asyncLoader
}
}
+ private _mount(def: InnerComponentDef) {
+ if ((__DEV__ || __FEATURE_PROD_DEVTOOLS__) && !def.name) {
+ // @ts-expect-error
+ def.name = 'VueElement'
+ }
+ this._app = this._createApp(def)
+ if (def.configureApp) {
+ def.configureApp(this._app)
+ }
+ this._app._ceVNode = this._createVNode()
+ this._app.mount(this._root)
+
+ // apply expose after mount
+ const exposed = this._instance && this._instance.exposed
+ if (!exposed) return
+ for (const key in exposed) {
+ if (!hasOwn(this, key)) {
+ // exposed properties are readonly
+ Object.defineProperty(this, key, {
+ // unwrap ref to be consistent with public instance behavior
+ get: () => unref(exposed[key]),
+ })
+ } else if (__DEV__) {
+ warn(`Exposed property "${key}" already exists on custom element.`)
+ }
+ }
+ }
+
private _resolveProps(def: InnerComponentDef) {
const { props } = def
const declaredPropKeys = isArray(props) ? props : Object.keys(props || {})
}
}
- private _applyExpose() {
- const exposed = this._instance && this._instance.exposed
- if (!exposed) return
- for (const key in exposed) {
- if (!hasOwn(this, key)) {
- // exposed properties are readonly
- Object.defineProperty(this, key, {
- // unwrap ref to be consistent with public instance behavior
- get: () => unref(exposed[key]),
- })
- } else if (__DEV__) {
- warn(`Exposed property "${key}" already exists on custom element.`)
- }
- }
- }
-
protected _setAttr(key: string) {
if (key.startsWith('data-v-')) return
let value = this.hasAttribute(key) ? this.getAttribute(key) : undefined
}
this._styleChildren.add(owner)
}
- const nonce = this._def.nonce
+ const nonce = this._nonce
for (let i = styles.length - 1; i >= 0; i--) {
const s = document.createElement('style')
if (nonce) s.setAttribute('nonce', nonce)
// rendered by the server, the template should not contain any user data.
component.template = container.innerHTML
// 2.x compat check
- if (__COMPAT__ && __DEV__) {
- for (let i = 0; i < container.attributes.length; i++) {
- const attr = container.attributes[i]
+ if (__COMPAT__ && __DEV__ && container.nodeType === 1) {
+ for (let i = 0; i < (container as Element).attributes.length; i++) {
+ const attr = (container as Element).attributes[i]
if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) {
compatUtils.warnDeprecation(
DeprecationTypes.GLOBAL_MOUNT_CONTAINER,
}
// clear content before mounting
- container.textContent = ''
+ if (container.nodeType === 1) {
+ container.textContent = ''
+ }
const proxy = mount(container, false, resolveRootNamespace(container))
if (container instanceof Element) {
container.removeAttribute('v-cloak')
return app
}) as CreateAppFunction<Element>
-function resolveRootNamespace(container: Element): ElementNamespace {
+function resolveRootNamespace(
+ container: Element | ShadowRoot,
+): ElementNamespace {
if (container instanceof SVGElement) {
return 'svg'
}
function normalizeContainer(
container: Element | ShadowRoot | string,
-): Element | null {
+): Element | ShadowRoot | null {
if (isString(container)) {
const res = document.querySelector(container)
if (__DEV__ && !res) {