Keep keys on the children of v-for elements in the Vue 3 compiler

What does this MR do and why?

The Vue 3 template compiler wrapper removes the :key from the direct children of every element with v-for. It must only do that for <template v-for>.

Vue 2 allows the key on the children of <template v-for>, but Vue 3 fails with "<template v-for> key should be placed on the <template> tag". So modifyKeysInsideTemplateTag moves the key to the <template>. Its guard was meant to skip every other tag:

if (!templateNode.tag === 'template' || !hasProp(templateNode, 'for')) {

!templateNode.tag === 'template' is always false, so the guard skipped nothing. On <div v-for="item in items" :key="item.id"><my-component :key="item.version" /></div>, the child lost key: item.version. A key like that forces the child to remount when its value changes, so it silently stopped working. Vue 2 and @vue/compiler-dom keep it.

No build uses the Vue 3 compiler yet. After the Vue 2 removal in !263207, it compiles every template, so this fix must come first.

This MR also:

  • Documents what the wrapper is for, and what each fix in transformAst does.
  • Tests the wrapper through compileTemplate from @vue/compiler-sfc, which is how vue-loader and @vue/vue3-jest call it. The tests check that it produces the same code as the default compiler, and that it gets the source string, not the AST from the SFC descriptor.

This MR is stacked on !263243 (merged), which adds the Vitest spec that these tests extend.

Templates that change

A scan of all 5130 .vue files found 8 elements with v-for that have a direct child with :key. All of them have a key on the parent too.

These 6 use the same identity on the parent and on the child, so the child key changes nothing:

  • simple_viewer.vue
  • packages_list_loader.vue (2 places)
  • multi_select_checkbox.vue
  • lifecycle_detail.vue
  • statuses_list.vue

These 2 use a different key on the child:

  • timeline_events_list.vue: the <li> uses the index, and the children use event.id. Vue 2 remounts the child after an event is deleted. Neither child has local state, so nothing visible changes.
  • status_modal.vue: <status-form> uses status.name. Vue 2 remounts the form when the name changes.

With this fix, the Vue 3 compiler keeps these keys, as Vue 2 does.

Screenshots or screen recordings

Not applicable. No build uses the Vue 3 compiler yet.

How to set up and validate locally

  1. Run yarn vitest:vue3migration run spec/frontend/config/vue3migration/compilers/vue3_template_spec.mjs. The test "keeps the key on the child of an element with v-for" fails without the fix.
  2. Run VUE_VERSION=3 VUE_COMPILER_VERSION=3 yarn jest spec/frontend/vue3migration. VUE_VERSION=3 alone still uses the Vue 2 template compiler. shared_sfc_mutation_spec.js fails 3 tests with the Vue 3 compiler on master too, so that failure is not from this MR.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

References

Merge request reports

Loading
Loading