Skip to content

Commit

Permalink
feat(customElement): support for expose on customElement, fix vuejs#5540
Browse files Browse the repository at this point in the history
  • Loading branch information
hcg1023 committed Jul 12, 2022
1 parent 8dcb6c7 commit cbe77f3
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 3 deletions.
62 changes: 62 additions & 0 deletions packages/runtime-dom/__tests__/customElement.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,68 @@ describe('defineCustomElement', () => {
})
})

describe('expose', () => {
test('expose attributes and callback', async () => {
type SetValue = (value: string) => void
let fn: jest.MockedFunction<SetValue>

const E = defineCustomElement({
setup(_, { expose }) {
const value = ref('hello')

const setValue: jest.MockedFunction<SetValue> = (fn = jest.fn(
(_value: string) => {
value.value = _value
}
))

expose({
setValue,
value
})

return () => h('div', null, [value.value])
}
})
customElements.define('my-el-expose', E)

container.innerHTML = `<my-el-expose></my-el-expose>`
const e = container.childNodes[0] as VueElement & {
value: string
setValue: jest.MockedFunction<SetValue>
}
expect(e.shadowRoot!.innerHTML).toBe(`<div>hello</div>`)
expect(e.value).toBe('hello')
expect(e.setValue).toBe(fn!)
e.setValue('world')
expect(e.value).toBe('world')
await nextTick()
expect(e.shadowRoot!.innerHTML).toBe(`<div>world</div>`)
})

test('expose a defined props', () => {
const E = defineCustomElement({
props: {
value: String
},
setup(props, { expose }) {
expose({
value: 'hello'
})

return () => h('div', null, [props.value])
}
})
customElements.define('my-el-expose-two', E)

container.innerHTML = `<my-el-expose-two value="world"></my-el-expose-two>`

expect(
`[Vue warn]: Exposed property "value" already exists on custom element.`
).toHaveBeenWarned()
})
})

describe('slots', () => {
const E = defineCustomElement({
render() {
Expand Down
38 changes: 35 additions & 3 deletions packages/runtime-dom/src/apiCustomElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,17 @@ import {
nextTick,
warn,
ConcreteComponent,
ComponentOptions
ComponentOptions,
unref
} from '@vue/runtime-core'
import { camelize, extend, hyphenate, isArray, toNumber } from '@vue/shared'
import {
camelize,
extend,
hasOwn,
hyphenate,
isArray,
toNumber
} from '@vue/shared'
import { hydrate, render } from '.'

export type VueElementConstructor<P = {}> = {
Expand Down Expand Up @@ -256,6 +264,9 @@ export class VueElement extends BaseClass {

// initial render
this._update()

// applyExpose
this._applyExpose()
}

const asyncDef = (this._def as ComponentOptions).__asyncLoader
Expand All @@ -266,6 +277,27 @@ export class VueElement extends BaseClass {
}
}

private _applyExpose() {
if (!this._instance || !this._instance.exposed) {
return
}
const exposed = this._instance.exposed
for (const key of Object.keys(exposed)) {
// if existing attributes are exposed
if (hasOwn(this, key)) {
warn(`Exposed property "${key}" already exists on custom element.`)
continue
}
// exposed properties are readonly
Object.defineProperty(this, key, {
get() {
// Unpack ref to avoid external modifications
return unref(exposed[key])
}
})
}
}

protected _setAttr(key: string) {
let value = this.getAttribute(key)
if (this._numberProps && this._numberProps[key]) {
Expand Down Expand Up @@ -346,7 +378,7 @@ export class VueElement extends BaseClass {
)
}

// locate nearest Vue custom element parent for provide/inject
// locate nearest vue custom element parent for provide/inject
let parent: Node | null = this
while (
(parent =
Expand Down

0 comments on commit cbe77f3

Please sign in to comment.