}"
`;
+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>")
})
})
- 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>`)
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
genRawProps(oper.props, context) || 'null',
fallbackArg,
noSlotted && 'true', // noSlotted
+ once && 'true', // v-once
),
)
props: IRProps[]
fallback?: BlockIRNode
noSlotted?: boolean
+ once?: boolean
parent?: number
anchor?: number
append?: boolean
props: irProps,
fallback,
noSlotted: !!(context.options.scopeId && !context.options.slotted),
+ once: context.inVOnce,
}
}
}
renderEffect,
setInsertionState,
template,
+ txt,
vaporInteropPlugin,
withVaporCtx,
} from '../src'
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', () => {
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
*/
rawProps?: LooseRawProps | null,
fallback?: VaporSlot,
noSlotted?: boolean,
+ once?: boolean,
): Block {
const _insertionParent = insertionParent
const _insertionAnchor = insertionAnchor
const prevSlotScopeIds = setCurrentSlotScopeIds(
slotScopeIds.length > 0 ? slotScopeIds : null,
)
+ const prev = inOnceSlot
try {
+ if (once) inOnceSlot = true
return slot(slotProps)
} finally {
+ inOnceSlot = prev
setCurrentSlotScopeIds(prevSlotScopeIds)
}
}),
}
// dynamic slot name or has dynamicSlots
- if (isDynamicName || rawSlots.$) {
+ if (!once && (isDynamicName || rawSlots.$)) {
renderEffect(renderSlot)
} else {
renderSlot()
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 {
}
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