feat(compiler-vapor): implement basic usage of `v-slot` (#203)

Co-authored-by: Doctorwu <doctorwu@moego.pet>
Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
Rizumu Ayaka 2024-05-12 17:57:00 +08:00 committed by GitHub
parent 1c54cae29a
commit 0c33ace61c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 485 additions and 43 deletions

View File

@ -29,15 +29,16 @@ const t0 = _template("<div></div>")
export function render(_ctx) {
const _component_Bar = _resolveComponent("Bar")
const _component_Comp = _resolveComponent("Comp")
const n0 = _createIf(() => (true), () => {
const n3 = t0()
const n2 = _createComponent(_component_Bar)
_withDirectives(n2, [[_resolveDirective("vHello"), void 0, void 0, { world: true }]])
_insert(n2, n3)
return n3
})
_insert(n0, n4)
const n4 = _createComponent(_component_Comp, null, true)
const n4 = _createComponent(_component_Comp, null, { default: () => {
const n0 = _createIf(() => (true), () => {
const n3 = t0()
const n2 = _createComponent(_component_Bar)
_withDirectives(n2, [[_resolveDirective("vHello"), void 0, void 0, { world: true }]])
_insert(n2, n3)
return n3
})
return n0
} }, null, true)
_withDirectives(n4, [[_resolveDirective("vTest")]])
return n4
}"

View File

@ -5,7 +5,7 @@ exports[`compiler: element transform > component > do not resolve component from
export function render(_ctx) {
const _component_Example = _resolveComponent("Example")
const n0 = _createComponent(_component_Example, null, true)
const n0 = _createComponent(_component_Example, null, null, null, true)
return n0
}"
`;
@ -25,7 +25,7 @@ exports[`compiler: element transform > component > generate single root componen
"import { createComponent as _createComponent } from 'vue/vapor';
export function render(_ctx) {
const n0 = _createComponent(_ctx.Comp, null, true)
const n0 = _createComponent(_ctx.Comp, null, null, null, true)
return n0
}"
`;
@ -35,21 +35,21 @@ exports[`compiler: element transform > component > import + resolve component 1`
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponent(_component_Foo, null, true)
const n0 = _createComponent(_component_Foo, null, null, null, true)
return n0
}"
`;
exports[`compiler: element transform > component > resolve component from setup bindings (inline const) 1`] = `
"(() => {
const n0 = _createComponent(Example, null, true)
const n0 = _createComponent(Example, null, null, null, true)
return n0
})()"
`;
exports[`compiler: element transform > component > resolve component from setup bindings (inline) 1`] = `
"(() => {
const n0 = _createComponent(_unref(Example), null, true)
const n0 = _createComponent(_unref(Example), null, null, null, true)
return n0
})()"
`;
@ -58,14 +58,14 @@ exports[`compiler: element transform > component > resolve component from setup
"import { createComponent as _createComponent } from 'vue/vapor';
export function render(_ctx) {
const n0 = _createComponent(_ctx.Example, null, true)
const n0 = _createComponent(_ctx.Example, null, null, null, true)
return n0
}"
`;
exports[`compiler: element transform > component > resolve namespaced component from props bindings (inline) 1`] = `
"(() => {
const n0 = _createComponent(Foo.Example, null, true)
const n0 = _createComponent(Foo.Example, null, null, null, true)
return n0
})()"
`;
@ -74,14 +74,14 @@ exports[`compiler: element transform > component > resolve namespaced component
"import { createComponent as _createComponent } from 'vue/vapor';
export function render(_ctx) {
const n0 = _createComponent(_ctx.Foo.Example, null, true)
const n0 = _createComponent(_ctx.Foo.Example, null, null, null, true)
return n0
}"
`;
exports[`compiler: element transform > component > resolve namespaced component from setup bindings (inline const) 1`] = `
"(() => {
const n0 = _createComponent(Foo.Example, null, true)
const n0 = _createComponent(Foo.Example, null, null, null, true)
return n0
})()"
`;
@ -90,7 +90,7 @@ exports[`compiler: element transform > component > resolve namespaced component
"import { createComponent as _createComponent } from 'vue/vapor';
export function render(_ctx) {
const n0 = _createComponent(_ctx.Foo.Example, null, true)
const n0 = _createComponent(_ctx.Foo.Example, null, null, null, true)
return n0
}"
`;
@ -102,7 +102,7 @@ export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponent(_component_Foo, [
{ onBar: () => $event => (_ctx.handleBar($event)) }
], true)
], null, null, true)
return n0
}"
`;
@ -117,7 +117,7 @@ export function render(_ctx) {
id: () => ("foo"),
class: () => ("bar")
}
], true)
], null, null, true)
return n0
}"
`;
@ -129,7 +129,7 @@ export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponent(_component_Foo, [
() => (_ctx.obj)
], true)
], null, null, true)
return n0
}"
`;
@ -142,7 +142,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Foo, [
{ id: () => ("foo") },
() => (_ctx.obj)
], true)
], null, null, true)
return n0
}"
`;
@ -155,7 +155,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Foo, [
() => (_ctx.obj),
{ id: () => ("foo") }
], true)
], null, null, true)
return n0
}"
`;
@ -169,7 +169,7 @@ export function render(_ctx) {
{ id: () => ("foo") },
() => (_ctx.obj),
{ class: () => ("bar") }
], true)
], null, null, true)
return n0
}"
`;
@ -181,7 +181,7 @@ export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponent(_component_Foo, [
() => (_toHandlers(_ctx.obj))
], true)
], null, null, true)
return n0
}"
`;
@ -195,7 +195,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Foo, [
() => ({ [_toHandlerKey(_ctx.foo-_ctx.bar)]: () => _ctx.bar }),
() => ({ [_toHandlerKey(_ctx.baz)]: () => _ctx.qux })
], true)
], null, null, true)
return n0
}"
`;
@ -208,7 +208,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Foo, [
() => ({ [_ctx.foo-_ctx.bar]: _ctx.bar }),
() => ({ [_ctx.baz]: _ctx.qux })
], true)
], null, null, true)
return n0
}"
`;

View File

@ -9,7 +9,7 @@ export function render(_ctx) {
{ modelValue: () => (_ctx.foo),
"onUpdate:modelValue": () => $event => (_ctx.foo = $event),
modelModifiers: () => ({ trim: true, "bar-baz": true }) }
], true)
], null, null, true)
return n0
}"
`;
@ -22,7 +22,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Comp, [
{ modelValue: () => (_ctx.foo),
"onUpdate:modelValue": () => $event => (_ctx.foo = $event) }
], true)
], null, null, true)
return n0
}"
`;
@ -41,7 +41,7 @@ export function render(_ctx) {
"onUpdate:bar": () => $event => (_ctx.bar = $event),
barModifiers: () => ({ number: true })
}
], true)
], null, null, true)
return n0
}"
`;
@ -54,7 +54,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Comp, [
{ bar: () => (_ctx.foo),
"onUpdate:bar": () => $event => (_ctx.foo = $event) }
], true)
], null, null, true)
return n0
}"
`;
@ -71,7 +71,7 @@ export function render(_ctx) {
() => ({ [_ctx.bar]: _ctx.bar,
["onUpdate:" + _ctx.bar]: () => $event => (_ctx.bar = $event),
[_ctx.bar + "Modifiers"]: () => ({ number: true }) })
], true)
], null, null, true)
return n0
}"
`;
@ -84,7 +84,7 @@ export function render(_ctx) {
const n0 = _createComponent(_component_Comp, [
() => ({ [_ctx.arg]: _ctx.foo,
["onUpdate:" + _ctx.arg]: () => $event => (_ctx.foo = $event) })
], true)
], null, null, true)
return n0
}"
`;

View File

@ -0,0 +1,73 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler: transform slot > dynamic slots name 1`] = `
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, template as _template } from 'vue/vapor';
const t0 = _template("foo")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n2 = _createComponent(_component_Comp, null, null, () => [{
name: _ctx.name,
fn: () => {
const n0 = t0()
return n0
}
}], true)
return n2
}"
`;
exports[`compiler: transform slot > implicit default slot 1`] = `
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n1 = _createComponent(_component_Comp, null, { default: () => {
const n0 = t0()
return n0
} }, null, true)
return n1
}"
`;
exports[`compiler: transform slot > named slots w/ implicit default slot 1`] = `
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, template as _template } from 'vue/vapor';
const t0 = _template("foo")
const t1 = _template("bar")
const t2 = _template("<span></span>")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n4 = _createComponent(_component_Comp, null, {
one: () => {
const n0 = t0()
return n0
},
default: () => {
const n2 = t1()
const n3 = t2()
return [n2, n3]
}
}, null, true)
return n4
}"
`;
exports[`compiler: transform slot > nested slots 1`] = `
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const _component_Bar = _resolveComponent("Bar")
const _component_Foo = _resolveComponent("Foo")
const n3 = _createComponent(_component_Foo, null, { one: () => {
const n1 = _createComponent(_component_Bar, null, { default: () => {
const n0 = t0()
return n0
} })
return n1
} }, null, true)
return n3
}"
`;

View File

@ -182,7 +182,9 @@ describe('compiler: element transform', () => {
bindingMetadata: { Comp: BindingTypes.SETUP_CONST },
})
expect(code).toMatchSnapshot()
expect(code).contains('_createComponent(_ctx.Comp, null, true)')
expect(code).contains(
'_createComponent(_ctx.Comp, null, null, null, true)',
)
})
test('generate multi root component', () => {

View File

@ -0,0 +1,174 @@
import { ErrorCodes, NodeTypes } from '@vue/compiler-core'
import {
IRNodeTypes,
transformChildren,
transformElement,
transformSlotOutlet,
transformText,
transformVBind,
transformVFor,
transformVIf,
transformVOn,
transformVSlot,
} from '../../src'
import { makeCompile } from './_utils'
const compileWithSlots = makeCompile({
nodeTransforms: [
transformText,
transformVIf,
transformVFor,
transformSlotOutlet,
transformElement,
transformVSlot,
transformChildren,
],
directiveTransforms: {
bind: transformVBind,
on: transformVOn,
},
})
describe('compiler: transform slot', () => {
test('implicit default slot', () => {
const { ir, code } = compileWithSlots(`<Comp><div/></Comp>`)
expect(code).toMatchSnapshot()
expect(ir.template).toEqual(['<div></div>'])
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: 1,
tag: 'Comp',
props: [[]],
slots: {
default: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
},
},
},
},
])
expect(ir.block.returns).toEqual([1])
expect(ir.block.dynamic).toMatchObject({
children: [{ id: 1 }],
})
})
test('named slots w/ implicit default slot', () => {
const { ir, code } = compileWithSlots(
`<Comp>
<template #one>foo</template>bar<span/>
</Comp>`,
)
expect(code).toMatchSnapshot()
expect(ir.template).toEqual(['foo', 'bar', '<span></span>'])
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: 4,
tag: 'Comp',
props: [[]],
slots: {
one: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{ template: 0 }],
},
},
default: {
type: IRNodeTypes.BLOCK,
dynamic: {
children: [{}, { template: 1 }, { template: 2 }],
},
},
},
},
])
})
test('nested slots', () => {
const { code } = compileWithSlots(
`<Foo>
<template #one><Bar><div/></Bar></template>
</Foo>`,
)
expect(code).toMatchSnapshot()
})
test('dynamic slots name', () => {
const { ir, code } = compileWithSlots(
`<Comp>
<template #[name]>foo</template>
</Comp>`,
)
expect(code).toMatchSnapshot()
expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
slots: undefined,
dynamicSlots: [
{
name: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'name',
isStatic: false,
},
fn: { type: IRNodeTypes.BLOCK },
},
],
},
])
})
describe('errors', () => {
test('error on extraneous children w/ named default slot', () => {
const onError = vi.fn()
const source = `<Comp><template #default>foo</template>bar</Comp>`
compileWithSlots(source, { onError })
const index = source.indexOf('bar')
expect(onError.mock.calls[0][0]).toMatchObject({
code: ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
loc: {
start: {
offset: index,
line: 1,
column: index + 1,
},
end: {
offset: index + 3,
line: 1,
column: index + 4,
},
},
})
})
test('error on duplicated slot names', () => {
const onError = vi.fn()
const source = `<Comp><template #foo></template><template #foo></template></Comp>`
compileWithSlots(source, { onError })
const index = source.lastIndexOf('#foo')
expect(onError.mock.calls[0][0]).toMatchObject({
code: ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES,
loc: {
start: {
offset: index,
line: 1,
column: index + 1,
},
end: {
offset: index + 4,
line: 1,
column: index + 5,
},
},
})
})
})
})

View File

@ -28,6 +28,7 @@ import { transformVIf } from './transforms/vIf'
import { transformVFor } from './transforms/vFor'
import { transformComment } from './transforms/transformComment'
import { transformSlotOutlet } from './transforms/transformSlotOutlet'
import { transformVSlot } from './transforms/vSlot'
import type { HackOptions } from './ir'
export { wrapTemplate } from './transforms/utils'
@ -108,6 +109,7 @@ export function getBaseTransformPreset(
transformTemplateRef,
transformText,
transformElement,
transformVSlot,
transformComment,
transformChildren,
],

View File

@ -1,6 +1,8 @@
import { camelize, extend, isArray } from '@vue/shared'
import type { CodegenContext } from '../generate'
import {
type ComponentDynamicSlot,
type ComponentSlots,
type CreateComponentIRNode,
IRDynamicPropsKind,
type IRProp,
@ -10,6 +12,7 @@ import {
import {
type CodeFragment,
NEWLINE,
SEGMENTS_ARRAY,
SEGMENTS_ARRAY_NEWLINE,
SEGMENTS_OBJECT,
SEGMENTS_OBJECT_NEWLINE,
@ -22,8 +25,8 @@ import { createSimpleExpression } from '@vue/compiler-dom'
import { genEventHandler } from './event'
import { genDirectiveModifiers, genDirectivesForElement } from './directive'
import { genModelHandler } from './modelValue'
import { genBlock } from './block'
// TODO: generate component slots
export function genCreateComponent(
oper: CreateComponentIRNode,
context: CodegenContext,
@ -31,7 +34,7 @@ export function genCreateComponent(
const { vaporHelper } = context
const tag = genTag()
const isRoot = oper.root
const { root, slots, dynamicSlots } = oper
const rawProps = genRawProps(oper.props, context)
return [
@ -40,8 +43,14 @@ export function genCreateComponent(
...genCall(
vaporHelper('createComponent'),
tag,
rawProps || (isRoot ? 'null' : false),
isRoot && 'true',
rawProps || (slots || dynamicSlots || root ? 'null' : false),
slots ? genSlots(slots, context) : dynamicSlots || root ? 'null' : false,
dynamicSlots
? genDynamicSlots(dynamicSlots, context)
: root
? 'null'
: false,
root && 'true',
),
...genDirectivesForElement(oper.id, context),
]
@ -134,3 +143,28 @@ function genModelModifiers(
const modifiersVal = genDirectiveModifiers(modelModifiers)
return [',', NEWLINE, ...modifiersKey, `: () => ({ ${modifiersVal} })`]
}
function genSlots(slots: ComponentSlots, context: CodegenContext) {
const slotList = Object.entries(slots)
return genMulti(
slotList.length > 1 ? SEGMENTS_OBJECT_NEWLINE : SEGMENTS_OBJECT,
...slotList.map(([name, slot]) => [name, ': ', ...genBlock(slot, context)]),
)
}
function genDynamicSlots(
dynamicSlots: ComponentDynamicSlot[],
context: CodegenContext,
) {
const slotsExpr = genMulti(
dynamicSlots.length > 1 ? SEGMENTS_ARRAY_NEWLINE : SEGMENTS_ARRAY,
...dynamicSlots.map(({ name, fn }) =>
genMulti(
SEGMENTS_OBJECT_NEWLINE,
['name: ', ...genExpression(name, context)],
['fn: ', ...genBlock(fn, context)],
),
),
)
return ['() => ', ...slotsExpr]
}

View File

@ -49,3 +49,4 @@ export { transformVFor } from './transforms/vFor'
export { transformVModel } from './transforms/vModel'
export { transformComment } from './transforms/transformComment'
export { transformSlotOutlet } from './transforms/transformSlotOutlet'
export { transformVSlot } from './transforms/vSlot'

View File

@ -199,12 +199,24 @@ export interface WithDirectiveIRNode extends BaseIRNode {
builtin?: VaporHelper
}
export interface ComponentSlotBlockIRNode extends BlockIRNode {
// TODO slot props
}
export type ComponentSlots = Record<string, ComponentSlotBlockIRNode>
export interface ComponentDynamicSlot {
name: SimpleExpressionNode
fn: ComponentSlotBlockIRNode
key?: string
}
export interface CreateComponentIRNode extends BaseIRNode {
type: IRNodeTypes.CREATE_COMPONENT_NODE
id: number
tag: string
props: IRProps[]
// TODO slots
slots?: ComponentSlots
dynamicSlots?: ComponentDynamicSlot[]
resolve: boolean
root: boolean

View File

@ -16,6 +16,8 @@ import {
import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared'
import {
type BlockIRNode,
type ComponentDynamicSlot,
type ComponentSlots,
DynamicFlag,
type HackOptions,
type IRDynamicInfo,
@ -77,11 +79,13 @@ export class TransformContext<T extends AllNode = AllNode> {
comment: CommentNode[] = []
component: Set<string> = this.ir.component
slots?: ComponentSlots
dynamicSlots?: ComponentDynamicSlot[]
private globalId = 0
constructor(
private ir: RootIRNode,
public ir: RootIRNode,
public node: T,
options: TransformOptions = {},
) {
@ -90,11 +94,14 @@ export class TransformContext<T extends AllNode = AllNode> {
}
enterBlock(ir: BlockIRNode, isVFor: boolean = false): () => void {
const { block, template, dynamic, childrenTemplate } = this
const { block, template, dynamic, childrenTemplate, slots, dynamicSlots } =
this
this.block = ir
this.dynamic = ir.dynamic
this.template = ''
this.childrenTemplate = []
this.slots = undefined
this.dynamicSlots = undefined
isVFor && this.inVFor++
return () => {
// exit
@ -103,6 +110,8 @@ export class TransformContext<T extends AllNode = AllNode> {
this.template = template
this.dynamic = dynamic
this.childrenTemplate = childrenTemplate
this.slots = slots
this.dynamicSlots = dynamicSlots
isVFor && this.inVFor--
}
}

View File

@ -15,7 +15,9 @@ import { DynamicFlag, type IRDynamicInfo, IRNodeTypes } from '../ir'
export const transformChildren: NodeTransform = (node, context) => {
const isFragment =
node.type === NodeTypes.ROOT ||
(node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.TEMPLATE)
(node.type === NodeTypes.ELEMENT &&
(node.tagType === ElementTypes.TEMPLATE ||
node.tagType === ElementTypes.COMPONENT))
if (!isFragment && node.type !== NodeTypes.ELEMENT) return

View File

@ -104,7 +104,11 @@ function transformComponentElement(
props: propsResult[0] ? propsResult[1] : [propsResult[1]],
resolve,
root,
slots: context.slots,
dynamicSlots: context.dynamicSlots,
})
context.slots = undefined
context.dynamicSlots = undefined
}
function resolveSetupReference(name: string, context: TransformContext) {

View File

@ -0,0 +1,120 @@
import {
type ElementNode,
ElementTypes,
ErrorCodes,
NodeTypes,
type TemplateChildNode,
createCompilerError,
isTemplateNode,
isVSlot,
} from '@vue/compiler-core'
import type { NodeTransform, TransformContext } from '../transform'
import { newBlock } from './utils'
import { type BlockIRNode, DynamicFlag, type VaporDirectiveNode } from '../ir'
import { findDir, resolveExpression } from '../utils'
// TODO dynamic slots
export const transformVSlot: NodeTransform = (node, context) => {
if (node.type !== NodeTypes.ELEMENT) return
let dir: VaporDirectiveNode | undefined
const { tagType, children } = node
const { parent } = context
const isDefaultSlot = tagType === ElementTypes.COMPONENT && children.length
const isSlotTemplate =
isTemplateNode(node) &&
parent &&
parent.node.type === NodeTypes.ELEMENT &&
parent.node.tagType === ElementTypes.COMPONENT
if (isDefaultSlot) {
const defaultChildren = children.filter(
n =>
isNonWhitespaceContent(node) &&
!(n.type === NodeTypes.ELEMENT && n.props.some(isVSlot)),
)
const [block, onExit] = createSlotBlock(
node,
context as TransformContext<ElementNode>,
)
const slots = (context.slots ||= {})
const dynamicSlots = (context.dynamicSlots ||= [])
return () => {
onExit()
if (defaultChildren.length) {
if (slots.default) {
context.options.onError(
createCompilerError(
ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
defaultChildren[0].loc,
),
)
} else {
slots.default = block
}
context.slots = slots
} else if (Object.keys(slots).length) {
context.slots = slots
}
if (dynamicSlots.length) context.dynamicSlots = dynamicSlots
}
} else if (isSlotTemplate && (dir = findDir(node, 'slot', true))) {
let { arg } = dir
context.dynamic.flags |= DynamicFlag.NON_TEMPLATE
const slots = context.slots!
const dynamicSlots = context.dynamicSlots!
const [block, onExit] = createSlotBlock(
node,
context as TransformContext<ElementNode>,
)
arg &&= resolveExpression(arg)
if (!arg || arg.isStatic) {
const slotName = arg ? arg.content : 'default'
if (slots[slotName]) {
context.options.onError(
createCompilerError(
ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES,
dir.loc,
),
)
} else {
slots[slotName] = block
}
} else {
dynamicSlots.push({
name: arg,
fn: block,
})
}
return () => onExit()
}
}
function createSlotBlock(
slotNode: ElementNode,
context: TransformContext<ElementNode>,
): [BlockIRNode, () => void] {
const branch: BlockIRNode = newBlock(slotNode)
const exitBlock = context.enterBlock(branch)
return [branch, exitBlock]
}
function isNonWhitespaceContent(node: TemplateChildNode): boolean {
if (node.type !== NodeTypes.TEXT && node.type !== NodeTypes.TEXT_CALL)
return true
return node.type === NodeTypes.TEXT
? !!node.content.trim()
: isNonWhitespaceContent(node.content)
}

View File

@ -5,6 +5,7 @@ import {
type ElementNode,
NodeTypes,
type SimpleExpressionNode,
findDir as _findDir,
findProp as _findProp,
createSimpleExpression,
isLiteralWhitelisted,
@ -19,6 +20,13 @@ export const findProp = _findProp as (
allowEmpty?: boolean,
) => AttributeNode | VaporDirectiveNode | undefined
/** find directive */
export const findDir = _findDir as (
node: ElementNode,
name: string | RegExp,
allowEmpty?: boolean,
) => VaporDirectiveNode | undefined
export function propToExpression(prop: AttributeNode | VaporDirectiveNode) {
return prop.type === NodeTypes.ATTRIBUTE
? prop.value