diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vSkip.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vSkip.spec.ts.snap
index a975095208b..5b8456bda97 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/vSkip.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vSkip.spec.ts.snap
@@ -36,33 +36,216 @@ return function render(_ctx, _cache) {
}"
`;
-exports[`compiler: v-skip > transform > on component 1`] = `
+exports[`compiler: v-skip > transform > on Teleport 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
- const { resolveComponent: _resolveComponent, resolveSkipComponent: _resolveSkipComponent, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+ const { withCtx: _withCtx, createCommentVNode: _createCommentVNode, Teleport: _Teleport, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+
+ return (_ctx.ok)
+ ? _createCommentVNode("v-skip", true)
+ : (_openBlock(), _createBlock(_Teleport, {
+ key: 1,
+ to: "target"
+ }))
+ }
+}"
+`;
+
+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 dynamic slot + default slot 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { withCtx: _withCtx, resolveComponent: _resolveComponent, resolveSkipComponent: _resolveSkipComponent, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+
+ const _component_Comp = _resolveComponent("Comp")
+
+ return (_openBlock(), _createBlock(_resolveSkipComponent(_ctx.ok, _component_Comp), null, {
+ [_ctx.foo]: _withCtx(() => ["foo"]),
+ default: _withCtx(() => ["default"]),
+ _: 2 /* DYNAMIC */
+ }, 1024 /* DYNAMIC_SLOTS */))
+ }
+}"
+`;
+
+exports[`compiler: v-skip > transform > on component with dynamic slot 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { withCtx: _withCtx, resolveComponent: _resolveComponent, resolveSkipComponent: _resolveSkipComponent, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+
+ const _component_Comp = _resolveComponent("Comp")
+
+ return (_openBlock(), _createBlock(_resolveSkipComponent(_ctx.ok, _component_Comp), null, {
+ [_ctx.foo]: _withCtx(() => ["foo"]),
+ _: 2 /* DYNAMIC */
+ }, 1024 /* DYNAMIC_SLOTS */))
+ }
+}"
+`;
+
+exports[`compiler: v-skip > transform > on component with implicit default slot + v-if 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { withCtx: _withCtx, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, resolveComponent: _resolveComponent, Fragment: _Fragment, createBlock: _createBlock } = _Vue
const _component_Comp = _resolveComponent("Comp")
- return (_openBlock(), _createBlock(_resolveSkipComponent(_ctx.ok, _component_Comp)))
+ return (_ctx.ok)
+ ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [
+ (_ctx.yes)
+ ? (_openBlock(), _createElementBlock("span", { key: 0 }, "default"))
+ : _createCommentVNode("v-if", true)
+ ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
+ : (_openBlock(), _createBlock(_component_Comp, { key: 1 }, {
+ default: _withCtx(() => [
+ (_ctx.yes)
+ ? (_openBlock(), _createElementBlock("span", { key: 0 }, "default"))
+ : _createCommentVNode("v-if", true)
+ ]),
+ _: 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 component with name default slot + v-if 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { withCtx: _withCtx, createSlots: _createSlots, resolveComponent: _resolveComponent, resolveSkipComponent: _resolveSkipComponent, openBlock: _openBlock, createBlock: _createBlock } = _Vue
+
+ const _component_Comp = _resolveComponent("Comp")
+
+ return (_openBlock(), _createBlock(_resolveSkipComponent(_ctx.ok, _component_Comp), null, _createSlots({ _: 2 /* DYNAMIC */ }, [
+ (_ctx.yes)
+ ? {
+ name: "default",
+ fn: _withCtx(() => ["default"]),
+ key: "0"
+ }
+ : undefined
+ ]), 1024 /* DYNAMIC_SLOTS */))
+ }
+}"
+`;
+
+exports[`compiler: v-skip > transform > on component without slot 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 dynamic component with default slot 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { withCtx: _withCtx, resolveDynamicComponent: _resolveDynamicComponent, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createElementBlock: _createElementBlock } = _Vue
+
+ return (_ctx.ok)
+ ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, ["foo"], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
+ : (_openBlock(), _createBlock(_resolveDynamicComponent(_ctx.Comp), { key: 1 }, {
+ default: _withCtx(() => ["foo"]),
+ _: 1 /* STABLE */
+ }))
}
}"
`;
-exports[`compiler: v-skip > transform > on dynamic component 1`] = `
+exports[`compiler: v-skip > transform > on dynamic component with dynamic slot 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
- const { renderSlot: _renderSlot, resolveDynamicComponent: _resolveDynamicComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock, resolveSkipComponent: _resolveSkipComponent } = _Vue
+ const { withCtx: _withCtx, resolveDynamicComponent: _resolveDynamicComponent, openBlock: _openBlock, createBlock: _createBlock, resolveSkipComponent: _resolveSkipComponent } = _Vue
return (_openBlock(), _createBlock(_resolveSkipComponent(_ctx.ok, _resolveDynamicComponent(_ctx.Comp)), null, {
- default: _withCtx(() => [
- _renderSlot(_ctx.$slots, "default")
- ]),
- _: 3 /* FORWARDED */
- }))
+ [_ctx.foo]: _withCtx(() => ["foo"]),
+ _: 2 /* DYNAMIC */
+ }, 1024 /* DYNAMIC_SLOTS */))
}
}"
`;
diff --git a/packages/compiler-core/__tests__/transforms/vSkip.spec.ts b/packages/compiler-core/__tests__/transforms/vSkip.spec.ts
index c9002fb87ff..a9760b9855f 100644
--- a/packages/compiler-core/__tests__/transforms/vSkip.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vSkip.spec.ts
@@ -255,28 +255,25 @@ describe('compiler: v-skip', () => {
expect(generate(root).code).toMatchSnapshot()
})
- test('on component', () => {
+ test('on component without slot', () => {
+ // equivalent to
const { root, node } = parseWithSkipTransform(``) as {
root: RootNode
- node: ComponentNode
+ node: SkipNode
}
- expect(node.type).toBe(NodeTypes.ELEMENT)
- expect(node.tagType).toBe(ElementTypes.COMPONENT)
- const codegenNode = node.codegenNode! as VNodeCall
- expect(codegenNode.type).toBe(NodeTypes.VNODE_CALL)
- const vnodeTag = codegenNode.tag as CallExpression
- expect(vnodeTag.type).toBe(NodeTypes.JS_CALL_EXPRESSION)
- expect(vnodeTag.callee).toBe(RESOLVE_SKIP_COMPONENT)
- expect((vnodeTag.arguments[0] as SimpleExpressionNode).content).toBe(
- `_ctx.ok`,
- )
+ expect(node.type).toBe(NodeTypes.SKIP)
+ 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(`Comp`)
expect(generate(root).code).toMatchSnapshot()
})
- test.todo('on component with default slot', () => {
+ test('on component with default slot', () => {
const { root, node } = parseWithSkipTransform(
`foo`,
- )
+ ) as { root: RootNode; node: SkipNode }
expect(node.type).toBe(NodeTypes.SKIP)
expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`)
expect((node.consequent as IfBranchNode).children.length).toBe(1)
@@ -294,13 +291,13 @@ describe('compiler: v-skip', () => {
expect(generate(root).code).toMatchSnapshot()
})
- test.todo('on component with multiple named slot', () => {
+ test('on component with multiple named slot', () => {
const { root, node } = parseWithSkipTransform(
`
default
foo
`,
- )
+ ) as { root: RootNode; node: SkipNode }
expect(node.type).toBe(NodeTypes.SKIP)
expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`)
expect((node.consequent as IfBranchNode).children.length).toBe(1)
@@ -318,14 +315,14 @@ describe('compiler: v-skip', () => {
expect(generate(root).code).toMatchSnapshot()
})
- test.todo('on component with multiple implicit slot', () => {
+ test('on component with multiple implicit slot', () => {
const { root, node } = parseWithSkipTransform(
`
foo
`,
- )
+ ) as { root: RootNode; node: SkipNode }
expect(node.type).toBe(NodeTypes.SKIP)
expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`)
expect((node.consequent as IfBranchNode).children.length).toBe(2)
@@ -349,57 +346,98 @@ describe('compiler: v-skip', () => {
expect(generate(root).code).toMatchSnapshot()
})
- test.todo('on component with dynamic slot + default slot', () => {
+ test('on component with name default slot + v-if', () => {
const { root, node } = parseWithSkipTransform(
`
- foo
- default
+ default
`,
+ ) as { root: RootNode; node: ComponentNode }
+ expect(node.type).toBe(NodeTypes.ELEMENT)
+ expect(node.tagType).toBe(ElementTypes.COMPONENT)
+ const codegenNode = node.codegenNode! as VNodeCall
+ expect(codegenNode.type).toBe(NodeTypes.VNODE_CALL)
+ const vnodeTag = codegenNode.tag as CallExpression
+ expect(vnodeTag.type).toBe(NodeTypes.JS_CALL_EXPRESSION)
+ expect(vnodeTag.callee).toBe(RESOLVE_SKIP_COMPONENT)
+ expect((vnodeTag.arguments[0] as SimpleExpressionNode).content).toBe(
+ `_ctx.ok`,
)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('on component with implicit default slot + v-if', () => {
+ const { root, node } = parseWithSkipTransform(
+ `
+ default
+ `,
+ ) as { root: RootNode; node: SkipNode }
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.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] as ElementNode).tagType).toBe(
- ElementTypes.COMPONENT,
- )
- expect((node.alternate.children[0] as ElementNode).tag).toBe(`Comp`)
expect(generate(root).code).toMatchSnapshot()
})
- test.todo('on component with name default slot + v-if', () => {
+ test('on component with dynamic slot', () => {
const { root, node } = parseWithSkipTransform(
`
- default
+ foo
`,
+ ) as { root: RootNode; node: ComponentNode }
+ expect(node.type).toBe(NodeTypes.ELEMENT)
+ expect(node.tagType).toBe(ElementTypes.COMPONENT)
+ const codegenNode = node.codegenNode! as VNodeCall
+ expect(codegenNode.type).toBe(NodeTypes.VNODE_CALL)
+ const vnodeTag = codegenNode.tag as CallExpression
+ expect(vnodeTag.type).toBe(NodeTypes.JS_CALL_EXPRESSION)
+ expect(vnodeTag.callee).toBe(RESOLVE_SKIP_COMPONENT)
+ expect((vnodeTag.arguments[0] as SimpleExpressionNode).content).toBe(
+ `_ctx.ok`,
)
- expect(node.type).toBe(NodeTypes.SKIP)
- expect((node.test as SimpleExpressionNode).content).toBe(`_ctx.ok`)
- expect(node.consequent.type === NodeTypes.JS_CALL_EXPRESSION).toBe(true)
expect(generate(root).code).toMatchSnapshot()
})
- test.todo('on component with implicit default slot + v-if', () => {
+ test('on component with dynamic slot + default slot', () => {
const { root, node } = parseWithSkipTransform(
`
- default
+ foo
+ default
`,
+ ) as { root: RootNode; node: ComponentNode }
+ expect(node.type).toBe(NodeTypes.ELEMENT)
+ expect(node.tagType).toBe(ElementTypes.COMPONENT)
+ const codegenNode = node.codegenNode! as VNodeCall
+ expect(codegenNode.type).toBe(NodeTypes.VNODE_CALL)
+ const vnodeTag = codegenNode.tag as CallExpression
+ expect(vnodeTag.type).toBe(NodeTypes.JS_CALL_EXPRESSION)
+ expect(vnodeTag.callee).toBe(RESOLVE_SKIP_COMPONENT)
+ expect((vnodeTag.arguments[0] as SimpleExpressionNode).content).toBe(
+ `_ctx.ok`,
)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
+ test('on dynamic component with default slot', () => {
+ const { root, node } = parseWithSkipTransform(
+ `foo`,
+ ) as { root: RootNode; node: SkipNode }
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.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(`component`)
expect(generate(root).code).toMatchSnapshot()
})
- test('on dynamic component', () => {
+ test('on dynamic component with dynamic slot', () => {
const { root, node } = parseWithSkipTransform(
`
-
+ foo
`,
) as { root: RootNode; node: ComponentNode }
expect(node.type).toBe(NodeTypes.ELEMENT)
@@ -415,9 +453,18 @@ describe('compiler: v-skip', () => {
expect(generate(root).code).toMatchSnapshot()
})
- test.todo('on Teleport', () => {})
-
- test.todo('built-in components', () => {})
+ test('on Teleport', () => {
+ const { root, node } = parseWithSkipTransform(
+ ``,
+ ) as { root: RootNode; node: SkipNode }
+ expect(node.type).toBe(NodeTypes.SKIP)
+ 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(`teleport`)
+ expect(generate(root).code).toMatchSnapshot()
+ })
})
describe('errors', () => {
@@ -432,22 +479,35 @@ describe('compiler: v-skip', () => {
])
})
+ test('on ', () => {
+ const onError = vi.fn()
+ parseWithSkipTransform(``, { onError })
+ expect(onError.mock.calls[0]).toMatchObject([
+ {
+ code: ErrorCodes.X_V_SKIP_MISPLACED,
+ },
+ ])
+ })
+
test('on ', () => {
const onError = vi.fn()
parseWithSkipTransform(``, { onError })
expect(onError.mock.calls[0]).toMatchObject([
{
- code: ErrorCodes.X_V_SKIP_ON_TEMPLATE,
+ code: ErrorCodes.X_V_SKIP_MISPLACED,
},
])
})
- test('on ', () => {
+ test('on component without default slot', () => {
const onError = vi.fn()
- parseWithSkipTransform(``, { onError })
+ parseWithSkipTransform(
+ `foo`,
+ { onError },
+ )
expect(onError.mock.calls[0]).toMatchObject([
{
- code: ErrorCodes.X_V_SKIP_ON_TEMPLATE,
+ code: ErrorCodes.X_V_SKIP_UNEXPECTED_SLOT,
},
])
})
diff --git a/packages/compiler-core/src/errors.ts b/packages/compiler-core/src/errors.ts
index 683981143e2..8bd6df968e3 100644
--- a/packages/compiler-core/src/errors.ts
+++ b/packages/compiler-core/src/errors.ts
@@ -91,7 +91,7 @@ export enum ErrorCodes {
X_INVALID_EXPRESSION,
X_KEEP_ALIVE_INVALID_CHILDREN,
X_V_SKIP_NO_EXPRESSION,
- X_V_SKIP_ON_TEMPLATE,
+ X_V_SKIP_MISPLACED,
X_V_SKIP_UNEXPECTED_SLOT,
X_V_SKIP_WITH_V_FOR,
@@ -184,7 +184,7 @@ export const errorMessages: Record = {
[ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN]: ` expects exactly one child component.`,
[ErrorCodes.X_VNODE_HOOKS]: `@vnode-* hooks in templates are no longer supported. Use the vue: prefix instead. For example, @vnode-mounted should be changed to @vue:mounted. @vnode-* hooks support has been removed in 3.4.`,
[ErrorCodes.X_V_SKIP_NO_EXPRESSION]: `v-skip is missing expression.`,
- [ErrorCodes.X_V_SKIP_ON_TEMPLATE]: `v-skip cannot be used on or tags.`,
+ [ErrorCodes.X_V_SKIP_MISPLACED]: `v-skip can only be used on elements or components.`,
[ErrorCodes.X_V_SKIP_UNEXPECTED_SLOT]: `v-skip requires the component to have a default slot without slot props`,
[ErrorCodes.X_V_SKIP_WITH_V_FOR]: `v-skip with v-for is not supported.`,
diff --git a/packages/compiler-core/src/transforms/vSkip.ts b/packages/compiler-core/src/transforms/vSkip.ts
index fc877ba84b3..3495a00b104 100644
--- a/packages/compiler-core/src/transforms/vSkip.ts
+++ b/packages/compiler-core/src/transforms/vSkip.ts
@@ -7,6 +7,7 @@ import {
NodeTypes,
type SimpleExpressionNode,
type SkipNode,
+ type TemplateChildNode,
type VNodeCall,
createCallExpression,
createConditionalExpression,
@@ -22,10 +23,10 @@ import {
ErrorCodes,
RESOLVE_SKIP_COMPONENT,
WITH_MEMO,
+ buildSlots,
createCompilerError,
findDir,
findProp,
- isSlotOutlet,
processExpression,
} from '@vue/compiler-core'
import { createCodegenNodeForBranch } from './vIf'
@@ -38,10 +39,7 @@ export const transformSkip: NodeTransform = createStructuralDirectiveTransform(
return processSkip(node, dir, context, (skipNode?: SkipNode) => {
return () => {
const codegenNode = node.codegenNode!
- if (
- node.tagType === ElementTypes.COMPONENT &&
- node.tag !== 'Teleport'
- ) {
+ if (!skipNode) {
if (codegenNode.type === NodeTypes.VNODE_CALL) {
codegenNode.tag = getVNodeTag(
context,
@@ -85,15 +83,22 @@ export function processSkip(
): (() => void) | undefined {
const loc = dir.exp ? dir.exp.loc : node.loc
if (
- (node.type === NodeTypes.ELEMENT && node.tag === 'template') ||
- isSlotOutlet(node)
+ // v-skip is not allowed on or
+ !(
+ node.type === NodeTypes.ELEMENT &&
+ (node.tagType === ElementTypes.ELEMENT ||
+ node.tagType === ElementTypes.COMPONENT) &&
+ node.tag !== 'template' &&
+ node.tag !== 'slot'
+ )
) {
- context.onError(createCompilerError(ErrorCodes.X_V_SKIP_ON_TEMPLATE, loc))
+ context.onError(createCompilerError(ErrorCodes.X_V_SKIP_MISPLACED, loc))
return
}
if (findDir(node, 'for')) {
context.onError(createCompilerError(ErrorCodes.X_V_SKIP_WITH_V_FOR, loc))
+ return
}
if (!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim()) {
@@ -109,12 +114,48 @@ export function processSkip(
validateBrowserExpression(dir.exp as SimpleExpressionNode, context)
}
- let skipNode: SkipNode | undefined
+ // element will be processed as a skip node
+ // - native element
+ // - teleport, since it has children
+ // - component without dynamic slots
+ let processAsSkipNode = false
+ const isComponent = node.tagType === ElementTypes.COMPONENT
+ let children: TemplateChildNode[] = []
if (
node.tagType === ElementTypes.ELEMENT ||
- (node.tagType === ElementTypes.COMPONENT && node.tag === 'Teleport')
+ (isComponent && node.tag === 'Teleport')
) {
- const children = node.children
+ processAsSkipNode = true
+ children = node.children
+ } else if (isComponent) {
+ const { slots, hasDynamicSlots } = buildSlots(
+ node,
+ context,
+ undefined,
+ true,
+ )
+ // find default slot if not has dynamic slots
+ if (!hasDynamicSlots && slots.type === NodeTypes.JS_OBJECT_EXPRESSION) {
+ processAsSkipNode = true
+ const prop = slots.properties.find(
+ p =>
+ p.type === NodeTypes.JS_PROPERTY &&
+ p.key.type === NodeTypes.SIMPLE_EXPRESSION &&
+ p.key.content === 'default' &&
+ p.value.params === undefined,
+ )
+ if (prop) {
+ children = prop.value.returns as TemplateChildNode[]
+ } else {
+ context.onError(
+ createCompilerError(ErrorCodes.X_V_SKIP_UNEXPECTED_SLOT, loc),
+ )
+ }
+ }
+ }
+
+ let skipNode: SkipNode | undefined
+ if (processAsSkipNode) {
// if children is empty, create comment node
const consequent =
children.length !== 0
diff --git a/packages/compiler-ssr/__tests__/ssrVSkip.spec.ts b/packages/compiler-ssr/__tests__/ssrVSkip.spec.ts
index 778a31c48fd..1a8aed40c22 100644
--- a/packages/compiler-ssr/__tests__/ssrVSkip.spec.ts
+++ b/packages/compiler-ssr/__tests__/ssrVSkip.spec.ts
@@ -195,13 +195,17 @@ describe('ssr: v-skip', () => {
test('on component', () => {
expect(compile(``).code).toMatchInlineSnapshot(`
- "const { resolveComponent: _resolveComponent } = require("vue")
+ "const { withCtx: _withCtx, createCommentVNode: _createCommentVNode, resolveComponent: _resolveComponent } = require("vue")
const { ssrRenderComponent: _ssrRenderComponent, ssrRenderSkipComponent: _ssrRenderSkipComponent } = require("vue/server-renderer")
return function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_Comp = _resolveComponent("Comp")
- _push(_ssrRenderSkipComponent(_push, _ctx.foo, _component_Comp, _attrs, null, _parent))
+ if (_ctx.foo) {
+ _createCommentVNode("v-skip", true)
+ } else {
+ _push(_ssrRenderSkipComponent(_push, _ctx.foo, _component_Comp, _attrs, null, _parent))
+ }
}"
`)
})
@@ -324,22 +328,26 @@ describe('ssr: v-skip', () => {
`,
).code,
).toMatchInlineSnapshot(`
- "const { resolveDynamicComponent: _resolveDynamicComponent, withCtx: _withCtx, renderSlot: _renderSlot, createVNode: _createVNode } = require("vue")
+ "const { withCtx: _withCtx, resolveDynamicComponent: _resolveDynamicComponent, renderSlot: _renderSlot, createVNode: _createVNode } = require("vue")
const { ssrRenderSlot: _ssrRenderSlot, ssrRenderVNode: _ssrRenderVNode } = require("vue/server-renderer")
return function ssrRender(_ctx, _push, _parent, _attrs) {
- _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.Comp), _attrs, {
- default: _withCtx((_, _push, _parent, _scopeId) => {
- if (_push) {
- _ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent, _scopeId)
- } else {
- return [
- _renderSlot(_ctx.$slots, "default")
- ]
- }
- }),
- _: 3 /* FORWARDED */
- }), _parent)
+ if (_ctx.ok) {
+ _ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent, _scopeId)
+ } else {
+ _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.Comp), _attrs, {
+ default: _withCtx((_, _push, _parent, _scopeId) => {
+ if (_push) {
+ _ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent, _scopeId)
+ } else {
+ return [
+ _renderSlot(_ctx.$slots, "default")
+ ]
+ }
+ }),
+ _: 3 /* FORWARDED */
+ }), _parent)
+ }
}"
`)
})
@@ -351,25 +359,29 @@ describe('ssr: v-skip', () => {
`).code,
).toMatchInlineSnapshot(`
- "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require("vue")
+ "const { withCtx: _withCtx, resolveComponent: _resolveComponent, createVNode: _createVNode } = require("vue")
const { ssrRenderComponent: _ssrRenderComponent, ssrRenderSkipComponent: _ssrRenderSkipComponent } = require("vue/server-renderer")
return function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_Comp = _resolveComponent("Comp")
_push(\`\`)
- _push(_ssrRenderSkipComponent(_push, _ctx.ok, _component_Comp, null, {
- default: _withCtx((_, _push, _parent, _scopeId) => {
- if (_push) {
- _push(\`\`)
- } else {
- return [
- _createVNode("span")
- ]
- }
- }),
- _: 1 /* STABLE */
- }, _parent))
+ if (_ctx.ok) {
+ _push(\`\`)
+ } else {
+ _push(_ssrRenderSkipComponent(_push, _ctx.ok, _component_Comp, null, {
+ default: _withCtx((_, _push, _parent, _scopeId) => {
+ if (_push) {
+ _push(\`\`)
+ } else {
+ return [
+ _createVNode("span")
+ ]
+ }
+ }),
+ _: 1 /* STABLE */
+ }, _parent))
+ }
_push(\`\`)
}"
`)