Skip to content

Latest commit

 

History

History
238 lines (165 loc) · 15.8 KB

MIGRATION-NOTE.md

File metadata and controls

238 lines (165 loc) · 15.8 KB

Vue 2 → Vue 3 Migration Note

Here are major challenges I faced during migration of Buefy from Vue 2 to Vue 3.

Component alone cannot mount itself

A component instance no longer has the $mount method in Vue 3. To mount a component, a Vue app instance is necessary. In Buefy, mounting a new component was utilized to create a component instance which is programmatically mounted; e.g., modal.open, toast.open. One Vue app instance can mount only one root component, so another Vue app instance has to be created with the createApp API to mount a new component. To programmatically mount a component, I changed the code to create a brand-new Vue app instance as a mounting point of the component. The major drawback of this workaround is that a brand-new Vue app inherits no plugins, thus even components of Buefy itself cannot be used in a programmatically mounted component. As far as I investigated, there is no API to duplicate a Vue app instance. This will be especially problematic when a user wants to programmatically open a Modal with a custom component. Components that a custom component depends on have to be explicitly specified in the components field. See #8b05481a20ec8bccb03f1c98ad5556442e79258b, and #294d5453d9eecd351b7ae91e3c891c085a0d9c20.

Component cannot be obtained from a slot

As far as I investigated, no component instance can be obtained from a slot on Vue 3.

This was problematic for Table as it tried to list TableColumns specified in the default slot. As a workaround, TableColumns registers itself to the parent Table when it is created, and removes it from the Table when it is unmounted. See #34e4f7a872969851a88b082b87cab5d79c2f25f4.

ProviderParentMixin, and InejctedChildMixin that are used by Steps, Carousel, etc. also faced this issue. When a sorted flag is set, ProviderParentMixin indexes InjectedChildMixins according to the index in the default slot. I was not able to figure out how to enumerate component instances in the default slot. So I introduced a new prop order to InjectedChildMixin, that is used to sort items. See #defe37960d284e6de81d3e273e1a18c33b12e2a5.

v-model binding changed

Default v-model prop and event are changed,

  • prop: valuemodelValue
  • event: inputupdate:modelValue

On Vue 3, we cannot use other name than "modelValue" for the default v-model binding.

See also Vue's migration guide.

No meaning of newing a component

Newing a component does not make sense on Vue 3. Newing a component was utilized to create a default Table column renderer. I made a new function mockTableColumn that creates an object that mocks the behavior of a Table column renderer. See #017531f4a1d910357c10799621101046f4098b15, and #ea627f685327326c3d979541c56a6a58a3ea63ea.

No $destroy

There is no equivalent of $destroy in Vue 3. This is problematic for Buefy, because a programmatically mounted component has to destroy itself. I worked around this problem by moving the responsibility for destruction from a component to a Vue app instance created when the component is programmatically mounted (see Section "No meaning of newing a component").

See also Vue's migration guide.

Boolean attribute is not removed if it is false

On Vue 2, setting a boolean attribute to false is equivalent to removing it. On Vue 3, setting a boolean attribute to false does not remove it, but null or undefined has to be specified to do so. This caused some components look disabled even if the flag is set false. I introduced computed values that become true or undefined, and associated them to boolean attributes instead. Here are related commits,

Global h function

On Vue 3, a render function no longer takes a "create element" function, aka h, but the h function is defined globally. The second argument props of the global h API has been simplified; e.g., staticClass is merged to class, on is removed.

See also,

on{EventName} binding

No $listeners

On Vue 3, there is no $listeners but it is a part of $attrs. A listener attribute name is "on" + event name.

See also Vue's migration guide.

h function argument

On Vue 3, the second argument (props) of the h function handles a field whose name starts with "on" followed by an event name as event listener for the event. For example, onClick listens for click event. This causes conflict in props of Dialog, and Modal. They intended to take onConfirm, and onCancel props as callback functions independent from event listeners. But they are interpreted as event listeners on Vue 3. As a workaround, the callback functions are renamed to confirmCallback, and cancelCallback respectively. See #a415f3aef052fdc39c5322372ae0f7d8665c6e9f.

See also Vue's migration guide.

The above workaround was reconsidered, and the compatibility with Buefy for Vue 2 was restored. So you have to specify onConfirm, and onCancel instead of confirmCallback, and cancelCallback to programmatically open a Dialog. See #76b7b91d06541a7e09b9122a9f557ab0d1f0f371.

Emitted events

On Vue 3, events emitted from a component has to be listed in the emits field. Failing to list events may cause accidental binding of event listeners when v-bind="$attrs" is used. For example, Taginput emits a typing event, and its child Autocomplete also emits a typing event. If the typing event is not listed in the emits field of Taginput, a listener for typing events from Taginput will be called twice when a single typing event is emitted, one from Taginput, and the other from Autocomplete. See #9e29ecc11439f223a1f8f2d140a495680df14903.

No directives

On Vue 3, the global h API no longer takes directives, use the global withDirectives API instead. Vue 3 exports deicated objects representing built-in directives,

For example, v-showvShow.

import { vShow } from 'vue'

See #5bc62ffbeba831f60fe1b755b2e5fe7c5e25d3ed, and #690d28996dd2eecc1e5059f5ef349351c017f533.

See also Vue's API reference.

Directive hooks are renamed

Directives defined in Buefy had to be updated.

See also Vue's migration guide.

Functional component must be a function

On Vue 3, a single file component cannot be functional. I had to rewrite MenuList as a function. See #f85445f7a806c52a524960e24714bbf2b4c80d95.

See also Vue's migration guide.

No automatic component resolution

The h global API does not resolve a component name. The resolveComponent global API has to be called. You can find an example in #730b0a22ac481255287c8a8725041e947e7c02ab.

For built-in components, Vue 3 exports dedicated objects, Transition for example.

import { Transition } from 'vue'

Transition CSS class names changed

As some transition CSS class names have been changed in Vue 3, I had to update src/scss/utils/_animations.scss. See #82136338ca6cdf7be3b4b2d134e457074f5ffcb1.

See also Vue's migration guide.

No $children

Vue 3 no longer provies $children of a component. Menu depended on $children to list menu items, so I had to introduced a register/unregister mechanism to Menu. See #f85445f7a806c52a524960e24714bbf2b4c80d95.

See also Vue's migration guide.

No _uid

The internal field _uid of a component has been removed (moved?) in Vue 3. TabbedChildMixin used to initialize its value prop with this._uid, and this no longer works (there is another reason that no this is available during prop initialization). A Vue client code should not depend on an internal field anyway, so I introduced a new function makeUniqueId defined in a new file src/utils/make-unique-id.js, that returns a random number. It depends on window.crypto.subtle. See #bd1fe22948dc1b65fe897d97c5891bcb131fb820.

v-for × ref does not list components

The special behavior of a v-for × ref="X" combination, where refs.X becomes an array of components refed by "X", is no longer supported on Vue 3. As a workaround, I replaced "X" so that every component in a loop has a unique ref ID. See #bd1fe22948dc1b65fe897d97c5891bcb131fb820.

inheritAttrs=false affects class and style

On Vue 3, class, and style are members of $attrs, so setting inheritAttrs=false does not apply class, and style to the root element. I had to manually copy class, and style to the root element. See #6f8952ec1389b1f74683f69fb04aac4beae62140.

See also Vue's guide.

$el is null during beforeMount

See #bf6e9cd680298b1a8b572bdb1d742f9ac0eca769.

Head scratchers

The following subtle errors made me scratch my head,

$scopedSlots is included in $slots

I had to replace many occurrences of $scopedSlots, but replacement was straightforward.

See also Vue's migration guide.

No .native modifier for v-on

I had to remove .native modifiers and list events in the emits option, but modification was straightforward.

See also Vue's migration guide.

beforeDestory is renamed to beforeUnmount

Updates were straightforward.

See Vue's migration guide.