diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vSkip.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vSkip.spec.ts.snap new file mode 100644 index 00000000000..113926c9b4b --- /dev/null +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vSkip.spec.ts.snap @@ -0,0 +1,263 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`compiler: v-skip > transform > basic 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { createCommentVNode: _createCommentVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue + + return (_ctx.ok) + ? _createCommentVNode("v-skip", true) + : (_openBlock(), _createElementBlock("div", { key: 1 })) + } +}" +`; + +exports[`compiler: v-skip > transform > nested v-skip 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { createCommentVNode: _createCommentVNode, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode, Fragment: _Fragment } = _Vue + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [ + (_ctx.nested) + ? _createCommentVNode("v-skip", true) + : (_openBlock(), _createElementBlock("span", { key: 1 })) + ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) + : (_openBlock(), _createElementBlock("div", { key: 1 }, [ + (_ctx.nested) + ? _createCommentVNode("v-skip", true) + : (_openBlock(), _createElementBlock("span", { key: 1 })) + ])) + } +}" +`; + +exports[`compiler: v-skip > transform > on component 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { withCtx: _withCtx, createCommentVNode: _createCommentVNode, resolveComponent: _resolveComponent, openBlock: _openBlock, createBlock: _createBlock } = _Vue + + const _component_Comp = _resolveComponent("Comp") + + return (_ctx.ok) + ? _createCommentVNode("v-skip", true) + : (_openBlock(), _createBlock(_component_Comp, { key: 1 })) + } +}" +`; + +exports[`compiler: v-skip > transform > on component with default slot 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { withCtx: _withCtx, resolveComponent: _resolveComponent, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createBlock: _createBlock } = _Vue + + const _component_Comp = _resolveComponent("Comp") + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, ["foo"], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) + : (_openBlock(), _createBlock(_component_Comp, { key: 1 }, { + default: _withCtx(() => ["foo"]), + _: 1 /* STABLE */ + })) + } +}" +`; + +exports[`compiler: v-skip > transform > on component with multiple implicit slot 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { withCtx: _withCtx, createElementVNode: _createElementVNode, resolveComponent: _resolveComponent, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createBlock: _createBlock } = _Vue + + const _component_Comp = _resolveComponent("Comp") + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [ + _createElementVNode("span"), + _createElementVNode("div") + ], 64 /* STABLE_FRAGMENT */)) + : (_openBlock(), _createBlock(_component_Comp, { key: 1 }, { + foo: _withCtx(() => ["foo"]), + default: _withCtx(() => [ + _createElementVNode("span"), + _createElementVNode("div") + ]), + _: 1 /* STABLE */ + })) + } +}" +`; + +exports[`compiler: v-skip > transform > on component with multiple named slot 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { withCtx: _withCtx, resolveComponent: _resolveComponent, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createBlock: _createBlock } = _Vue + + const _component_Comp = _resolveComponent("Comp") + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, ["default"], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) + : (_openBlock(), _createBlock(_component_Comp, { key: 1 }, { + default: _withCtx(() => ["default"]), + foo: _withCtx(() => ["foo"]), + _: 1 /* STABLE */ + })) + } +}" +`; + +exports[`compiler: v-skip > transform > on dynamic component 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { withCtx: _withCtx, renderSlot: _renderSlot, resolveDynamicComponent: _resolveDynamicComponent, openBlock: _openBlock, createBlock: _createBlock } = _Vue + + return (_ctx.ok) + ? _renderSlot(_ctx.$slots, "default", { key: 0 }) + : (_openBlock(), _createBlock(_resolveDynamicComponent(_ctx.Comp), { key: 1 }, { + default: _withCtx(() => [ + _renderSlot(_ctx.$slots, "default", { key: 0 }) + ]), + _: 3 /* FORWARDED */ + })) + } +}" +`; + +exports[`compiler: v-skip > transform > v-else + v-skip 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment } = _Vue + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock("div", { key: 0 })) + : (_openBlock(), _createElementBlock(_Fragment, { key: 1 }, [ + (_ctx.nested) + ? _createCommentVNode("v-skip", true) + : (_openBlock(), _createElementBlock("div", { key: 1 })) + ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) + } +}" +`; + +exports[`compiler: v-skip > transform > v-else-if + v-skip 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment } = _Vue + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock("div", { key: 0 })) + : (_ctx.yes) + ? (_openBlock(), _createElementBlock(_Fragment, { key: 1 }, [ + (_ctx.nested) + ? _createCommentVNode("v-skip", true) + : (_openBlock(), _createElementBlock("div", { key: 1 })) + ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) + : _createCommentVNode("v-if", true) + } +}" +`; + +exports[`compiler: v-skip > transform > v-if + v-skip 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { createCommentVNode: _createCommentVNode, openBlock: _openBlock, createElementBlock: _createElementBlock, Fragment: _Fragment } = _Vue + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [ + (_ctx.nested) + ? _createCommentVNode("v-skip", true) + : (_openBlock(), _createElementBlock("div", { key: 1 })) + ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) + : _createCommentVNode("v-if", true) + } +}" +`; + +exports[`compiler: v-skip > transform > with component children 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { resolveComponent: _resolveComponent, createVNode: _createVNode, openBlock: _openBlock, createBlock: _createBlock, createElementBlock: _createElementBlock } = _Vue + + const _component_Comp = _resolveComponent("Comp") + + return (_ctx.ok) + ? (_openBlock(), _createBlock(_component_Comp, { key: 0 })) + : (_openBlock(), _createElementBlock("div", { key: 1 }, [ + (_openBlock(), _createBlock(_component_Comp, { key: 0 })) + ])) + } +}" +`; + +exports[`compiler: v-skip > transform > with element children 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock("span", { key: 0 })) + : (_openBlock(), _createElementBlock("div", { key: 1 }, [ + (_openBlock(), _createElementBlock("span", { key: 0 })) + ])) + } +}" +`; + +exports[`compiler: v-skip > transform > with multiple children 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { createElementVNode: _createElementVNode, resolveComponent: _resolveComponent, createVNode: _createVNode, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue + + const _component_Comp = _resolveComponent("Comp") + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [ + _createElementVNode("span"), + _createVNode(_component_Comp) + ], 64 /* STABLE_FRAGMENT */)) + : (_openBlock(), _createElementBlock("div", { key: 1 }, [ + _createElementVNode("span"), + _createVNode(_component_Comp) + ])) + } +}" +`; + +exports[`compiler: v-skip > transform > with text children 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue + + return (_ctx.ok) + ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, ["foo"], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) + : (_openBlock(), _createElementBlock("div", { key: 1 }, "foo")) + } +}" +`; diff --git a/packages/compiler-core/__tests__/transforms/vSkip.spec.ts b/packages/compiler-core/__tests__/transforms/vSkip.spec.ts index 595c62f1a2b..15572cb9e1f 100644 --- a/packages/compiler-core/__tests__/transforms/vSkip.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vSkip.spec.ts @@ -1,15 +1,18 @@ import { type CompilerOptions, type ElementNode, + ElementTypes, + type IfBranchNode, type IfNode, NodeTypes, type RootNode, type SimpleExpressionNode, type SkipNode, + generate, baseParse as parse, transform, - transformBind, transformElement, + transformExpression, } from '@vue/compiler-core' import { transformIf } from '../../src/transforms/vIf' import { transformFor } from '../../src/transforms/vFor' @@ -18,7 +21,7 @@ import { transformSkip } from '../../src/transforms/vSkip' export function parseWithSkipTransform( template: string, - options: CompilerOptions = {}, + options: CompilerOptions = { prefixIdentifiers: true }, ): { root: RootNode node: SkipNode @@ -29,12 +32,10 @@ export function parseWithSkipTransform( transformIf, transformSkip, transformFor, + transformExpression, transformSlotOutlet, transformElement, ], - directiveTransforms: { - bind: transformBind, - }, ...options, }) return { @@ -46,102 +47,151 @@ export function parseWithSkipTransform( describe('compiler: v-skip', () => { describe('transform', () => { test('basic', () => { - const { node } = parseWithSkipTransform(`
`) + const { root, node } = parseWithSkipTransform(`
`) expect(node.type).toBe(NodeTypes.SKIP) - expect((node.test as SimpleExpressionNode).content).toBe(`ok`) - expect(node.consequent.children.length).toBe(0) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect(node.consequent.type === NodeTypes.JS_CALL_EXPRESSION).toBe(true) expect(node.alternate.children.length).toBe(1) expect(node.alternate.children[0].type).toBe(NodeTypes.ELEMENT) expect((node.alternate.children[0] as ElementNode).tag).toBe(`div`) + expect(generate(root).code).toMatchSnapshot() }) test('with text children', () => { - const { node } = parseWithSkipTransform(`
foo
`) + const { root, node } = parseWithSkipTransform( + `
foo
`, + ) expect(node.type).toBe(NodeTypes.SKIP) - expect((node.test as SimpleExpressionNode).content).toBe(`ok`) - expect(node.consequent.children.length).toBe(1) - expect(node.consequent.children[0].type).toBe(NodeTypes.TEXT) - expect((node.consequent.children[0] as any).content).toBe(`foo`) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect((node.consequent as IfBranchNode).children.length).toBe(1) + expect((node.consequent as IfBranchNode).children[0].type).toBe( + NodeTypes.TEXT, + ) + expect( + ((node.consequent as IfBranchNode).children[0] as any).content, + ).toBe(`foo`) expect(node.alternate.children.length).toBe(1) expect(node.alternate.children[0].type).toBe(NodeTypes.ELEMENT) expect((node.alternate.children[0] as ElementNode).tag).toBe(`div`) + expect(generate(root).code).toMatchSnapshot() }) test('with element children', () => { - const { node } = parseWithSkipTransform(`
`) + const { root, node } = parseWithSkipTransform( + `
`, + ) expect(node.type).toBe(NodeTypes.SKIP) - expect((node.test as SimpleExpressionNode).content).toBe(`ok`) - expect(node.consequent.children.length).toBe(1) - expect(node.consequent.children[0].type).toBe(NodeTypes.ELEMENT) - expect((node.consequent.children[0] as ElementNode).tag).toBe(`span`) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect((node.consequent as IfBranchNode).children.length).toBe(1) + expect((node.consequent as IfBranchNode).children[0].type).toBe( + NodeTypes.ELEMENT, + ) + expect( + ((node.consequent as IfBranchNode).children[0] as ElementNode).tag, + ).toBe(`span`) expect(node.alternate.children.length).toBe(1) expect(node.alternate.children[0].type).toBe(NodeTypes.ELEMENT) expect((node.alternate.children[0] as ElementNode).tag).toBe(`div`) + expect(generate(root).code).toMatchSnapshot() }) test('with component children', () => { - const { node } = parseWithSkipTransform(`
`) + const { root, node } = parseWithSkipTransform( + `
`, + ) expect(node.type).toBe(NodeTypes.SKIP) - expect((node.test as SimpleExpressionNode).content).toBe(`ok`) - expect(node.consequent.children.length).toBe(1) - expect(node.consequent.children[0].type).toBe(NodeTypes.ELEMENT) - expect((node.consequent.children[0] as ElementNode).tag).toBe(`Comp`) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect((node.consequent as IfBranchNode).children.length).toBe(1) + expect((node.consequent as IfBranchNode).children[0].type).toBe( + NodeTypes.ELEMENT, + ) + expect( + ((node.consequent as IfBranchNode).children[0] as ElementNode).tag, + ).toBe(`Comp`) expect(node.alternate.children.length).toBe(1) expect(node.alternate.children[0].type).toBe(NodeTypes.ELEMENT) expect((node.alternate.children[0] as ElementNode).tag).toBe(`div`) + expect(generate(root).code).toMatchSnapshot() }) test('with multiple children', () => { - const { node } = parseWithSkipTransform( + const { root, node } = parseWithSkipTransform( `
`, ) expect(node.type).toBe(NodeTypes.SKIP) - expect((node.test as SimpleExpressionNode).content).toBe(`ok`) - expect(node.consequent.children.length).toBe(2) - expect(node.consequent.children[0].type).toBe(NodeTypes.ELEMENT) - expect((node.consequent.children[0] as ElementNode).tag).toBe(`span`) - expect(node.consequent.children[1].type).toBe(NodeTypes.ELEMENT) - expect((node.consequent.children[1] as ElementNode).tag).toBe(`Comp`) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect((node.consequent as IfBranchNode).children.length).toBe(2) + expect((node.consequent as IfBranchNode).children[0].type).toBe( + NodeTypes.ELEMENT, + ) + expect( + ((node.consequent as IfBranchNode).children[0] as ElementNode).tag, + ).toBe(`span`) + expect((node.consequent as IfBranchNode).children[1].type).toBe( + NodeTypes.ELEMENT, + ) + expect( + ((node.consequent as IfBranchNode).children[1] as ElementNode).tag, + ).toBe(`Comp`) expect(node.alternate.children.length).toBe(1) expect(node.alternate.children[0].type).toBe(NodeTypes.ELEMENT) expect((node.alternate.children[0] as ElementNode).tag).toBe(`div`) + expect(generate(root).code).toMatchSnapshot() }) test('nested v-skip', () => { - const { node } = parseWithSkipTransform( - `
`, + const { root, node } = parseWithSkipTransform( + `
`, ) expect(node.type).toBe(NodeTypes.SKIP) - expect((node.test as SimpleExpressionNode).content).toBe(`ok`) - expect(node.consequent.children.length).toBe(1) - expect(node.consequent.children[0].type).toBe(NodeTypes.SKIP) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect((node.consequent as IfBranchNode).children.length).toBe(1) + expect((node.consequent as IfBranchNode).children[0].type).toBe( + NodeTypes.SKIP, + ) expect( - ((node.consequent.children[0] as SkipNode).test as SimpleExpressionNode) - .content, - ).toBe(`nested`) + ( + ((node.consequent as IfBranchNode).children[0] as SkipNode) + .test as SimpleExpressionNode + ).content, + ).toBe(`_ctx.nested`) expect(node.alternate.children.length).toBe(1) expect(node.alternate.children[0].type).toBe(NodeTypes.ELEMENT) expect((node.alternate.children[0] as ElementNode).tag).toBe(`div`) + const nestedNode = (node.consequent as IfBranchNode) + .children[0] as SkipNode + expect(nestedNode.type).toBe(NodeTypes.SKIP) + expect((nestedNode.test as SimpleExpressionNode).content).toBe( + `_ctx.nested`, + ) + expect(nestedNode.consequent.type === NodeTypes.JS_CALL_EXPRESSION).toBe( + true, + ) + expect(nestedNode.alternate.children.length).toBe(1) + expect(nestedNode.alternate.children[0].type).toBe(NodeTypes.ELEMENT) + expect((nestedNode.alternate.children[0] as ElementNode).tag).toBe(`span`) + expect(generate(root).code).toMatchSnapshot() }) test('v-if + v-skip', () => { - const { node } = parseWithSkipTransform( + const { root, node } = parseWithSkipTransform( `
`, ) expect(node.type).toBe(NodeTypes.IF) const ifNode = node as unknown as IfNode const branch = ifNode.branches[0] - expect((branch.condition as SimpleExpressionNode).content).toBe(`ok`) + expect((branch.condition as SimpleExpressionNode).content).toBe(`_ctx.ok`) expect(branch.children.length).toBe(1) // skipNode expect(branch.children[0].type).toBe(NodeTypes.SKIP) expect( ((branch.children[0] as SkipNode).test as SimpleExpressionNode).content, - ).toBe(`nested`) + ).toBe(`_ctx.nested`) + expect(generate(root).code).toMatchSnapshot() }) test('v-else + v-skip', () => { - const { node } = parseWithSkipTransform( + const { root, node } = parseWithSkipTransform( `
`, ) expect(node.type).toBe(NodeTypes.IF) @@ -152,66 +202,89 @@ describe('compiler: v-skip', () => { expect(branch.children[0].type).toBe(NodeTypes.SKIP) expect( ((branch.children[0] as SkipNode).test as SimpleExpressionNode).content, - ).toBe(`nested`) + ).toBe(`_ctx.nested`) + expect(generate(root).code).toMatchSnapshot() }) test('v-else-if + v-skip', () => { - const { node } = parseWithSkipTransform( + const { root, node } = parseWithSkipTransform( `
`, ) expect(node.type).toBe(NodeTypes.IF) const elseIfNode = node as unknown as IfNode const branch = elseIfNode.branches[1] - expect((branch.condition as SimpleExpressionNode).content).toBe(`yes`) + expect((branch.condition as SimpleExpressionNode).content).toBe( + `_ctx.yes`, + ) expect(branch.children.length).toBe(1) // skipNode expect(branch.children[0].type).toBe(NodeTypes.SKIP) expect( ((branch.children[0] as SkipNode).test as SimpleExpressionNode).content, - ).toBe(`nested`) + ).toBe(`_ctx.nested`) + expect(generate(root).code).toMatchSnapshot() }) test('on component', () => { - const { node } = parseWithSkipTransform(``) + const { root, node } = parseWithSkipTransform(``) expect(node.type).toBe(NodeTypes.SKIP) - expect((node.test as SimpleExpressionNode).content).toBe(`ok`) - expect(node.consequent.children.length).toBe(0) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect(node.consequent.type === NodeTypes.JS_CALL_EXPRESSION).toBe(true) expect(node.alternate.children.length).toBe(1) - expect(node.alternate.children[0].type).toBe(NodeTypes.ELEMENT) + expect((node.alternate.children[0] as ElementNode).tagType).toBe( + ElementTypes.COMPONENT, + ) expect((node.alternate.children[0] as ElementNode).tag).toBe(`Comp`) + expect(generate(root).code).toMatchSnapshot() }) test('on component with default slot', () => { - const { node } = parseWithSkipTransform(`foo`) + const { root, node } = parseWithSkipTransform( + `foo`, + ) expect(node.type).toBe(NodeTypes.SKIP) - expect((node.test as SimpleExpressionNode).content).toBe(`ok`) - expect(node.consequent.children.length).toBe(1) - expect(node.consequent.children[0].type).toBe(NodeTypes.TEXT) - expect((node.consequent.children[0] as any).content).toBe(`foo`) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect((node.consequent as IfBranchNode).children.length).toBe(1) + expect((node.consequent as IfBranchNode).children[0].type).toBe( + NodeTypes.TEXT, + ) + expect( + ((node.consequent as IfBranchNode).children[0] as any).content, + ).toBe(`foo`) expect(node.alternate.children.length).toBe(1) - expect(node.alternate.children[0].type).toBe(NodeTypes.ELEMENT) + expect((node.alternate.children[0] as ElementNode).tagType).toBe( + ElementTypes.COMPONENT, + ) expect((node.alternate.children[0] as ElementNode).tag).toBe(`Comp`) + expect(generate(root).code).toMatchSnapshot() }) test('on component with multiple named slot', () => { - const { node } = parseWithSkipTransform( + const { root, node } = parseWithSkipTransform( ` `, ) expect(node.type).toBe(NodeTypes.SKIP) - expect((node.test as SimpleExpressionNode).content).toBe(`ok`) - expect(node.consequent.children.length).toBe(1) - expect(node.consequent.children[0].type).toBe(NodeTypes.TEXT) - expect((node.consequent.children[0] as any).content).toBe(`default`) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect((node.consequent as IfBranchNode).children.length).toBe(1) + expect((node.consequent as IfBranchNode).children[0].type).toBe( + NodeTypes.TEXT, + ) + expect( + ((node.consequent as IfBranchNode).children[0] as any).content, + ).toBe(`default`) expect(node.alternate.children.length).toBe(1) - expect(node.alternate.children[0].type).toBe(NodeTypes.ELEMENT) + expect((node.alternate.children[0] as ElementNode).tagType).toBe( + ElementTypes.COMPONENT, + ) expect((node.alternate.children[0] as ElementNode).tag).toBe(`Comp`) + expect(generate(root).code).toMatchSnapshot() }) test('on component with multiple implicit slot', () => { - const { node } = parseWithSkipTransform( + const { root, node } = parseWithSkipTransform( ` @@ -219,19 +292,51 @@ describe('compiler: v-skip', () => { `, ) expect(node.type).toBe(NodeTypes.SKIP) - expect((node.test as SimpleExpressionNode).content).toBe(`ok`) - expect(node.consequent.children.length).toBe(2) - expect(node.consequent.children[0].type).toBe(NodeTypes.ELEMENT) - expect((node.consequent.children[0] as ElementNode).tag).toBe(`span`) - expect(node.consequent.children[1].type).toBe(NodeTypes.ELEMENT) - expect((node.consequent.children[1] as ElementNode).tag).toBe(`div`) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect((node.consequent as IfBranchNode).children.length).toBe(2) + expect((node.consequent as IfBranchNode).children[0].type).toBe( + NodeTypes.ELEMENT, + ) + expect( + ((node.consequent as IfBranchNode).children[0] as ElementNode).tag, + ).toBe(`span`) + expect((node.consequent as IfBranchNode).children[1].type).toBe( + NodeTypes.ELEMENT, + ) + expect( + ((node.consequent as IfBranchNode).children[1] as ElementNode).tag, + ).toBe(`div`) expect(node.alternate.children.length).toBe(1) - expect(node.alternate.children[0].type).toBe(NodeTypes.ELEMENT) + expect((node.alternate.children[0] as ElementNode).tagType).toBe( + ElementTypes.COMPONENT, + ) expect((node.alternate.children[0] as ElementNode).tag).toBe(`Comp`) + expect(generate(root).code).toMatchSnapshot() }) - }) - describe.todo('codegen', () => {}) + test('on dynamic component', () => { + const { root, node } = parseWithSkipTransform( + ` + + `, + ) + expect(node.type).toBe(NodeTypes.SKIP) + expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`) + expect((node.consequent as IfBranchNode).children.length).toBe(1) + expect((node.consequent as IfBranchNode).children[0].type).toBe( + NodeTypes.ELEMENT, + ) + expect( + ((node.consequent as IfBranchNode).children[0] as ElementNode).tag, + ).toBe(`slot`) + expect(node.alternate.children.length).toBe(1) + expect((node.alternate.children[0] as ElementNode).tagType).toBe( + ElementTypes.COMPONENT, + ) + expect((node.alternate.children[0] as ElementNode).tag).toBe(`component`) + expect(generate(root).code).toMatchSnapshot() + }) + }) describe.todo('errors', () => {}) }) diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index fed8cd2cfc2..d2402331184 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -413,7 +413,7 @@ export interface FunctionExpression extends Node { export interface SkipNode extends Node { type: NodeTypes.SKIP test: ExpressionNode - consequent: IfBranchNode + consequent: IfBranchNode | CallExpression alternate: IfBranchNode newline: boolean codegenNode?: ConditionalExpression @@ -468,7 +468,7 @@ export interface TemplateLiteral extends Node { export interface IfStatement extends Node { type: NodeTypes.JS_IF_STATEMENT test: ExpressionNode - consequent: BlockStatement + consequent: BlockStatement | CallExpression alternate: IfStatement | BlockStatement | ReturnStatement | undefined } diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index ca395b55fcd..bfdc5bb3cf4 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -467,7 +467,9 @@ export function traverseNode( } break case NodeTypes.SKIP: - traverseNode(node.consequent, context) + const { consequent } = node + if (consequent.type === NodeTypes.IF_BRANCH) + traverseNode(consequent, context) traverseNode(node.alternate, context) break case NodeTypes.IF_BRANCH: diff --git a/packages/compiler-core/src/transforms/vSkip.ts b/packages/compiler-core/src/transforms/vSkip.ts index 6121dfaa0a2..2f3f4d07531 100644 --- a/packages/compiler-core/src/transforms/vSkip.ts +++ b/packages/compiler-core/src/transforms/vSkip.ts @@ -7,6 +7,7 @@ import { type SimpleExpressionNode, type SkipNode, type TemplateChildNode, + createCallExpression, createConditionalExpression, createSimpleExpression, } from '../ast' @@ -16,6 +17,7 @@ import { createStructuralDirectiveTransform, } from '../transform' import { + CREATE_COMMENT, ErrorCodes, buildSlots, createCompilerError, @@ -34,10 +36,16 @@ export const transformSkip: NodeTransform = createStructuralDirectiveTransform( (node, dir, context) => { return processSkip(node, dir, context, skipNode => { return () => { + const { consequent, alternate, test } = skipNode + const consequentNode = + consequent.type === NodeTypes.IF_BRANCH + ? createCodegenNodeForBranch(consequent, 0, context) + : consequent + skipNode.codegenNode = createConditionalExpression( - dir.exp!, - createCodegenNodeForBranch(skipNode.consequent, 0, context), - createCodegenNodeForBranch(skipNode.alternate, 1, context), + test, + consequentNode, + createCodegenNodeForBranch(alternate, 1, context), ) } }) @@ -100,13 +108,20 @@ export function processSkip( children = node.children } - const consequent: IfBranchNode = { - type: NodeTypes.IF_BRANCH, - loc: node.loc, - condition: undefined, - children, - userKey: findProp(node, `key`), - } + // if children is empty, create comment node + const consequent = + children.length !== 0 + ? ({ + type: NodeTypes.IF_BRANCH, + loc: node.loc, + condition: undefined, + children, + userKey: findProp(node, `key`), + } as IfBranchNode) + : createCallExpression(context.helper(CREATE_COMMENT), [ + __DEV__ ? '"v-skip"' : '""', + 'true', + ]) const alternate: IfBranchNode = { type: NodeTypes.IF_BRANCH, diff --git a/packages/compiler-core/src/transforms/vSlot.ts b/packages/compiler-core/src/transforms/vSlot.ts index 4c90dff1220..72d90120475 100644 --- a/packages/compiler-core/src/transforms/vSlot.ts +++ b/packages/compiler-core/src/transforms/vSlot.ts @@ -413,8 +413,10 @@ function hasForwardedSlots(children: TemplateChildNode[]): boolean { if (hasForwardedSlots(child.branches)) return true break case NodeTypes.SKIP: + const consequent = child.consequent if ( - hasForwardedSlots(child.consequent.children) || + (consequent.type === NodeTypes.IF_BRANCH && + hasForwardedSlots(consequent.children)) || hasForwardedSlots(child.alternate.children) ) return true diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts index b350298ca10..faaddb2b2c0 100644 --- a/packages/compiler-core/src/utils.ts +++ b/packages/compiler-core/src/utils.ts @@ -529,9 +529,11 @@ export function hasScopeRef( case NodeTypes.IF: return node.branches.some(b => hasScopeRef(b, ids)) case NodeTypes.SKIP: + const { consequent } = node return ( hasScopeRef(node.test, ids) || - node.consequent.children.some(c => hasScopeRef(c, ids)) || + (consequent.type === NodeTypes.IF_BRANCH && + consequent.children.some(c => hasScopeRef(c, ids))) || node.alternate.children.some(c => hasScopeRef(c, ids)) ) case NodeTypes.IF_BRANCH: diff --git a/packages/compiler-ssr/src/transforms/ssrVSkip.ts b/packages/compiler-ssr/src/transforms/ssrVSkip.ts index 751d3336f03..e0780fa0c42 100644 --- a/packages/compiler-ssr/src/transforms/ssrVSkip.ts +++ b/packages/compiler-ssr/src/transforms/ssrVSkip.ts @@ -1,5 +1,6 @@ import { type NodeTransform, + NodeTypes, type SkipNode, createIfStatement, createStructuralDirectiveTransform, @@ -15,10 +16,18 @@ export function ssrProcessSkip( node: SkipNode, context: SSRTransformContext, ): void { + const { consequent, alternate, test } = node + + // if consequent is an if branch, process it as well + const consequentNode = + consequent.type === NodeTypes.IF_BRANCH + ? processIfBranch(consequent, context) + : consequent + const ifStatement = createIfStatement( - node.test, - processIfBranch(node.consequent, context), - processIfBranch(node.alternate, context), + test, + consequentNode, + processIfBranch(alternate, context), ) context.pushStatement(ifStatement) }