Many organizations with component libraries are working on upgrades for their Vue 2 apps so that they can support Vue 3. Sometimes it's not easy! Ideally, you could write your code once and cross-compile it for different Vue runtimes. In this talk, we'll do exactly that. At the end, you'll have a recipe for shipping libraries that support both Vue 2 and Vue 3.
Building Backwards Compatible Vue Libraries
From:

Vue.js London 2023
Transcription
♪♪ Hello, hello, hello, hello. All right, good morning, I mean afternoon. How, all right, we're going to do the hands thing. So get your hands ready. How many people use vue here in production? There we go. And this is to make sure you're awake. How many people are tired? Yeah, it's been long, me too. But I'm excited because the next talk after mine is really frigging cool. So let's do mine. I tailor my talks to the people in the room. So when I originally wrote this talk, I wrote it because I was building a library. I was building a component design system. Hands, all right, I get hands. How many people are in a vue 2 to 3 migration? Yeah, yeah. Is it fun? No, yeah? Yeah, no, it sucks. So my case is I think actually the worst case of a vue 2 to 3 migration. And I'll show you my repo structure in a hot second. Are people building apps? Are you building apps or libraries? Shout it. Apps. All right. So this talk we're going to change gears from what I planned. These were the questions I planned to ask. So a little bit about me. I had a really good introduction. I work full time in open source at ionic on the Stencil compiler. The Stencil compiler is supposed to be an agnostic way that you can build web components, TLDR, web components, and then ship to multiple different teams, different frameworks. Sounds cool. So team A, B, C, and D can all write angular, react, and vue. And the design system team doesn't have to care. They can write in this little meta language. That's what Stencil does. I've been there like four weeks. So that's all I got. Previously I worked at cypress. I worked in open source. Have people used cypress? Shout it. Yeah. I'm a big cypress fan. And I built the component test runner. Also did the Faker JS core thing. And I've been around the vue.js ecosystem for a hot second, three, four years. And I'm now a solid JS contributor. I love reactivity. So that's me. And then I did a brief stint in user space again. So I went from cypress for two and a half years in open source full time to Path AI in Boston. Actually in Cambridge. I call it fake Cambridge because I'm from the U.S. And you guys might all be from here. So in user space, as I call it, building apps is different. And I felt like I wanted to reconnect. I wanted to reconnect after being in open source for so long. I was like, what are the real problems people face? And the answer is vue 2 to 3 migrations. I joined in July 2022. And I've spent most of that time, up until the last four weeks, in user space trying to migrate from vue 2 to 3. So I have a lot of opinions. So my problem was I had many teams, many consumers of the component library I was working on. And we needed to upgrade to vue 3. And we needed to. This is a distinction I will make very clear why in a second. We were a med tech company. We had all of the regulations that the U.S. likes to place on med tech, which is less than what happens in Europe and other places in the world. But this is the repo structure. We have four web apps. Seems nice. We have one shared vue lib. And each little box is a compilation step using vite or Web Pack or vue CLI or Jest for testing or Playwright for testing. It keeps every option. Because it was built by people who are not front-end developers. And that's fine. I was like, if anybody can detangle this, I think I can do it. Again. And the reason this looks like this is because the FDA in the U.S. requires you to, by repository, delineate the different requirements. Actually, the way this thing actually works is you take all your code for each vue web app and put it on a USB stick. Yeah. You put it on a USB stick and you have somebody review your code and tests. And they do it. They don't like, cursorily, like, looks good to me. They give code comments on your tests. They're like, why are you... I joked. I was like, if somebody ever gives me code comments on cypress component tests on the thing I built, I don't know how I'm going to feel about that. But... So this is my problem. This isn't the ugliest slide. This is the ugliest slide. And you might be like, what the heck are these colors? Well, each box is a repo. Each color is a team. And as we know, teams release at different cadences. And so when you're thinking, you know, you're at the bottom. I am at the vue component library. Red. You know, I am the solo team. We had seven repositories, five teams, 25 engineers, and four products that all had separate release cycles. And that sucks. Like, that really sucks. Because it's unrealistic to expect each repo in the middle, right? Are blue and yellow. And then at the end, little green boxes to maintain long-running vue 2.7 and vue 3 branches. We didn't even consider vue 2.6. We were just like, all right, vue 2.7, this is where we're going to start. Because we know that that's the closest to vue 3. There are reasons for that, find me after. So vue 3 is end of life too. What should companies do? It depends. You have to care about security patches as much as we did being in med tech. Maybe. If you have not looked at the migration docs for vue 2 to 3 recently, they have changed. And they have much more detail than they previously did. And I'm talking like in the last three months, two months. So stepping back, I wanted literally stepping back, I wanted a component library that could be actively developed with confidence. I wanted to ship a button with Tailwind. Yeah, yeah, yeah. I wanted to ship a button with Tailwind that could be developed and then afterwards didn't require a big migration effort from all of those nice repositories we talked about or the middle libraries. A humble goal, I thought. It was not. But I tried. I was like, okay, we're going to ship something called... I made a little template. It's open source. It's neat. And it's a project structure for component library authors. Because ideally... Ideally. Actually, another audience participation situation here. Have you, during the course of your vue 3 migration, noticed that some repositories have just stopped supporting vue 2 and just cut over to vue 3? Yeah? Yeah, you're not... Yeah. So TLDR, what I wish that we would do in the future is when we're shipping libraries, we want to make a monorepo. We'll get there. And we want to sim link the source directory. This is my genius plan. It was not genius. And we want to reuse the build and test tooling to make sure that when we cross compile a library, like let's say vue Popper or whatever, that the tests and build tooling would test against both user versions. I'll explain why. And finally, we want to, like, deploy the thing. Cool. So step one, make a monorepo. I choose PMPM workspaces. I think it's the only sane choice right now. And then you see this little source thing here with the arrow. If you've ever committed a source link to GitHub, that's what that looks like. And it looks like that if you open it up. That's the code. And I have a root package JSON. It lets you dev as a library author, again, against both versions. And this is my idealistic, like, what I wish would have happened situation. Source code. Ideally, looks normal. This is my example. I may live code, I may not. Depends on time. So on the left you have your script setup flavor of javascript. You know, options api would work, too. So long as you write it in a way that works for both. vue 2 and vue 3. So if you're writing, let's say, the emit function here with update model value and input, ideally it should work. Backing, moving forward. So reusing the build and test tooling. This is, like, the most important part, is to get everybody to reuse the build tooling. If you're using something like Tailwind or Uno css, when you compile your library down, you want to do it to both. You want to reuse your Tailwind config in both targets. You rely heavily on your test environment to ensure it works against these multiple user versions. And most libraries actually don't do this. Most libraries stop at unit tests. They don't ensure that the api works against target user version 2.7, target user version 2.6, 3, 3.3. Like, the test infrastructure for libraries could be improved, to put it lightly. And you will use runtime utils or build steps to support multiple things, like model value, value, et cetera. These breaking changes we have. So let's make sure it still works when we're developing our library, when we're developing our component. I'm not going to say design system, because that's separate. When we're developing our component library, we want to make sure it still works, and we want to reuse the tests demo time. That exercise the usage, not the internals. I don't... do people call... do people use view test utils? Yeah? Great. Do you use the emitted function on view test utils? Don't. I have opinions. So let's take the counters component test. This happens to be written in cypress. I don't really care what you use. The point is that you're exercising the component by clicking on it. Line 11, where I say assertions, I'm going to show you what that looks like. Same point, slamming it home. Backwards compatible means that the api contract should be the same. So if you have a counter component, it should support vmodel regardless of the target user version. Min and max should still be numbers. Yeah? Yeah. All right. What do we got? Time? Where's my guy? He's supposed to tell me I'm fine. So I have a repo structure. I have packages, which is a pnpm workspace root. And I have two folders, libvue2, libvue3. Libvue2 has a source directory, but it's not actually libvue2. This is technically, even though this little path thing is like libvue2, it lies. It lies, because if I say hello, vuejs, and I go to vue3 one, this is how symlinks work. Right? We're in countervue. It's like hello, vuejs. So you might guess what I'm about to do. I'm going to write code that's backwards compatible. Again, this was the dream for me. This is like the dream. So I called this repo template petite. It's on my GitHub. I'll show you the link at the end. But we're going to develop in vue2, and we're going to develop in vue3. This is cypress, if you've never seen it. I built it two and a half years of my life. All right. So we have it open in Canary, and we have it open in Chrome. Ignore the little Cypresses. They're Electron apps. It's okay. We're going to click on counter.tsx, and we're in vue version 3.2 here. We can count up to 10. Oh, this is small. No, we're not doing that. 3.2. And we can count down to 10. I have a demo component because somebody gave me the feedback that it's better to write in SFCs for demos instead of JSX. I don't really mind either way. The important part is this. This is how you should use said counter component. You should pass it a min and max and a vmodel. Have people done things like this before? Yeah, me too. All right. So in our counter demo, I print the vue version just for kicks to make this demo look prettier. But the thing I really care about is the counter. I want to make sure the counter works across vue 3 and vue 2. vue 3 can go up to negative 10. vue 2 should also be able to go up to 10 and negative 10. So you can tell we're in vue 3 because of this tiny text here. Tiny text. And this nice big text. But vue 2 is in 2.7. But you'll notice that the test is failing. And that's a bummer because it means that this component that we've written is not vue 2 compatible. It's vue 3 compatible, and that's cool. But ideally, our consumers... Oh, come on, zoom. It's not zooming. I swear I did this like a million times. All right. It's big enough now. Boop. Okay. vue 2, vue 3. Counter component here does not work. And that's sad because we want our user stuff to work in multiple versions. So let's figure it out. You can see we have model value. This is how you define the model in vue 3. And back in the day, where the day is most days for most of you, we have value. And the way we're going to fix this is saying we have value. It's basically the same. And with a lot of the stuff that Evan was talking about, by the way, this is going to be a lot easier to define in, like, vue 3, vue 3.3 and beyond, right? We can have prettier defined props, syntaxes. And we're updating model value. But what we really want to do is update... What do we want to do? How do we make a vue 2 compatible? Hmm? Update value? Hmm? The way you write custom vue models? Input. And we're emitting model value down here on increment and decrement. Speak up. What are we doing? We're doing input. And I do input here. And, again, like, this is the ugliest way to do this. This is terrible. But everybody here understands abstraction. And there are patterns to make this look less ugly. Great. And so now if I reload, I should be able to click. Can't click. This is what I get. Oh, it's because I didn't do change. Isn't it change? It is change. Nope. Hmm? Oh, Jessica. I wrote this demo this morning. Yep. Shit. If anybody wants to pair program with me right now, feel free to yell out what I should do. Change? Oh, I remember. I remember. You know what it is? I'm only taking counter here. Props.value. Or zero. Reload. Nope. And it was right. Okay. I was right the first time. Change. Sad panda. I'm usually very good at this, actually. I'm pretty decent at live coding. It's one of the few skills I prize in myself. Hmm. Can I practice this, too? Save it. Run it. Nope. Nope. I think this is the first time I've ever had it go wrong. Usually... What? Model value is defaulting to zero. Model value is defaulting to zero. Hmm. I think it's something to do with... I think it's something to do with the counter. Change. Events. Hmm? Input and default. Okay. Input and default. Mm-mm. Put the default back. Line nine. Put the default back. You know what? I did it earlier. I should have done this. I got nervous on stage. And I think I took my correct version. Yeah, I took my correct version. We can figure out what it is later. But I had this in my back pocket the whole time, and I forgot about it. That sucks. All right, let's add the data test ID real quick. Test ID is counter. Count. And here we go. View two. Please. Thank you. I'm actually sweating. I am physically sweating. That was so stressful. All right. And then the view three version also works. And one of the only things that... So, hairy breaking change number one in view two to three is V model, right? Hairy change, not so hairy, it's pretty obvious, is to delete the wrapper divs. Yeah? Because view three gave us partials, right? Template partials. View two did not. So view three, view three dot two dot three nine, very happy. View two, very sad. V is very, very sad right now. Because component templates should contain exactly one element. And this is the kind of thing that you can catch with a test infrastructure like this. So, backing up the bus, it's Q&A time. I am over time. I'm going to zoom very quickly through this. Deploying your packages. I want my library to support Zember, right? I want to have breaking changes between components, between versions, and I don't want to couple to view's next major version. You may have noticed in the ecosystem, some people just change and stop support for view two. They were just like, nope, my library only supports view three and up. And they did that with a major version bump. That's an option, one package at library slash view. Or you can do end packages at library slash view two or view three or solid. But I want to deploy multiple versions of my monorepo and target different versions of view. So, I would implore library authors not to break your users in the future. I would implore application developers to understand how difficult it can be. It really sucks, and I went through that as a user, right? I changed jobs to really, truly experience the problems that we're all facing. And I would implore library authors to invest in tooling that lets you build, test, and deploy libs against multiple framework versions. If you want to see the repo structure, feel free to look at it. That's the most valuable thing in there. Thank you all. Thank you to the following people for helping me talk through the architecture in, oh, my goodness, it must have been July when I first started the pain. Daniel Rowe, who we all love. Thorsten Lundberg, been a core team member for ages. And Johnson Chu. I'm going to mess it up. Bill Vallar does a lot of work for the vue community and answered a ton of questions for me. Things like how do you write a backwards compatible define V model, right, so you don't have to write all that ugly code I was writing that I messed up for like ten minutes. Tidbits of advice for application developers. Reduce the number of layers. Why? If I wasn't in a med tech company, I wouldn't have had so many boxes. I would have been like, monorepo, ship it, because that would make CI much easier. The tool chain is what gets you. It's not the vue runtime. The vue runtime I had up in a day, it's the tool chains and differences in the tool chain between vue CLI, Jest, et cetera, that's what really gets you. And vue 3 migration, stable. Everything else, that's where the pain is. So good advice. That's it. Thank you. Thank you so much. Really, really enjoyed that talk as well. Also, I must say, the top question when we came in was people asking you to please continue doing live coding because we've seen you do it before, you're amazing, and continue being brave. So give a round of applause for live coding. And not just live coding. Like I said, you said live coding is one of the only talents you had. That's a lie. You were holding one can of water. I went and invested in a couple extra ones because the next question is what is your favorite juggling pattern? I know that you can juggle. I think you should show it. Do you think that she should show us some juggling? Yes. All right. Go on. All right. So which of these. You should have like the music guys just to lay a beat that you can juggle to somewhere. See if you can lay a beat. You can do it. You got it. The Venn diagram of nerds and juggling is a circle. I do this at MIT. I've been doing this for four months, and it's very hard to do with these cans, right? I usually bring my juggling equipment. So I'm going to do two. We're going to start. Make sure I'm not burnt out from just giving a talk where I failed with live code. Two. Seems legit. I usually do clubs. I don't like balls too much. You can do more interesting patterns. They're easier. You want it? You got it. Make some noise. Okay. I got to tuck your tail, get the posture. Nope. So in juggling competitions, you get three tries. It's true. One. And then one more. Woo! Thank you very much. That is awesome. Very, very impressive. I love how talented all our speakers are as well. One thing that I did want to give you a chance to maybe go in your slides. In one of your slides, it said that vue 3 was near end of life. And I genuinely was like, wait, what? You just watched a talk from the Hero Devs guy who was like, end of life, vue 3, risk level at 97 out of 100. Yeah. vue 3 is end of life soon. December 2023, it's 2023 now. December 2023. Many people don't have to upgrade to vue 3. Consider not. Look at your use case. Fair enough. Now we know. That is definitely something I'm going to Google when I come off the stage. And let's continue with some more technical questions. So how long after vue 2 end of life do you think libraries should continue supporting vue 2? It's a struggle as a library author. And this is like every framework. Library authors in javascript don't have good advice on how to build the thing. Like, there's just no good resources. I actually found... There's one better one. But currently, I think Turbo Repo... Anybody know Turbo Repo? Yeah. Turbo Repo's docs on TSUP are phenomenal for aspiring library creators. I would like more of that from the javascript community in general. It would be really healthy for all of us. But until now, like, library authors kind of do what they can. There's really no good patterns. No, definitely. I get that. I get that. And just to confirm, are you saying that vue 3 is end of life, not vue 2? vue 2 is end of life. Did I put that up? Holy shit. So the slide says vue 3 is end of life. I asked the question. You were like, yes, it is end of life. I'm going to hide behind this sign. No worries. It's been a long day. It's been a long day. Yeah. I was trying to keep the hype. I was fact checking. This is my job as moderator, to fact check. But anyway, let's get back on track. So we've got another question from Alvaro, which is, how do you keep track of npm dependencies for each version and keep everything compatible when these dependencies change their api? For example, vue 2.5, vue 2.5.2, and vue 3 have breaking changes in your template that needs to be different. So the thing is I need clarifying information with that. So as a library author or as an application developer? That's the first clarifying question. So as a library author, you really, as a library author, you should be writing tests before you ship, because you have a fixed version that you need to be compatible with. As an application developer, assuring the quality of your app is very different, and you have things like monitoring and canary deploys to make sure you're good. But as a library author, you need to be writing tests where you scaffold a vue 2.5.2 and a vue 3.5.3 app and see if your stuff still works. See if your component library still works, if you're building on top of vue 2.5, for instance. Yeah. Awesome. Really, really good advice. And as well, someone else has asked, have you thought about using vue Demi for different library versions? Yeah, absolutely. vue Demi is great if you're building anything that does not have SFCs and needs a compile step in between. We tried using vue Demi in the migration I led, and vue Demi just does not support SFCs. And every single box that I showed on that screen exported some kind of SFC. If you're writing a library code, please just try not to make it headless. Like, make it headless. Don't use raw render functions, because those break a ton. And then, yeah, just raw composition api, if you can get away with it. And refs. So somebody mentioned template refs earlier. Template refs would be the way to go if you're trying to do any sort of styling. Use vue Use. Like, literally go to the vue Use source code and see how they do it. That is the best way. I like their infinite scrolling component, the headless infinite scrolling component of vue Use. Those kinds of patterns. That's what you want to look at. And go on GitHub. Look at the source code. Awesome. And if people want to find more about you, ask more questions, or maybe see more juggling, where can they find you? They can find me upstairs for the next, like, I don't know, 40 minutes. Maybe at the after party and on Twitter. Awesome. Let's give it up for Jessica. Woo! Thank you.