diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
index 3da778eb675..225861919da 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
@@ -135,6 +135,26 @@ return function render(_ctx, _cache) {
}"
`;
+exports[`compiler: v-for > codegen > template w/ v-for + custom directive should not be STABLE_FRAGMENT 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, resolveDirective: _resolveDirective, withDirectives: _withDirectives, createCommentVNode: _createCommentVNode } = _Vue
+
+ const _directive_focus = _resolveDirective("focus")
+
+ return show
+ ? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(arr, (i) => {
+ return _withDirectives((_openBlock(), _createElementBlock("h1", { key: i })), [
+ [_directive_focus]
+ ])
+ }), 128 /* KEYED_FRAGMENT */))
+ : _createCommentVNode("v-if", true)
+ }
+}"
+`;
+
exports[`compiler: v-for > codegen > v-for on 1`] = `
"const _Vue = Vue
diff --git a/packages/compiler-core/__tests__/transforms/vFor.spec.ts b/packages/compiler-core/__tests__/transforms/vFor.spec.ts
index fead2476ac5..8ad5fba9999 100644
--- a/packages/compiler-core/__tests__/transforms/vFor.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vFor.spec.ts
@@ -1073,5 +1073,12 @@ describe('compiler: v-for', () => {
},
})
})
+
+ test('template w/ v-for + custom directive should not be STABLE_FRAGMENT', () => {
+ const { root } = parseWithForTransform(
+ '',
+ )
+ expect(generate(root).code).toMatchSnapshot()
+ })
})
})
diff --git a/packages/compiler-core/src/transforms/vFor.ts b/packages/compiler-core/src/transforms/vFor.ts
index 0dca0ba9ab4..ec32ef41438 100644
--- a/packages/compiler-core/src/transforms/vFor.ts
+++ b/packages/compiler-core/src/transforms/vFor.ts
@@ -105,10 +105,16 @@ export const transformFor: NodeTransform = createStructuralDirectiveTransform(
)
}
}
-
+ const maybeRuntimeDir = findDir(
+ node,
+ /^(?!if$|else$|else-if$|bind$|for$|memo$|on$|once$|slot$|model$).*$/,
+ true,
+ )
const isStableFragment =
forNode.source.type === NodeTypes.SIMPLE_EXPRESSION &&
- forNode.source.constType > ConstantTypes.NOT_CONSTANT
+ forNode.source.constType > ConstantTypes.NOT_CONSTANT &&
+ !maybeRuntimeDir
+
const fragmentFlag = isStableFragment
? PatchFlags.STABLE_FRAGMENT
: keyProp
diff --git a/packages/vue/__tests__/index.spec.ts b/packages/vue/__tests__/index.spec.ts
index 0c969f15981..ee54042ad51 100644
--- a/packages/vue/__tests__/index.spec.ts
+++ b/packages/vue/__tests__/index.spec.ts
@@ -276,6 +276,38 @@ describe('compiler + runtime integration', () => {
expect(container.innerHTML).toBe(`
`)
})
+ it('should trigger custom directive unmounted hook with v-for', async () => {
+ const mounted = vi.fn()
+ const unmounted = vi.fn()
+ const visible = ref(true)
+ const App = {
+ directives: {
+ foo: {
+ mounted,
+ unmounted,
+ },
+ },
+ setup() {
+ const arr = [1, 2, 3]
+ return {
+ arr,
+ visible,
+ }
+ },
+ template: `
+
+ `,
+ }
+
+ const container = document.createElement('div')
+ createApp(App).mount(container)
+ await nextTick()
+ expect(mounted).toHaveBeenCalledTimes(3)
+ visible.value = false
+ await nextTick()
+ expect(unmounted).toHaveBeenCalledTimes(3)
+ })
+
test('v-for + v-once', async () => {
const list = reactive([1])
const App = {