mirror of https://github.com/vuejs/core.git
feat(compiler-vapor): `v-if` (#96)
Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
parent
2b5e8e4df6
commit
ede6c29434
|
@ -44,6 +44,10 @@ PR are welcome!
|
|||
- #17
|
||||
- needs #19 first
|
||||
- [ ] `v-if` / `v-else` / `v-else-if`
|
||||
- [x] `v-if`
|
||||
- [ ] `v-else`
|
||||
- [ ] `v-else-if`
|
||||
- [ ] with `<template>`
|
||||
- #9
|
||||
- [ ] `v-for`
|
||||
- #21
|
||||
|
|
|
@ -175,7 +175,6 @@ exports[`compile > dynamic root 1`] = `
|
|||
|
||||
export function render(_ctx) {
|
||||
const t0 = _fragment()
|
||||
|
||||
const n0 = t0()
|
||||
const n1 = _createTextNode(1)
|
||||
const n2 = _createTextNode(2)
|
||||
|
@ -217,7 +216,6 @@ export function render(_ctx) {
|
|||
exports[`compile > expression parsing > interpolation 1`] = `
|
||||
"(() => {
|
||||
const t0 = _fragment()
|
||||
|
||||
const n0 = t0()
|
||||
const n1 = _createTextNode(a + b.value)
|
||||
_append(n0, n1)
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`compiler: v-if > basic v-if 1`] = `
|
||||
"import { template as _template, fragment as _fragment, createIf as _createIf, children as _children, renderEffect as _renderEffect, setText as _setText, append as _append } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const t0 = _template("<div></div>")
|
||||
const t1 = _fragment()
|
||||
const n0 = t1()
|
||||
const n1 = _createIf(() => (_ctx.ok), () => {
|
||||
const n2 = t0()
|
||||
const { 0: [n3],} = _children(n2)
|
||||
_renderEffect(() => {
|
||||
_setText(n3, _ctx.msg)
|
||||
})
|
||||
return n2
|
||||
})
|
||||
_append(n0, n1)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: v-if > dedupe same template 1`] = `
|
||||
"import { template as _template, fragment as _fragment, createIf as _createIf, append as _append } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const t0 = _template("<div>hello</div>")
|
||||
const t1 = _fragment()
|
||||
const n0 = t1()
|
||||
const n1 = _createIf(() => (_ctx.ok), () => {
|
||||
const n2 = t0()
|
||||
return n2
|
||||
})
|
||||
const n3 = _createIf(() => (_ctx.ok), () => {
|
||||
const n4 = t0()
|
||||
return n4
|
||||
})
|
||||
_append(n0, n1, n3)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: v-if > template v-if 1`] = `
|
||||
"import { template as _template, fragment as _fragment, createIf as _createIf, children as _children, renderEffect as _renderEffect, setText as _setText, append as _append } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const t0 = _template("<div></div>hello<p></p>")
|
||||
const t1 = _fragment()
|
||||
const n0 = t1()
|
||||
const n1 = _createIf(() => (_ctx.ok), () => {
|
||||
const n2 = t0()
|
||||
const { 2: [n3],} = _children(n2)
|
||||
_renderEffect(() => {
|
||||
_setText(n3, _ctx.msg)
|
||||
})
|
||||
return n2
|
||||
})
|
||||
_append(n0, n1)
|
||||
return n0
|
||||
}"
|
||||
`;
|
|
@ -0,0 +1,52 @@
|
|||
import { makeCompile } from './_utils'
|
||||
import {
|
||||
transformElement,
|
||||
transformInterpolation,
|
||||
transformOnce,
|
||||
transformVIf,
|
||||
transformVText,
|
||||
} from '../../src'
|
||||
|
||||
const compileWithVIf = makeCompile({
|
||||
nodeTransforms: [
|
||||
transformOnce,
|
||||
transformInterpolation,
|
||||
transformVIf,
|
||||
transformElement,
|
||||
],
|
||||
directiveTransforms: {
|
||||
text: transformVText,
|
||||
},
|
||||
})
|
||||
|
||||
describe('compiler: v-if', () => {
|
||||
test('basic v-if', () => {
|
||||
const { code } = compileWithVIf(`<div v-if="ok">{{msg}}</div>`)
|
||||
expect(code).matchSnapshot()
|
||||
})
|
||||
|
||||
test('template v-if', () => {
|
||||
const { code } = compileWithVIf(
|
||||
`<template v-if="ok"><div/>hello<p v-text="msg"/></template>`,
|
||||
)
|
||||
expect(code).matchSnapshot()
|
||||
})
|
||||
|
||||
test('dedupe same template', () => {
|
||||
const { code, ir } = compileWithVIf(
|
||||
`<div v-if="ok">hello</div><div v-if="ok">hello</div>`,
|
||||
)
|
||||
expect(code).matchSnapshot()
|
||||
expect(ir.template).lengthOf(2)
|
||||
})
|
||||
|
||||
test.todo('v-if with v-once')
|
||||
test.todo('component v-if')
|
||||
test.todo('v-if + v-else')
|
||||
test.todo('v-if + v-else-if')
|
||||
test.todo('v-if + v-else-if + v-else')
|
||||
test.todo('comment between branches')
|
||||
describe.todo('errors')
|
||||
describe.todo('codegen')
|
||||
test.todo('v-on with v-if')
|
||||
})
|
|
@ -24,6 +24,7 @@ import { transformRef } from './transforms/transformRef'
|
|||
import { transformInterpolation } from './transforms/transformInterpolation'
|
||||
import type { HackOptions } from './ir'
|
||||
import { transformVModel } from './transforms/vModel'
|
||||
import { transformVIf } from './transforms/vIf'
|
||||
|
||||
export type CompilerOptions = HackOptions<BaseCompilerOptions>
|
||||
|
||||
|
@ -96,7 +97,13 @@ export function getBaseTransformPreset(
|
|||
prefixIdentifiers?: boolean,
|
||||
): TransformPreset {
|
||||
return [
|
||||
[transformOnce, transformRef, transformInterpolation, transformElement],
|
||||
[
|
||||
transformOnce,
|
||||
transformRef,
|
||||
transformInterpolation,
|
||||
transformVIf,
|
||||
transformElement,
|
||||
],
|
||||
{
|
||||
bind: transformVBind,
|
||||
on: transformVOn,
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
locStub,
|
||||
} from '@vue/compiler-dom'
|
||||
import {
|
||||
type BlockFunctionIRNode,
|
||||
type IRDynamicChildren,
|
||||
IRNodeTypes,
|
||||
type OperationNode,
|
||||
|
@ -26,6 +27,7 @@ import { genSetRef } from './generators/ref'
|
|||
import { genSetModelValue } from './generators/modelValue'
|
||||
import { genAppendNode, genInsertNode, genPrependNode } from './generators/dom'
|
||||
import { genWithDirective } from './generators/directive'
|
||||
import { genIf } from './generators/if'
|
||||
|
||||
interface CodegenOptions extends BaseCodegenOptions {
|
||||
expressionPlugins?: ParserPlugin[]
|
||||
|
@ -271,49 +273,11 @@ export function generate(
|
|||
)
|
||||
} else {
|
||||
// fragment
|
||||
pushNewline(
|
||||
`const t0 = ${vaporHelper('fragment')}()\n`,
|
||||
NewlineType.End,
|
||||
)
|
||||
pushNewline(`const t${i} = ${vaporHelper('fragment')}()`)
|
||||
}
|
||||
})
|
||||
|
||||
{
|
||||
pushNewline(`const n${ir.dynamic.id} = t0()`)
|
||||
|
||||
const children = genChildren(ir.dynamic.children)
|
||||
if (children) {
|
||||
pushNewline(
|
||||
`const ${children} = ${vaporHelper('children')}(n${ir.dynamic.id})`,
|
||||
)
|
||||
}
|
||||
|
||||
const directiveOps = ir.operation.filter(
|
||||
(oper): oper is WithDirectiveIRNode =>
|
||||
oper.type === IRNodeTypes.WITH_DIRECTIVE,
|
||||
)
|
||||
for (const directives of groupDirective(directiveOps)) {
|
||||
genWithDirective(directives, ctx)
|
||||
}
|
||||
|
||||
for (const operation of ir.operation) {
|
||||
genOperation(operation, ctx)
|
||||
}
|
||||
|
||||
for (const { operations } of ir.effect) {
|
||||
pushNewline(`${vaporHelper('renderEffect')}(() => {`)
|
||||
withIndent(() => {
|
||||
for (const operation of operations) {
|
||||
genOperation(operation, ctx)
|
||||
}
|
||||
})
|
||||
pushNewline('})')
|
||||
}
|
||||
|
||||
// TODO multiple-template
|
||||
// TODO return statement in IR
|
||||
pushNewline(`return n${ir.dynamic.id}`)
|
||||
}
|
||||
genBlockFunctionContent(ir, ctx)
|
||||
})
|
||||
|
||||
newline()
|
||||
|
@ -396,6 +360,8 @@ function genOperation(oper: OperationNode, context: CodegenContext) {
|
|||
return genPrependNode(oper, context)
|
||||
case IRNodeTypes.APPEND_NODE:
|
||||
return genAppendNode(oper, context)
|
||||
case IRNodeTypes.IF:
|
||||
return genIf(oper, context)
|
||||
case IRNodeTypes.WITH_DIRECTIVE:
|
||||
// generated, skip
|
||||
return
|
||||
|
@ -404,6 +370,45 @@ function genOperation(oper: OperationNode, context: CodegenContext) {
|
|||
}
|
||||
}
|
||||
|
||||
export function genBlockFunctionContent(
|
||||
ir: BlockFunctionIRNode | RootIRNode,
|
||||
ctx: CodegenContext,
|
||||
) {
|
||||
const { pushNewline, withIndent, vaporHelper } = ctx
|
||||
pushNewline(`const n${ir.dynamic.id} = t${ir.templateIndex}()`)
|
||||
|
||||
const children = genChildren(ir.dynamic.children)
|
||||
if (children) {
|
||||
pushNewline(
|
||||
`const ${children} = ${vaporHelper('children')}(n${ir.dynamic.id})`,
|
||||
)
|
||||
}
|
||||
|
||||
const directiveOps = ir.operation.filter(
|
||||
(oper): oper is WithDirectiveIRNode =>
|
||||
oper.type === IRNodeTypes.WITH_DIRECTIVE,
|
||||
)
|
||||
for (const directives of groupDirective(directiveOps)) {
|
||||
genWithDirective(directives, ctx)
|
||||
}
|
||||
|
||||
for (const operation of ir.operation) {
|
||||
genOperation(operation, ctx)
|
||||
}
|
||||
|
||||
for (const { operations } of ir.effect) {
|
||||
pushNewline(`${vaporHelper('renderEffect')}(() => {`)
|
||||
withIndent(() => {
|
||||
for (const operation of operations) {
|
||||
genOperation(operation, ctx)
|
||||
}
|
||||
})
|
||||
pushNewline('})')
|
||||
}
|
||||
|
||||
pushNewline(`return n${ir.dynamic.id}`)
|
||||
}
|
||||
|
||||
function groupDirective(ops: WithDirectiveIRNode[]): WithDirectiveIRNode[][] {
|
||||
const directiveMap: Record<number, WithDirectiveIRNode[]> = {}
|
||||
for (const oper of ops) {
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import { type CodegenContext, genBlockFunctionContent } from '../generate'
|
||||
import type { BlockFunctionIRNode, IfIRNode } from '../ir'
|
||||
import { genExpression } from './expression'
|
||||
|
||||
export function genIf(oper: IfIRNode, context: CodegenContext) {
|
||||
const { pushFnCall, vaporHelper, pushNewline, push, withIndent } = context
|
||||
const { condition, positive, negative } = oper
|
||||
|
||||
pushNewline(`const n${oper.id} = `)
|
||||
pushFnCall(
|
||||
vaporHelper('createIf'),
|
||||
() => {
|
||||
push('() => (')
|
||||
genExpression(condition, context)
|
||||
push(')')
|
||||
},
|
||||
() => genBlockFunction(positive),
|
||||
!!negative && (() => genBlockFunction(negative!)),
|
||||
)
|
||||
|
||||
function genBlockFunction(oper: BlockFunctionIRNode) {
|
||||
push('() => {')
|
||||
withIndent(() => {
|
||||
genBlockFunctionContent(oper, context)
|
||||
})
|
||||
pushNewline('}')
|
||||
}
|
||||
}
|
|
@ -12,3 +12,4 @@ export { transformVOn } from './transforms/vOn'
|
|||
export { transformOnce } from './transforms/vOnce'
|
||||
export { transformVShow } from './transforms/vShow'
|
||||
export { transformVText } from './transforms/vText'
|
||||
export { transformVIf } from './transforms/vIf'
|
||||
|
|
|
@ -5,6 +5,7 @@ import type {
|
|||
RootNode,
|
||||
SimpleExpressionNode,
|
||||
SourceLocation,
|
||||
TemplateChildNode,
|
||||
} from '@vue/compiler-dom'
|
||||
import type { Prettify } from '@vue/shared'
|
||||
import type { DirectiveTransform, NodeTransform } from './transform'
|
||||
|
@ -27,6 +28,9 @@ export enum IRNodeTypes {
|
|||
CREATE_TEXT_NODE,
|
||||
|
||||
WITH_DIRECTIVE,
|
||||
|
||||
IF,
|
||||
BLOCK_FUNCTION,
|
||||
}
|
||||
|
||||
export interface BaseIRNode {
|
||||
|
@ -37,16 +41,30 @@ export interface BaseIRNode {
|
|||
// TODO refactor
|
||||
export type VaporHelper = keyof typeof import('../../runtime-vapor/src')
|
||||
|
||||
export interface RootIRNode extends BaseIRNode {
|
||||
type: IRNodeTypes.ROOT
|
||||
source: string
|
||||
node: RootNode
|
||||
template: Array<TemplateFactoryIRNode | FragmentFactoryIRNode>
|
||||
export interface BlockFunctionIRNode extends BaseIRNode {
|
||||
type: IRNodeTypes.BLOCK_FUNCTION
|
||||
node: RootNode | TemplateChildNode
|
||||
templateIndex: number
|
||||
dynamic: IRDynamicInfo
|
||||
effect: IREffect[]
|
||||
operation: OperationNode[]
|
||||
}
|
||||
|
||||
export interface RootIRNode extends Omit<BlockFunctionIRNode, 'type'> {
|
||||
type: IRNodeTypes.ROOT
|
||||
node: RootNode
|
||||
source: string
|
||||
template: Array<TemplateFactoryIRNode | FragmentFactoryIRNode>
|
||||
}
|
||||
|
||||
export interface IfIRNode extends BaseIRNode {
|
||||
type: IRNodeTypes.IF
|
||||
id: number
|
||||
condition: IRExpression
|
||||
positive: BlockFunctionIRNode
|
||||
negative?: BlockFunctionIRNode
|
||||
}
|
||||
|
||||
export interface TemplateFactoryIRNode extends BaseIRNode {
|
||||
type: IRNodeTypes.TEMPLATE_FACTORY
|
||||
template: string
|
||||
|
@ -158,6 +176,9 @@ export type OperationNode =
|
|||
| PrependNodeIRNode
|
||||
| AppendNodeIRNode
|
||||
| WithDirectiveIRNode
|
||||
| IfIRNode
|
||||
|
||||
export type BlockIRNode = RootIRNode | BlockFunctionIRNode
|
||||
|
||||
export interface IRDynamicInfo {
|
||||
id: number | null
|
||||
|
|
|
@ -3,14 +3,16 @@ import {
|
|||
type TransformOptions as BaseTransformOptions,
|
||||
type CompilerCompatOptions,
|
||||
type ElementNode,
|
||||
ElementTypes,
|
||||
NodeTypes,
|
||||
type ParentNode,
|
||||
type RootNode,
|
||||
type TemplateChildNode,
|
||||
defaultOnError,
|
||||
defaultOnWarn,
|
||||
isVSlot,
|
||||
} from '@vue/compiler-dom'
|
||||
import { EMPTY_OBJ, NOOP, extend, isArray } from '@vue/shared'
|
||||
import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared'
|
||||
import {
|
||||
type IRDynamicInfo,
|
||||
type IRExpression,
|
||||
|
@ -18,7 +20,13 @@ import {
|
|||
type OperationNode,
|
||||
type RootIRNode,
|
||||
} from './ir'
|
||||
import type { HackOptions, VaporDirectiveNode } from './ir'
|
||||
import type {
|
||||
BlockIRNode,
|
||||
FragmentFactoryIRNode,
|
||||
HackOptions,
|
||||
TemplateFactoryIRNode,
|
||||
VaporDirectiveNode,
|
||||
} from './ir'
|
||||
|
||||
export type NodeTransform = (
|
||||
node: RootNode | TemplateChildNode,
|
||||
|
@ -31,6 +39,14 @@ export type DirectiveTransform = (
|
|||
context: TransformContext<ElementNode>,
|
||||
) => void
|
||||
|
||||
// A structural directive transform is technically also a NodeTransform;
|
||||
// Only v-if and v-for fall into this category.
|
||||
export type StructuralDirectiveTransform = (
|
||||
node: RootNode | TemplateChildNode,
|
||||
dir: VaporDirectiveNode,
|
||||
context: TransformContext<RootNode | TemplateChildNode>,
|
||||
) => void | (() => void)
|
||||
|
||||
export type TransformOptions = HackOptions<BaseTransformOptions>
|
||||
|
||||
export interface TransformContext<T extends AllNode = AllNode> {
|
||||
|
@ -38,6 +54,7 @@ export interface TransformContext<T extends AllNode = AllNode> {
|
|||
parent: TransformContext<ParentNode> | null
|
||||
root: TransformContext<RootNode>
|
||||
index: number
|
||||
block: BlockIRNode
|
||||
options: Required<
|
||||
Omit<TransformOptions, 'filename' | keyof CompilerCompatOptions>
|
||||
>
|
||||
|
@ -48,6 +65,7 @@ export interface TransformContext<T extends AllNode = AllNode> {
|
|||
|
||||
inVOnce: boolean
|
||||
|
||||
enterBlock(ir: TransformContext['block']): () => void
|
||||
reference(): number
|
||||
increaseId(): number
|
||||
registerTemplate(): number
|
||||
|
@ -84,20 +102,34 @@ const defaultOptions = {
|
|||
|
||||
// TODO use class for better perf
|
||||
function createRootContext(
|
||||
ir: RootIRNode,
|
||||
root: RootIRNode,
|
||||
node: RootNode,
|
||||
options: TransformOptions = {},
|
||||
): TransformContext<RootNode> {
|
||||
let globalId = 0
|
||||
const { effect, operation: operation } = ir
|
||||
|
||||
const ctx: TransformContext<RootNode> = {
|
||||
node,
|
||||
parent: null,
|
||||
index: 0,
|
||||
root: null!, // set later
|
||||
block: root,
|
||||
enterBlock(ir) {
|
||||
const { block, template, dynamic, childrenTemplate } = this
|
||||
this.block = ir
|
||||
this.dynamic = ir.dynamic
|
||||
this.template = ''
|
||||
this.childrenTemplate = []
|
||||
return () => {
|
||||
// exit
|
||||
this.block = block
|
||||
this.template = template
|
||||
this.dynamic = dynamic
|
||||
this.childrenTemplate = childrenTemplate
|
||||
}
|
||||
},
|
||||
options: extend({}, defaultOptions, options),
|
||||
dynamic: ir.dynamic,
|
||||
dynamic: root.dynamic,
|
||||
inVOnce: false,
|
||||
|
||||
increaseId: () => globalId++,
|
||||
|
@ -113,13 +145,13 @@ function createRootContext(
|
|||
) {
|
||||
return this.registerOperation(...operations)
|
||||
}
|
||||
const existing = effect.find((e) =>
|
||||
const existing = this.block.effect.find((e) =>
|
||||
isSameExpression(e.expressions, expressions as IRExpression[]),
|
||||
)
|
||||
if (existing) {
|
||||
existing.operations.push(...operations)
|
||||
} else {
|
||||
effect.push({
|
||||
this.block.effect.push({
|
||||
expressions: expressions as IRExpression[],
|
||||
operations,
|
||||
})
|
||||
|
@ -140,24 +172,34 @@ function createRootContext(
|
|||
template: '',
|
||||
childrenTemplate: [],
|
||||
registerTemplate() {
|
||||
if (!ctx.template) return -1
|
||||
let templateNode: TemplateFactoryIRNode | FragmentFactoryIRNode
|
||||
|
||||
const idx = ir.template.findIndex(
|
||||
(t) =>
|
||||
t.type === IRNodeTypes.TEMPLATE_FACTORY &&
|
||||
t.template === ctx.template,
|
||||
)
|
||||
if (idx !== -1) return idx
|
||||
if (this.template) {
|
||||
const idx = root.template.findIndex(
|
||||
(t) =>
|
||||
t.type === IRNodeTypes.TEMPLATE_FACTORY &&
|
||||
t.template === this.template,
|
||||
)
|
||||
if (idx !== -1) {
|
||||
return (this.block.templateIndex = idx)
|
||||
}
|
||||
|
||||
ir.template.push({
|
||||
type: IRNodeTypes.TEMPLATE_FACTORY,
|
||||
template: ctx.template,
|
||||
loc: node.loc,
|
||||
})
|
||||
return ir.template.length - 1
|
||||
templateNode = {
|
||||
type: IRNodeTypes.TEMPLATE_FACTORY,
|
||||
template: this.template,
|
||||
loc: node.loc,
|
||||
}
|
||||
} else {
|
||||
templateNode = {
|
||||
type: IRNodeTypes.FRAGMENT_FACTORY,
|
||||
loc: node.loc,
|
||||
}
|
||||
}
|
||||
root.template.push(templateNode)
|
||||
return (this.block.templateIndex = root.template.length - 1)
|
||||
},
|
||||
registerOperation(...node) {
|
||||
operation.push(...node)
|
||||
this.block.operation.push(...node)
|
||||
},
|
||||
}
|
||||
ctx.root = ctx
|
||||
|
@ -199,6 +241,7 @@ export function transform(
|
|||
source: root.source,
|
||||
loc: root.loc,
|
||||
template: [],
|
||||
templateIndex: -1,
|
||||
dynamic: {
|
||||
id: null,
|
||||
referenced: true,
|
||||
|
@ -211,17 +254,9 @@ export function transform(
|
|||
}
|
||||
|
||||
const ctx = createRootContext(ir, root, options)
|
||||
transformNode(ctx)
|
||||
|
||||
if (ctx.node.type === NodeTypes.ROOT) {
|
||||
ctx.registerTemplate()
|
||||
}
|
||||
if (ir.template.length === 0) {
|
||||
ir.template.push({
|
||||
type: IRNodeTypes.FRAGMENT_FACTORY,
|
||||
loc: root.loc,
|
||||
})
|
||||
}
|
||||
transformNode(ctx)
|
||||
ctx.registerTemplate()
|
||||
|
||||
return ir
|
||||
}
|
||||
|
@ -251,7 +286,6 @@ function transformNode(
|
|||
node = context.node
|
||||
}
|
||||
}
|
||||
|
||||
switch (node.type) {
|
||||
case NodeTypes.ROOT:
|
||||
case NodeTypes.ELEMENT: {
|
||||
|
@ -351,3 +385,37 @@ function processDynamicChildren(ctx: TransformContext<RootNode | ElementNode>) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createStructuralDirectiveTransform(
|
||||
name: string | RegExp,
|
||||
fn: StructuralDirectiveTransform,
|
||||
): NodeTransform {
|
||||
const matches = isString(name)
|
||||
? (n: string) => n === name
|
||||
: (n: string) => name.test(n)
|
||||
|
||||
return (node, context) => {
|
||||
if (node.type === NodeTypes.ELEMENT) {
|
||||
const { props } = node
|
||||
// structural directive transforms are not concerned with slots
|
||||
// as they are handled separately in vSlot.ts
|
||||
if (node.tagType === ElementTypes.TEMPLATE && props.some(isVSlot)) {
|
||||
return
|
||||
}
|
||||
const exitFns = []
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
const prop = props[i]
|
||||
if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {
|
||||
// structural directives are removed to avoid infinite recursion
|
||||
// also we remove them *before* applying so that it can further
|
||||
// traverse itself in case it moves the node around
|
||||
props.splice(i, 1)
|
||||
i--
|
||||
const onExit = fn(node, prop as VaporDirectiveNode, context)
|
||||
if (onExit) exitFns.push(onExit)
|
||||
}
|
||||
}
|
||||
return exitFns
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
import {
|
||||
ElementTypes,
|
||||
NodeTypes,
|
||||
type RootNode,
|
||||
type TemplateChildNode,
|
||||
type TemplateNode,
|
||||
} from '@vue/compiler-dom'
|
||||
import {
|
||||
type TransformContext,
|
||||
createStructuralDirectiveTransform,
|
||||
} from '../transform'
|
||||
import {
|
||||
type BlockFunctionIRNode,
|
||||
IRNodeTypes,
|
||||
type IfIRNode,
|
||||
type VaporDirectiveNode,
|
||||
} from '../ir'
|
||||
import { extend } from '@vue/shared'
|
||||
|
||||
export const transformVIf = createStructuralDirectiveTransform(
|
||||
/^(if|else|else-if)$/,
|
||||
processIf,
|
||||
)
|
||||
|
||||
export function processIf(
|
||||
node: RootNode | TemplateChildNode,
|
||||
dir: VaporDirectiveNode,
|
||||
context: TransformContext<RootNode | TemplateChildNode>,
|
||||
) {
|
||||
// TODO refactor this
|
||||
const parentContext = extend({}, context, {
|
||||
currentScopeIR: context.block,
|
||||
})
|
||||
|
||||
if (dir.name === 'if') {
|
||||
const id = context.reference()
|
||||
context.dynamic.ghost = true
|
||||
const [branch, onExit] = createIfBranch(node, dir, context)
|
||||
const operation: IfIRNode = {
|
||||
type: IRNodeTypes.IF,
|
||||
id,
|
||||
loc: dir.loc,
|
||||
condition: dir.exp!,
|
||||
positive: branch,
|
||||
}
|
||||
parentContext.registerOperation(operation)
|
||||
return onExit
|
||||
}
|
||||
}
|
||||
|
||||
export function createIfBranch(
|
||||
node: RootNode | TemplateChildNode,
|
||||
dir: VaporDirectiveNode,
|
||||
context: TransformContext<RootNode | TemplateChildNode>,
|
||||
): [BlockFunctionIRNode, () => void] {
|
||||
if (
|
||||
node.type === NodeTypes.ELEMENT &&
|
||||
node.tagType !== ElementTypes.TEMPLATE
|
||||
) {
|
||||
node = extend({}, node, {
|
||||
tagType: ElementTypes.TEMPLATE,
|
||||
children: [node],
|
||||
} as TemplateNode)
|
||||
context.node = node
|
||||
}
|
||||
|
||||
const branch: BlockFunctionIRNode = {
|
||||
type: IRNodeTypes.BLOCK_FUNCTION,
|
||||
loc: dir.loc,
|
||||
node: node,
|
||||
templateIndex: -1,
|
||||
dynamic: {
|
||||
id: null,
|
||||
referenced: true,
|
||||
ghost: true,
|
||||
placeholder: null,
|
||||
children: {},
|
||||
},
|
||||
effect: [],
|
||||
operation: [],
|
||||
}
|
||||
|
||||
const exitBlock = context.enterBlock(branch)
|
||||
context.reference()
|
||||
const onExit = () => {
|
||||
context.template += context.childrenTemplate.join('')
|
||||
context.registerTemplate()
|
||||
exitBlock()
|
||||
}
|
||||
return [branch, onExit]
|
||||
}
|
|
@ -18,7 +18,22 @@ export function insert(block: Block, parent: Node, anchor: Node | null = null) {
|
|||
// }
|
||||
}
|
||||
|
||||
export function prepend(parent: ParentBlock, ...nodes: Node[]) {
|
||||
export function prepend(parent: ParentBlock, ...blocks: Block[]) {
|
||||
const nodes: Node[] = []
|
||||
|
||||
for (const block of blocks) {
|
||||
if (block instanceof Node) {
|
||||
nodes.push(block)
|
||||
} else if (isArray(block)) {
|
||||
prepend(parent, ...block)
|
||||
} else {
|
||||
prepend(parent, block.nodes)
|
||||
block.anchor && prepend(parent, block.anchor)
|
||||
}
|
||||
}
|
||||
|
||||
if (!nodes.length) return
|
||||
|
||||
if (parent instanceof Node) {
|
||||
// TODO use insertBefore for better performance https://jsbench.me/rolpg250hh/1
|
||||
parent.prepend(...nodes)
|
||||
|
@ -27,7 +42,22 @@ export function prepend(parent: ParentBlock, ...nodes: Node[]) {
|
|||
}
|
||||
}
|
||||
|
||||
export function append(parent: ParentBlock, ...nodes: Node[]) {
|
||||
export function append(parent: ParentBlock, ...blocks: Block[]) {
|
||||
const nodes: Node[] = []
|
||||
|
||||
for (const block of blocks) {
|
||||
if (block instanceof Node) {
|
||||
nodes.push(block)
|
||||
} else if (isArray(block)) {
|
||||
append(parent, ...block)
|
||||
} else {
|
||||
append(parent, block.nodes)
|
||||
block.anchor && append(parent, block.anchor)
|
||||
}
|
||||
}
|
||||
|
||||
if (!nodes.length) return
|
||||
|
||||
if (parent instanceof Node) {
|
||||
// TODO use insertBefore for better performance
|
||||
parent.append(...nodes)
|
||||
|
|
Loading…
Reference in New Issue