Skip to content

Commit 615db5e

Browse files
authored
feat: implement defineVaporCustomElement (#14017)
1 parent d2eebe4 commit 615db5e

File tree

24 files changed

+2909
-234
lines changed

24 files changed

+2909
-234
lines changed

packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,15 @@ export function render(_ctx) {
333333
}"
334334
`;
335335
336+
exports[`compiler: element transform > custom element 1`] = `
337+
"import { createPlainElement as _createPlainElement } from 'vue';
338+
339+
export function render(_ctx) {
340+
const n0 = _createPlainElement("my-custom-element", null, null, true)
341+
return n0
342+
}"
343+
`;
344+
336345
exports[`compiler: element transform > dynamic component > capitalized version w/ static binding 1`] = `
337346
"import { resolveDynamicComponent as _resolveDynamicComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
338347

packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,6 +1042,17 @@ describe('compiler: element transform', () => {
10421042
expect(code).contain('return null')
10431043
})
10441044

1045+
test('custom element', () => {
1046+
const { code } = compileWithElementTransform(
1047+
'<my-custom-element></my-custom-element>',
1048+
{
1049+
isCustomElement: tag => tag === 'my-custom-element',
1050+
},
1051+
)
1052+
expect(code).toMatchSnapshot()
1053+
expect(code).toContain('createPlainElement')
1054+
})
1055+
10451056
test('svg', () => {
10461057
const t = `<svg><circle r="40"></circle></svg>`
10471058
const { code, ir } = compileWithElementTransform(t)

packages/compiler-vapor/src/generators/component.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,11 @@ export function genCreateComponent(
7373
...genCall(
7474
operation.dynamic && !operation.dynamic.isStatic
7575
? helper('createDynamicComponent')
76-
: operation.asset
77-
? helper('createComponentWithFallback')
78-
: helper('createComponent'),
76+
: operation.isCustomElement
77+
? helper('createPlainElement')
78+
: operation.asset
79+
? helper('createComponentWithFallback')
80+
: helper('createComponent'),
7981
tag,
8082
rawProps,
8183
rawSlots,
@@ -86,7 +88,9 @@ export function genCreateComponent(
8688
]
8789

8890
function genTag() {
89-
if (operation.dynamic) {
91+
if (operation.isCustomElement) {
92+
return JSON.stringify(operation.tag)
93+
} else if (operation.dynamic) {
9094
if (operation.dynamic.isStatic) {
9195
return genCall(
9296
helper('resolveDynamicComponent'),

packages/compiler-vapor/src/ir/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ export interface CreateComponentIRNode extends BaseIRNode {
204204
root: boolean
205205
once: boolean
206206
dynamic?: SimpleExpressionNode
207+
isCustomElement: boolean
207208
parent?: number
208209
anchor?: number
209210
append?: boolean

packages/compiler-vapor/src/transforms/transformElement.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,12 @@ export const transformElement: NodeTransform = (node, context) => {
5858
)
5959
return
6060

61-
const isComponent = node.tagType === ElementTypes.COMPONENT
61+
// treat custom elements as components because the template helper cannot
62+
// resolve them properly; they require creation via createElement
63+
const isCustomElement = !!context.options.isCustomElement(node.tag)
64+
const isComponent =
65+
node.tagType === ElementTypes.COMPONENT || isCustomElement
66+
6267
const isDynamicComponent = isComponentTag(node.tag)
6368
const propsResult = buildProps(
6469
node,
@@ -78,9 +83,10 @@ export const transformElement: NodeTransform = (node, context) => {
7883
parent = parent.parent
7984
}
8085
const singleRoot =
81-
context.root === parent &&
82-
parent.node.children.filter(child => child.type !== NodeTypes.COMMENT)
83-
.length === 1
86+
(context.root === parent &&
87+
parent.node.children.filter(child => child.type !== NodeTypes.COMMENT)
88+
.length === 1) ||
89+
isCustomElement
8490

8591
if (isComponent) {
8692
transformComponentElement(
@@ -89,6 +95,7 @@ export const transformElement: NodeTransform = (node, context) => {
8995
singleRoot,
9096
context,
9197
isDynamicComponent,
98+
isCustomElement,
9299
)
93100
} else {
94101
transformNativeElement(
@@ -108,6 +115,7 @@ function transformComponentElement(
108115
singleRoot: boolean,
109116
context: TransformContext,
110117
isDynamicComponent: boolean,
118+
isCustomElement: boolean,
111119
) {
112120
const dynamicComponent = isDynamicComponent
113121
? resolveDynamicComponent(node)
@@ -116,7 +124,7 @@ function transformComponentElement(
116124
let { tag } = node
117125
let asset = true
118126

119-
if (!dynamicComponent) {
127+
if (!dynamicComponent && !isCustomElement) {
120128
const fromSetup = resolveSetupReference(tag, context)
121129
if (fromSetup) {
122130
tag = fromSetup
@@ -161,6 +169,7 @@ function transformComponentElement(
161169
slots: [...context.slots],
162170
once: context.inVOnce,
163171
dynamic: dynamicComponent,
172+
isCustomElement,
164173
}
165174
context.slots = []
166175
}

packages/runtime-core/src/apiCreateApp.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ export interface App<HostElement = any> {
111111
*/
112112
_ceVNode?: VNode
113113

114+
/**
115+
* @internal vapor custom element instance
116+
*/
117+
_ceComponent?: GenericComponentInstance | null
118+
114119
/**
115120
* v2 compat only
116121
*/

packages/runtime-core/src/componentCurrentInstance.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
} from './component'
66
import { currentRenderingInstance } from './componentRenderContext'
77
import { type EffectScope, setCurrentScope } from '@vue/reactivity'
8+
import { warn } from './warning'
89

910
/**
1011
* @internal
@@ -90,3 +91,36 @@ export const setCurrentInstance = (
9091
simpleSetCurrentInstance(instance)
9192
}
9293
}
94+
95+
const internalOptions = ['ce'] as const
96+
97+
/**
98+
* @internal
99+
*/
100+
export const useInstanceOption = <K extends (typeof internalOptions)[number]>(
101+
key: K,
102+
silent = false,
103+
): {
104+
hasInstance: boolean
105+
value: GenericComponentInstance[K] | undefined
106+
} => {
107+
const instance = getCurrentGenericInstance()
108+
if (!instance) {
109+
if (__DEV__ && !silent) {
110+
warn(`useInstanceOption called without an active component instance.`)
111+
}
112+
return { hasInstance: false, value: undefined }
113+
}
114+
115+
if (!internalOptions.includes(key)) {
116+
if (__DEV__) {
117+
warn(
118+
`useInstanceOption only accepts ` +
119+
` ${internalOptions.map(k => `'${k}'`).join(', ')} as key, got '${key}'.`,
120+
)
121+
}
122+
return { hasInstance: true, value: undefined }
123+
}
124+
125+
return { hasInstance: true, value: instance[key] }
126+
}

packages/runtime-core/src/hmr.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,16 @@ function reload(id: string, newComp: HMRComponent): void {
123123
// create a snapshot which avoids the set being mutated during updates
124124
const instances = [...record.instances]
125125

126-
if (newComp.__vapor) {
126+
if (newComp.__vapor && !instances.some(i => i.ceReload)) {
127+
// For multiple instances with the same __hmrId, remove styles first before reload
128+
// to avoid the second instance's style removal deleting the first instance's
129+
// newly added styles (since hmrReload is synchronous)
130+
for (const instance of instances) {
131+
// update custom element child style
132+
if (instance.root && instance.root.ce && instance !== instance.root) {
133+
instance.root.ce._removeChildStyle(instance.type)
134+
}
135+
}
127136
for (const instance of instances) {
128137
instance.hmrReload!(newComp)
129138
}

packages/runtime-core/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ export {
105105
// plugins
106106
export { getCurrentInstance } from './component'
107107

108+
/**
109+
* @internal
110+
*/
111+
export { useInstanceOption } from './component'
112+
108113
// For raw render function users
109114
export { h } from './h'
110115
// Advanced render function utilities

0 commit comments

Comments
 (0)