import { hyphenate } from '@vue/shared' import { SLOT_DEFAULT_NAME, dynamicSlotName } from '@dcloudio/uni-shared' import { formatMiniProgramEvent, isElementNode, isUserComponent, MiniProgramCompilerOptions, } from '@dcloudio/uni-cli-shared' import { ComponentNode, DirectiveNode, ElementNode, ElementTypes, ExpressionNode, findProp, isSlotOutlet, NodeTypes, RootNode, SimpleExpressionNode, SlotOutletNode, TemplateChildNode, TemplateNode, TextNode, } from '@vue/compiler-core' import { TemplateCodegenOptions } from '../options' import { genExpr } from '../codegen' import { ForElementNode, isForElementNode } from '../transforms/vFor' import { IfElementNode, isIfElementNode } from '../transforms/vIf' import { findSlotName } from '../transforms/vSlot' import { TransformContext } from '../transform' import { ATTR_VUE_PROPS } from '../transforms/utils' interface TemplateCodegenContext { code: string directive: string scopeId?: string | null event: MiniProgramCompilerOptions['event'] slot: MiniProgramCompilerOptions['slot'] lazyElement: MiniProgramCompilerOptions['lazyElement'] component: MiniProgramCompilerOptions['component'] isBuiltInComponent: TransformContext['isBuiltInComponent'] push(code: string): void } export function generate( { children }: RootNode, { slot, event, scopeId, emitFile, filename, directive, lazyElement, isBuiltInComponent, component, }: TemplateCodegenOptions ) { const context: TemplateCodegenContext = { slot, event, code: '', scopeId, directive, lazyElement, component, isBuiltInComponent, push(code) { context.code += code }, } children.forEach((node) => { genNode(node, context) }) emitFile!({ type: 'asset', fileName: filename, source: context.code }) } export function genNode( node: TemplateChildNode, context: TemplateCodegenContext ) { switch (node.type) { case NodeTypes.IF: return node.branches.forEach((node) => { genNode(node as unknown as IfElementNode, context) }) case NodeTypes.TEXT: return genText(node, context) case NodeTypes.INTERPOLATION: return genExpression(node.content, context) case NodeTypes.ELEMENT: if (node.tagType === ElementTypes.SLOT) { return genSlot(node, context) } else if (node.tagType === ElementTypes.COMPONENT) { return genComponent(node, context) } else if (node.tagType === ElementTypes.TEMPLATE) { return genTemplate(node, context) } else if (isLazyElement(node, context)) { return genLazyElement(node, context) } return genElement(node, context) } } function genText(node: TextNode, { push }: TemplateCodegenContext) { push(node.content) } function genExpression(node: ExpressionNode, { push }: TemplateCodegenContext) { push(`{{${genExpr(node)}}}`) } function genVIf(exp: string, { push, directive }: TemplateCodegenContext) { push(` ${directive}if="{{${exp}}}"`) } function genVElseIf(exp: string, { push, directive }: TemplateCodegenContext) { push(` ${directive}elif="{{${exp}}}"`) } function genVElse({ push, directive }: TemplateCodegenContext) { push(` ${directive}else`) } function genVFor( node: ForElementNode, { push, directive }: TemplateCodegenContext ) { const { sourceCode, valueAlias, indexAlias } = node.vFor push(` ${directive}for="${sourceCode}"`) if (valueAlias) { push(` ${directive}for-item="${valueAlias}"`) } if (valueAlias === 'index') { push(` ${directive}for-index="${indexAlias}"`) } const keyProp = findProp(node, 'key', true) if (keyProp) { const key = ((keyProp as DirectiveNode).exp as SimpleExpressionNode).content push(` ${directive}key="${key.includes('.') ? key.split('.')[1] : key}"`) node.props.splice(node.props.indexOf(keyProp), 1) } } function genSlot(node: SlotOutletNode, context: TemplateCodegenContext) { // 移除掉所有非name属性,即移除作用域插槽的绑定指令 node.props = node.props.filter((prop) => { if (prop.type === NodeTypes.ATTRIBUTE) { return prop.name === 'name' } else if (prop.arg?.type === NodeTypes.SIMPLE_EXPRESSION) { return prop.arg.content === 'name' } }) if (!node.children.length || context.slot.fallbackContent) { // 无后备内容或支持后备内容 return genElement(node, context) } const { push } = context const isVIfSlot = isIfElementNode(node) if (isVIfSlot) { push(``) delete (node as any).vIf } const children = node.children.slice() node.children.length = 0 push(``) genElement(node, context) push(``) push(``) children.forEach((node) => { genNode(node, context) }) push(``) if (isVIfSlot) { push(``) } } function genTemplate(node: TemplateNode, context: TemplateCodegenContext) { const slotProp = node.props.find( (prop) => prop.type === NodeTypes.DIRECTIVE && (prop.name === 'slot' || (prop.name === 'bind' && prop.arg?.type === NodeTypes.SIMPLE_EXPRESSION && prop.arg.content === 'slot')) ) as DirectiveNode | undefined // 为 bind 时,通常是作用域插槽生成的 vSlot.ts:197 createBindDirectiveNode('slot',...) if (slotProp && (slotProp.name === 'bind' || findSlotName(slotProp))) { /** * 仅百度、字节支持使用 block 作为命名插槽根节点 * 此处为了统一仅默认替换为view *