diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts index b79ac2901..459b39448 100644 --- a/packages/compiler-vapor/src/generate.ts +++ b/packages/compiler-vapor/src/generate.ts @@ -28,6 +28,7 @@ import { type PrependNodeIRNode, type AppendNodeIRNode, IRNodeTypes, + CreateComponentIRNode, } from './ir' import { SourceMapGenerator } from 'source-map-js' import { camelize, isGloballyAllowed, isString, makeMap } from '@vue/shared' @@ -376,6 +377,8 @@ function genOperation(oper: OperationNode, context: CodegenContext) { return genSetHtml(oper, context) case IRNodeTypes.CREATE_TEXT_NODE: return genCreateTextNode(oper, context) + case IRNodeTypes.CREATE_COMPONENT_NODE: + return genCreateComponentNode(oper, context) case IRNodeTypes.INSERT_NODE: return genInsertNode(oper, context) case IRNodeTypes.PREPEND_NODE: @@ -437,6 +440,20 @@ function genCreateTextNode( ) } +function genCreateComponentNode( + oper: CreateComponentIRNode, + context: CodegenContext, +) { + const { pushNewline, pushFnCall, vaporHelper } = context + pushNewline(`const n${oper.id} = `) + // TODO: support props + pushFnCall( + vaporHelper('createComponent'), + () => genExpression(oper.tag, context), + '{}', + ) +} + function genInsertNode(oper: InsertNodeIRNode, context: CodegenContext) { const { newline, pushFnCall, vaporHelper } = context const elements = ([] as number[]).concat(oper.element) diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts index 2af5455d6..a217ce6d6 100644 --- a/packages/compiler-vapor/src/ir.ts +++ b/packages/compiler-vapor/src/ir.ts @@ -22,6 +22,7 @@ export enum IRNodeTypes { PREPEND_NODE, APPEND_NODE, CREATE_TEXT_NODE, + CREATE_COMPONENT_NODE, WITH_DIRECTIVE, } @@ -123,6 +124,12 @@ export interface WithDirectiveIRNode extends BaseIRNode { dir: VaporDirectiveNode } +export interface CreateComponentIRNode extends BaseIRNode { + type: IRNodeTypes.CREATE_COMPONENT_NODE + id: number + tag: string +} + export type IRNode = | OperationNode | RootIRNode @@ -138,6 +145,7 @@ export type OperationNode = | PrependNodeIRNode | AppendNodeIRNode | WithDirectiveIRNode + | CreateComponentIRNode export interface IRDynamicInfo { id: number | null diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index 4bca80478..8d8392c64 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -25,7 +25,17 @@ export const transformElement: NodeTransform = (node, ctx) => { const { tag, props } = node const isComponent = node.tagType === ElementTypes.COMPONENT - ctx.template += `<${tag}` + if (isComponent) { + ctx.dynamic.ghost = true + ctx.registerOperation({ + type: IRNodeTypes.CREATE_COMPONENT_NODE, + loc: node.loc, + id: ctx.reference(), + tag, + }) + } else { + ctx.template += `<${tag}` + } if (props.length) { buildProps( node, @@ -34,7 +44,9 @@ export const transformElement: NodeTransform = (node, ctx) => { isComponent, ) } - ctx.template += `>` + ctx.childrenTemplate.join('') + if (!isComponent) { + ctx.template += `>` + ctx.childrenTemplate.join('') + } // TODO remove unnecessary close tag, e.g. if it's the last element of the template if (!isVoidTag(tag)) { diff --git a/packages/runtime-vapor/src/apiCreateComponent.ts b/packages/runtime-vapor/src/apiCreateComponent.ts new file mode 100644 index 000000000..70d7b37f1 --- /dev/null +++ b/packages/runtime-vapor/src/apiCreateComponent.ts @@ -0,0 +1,8 @@ +import { Data } from '@vue/shared' +import { Component } from './component' +import { render } from './render' + +export function createComponent(comp: Component, props: Data = {}) { + const instance = render(comp, props) + return instance.block +} diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index 7f6165677..4a10f5226 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -45,4 +45,5 @@ export * from './directive' export * from './dom' export * from './directives/vShow' export * from './apiLifecycle' +export * from './apiCreateComponent' export { getCurrentInstance, type ComponentInternalInstance } from './component' diff --git a/packages/runtime-vapor/src/render.ts b/packages/runtime-vapor/src/render.ts index a2f505ecd..66b524ec9 100644 --- a/packages/runtime-vapor/src/render.ts +++ b/packages/runtime-vapor/src/render.ts @@ -20,11 +20,12 @@ export type BlockFn = (props: any, ctx: any) => Block export function render( comp: Component, props: Data, - container: string | ParentNode, + container?: string | ParentNode, ): ComponentInternalInstance { const instance = createComponentInstance(comp) initProps(instance, props) - return mountComponent(instance, (container = normalizeContainer(container))) + if (container) container = normalizeContainer(container) + return mountComponent(instance, container as ParentNode) } export function normalizeContainer(container: string | ParentNode): ParentNode { @@ -35,9 +36,9 @@ export function normalizeContainer(container: string | ParentNode): ParentNode { export function mountComponent( instance: ComponentInternalInstance, - container: ParentNode, + container?: ParentNode, ) { - instance.container = container + if (container) instance.container = container setCurrentInstance(instance) const block = instance.scope.run(() => { @@ -68,8 +69,10 @@ export function mountComponent( bm && invokeArrayFns(bm) invokeDirectiveHook(instance, 'beforeMount') - insert(block, instance.container) - instance.isMountedRef.value = true + if (instance.container) { + insert(block, instance.container) + instance.isMountedRef.value = true + } // hook: mounted invokeDirectiveHook(instance, 'mounted')