Vue 3 build of vue-virtual-scroller crashes under @vue/compat: this.$slots.default is not a function
Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.
Summary
vendor/vue-virtual-scroller has two vendored copies that use opposite slot APIs. Under @vue/compat, webpack picks the copy that crashes and Vite picks the copy that works. Any page that opts into Vue 3 with Option 1 and renders DynamicScroller throws at runtime in a webpack build.
The error
commons-pages.projects.artifacts.browse.vue3-pages.projects.artifacts.file.vue3-pages.projects.artifacts.vue3.chunk.js
Uncaught TypeError: this.$slots.default is not a functionSeen in rspec system predictive 21/22 on !250423 (merged): https://gitlab.com/gitlab-org/gitlab/-/jobs/16024777499
The failing example is spec/features/projects/artifacts/user_views_project_artifacts_page_spec.rb:21. Feature specs run with feature flags enabled, so the page is served the .vue3 chunk.
Cause
The two copies differ in one line:
| File | Line | Code |
|---|---|---|
vendor/assets/javascripts/vue-virtual-scroller/src/components/DynamicScrollerItem.vue |
216 | h(this.tag, this.$slots.default) |
vendor/assets/javascripts/vue-virtual-scroller-vue3/src/components/DynamicScrollerItem.vue |
245 | h(this.tag, this.$slots.default()) |
Under @vue/compat, $slots.default keeps the Vue 2 shape. It is an array of VNodes, not a function. The -vue3 copy declares no compatConfig, so it never gets Vue 3 slot semantics and calling $slots.default() throws.
The -vue3 copy is correct for a pure Vue 3 runtime. It is wrong for compat mode, which is what Option 1 uses.
Why the two bundlers disagree
vendor is a global alias to vendor/assets/javascripts (config/helpers/aliases.js:13), so vendor/vue-virtual-scroller normally resolves to the plain copy.
CONTEXT_ALIASES (config/helpers/context_aliases_shared.js:15-18) redirects that specifier to the -vue3 copy. It is merged into the global alias map only when the whole build is Vue 3 (config/helpers/aliases.js:100-102). Under Option 1 the build is Vue 2, so the redirect is instead applied per module by the infection plugins, for infected importers only.
Both plugins look the key up the same way, with an exact match on the import specifier:
config/plugins/webpack_vue3_infection_plugin.js:82config/helpers/vite_plugin_vue3_infection.mjs:108
artifacts_table_row_details.vue:3 imports the exact string 'vendor/vue-virtual-scroller', so both should match. In practice they do not:
- webpack applies the redirect and serves the
-vue3copy, which crashes. - Vite dev does not. Fetching the compiled module from the dev server shows the import resolved to
vendor/assets/javascripts/vue-virtual-scroller/src/index.js, the plain copy, which works under compat.
I did not determine why the Vite branch does not fire. That is the second half of this bug.
Impact
Ten modules import the scroller:
app/assets/javascripts/ci/artifacts/components/artifacts_table_row_details.vue
app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue
app/assets/javascripts/vue_merge_request_widget/widgets/test_report/index.vue
app/assets/javascripts/diffs/components/app.vue
app/assets/javascripts/diffs/components/diff_file.vue
app/assets/javascripts/diffs/components/tree_list.vue
app/assets/javascripts/diffs/components/diff_view.vue
app/assets/javascripts/vue_shared/components/file_finder/index.vue
ee/app/assets/javascripts/dependencies/components/filtered_search/tokens/license_token.vue
ee/app/assets/javascripts/vue_merge_request_widget/widgets/security_reports/mr_widget_security_report_details.vueNo page currently on a Vue 3 rollout renders any of them, so master is not broken today. The artifacts page in !250423 (merged) would be the first, and that MR is blocked on this.
Most of the rest is the merge request page: the diffs view and the MR widget. Whatever fix is chosen also gates the diffs migration, which is a much larger target.
There is a second cost. Because Vite serves the working copy, this class of bug is invisible in GDK. Manual verification of an Option 1 migration cannot clear this dependency. Only a webpack or rspack build can.
Possible fixes
- Add
compatConfig: { MODE: 3 }to the components in the-vue3copy, so they get real Vue 3 slots under compat. - Remove the
vendor/vue-virtual-scrollerentry fromCONTEXT_ALIASESand keep the plain copy under compat, matching what Vite already does. - Align Vite and webpack first, then decide. The divergence is a defect on its own, because local verification and CI test different code.
Option 1 keeps the redirect and fixes the vendored copy. Option 2 is smaller but leaves the -vue3 copy unused under Option 1 migrations.
Related
- Blocked merge request: !250423 (merged)
- Earlier build-time fix for the same dependency: !250882 (merged)
- Vue 3 migration epic: &23285
- Unused npm alias cleanup: #619387 (closed)