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
|
- #17
|
||||||
- needs #19 first
|
- needs #19 first
|
||||||
- [ ] `v-if` / `v-else` / `v-else-if`
|
- [ ] `v-if` / `v-else` / `v-else-if`
|
||||||
|
- [x] `v-if`
|
||||||
|
- [ ] `v-else`
|
||||||
|
- [ ] `v-else-if`
|
||||||
|
- [ ] with `<template>`
|
||||||
- #9
|
- #9
|
||||||
- [ ] `v-for`
|
- [ ] `v-for`
|
||||||
- #21
|
- #21
|
||||||
|
|
|
@ -175,7 +175,6 @@ exports[`compile > dynamic root 1`] = `
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _fragment()
|
const t0 = _fragment()
|
||||||
|
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const n1 = _createTextNode(1)
|
const n1 = _createTextNode(1)
|
||||||
const n2 = _createTextNode(2)
|
const n2 = _createTextNode(2)
|
||||||
|
@ -217,7 +216,6 @@ export function render(_ctx) {
|
||||||
exports[`compile > expression parsing > interpolation 1`] = `
|
exports[`compile > expression parsing > interpolation 1`] = `
|
||||||
"(() => {
|
"(() => {
|
||||||
const t0 = _fragment()
|
const t0 = _fragment()
|
||||||
|
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const n1 = _createTextNode(a + b.value)
|
const n1 = _createTextNode(a + b.value)
|
||||||
_append(n0, n1)
|
_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 { transformInterpolation } from './transforms/transformInterpolation'
|
||||||
import type { HackOptions } from './ir'
|
import type { HackOptions } from './ir'
|
||||||
import { transformVModel } from './transforms/vModel'
|
import { transformVModel } from './transforms/vModel'
|
||||||
|
import { transformVIf } from './transforms/vIf'
|
||||||
|
|
||||||
export type CompilerOptions = HackOptions<BaseCompilerOptions>
|
export type CompilerOptions = HackOptions<BaseCompilerOptions>
|
||||||
|
|
||||||
|
@ -96,7 +97,13 @@ export function getBaseTransformPreset(
|
||||||
prefixIdentifiers?: boolean,
|
prefixIdentifiers?: boolean,
|
||||||
): TransformPreset {
|
): TransformPreset {
|
||||||
return [
|
return [
|
||||||
[transformOnce, transformRef, transformInterpolation, transformElement],
|
[
|
||||||
|
transformOnce,
|
||||||
|
transformRef,
|
||||||
|
transformInterpolation,
|
||||||
|
transformVIf,
|
||||||
|
transformElement,
|
||||||
|
],
|
||||||
{
|
{
|
||||||
bind: transformVBind,
|
bind: transformVBind,
|
||||||
on: transformVOn,
|
on: transformVOn,
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
locStub,
|
locStub,
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import {
|
import {
|
||||||
|
type BlockFunctionIRNode,
|
||||||
type IRDynamicChildren,
|
type IRDynamicChildren,
|
||||||
IRNodeTypes,
|
IRNodeTypes,
|
||||||
type OperationNode,
|
type OperationNode,
|
||||||
|
@ -26,6 +27,7 @@ import { genSetRef } from './generators/ref'
|
||||||
import { genSetModelValue } from './generators/modelValue'
|
import { genSetModelValue } from './generators/modelValue'
|
||||||
import { genAppendNode, genInsertNode, genPrependNode } from './generators/dom'
|
import { genAppendNode, genInsertNode, genPrependNode } from './generators/dom'
|
||||||
import { genWithDirective } from './generators/directive'
|
import { genWithDirective } from './generators/directive'
|
||||||
|
import { genIf } from './generators/if'
|
||||||
|
|
||||||
interface CodegenOptions extends BaseCodegenOptions {
|
interface CodegenOptions extends BaseCodegenOptions {
|
||||||
expressionPlugins?: ParserPlugin[]
|
expressionPlugins?: ParserPlugin[]
|
||||||
|
@ -271,49 +273,11 @@ export function generate(
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// fragment
|
// fragment
|
||||||
pushNewline(
|
pushNewline(`const t${i} = ${vaporHelper('fragment')}()`)
|
||||||
`const t0 = ${vaporHelper('fragment')}()\n`,
|
|
||||||
NewlineType.End,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
{
|
genBlockFunctionContent(ir, ctx)
|
||||||
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}`)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
newline()
|
newline()
|
||||||
|
@ -396,6 +360,8 @@ function genOperation(oper: OperationNode, context: CodegenContext) {
|
||||||
return genPrependNode(oper, context)
|
return genPrependNode(oper, context)
|
||||||
case IRNodeTypes.APPEND_NODE:
|
case IRNodeTypes.APPEND_NODE:
|
||||||
return genAppendNode(oper, context)
|
return genAppendNode(oper, context)
|
||||||
|
case IRNodeTypes.IF:
|
||||||
|
return genIf(oper, context)
|
||||||
case IRNodeTypes.WITH_DIRECTIVE:
|
case IRNodeTypes.WITH_DIRECTIVE:
|
||||||
// generated, skip
|
// generated, skip
|
||||||
return
|
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[][] {
|
function groupDirective(ops: WithDirectiveIRNode[]): WithDirectiveIRNode[][] {
|
||||||
const directiveMap: Record<number, WithDirectiveIRNode[]> = {}
|
const directiveMap: Record<number, WithDirectiveIRNode[]> = {}
|
||||||
for (const oper of ops) {
|
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 { transformOnce } from './transforms/vOnce'
|
||||||
export { transformVShow } from './transforms/vShow'
|
export { transformVShow } from './transforms/vShow'
|
||||||
export { transformVText } from './transforms/vText'
|
export { transformVText } from './transforms/vText'
|
||||||
|
export { transformVIf } from './transforms/vIf'
|
||||||
|
|
|
@ -5,6 +5,7 @@ import type {
|
||||||
RootNode,
|
RootNode,
|
||||||
SimpleExpressionNode,
|
SimpleExpressionNode,
|
||||||
SourceLocation,
|
SourceLocation,
|
||||||
|
TemplateChildNode,
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import type { Prettify } from '@vue/shared'
|
import type { Prettify } from '@vue/shared'
|
||||||
import type { DirectiveTransform, NodeTransform } from './transform'
|
import type { DirectiveTransform, NodeTransform } from './transform'
|
||||||
|
@ -27,6 +28,9 @@ export enum IRNodeTypes {
|
||||||
CREATE_TEXT_NODE,
|
CREATE_TEXT_NODE,
|
||||||
|
|
||||||
WITH_DIRECTIVE,
|
WITH_DIRECTIVE,
|
||||||
|
|
||||||
|
IF,
|
||||||
|
BLOCK_FUNCTION,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseIRNode {
|
export interface BaseIRNode {
|
||||||
|
@ -37,16 +41,30 @@ export interface BaseIRNode {
|
||||||
// TODO refactor
|
// TODO refactor
|
||||||
export type VaporHelper = keyof typeof import('../../runtime-vapor/src')
|
export type VaporHelper = keyof typeof import('../../runtime-vapor/src')
|
||||||
|
|
||||||
export interface RootIRNode extends BaseIRNode {
|
export interface BlockFunctionIRNode extends BaseIRNode {
|
||||||
type: IRNodeTypes.ROOT
|
type: IRNodeTypes.BLOCK_FUNCTION
|
||||||
source: string
|
node: RootNode | TemplateChildNode
|
||||||
node: RootNode
|
templateIndex: number
|
||||||
template: Array<TemplateFactoryIRNode | FragmentFactoryIRNode>
|
|
||||||
dynamic: IRDynamicInfo
|
dynamic: IRDynamicInfo
|
||||||
effect: IREffect[]
|
effect: IREffect[]
|
||||||
operation: OperationNode[]
|
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 {
|
export interface TemplateFactoryIRNode extends BaseIRNode {
|
||||||
type: IRNodeTypes.TEMPLATE_FACTORY
|
type: IRNodeTypes.TEMPLATE_FACTORY
|
||||||
template: string
|
template: string
|
||||||
|
@ -158,6 +176,9 @@ export type OperationNode =
|
||||||
| PrependNodeIRNode
|
| PrependNodeIRNode
|
||||||
| AppendNodeIRNode
|
| AppendNodeIRNode
|
||||||
| WithDirectiveIRNode
|
| WithDirectiveIRNode
|
||||||
|
| IfIRNode
|
||||||
|
|
||||||
|
export type BlockIRNode = RootIRNode | BlockFunctionIRNode
|
||||||
|
|
||||||
export interface IRDynamicInfo {
|
export interface IRDynamicInfo {
|
||||||
id: number | null
|
id: number | null
|
||||||
|
|
|
@ -3,14 +3,16 @@ import {
|
||||||
type TransformOptions as BaseTransformOptions,
|
type TransformOptions as BaseTransformOptions,
|
||||||
type CompilerCompatOptions,
|
type CompilerCompatOptions,
|
||||||
type ElementNode,
|
type ElementNode,
|
||||||
|
ElementTypes,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
type ParentNode,
|
type ParentNode,
|
||||||
type RootNode,
|
type RootNode,
|
||||||
type TemplateChildNode,
|
type TemplateChildNode,
|
||||||
defaultOnError,
|
defaultOnError,
|
||||||
defaultOnWarn,
|
defaultOnWarn,
|
||||||
|
isVSlot,
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import { EMPTY_OBJ, NOOP, extend, isArray } from '@vue/shared'
|
import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared'
|
||||||
import {
|
import {
|
||||||
type IRDynamicInfo,
|
type IRDynamicInfo,
|
||||||
type IRExpression,
|
type IRExpression,
|
||||||
|
@ -18,7 +20,13 @@ import {
|
||||||
type OperationNode,
|
type OperationNode,
|
||||||
type RootIRNode,
|
type RootIRNode,
|
||||||
} from './ir'
|
} from './ir'
|
||||||
import type { HackOptions, VaporDirectiveNode } from './ir'
|
import type {
|
||||||
|
BlockIRNode,
|
||||||
|
FragmentFactoryIRNode,
|
||||||
|
HackOptions,
|
||||||
|
TemplateFactoryIRNode,
|
||||||
|
VaporDirectiveNode,
|
||||||
|
} from './ir'
|
||||||
|
|
||||||
export type NodeTransform = (
|
export type NodeTransform = (
|
||||||
node: RootNode | TemplateChildNode,
|
node: RootNode | TemplateChildNode,
|
||||||
|
@ -31,6 +39,14 @@ export type DirectiveTransform = (
|
||||||
context: TransformContext<ElementNode>,
|
context: TransformContext<ElementNode>,
|
||||||
) => void
|
) => 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 type TransformOptions = HackOptions<BaseTransformOptions>
|
||||||
|
|
||||||
export interface TransformContext<T extends AllNode = AllNode> {
|
export interface TransformContext<T extends AllNode = AllNode> {
|
||||||
|
@ -38,6 +54,7 @@ export interface TransformContext<T extends AllNode = AllNode> {
|
||||||
parent: TransformContext<ParentNode> | null
|
parent: TransformContext<ParentNode> | null
|
||||||
root: TransformContext<RootNode>
|
root: TransformContext<RootNode>
|
||||||
index: number
|
index: number
|
||||||
|
block: BlockIRNode
|
||||||
options: Required<
|
options: Required<
|
||||||
Omit<TransformOptions, 'filename' | keyof CompilerCompatOptions>
|
Omit<TransformOptions, 'filename' | keyof CompilerCompatOptions>
|
||||||
>
|
>
|
||||||
|
@ -48,6 +65,7 @@ export interface TransformContext<T extends AllNode = AllNode> {
|
||||||
|
|
||||||
inVOnce: boolean
|
inVOnce: boolean
|
||||||
|
|
||||||
|
enterBlock(ir: TransformContext['block']): () => void
|
||||||
reference(): number
|
reference(): number
|
||||||
increaseId(): number
|
increaseId(): number
|
||||||
registerTemplate(): number
|
registerTemplate(): number
|
||||||
|
@ -84,20 +102,34 @@ const defaultOptions = {
|
||||||
|
|
||||||
// TODO use class for better perf
|
// TODO use class for better perf
|
||||||
function createRootContext(
|
function createRootContext(
|
||||||
ir: RootIRNode,
|
root: RootIRNode,
|
||||||
node: RootNode,
|
node: RootNode,
|
||||||
options: TransformOptions = {},
|
options: TransformOptions = {},
|
||||||
): TransformContext<RootNode> {
|
): TransformContext<RootNode> {
|
||||||
let globalId = 0
|
let globalId = 0
|
||||||
const { effect, operation: operation } = ir
|
|
||||||
|
|
||||||
const ctx: TransformContext<RootNode> = {
|
const ctx: TransformContext<RootNode> = {
|
||||||
node,
|
node,
|
||||||
parent: null,
|
parent: null,
|
||||||
index: 0,
|
index: 0,
|
||||||
root: null!, // set later
|
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),
|
options: extend({}, defaultOptions, options),
|
||||||
dynamic: ir.dynamic,
|
dynamic: root.dynamic,
|
||||||
inVOnce: false,
|
inVOnce: false,
|
||||||
|
|
||||||
increaseId: () => globalId++,
|
increaseId: () => globalId++,
|
||||||
|
@ -113,13 +145,13 @@ function createRootContext(
|
||||||
) {
|
) {
|
||||||
return this.registerOperation(...operations)
|
return this.registerOperation(...operations)
|
||||||
}
|
}
|
||||||
const existing = effect.find((e) =>
|
const existing = this.block.effect.find((e) =>
|
||||||
isSameExpression(e.expressions, expressions as IRExpression[]),
|
isSameExpression(e.expressions, expressions as IRExpression[]),
|
||||||
)
|
)
|
||||||
if (existing) {
|
if (existing) {
|
||||||
existing.operations.push(...operations)
|
existing.operations.push(...operations)
|
||||||
} else {
|
} else {
|
||||||
effect.push({
|
this.block.effect.push({
|
||||||
expressions: expressions as IRExpression[],
|
expressions: expressions as IRExpression[],
|
||||||
operations,
|
operations,
|
||||||
})
|
})
|
||||||
|
@ -140,24 +172,34 @@ function createRootContext(
|
||||||
template: '',
|
template: '',
|
||||||
childrenTemplate: [],
|
childrenTemplate: [],
|
||||||
registerTemplate() {
|
registerTemplate() {
|
||||||
if (!ctx.template) return -1
|
let templateNode: TemplateFactoryIRNode | FragmentFactoryIRNode
|
||||||
|
|
||||||
const idx = ir.template.findIndex(
|
if (this.template) {
|
||||||
(t) =>
|
const idx = root.template.findIndex(
|
||||||
t.type === IRNodeTypes.TEMPLATE_FACTORY &&
|
(t) =>
|
||||||
t.template === ctx.template,
|
t.type === IRNodeTypes.TEMPLATE_FACTORY &&
|
||||||
)
|
t.template === this.template,
|
||||||
if (idx !== -1) return idx
|
)
|
||||||
|
if (idx !== -1) {
|
||||||
|
return (this.block.templateIndex = idx)
|
||||||
|
}
|
||||||
|
|
||||||
ir.template.push({
|
templateNode = {
|
||||||
type: IRNodeTypes.TEMPLATE_FACTORY,
|
type: IRNodeTypes.TEMPLATE_FACTORY,
|
||||||
template: ctx.template,
|
template: this.template,
|
||||||
loc: node.loc,
|
loc: node.loc,
|
||||||
})
|
}
|
||||||
return ir.template.length - 1
|
} else {
|
||||||
|
templateNode = {
|
||||||
|
type: IRNodeTypes.FRAGMENT_FACTORY,
|
||||||
|
loc: node.loc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
root.template.push(templateNode)
|
||||||
|
return (this.block.templateIndex = root.template.length - 1)
|
||||||
},
|
},
|
||||||
registerOperation(...node) {
|
registerOperation(...node) {
|
||||||
operation.push(...node)
|
this.block.operation.push(...node)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
ctx.root = ctx
|
ctx.root = ctx
|
||||||
|
@ -199,6 +241,7 @@ export function transform(
|
||||||
source: root.source,
|
source: root.source,
|
||||||
loc: root.loc,
|
loc: root.loc,
|
||||||
template: [],
|
template: [],
|
||||||
|
templateIndex: -1,
|
||||||
dynamic: {
|
dynamic: {
|
||||||
id: null,
|
id: null,
|
||||||
referenced: true,
|
referenced: true,
|
||||||
|
@ -211,17 +254,9 @@ export function transform(
|
||||||
}
|
}
|
||||||
|
|
||||||
const ctx = createRootContext(ir, root, options)
|
const ctx = createRootContext(ir, root, options)
|
||||||
transformNode(ctx)
|
|
||||||
|
|
||||||
if (ctx.node.type === NodeTypes.ROOT) {
|
transformNode(ctx)
|
||||||
ctx.registerTemplate()
|
ctx.registerTemplate()
|
||||||
}
|
|
||||||
if (ir.template.length === 0) {
|
|
||||||
ir.template.push({
|
|
||||||
type: IRNodeTypes.FRAGMENT_FACTORY,
|
|
||||||
loc: root.loc,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return ir
|
return ir
|
||||||
}
|
}
|
||||||
|
@ -251,7 +286,6 @@ function transformNode(
|
||||||
node = context.node
|
node = context.node
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case NodeTypes.ROOT:
|
case NodeTypes.ROOT:
|
||||||
case NodeTypes.ELEMENT: {
|
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) {
|
if (parent instanceof Node) {
|
||||||
// TODO use insertBefore for better performance https://jsbench.me/rolpg250hh/1
|
// TODO use insertBefore for better performance https://jsbench.me/rolpg250hh/1
|
||||||
parent.prepend(...nodes)
|
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) {
|
if (parent instanceof Node) {
|
||||||
// TODO use insertBefore for better performance
|
// TODO use insertBefore for better performance
|
||||||
parent.append(...nodes)
|
parent.append(...nodes)
|
||||||
|
|
Loading…
Reference in New Issue