Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ export function render(_ctx) {
}"
`;

exports[`compiler: v-once > on slot outlet 1`] = `
"import { setInsertionState as _setInsertionState, createSlot as _createSlot, template as _template } from 'vue';
const t0 = _template("<div></div>", true)

export function render(_ctx) {
const n1 = t0()
_setInsertionState(n1, null, true)
const n0 = _createSlot("default", null, null, null, true)
return n1
}"
`;

exports[`compiler: v-once > with v-for 1`] = `
"import { createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<div></div>")
Expand Down
8 changes: 7 additions & 1 deletion packages/compiler-vapor/__tests__/transforms/vOnce.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,13 @@ describe('compiler: v-once', () => {
})
})

test.todo('on slot outlet')
test('on slot outlet', () => {
const { ir, code } = compileWithOnce(`<div><slot v-once /></div>`)
expect(code).toMatchSnapshot()

expect(ir.block.effect).lengthOf(0)
expect(ir.block.operation).lengthOf(0)
})

test('inside v-once', () => {
const { ir, code } = compileWithOnce(`<div v-once><div v-once/></div>`)
Expand Down
3 changes: 2 additions & 1 deletion packages/compiler-vapor/src/generators/slotOutlet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function genSlotOutlet(
context: CodegenContext,
): CodeFragment[] {
const { helper } = context
const { id, name, fallback, noSlotted } = oper
const { id, name, fallback, noSlotted, once } = oper
const [frag, push] = buildCodeFragment()

const nameExpr = name.isStatic
Expand All @@ -31,6 +31,7 @@ export function genSlotOutlet(
genRawProps(oper.props, context) || 'null',
fallbackArg,
noSlotted && 'true', // noSlotted
once && 'true', // v-once
),
)

Expand Down
1 change: 1 addition & 0 deletions packages/compiler-vapor/src/ir/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ export interface SlotOutletIRNode extends BaseIRNode {
props: IRProps[]
fallback?: BlockIRNode
noSlotted?: boolean
once?: boolean
parent?: number
anchor?: number
append?: boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
props: irProps,
fallback,
noSlotted: !!(context.options.scopeId && !context.options.slotted),
once: context.inVOnce,
}
}
}
Expand Down
37 changes: 37 additions & 0 deletions packages/runtime-vapor/__tests__/componentSlots.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
renderEffect,
setInsertionState,
template,
txt,
vaporInteropPlugin,
withVaporCtx,
} from '../src'
Expand Down Expand Up @@ -774,6 +775,42 @@ describe('component: slots', () => {
await nextTick()
expect(html()).toBe('<span>2</span><!--for--><!--slot-->')
})

test('work with v-once', async () => {
const Child = defineVaporComponent({
setup() {
return createSlot(
'default',
null,
undefined,
undefined,
true /* once */,
)
},
})

const count = ref(0)

const { html } = define({
setup() {
return createComponent(Child, null, {
default: withVaporCtx(() => {
const n3 = template('<div> </div>')() as any
const x3 = txt(n3) as any
renderEffect(() => setText(x3, toDisplayString(count.value)))
return n3
}),
})
},
}).render()

expect(html()).toBe('<div>0</div><!--slot-->')

// expect no changes due to v-once
count.value++
await nextTick()
expect(html()).toBe('<div>0</div><!--slot-->')
})
})

describe('forwarded slot', () => {
Expand Down
12 changes: 11 additions & 1 deletion packages/runtime-vapor/src/componentSlots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ import { DynamicFragment, type VaporFragment } from './fragment'
import { createElement } from './dom/node'
import { setDynamicProps } from './dom/prop'

/**
* Flag to indicate if we are executing a once slot.
* When true, renderEffect should skip creating reactive effect.
*/
export let inOnceSlot = false

/**
* Current slot scopeIds for vdom interop
*/
Expand Down Expand Up @@ -163,6 +169,7 @@ export function createSlot(
rawProps?: LooseRawProps | null,
fallback?: VaporSlot,
noSlotted?: boolean,
once?: boolean,
): Block {
const _insertionParent = insertionParent
const _insertionAnchor = insertionAnchor
Expand Down Expand Up @@ -236,9 +243,12 @@ export function createSlot(
const prevSlotScopeIds = setCurrentSlotScopeIds(
slotScopeIds.length > 0 ? slotScopeIds : null,
)
const prev = inOnceSlot
try {
if (once) inOnceSlot = true
return slot(slotProps)
} finally {
inOnceSlot = prev
setCurrentSlotScopeIds(prevSlotScopeIds)
}
}),
Expand All @@ -249,7 +259,7 @@ export function createSlot(
}

// dynamic slot name or has dynamicSlots
if (isDynamicName || rawSlots.$) {
if (!once && (isDynamicName || rawSlots.$)) {
renderEffect(renderSlot)
} else {
renderSlot()
Expand Down
4 changes: 4 additions & 0 deletions packages/runtime-vapor/src/renderEffect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
warn,
} from '@vue/runtime-dom'
import { type VaporComponentInstance, isVaporComponent } from './component'
import { inOnceSlot } from './componentSlots'
import { invokeArrayFns } from '@vue/shared'

export class RenderEffect extends ReactiveEffect {
Expand Down Expand Up @@ -88,6 +89,9 @@ export class RenderEffect extends ReactiveEffect {
}

export function renderEffect(fn: () => void, noLifecycle = false): void {
// in once slot, just run the function directly
if (inOnceSlot) return fn()

const effect = new RenderEffect(fn)
if (noLifecycle) {
effect.fn = fn
Expand Down
Loading