diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts index 7d0cea4e6..f374b3901 100644 --- a/packages/compiler-vapor/src/generate.ts +++ b/packages/compiler-vapor/src/generate.ts @@ -3,6 +3,7 @@ import { type CodegenOptions, type CodegenResult, NewlineType, + NodeTypes, type Position, type SourceLocation, advancePositionWithClone, @@ -14,6 +15,7 @@ import { } from '@vue/compiler-dom' import { type AppendNodeIRNode, + type CreateComponentIRNode, type CreateTextNodeIRNode, type IRDynamicChildren, type IRExpression, @@ -376,6 +378,8 @@ function genOperation(oper: OperationNode, context: CodegenContext) { return genSetHtml(oper, context) case IRNodeTypes.CREATE_TEXT_NODE: return genCreateTextNode(oper, context) + case IRNodeTypes.CREATE_COMPONENT: + return genCreateComponent(oper, context) case IRNodeTypes.INSERT_NODE: return genInsertNode(oper, context) case IRNodeTypes.PREPEND_NODE: @@ -437,6 +441,171 @@ function genCreateTextNode( ) } +function genCreateComponent( + oper: CreateComponentIRNode, + context: CodegenContext, +) { + const { pushNewline, pushFnCall, vaporHelper } = context + + pushNewline(`const n${oper.id} = `) + + let props = null + if (oper.props.length && false) { + console.log('ZXZXX') + // props = oper.props + // .map((prop) => { + // const { name, value, exp, arg } = prop + // console.log('using ', prop) + + // // const key = oper.runtimeCamelize + // let key = null + + // // const content = + + // let content = null + + // switch (prop?.type) { + // case NodeTypes.ATTRIBUTE: { + // content = value.content + // key = camelize(name) + // break + // } + // case NodeTypes.DIRECTIVE: { + // console.log('found ', prop) + // // content = genExpression(prop.exp, context) + // key = camelize(arg.content) + // content = genWithDirective(exp, context) + // break + // } + // } + + // return `${key}: ${JSON.stringify(value?.content)}` + // }) + // .join(', ') + + console.log('props', props) + + // console.log('ppp', oper.props) + + // genWithDirective(oper, ctx) + + // console.log('res ', genWithDirective({ dir: oper.props[1] }, context)) + + const dir = oper.props[1] + const { push, newline, pushFnCall, pushMulti, vaporHelper } = context + + // genExpression(dir.exp, context) + if (dir.exp) { + push(', () => ') + genExpression(dir.exp, context) + } else if (dir.arg || dir.modifiers.length) { + push(', void 0') + } + + if (dir.arg) { + push(', ') + genExpression(dir.arg, context) + } else if (dir.modifiers.length) { + push(', void 0') + } + + if (dir.modifiers.length) { + push(', ') + push('{ ') + push(genDirectiveModifiers(dir.modifiers)) + push(' }') + } + + /* + pushFnCall( + vaporHelper('setAttr'), + `n${oper.element}`, + // 2. key name + () => { + if (oper.runtimeCamelize) { + pushFnCall(helper('camelize'), () => genExpression(oper.key, context)) + } else { + genExpression(oper.key, context) + } + }, + 'undefined', + () => genExpression(oper.value, context), + ) */ + } + if (oper.props.length) { + props = oper.props + .map((prop) => { + const { name, value, exp, arg } = prop + // const key = oper.runtimeCamelize + let key = null + + // const content = + + let content = null + + switch (prop?.type) { + case NodeTypes.ATTRIBUTE: { + content = value ? JSON.stringify(value.content) : undefined + key = camelize(name) + break + } + case NodeTypes.DIRECTIVE: { + console.log('found ', prop) + // content = genExpression(prop.exp, context) + key = camelize(arg.content) + + // content = genWithDirective({ dir: prop }, context) + // genExpression(dir.exp, context) + + genIdentifier( + exp.content, + { + inline: context.inline, + bindingMetadata: context.bindingMetadata, + push: (c) => (content = c), + }, + { + start: advancePositionWithClone( + exp.loc.start, + exp.content, + exp.loc.start, + ), + end: advancePositionWithClone( + exp.loc.start, + exp.content, + exp.loc.end, + ), + source: exp.content, + }, + ) + + return `get ${key}() { return ${content} }` + + // genExpression(exp, { + // ...context, + // push: (c) => (content = c), + // }) + break + } + } + + return `${key}: ${content}` + }) + .join(', ') + + console.log('props', props) + } + + pushFnCall( + vaporHelper('createComponent'), + () => genExpression(oper.tag, context), + // () => { + // // oper.props.map((x) => genExpression(x, context)) + // }, + props ? `{ ${props} }` : '{}', + ) +} + 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..4ddc31428 100644 --- a/packages/compiler-vapor/src/ir.ts +++ b/packages/compiler-vapor/src/ir.ts @@ -1,4 +1,5 @@ import type { + AttributeNode, CompoundExpressionNode, DirectiveNode, RootNode, @@ -22,6 +23,7 @@ export enum IRNodeTypes { PREPEND_NODE, APPEND_NODE, CREATE_TEXT_NODE, + CREATE_COMPONENT, WITH_DIRECTIVE, } @@ -98,6 +100,16 @@ export interface CreateTextNodeIRNode extends BaseIRNode { value: IRExpression } +export interface CreateComponentIRNode extends BaseIRNode { + type: IRNodeTypes.CREATE_COMPONENT + id: number + tag: string + + children: string[] + props: AttributeNode[] + // children: IRExpression +} + export interface InsertNodeIRNode extends BaseIRNode { type: IRNodeTypes.INSERT_NODE element: number | number[] @@ -134,6 +146,7 @@ export type OperationNode = | SetEventIRNode | SetHtmlIRNode | CreateTextNodeIRNode + | CreateComponentIRNode | InsertNodeIRNode | PrependNodeIRNode | AppendNodeIRNode diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index 5b3787571..5bc3f2002 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -25,20 +25,35 @@ export const transformElement: NodeTransform = (node, ctx) => { const { tag, props } = node const isComponent = node.tagType === ElementTypes.COMPONENT - ctx.template += `<${tag}` - if (props.length) { - buildProps( - node, - ctx as TransformContext, - undefined, - isComponent, - ) - } - ctx.template += `>` + ctx.childrenTemplate.join('') + if (isComponent) { + ctx.dynamic.ghost = true + + ctx.registerOperation({ + type: IRNodeTypes.CREATE_COMPONENT, + id: ctx.reference(), + loc: node.loc, + tag: tag, + children: ctx.childrenTemplate, + // TODO add type + props: props, + }) + } else { + ctx.template += `<${tag}` + + if (props.length) { + buildProps( + node, + ctx as TransformContext, + undefined, + 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)) { - ctx.template += `` + // TODO remove unnecessary close tag, e.g. if it's the last element of the template + if (!isVoidTag(tag)) { + ctx.template += `` + } } } } @@ -49,6 +64,7 @@ function buildProps( props: ElementNode['props'] = node.props, isComponent: boolean, ) { + if (isComponent) console.log('props===== ', props) for (const prop of props) { transformProp(prop as VaporDirectiveNode | AttributeNode, node, context) } diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index d1e5723f2..cd3fcaf8c 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -1,7 +1,7 @@ import { EffectScope, type Ref, ref } from '@vue/reactivity' import { EMPTY_OBJ } from '@vue/shared' -import type { Block } from './render' +import { type Block, render } from './render' import type { DirectiveBinding } from './directive' import { type ComponentPropsOptions, @@ -217,3 +217,7 @@ export const createComponentInstance = ( } return instance } + +export function createComponent(component: Component, props: Data) { + return render(component, props, null).block +} diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index d34c9f483..22fb7f656 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -46,4 +46,9 @@ export * from './directive' export * from './dom' export * from './directives/vShow' export * from './apiLifecycle' -export { getCurrentInstance, type ComponentInternalInstance } from './component' + +export { + getCurrentInstance, + type ComponentInternalInstance, + createComponent, +} from './component' diff --git a/packages/runtime-vapor/src/render.ts b/packages/runtime-vapor/src/render.ts index bce3a2be7..2a3365c7c 100644 --- a/packages/runtime-vapor/src/render.ts +++ b/packages/runtime-vapor/src/render.ts @@ -24,14 +24,16 @@ export function getIsRendering() { export function render( comp: Component, props: Data, - container: string | ParentNode, + container: string | ParentNode | null, ): ComponentInternalInstance { const instance = createComponentInstance(comp) initProps(instance, props) return mountComponent(instance, (container = normalizeContainer(container))) } -export function normalizeContainer(container: string | ParentNode): ParentNode { +export function normalizeContainer( + container: string | ParentNode | null, +): ParentNode | null { return typeof container === 'string' ? // eslint-disable-next-line no-restricted-globals (document.querySelector(container) as ParentNode) @@ -40,7 +42,7 @@ export function normalizeContainer(container: string | ParentNode): ParentNode { export function mountComponent( instance: ComponentInternalInstance, - container: ParentNode, + container: ParentNode | null, ) { instance.container = container @@ -76,9 +78,10 @@ export function mountComponent( bm && invokeArrayFns(bm) invokeDirectiveHook(instance, 'beforeMount') - insert(block, instance.container) - instance.isMountedRef.value = true - + if (container) { + insert(block, instance.container) + instance.isMountedRef.value = true + } // hook: mounted invokeDirectiveHook(instance, 'mounted') m && invokeArrayFns(m) diff --git a/playground/src/component-child.vue b/playground/src/component-child.vue new file mode 100644 index 000000000..6c4ca79cc --- /dev/null +++ b/playground/src/component-child.vue @@ -0,0 +1,19 @@ + + diff --git a/playground/src/component.vue b/playground/src/component.vue new file mode 100644 index 000000000..61e890047 --- /dev/null +++ b/playground/src/component.vue @@ -0,0 +1,21 @@ + + +