Introduction In the ever-evolving world of web development, choosing the right framework can be...
Embracing Progress: Part 2 - All aboard the Struggle Bus!
In our previous post in this series, we covered why we chose the Vue 3 upgrade path, as opposed to choosing a different framework. In this post, we’ll be covering some of the challenges we faced in upgrading from Vue 2 to Vue 3, and how we overcame them. As with any major upgrade, there’s a lot to consider. Vue 3 brings in many things, while not required, we would gain great benefits from in the long term.
Changing from a more verbose, options based component definition to a much simpler “React hooks”-style setup script. According to ChatGPT:
“The Composition API in Vue 3 offers enhanced code reusability and organization, allowing for more flexible and scalable component logic management compared to the Options API.”
The challenge of course is that ALL of our components needed to be rewritten. And while our assistant doesn’t seem large, it isn’t tiny either! We have close to 50 components within our assistant! For us this was an especially large challenge, as we were using the previously recommended vue-class-component and vue-property-decorator libraries. In the previous post I showed an example of the Composition API vs the Options API, however that isn’t a true reflection of what our code looked like. In our case, the Options API code looked more like this:
And this is where we wanted to go:
Furthermore, props decorations were considerably different, and had to be moved to a more functional style approach. Thankfully we live in the future, and AI was there to help! With a little bit of prompting, Copilot Chat was able to provide us with mostly correct refactors, which required some sanity checking and testing.
Localization & Plugins
Vue 3 no longer has a global Vue object. With the push to the Composition API, it also pushes you away from using magic $-prefixed variables in your templates. This is a good thing, however many Vue plugins rely on these $ prefixed variables, and while they still mostly work, sometimes the behavior and setup of these plugins is considerably different. vue-i18n is a great example of fragmentation between Vue 2 and Vue 3 of how it works. There’s different versions for the respective frameworks, and they have some odd quirks!
Unless configured in a very specific way, usage of the global $t in the Vue 3 version doesn’t work. There also appears to be a conflict when running embedded on Magento stores that include their own build of vue-i18n which breaks the message compiler.
And finally, the behavior between using the new Composition API within vue-i18n and using the old $t/$tm is subtly different and instead of reworking and fixing all of our localization code, we decided to stick with the $t/$tm syntax. Over time, as it becomes more stable, we’ll likely migrate this to the new useI18n Composition code.
vue-cli is dead, long live vite. Rollup based systems like vite have become increasingly popular over webpack based build systems. They provide much faster build times and in 90% of the cases provide exactly the same functionality.
Loaders: Unfortunately, some of our use cases, namely complex web components in a remotely loaded bundle, are in the remaining 10%. vite doesn’t handle configuring code-splitting as gracefully as webpack and doesn’t have its own module loader by design, something which we were using for convenience and to cut down loading unused chunks for certain flows. We opted for ES Modules - which are now thankfully as widely supported as Web Components and things a lot, although do provide much messier, and harder to configure build output.
vue-cli build output (12 files):
vite ESM output (50 files):
Icons: In terms of plugins, we added an SVG Loader and set our SVGs to all load as components, replacing the need for our old icon loader component. While this was a nice bonus, it did require us to refactor a bunch more code.
Testing: While vite does support jest with some work, testing with vitest is natively supported! This is great, however, changing the testing framework also requires some upgrades. vitest is jest compatible, so other than changing a few imports, for the most part the code just worked. One major difference however, is that snapshots are slightly different, and have a randomly generated data attribute, so they needed to be regenerated every time a component was modified. This is not really practical, so we brought in a third-party snapshot serializer, which no longer regenerated these data attributes.
The migration to vite was probably the hardest part of the vue 3 migration, and we could have skipped it, but since vue-cli is now in maintenance mode, it was a matter of time before this change had to happen.
Web component entry
While Vue 3 claims to have native Web component support, and technically it does, it is unfortunately not nearly as mature as the web component support of Vue 2. Firstly, Vue 3 just assumes your Web component is a single component, and not a hierarchy of nested components. It’s intended to be used to share library components such as buttons, or icons and not entire apps. Sadly even simple components often need other components. What is a button group but a group of buttons?
The main issues are around the styling of nested components. By default, Vue 3 only applies the style of the root component and the global styles. Nested components do not get styling applied to them. There are several packages that resolve this, but we opted for the simplest option, by modifying a very small library from Github user EranGrin. This library did not play nicely with Transitions, so we made some modifications and submitted a pull request back to the original repository.
As with any major upgrade, there’s a lot of moving parts. Not only are code modifications always laborious and often risky - especially when you have had to change your testing framework too, but replacing the bundler and dealing with core incompatibilities makes things orders of magnitude more complex. In the next and final post, we’ll talk about how we rolled this out, with no one noticing. This is the fun part!