Learn about how to create a Vue.js library, and what best practices you should follow. We'll also learn about open source, and how maintainers work. We'll touch on xState, the composition API and testing strategies including Cypess and Vue test utils!
Creating My First Open Source Vue 3 Library
AI Generated Video Summary
Let's talk about Vue 3 and creating your first open source library. We'll discuss design choices, a personal example of creating a Vue 3 open source library, community and open source, lessons learned, and key takeaways for creating an open source project. We'll also cover building a Vue 3 library, the Authenticator project and its requirements, code sharing and best practices, using Xstate for state management, Vue 3 best practices, testing strategies, open sourcing and community feedback, documentation driven development, challenges and improvements, and the roadmap for the future.
1. Introduction to Vue 3 and Open Source
Let's talk about Vue 3 and creating your first open source library. We'll discuss design choices, a personal example of creating a Vue 3 open source library, community and open source, lessons learned, and key takeaways for creating an open source project.
Let's talk about Vue 3 and creating your first open source library. So there's a few goals that I want to have for this talk today. We're going to talk about design choices and requirements and I'm going to talk about a personal example of how I created a Vue 3 open source library in the last couple of years. We're gonna talk about community and open source and how to deal with that. We're gonna look at some lessons learned that I've had through this whole process, things that I would have done differently, and things that went really well. And then I'll have a conclusion and some things you can take away that if you are going to create an open source project, that you may want to take into consideration.
2. Building a Vue 3 Library
So let's do a little thought experiment before we get too far. Let's imagine you want to create a Vue 3 library. Say you're on a team, you have some utilities, you have something you want to open source and put into the wild that anybody can contribute with. What are some things you need to think about as you create this open source library? And what things do you need to take care of? So essentially, let's think about how should you build it?
3. Authenticator Project and Requirements
In this part, I will discuss a project I worked on as an Amplify UI team member. The project is an authentication system called the Authenticator, which provides authentication and authorization features. It is customizable, supports federated logins and multi-factor authentication, and was rewritten using Vue 3. We had previous versions using Vue 2 and web components, but we encountered issues and decided to rewrite it. Throughout the process, we focused on accessibility, bundle size, and code reuse for React and Angular.
So what I'm going to show you now is a project that I worked on in the last two years as an Amplify UI team member, and I'll tell you some of the decisions that we made and how it went. So who am I? I am a developer at AWS working on the Amplify open-source libraries. You may have seen me online. I have a YouTube channel called ProgramWithEric at eric.video. I've also written some blog posts and I'm also a developer advocate, so recently I have moved roles to being a DA, which has been amazing since I get to talk to so many developers and really talk about Amplify and all the tools that it has.
I like to start the talk with this XKCD comic that talks about how to write good code. You can see at the top you see start project, do things right or do them fast, and then if you go fast, does it work yet? Or if you do things right, do you code well? Are they done yet? But at the bottom, this often happens, is you throw out all your work and start over again. As soon as you create code in any sort of project, after you're quote-unquote done, it becomes something that's a legacy app that needs to be maintained. And what happens is a year, two years pass by, new teams come in, new people come in, the technology's old, it often gets rewritten. And that's actually similar to something that happened with this project that I'm going to talk to you about.
So the My Project that I want to mention in this talk is this is what I fondly describe as the authenticator, an authentication system. It's a widget, it's sort of like a component that you can add into your application that gives you authentication so you can log into an app, you can sign into, you can create a new account. But it also has authorization, so it's backed by Cognito so you can protect your routes for only authorized users. It's just a minimum amount of boilerplate code that you add to your application to add in an authorization authentication system. And it's completely customizable, supports all sorts of things like federated logins with Google or Amazon, and it also supports multi-factor authentication, so it was quite a big project to take on.
Now we did have two prior versions of this Authenticator in the wild. So it was originally written, I believe, around in 2019, where it was using a really old version of Vue 2. It had then been rewritten to use web components and using Stencil. And this was our third round of rewriting this application, this component, to be more user-friendly. We found that using web components and Stencil had a lot of interesting problems with, especially, password managers and the way the DOM works, and ran into some customization issues. So we decided to rewrite this web component, which was actually working not just for Vue, but React and Angular, and rewrite it also all in the native libraries that they support. So we rewrote the Authenticator using Vue 3. And I'll show you how that worked.
So as this process is going on, I started the team, like I said, happened around a couple of years ago, we came up with a list of requirements that we wanted to make sure we hit. So we had these requirements. First and foremost, accessibility was extremely important, and we'll get into that. We wanted to make sure we also had a small bundle size. One thing that happens often with open source projects, you put them into your application, then you notice that your bundle size doubles in size or triples in size. And we wanted to make sure that we can make it as small as possible. We also, since we were rewriting the Vue Authenticator, we wanted to make sure that we also included code for React and Angular and that we were able to reuse code.
4. Code Sharing and Best Practices
We used Yarn workspaces with the monorepo and a different way of sharing code. We focused on Vue 3 best practices, testing strategies, accessibility, and bundle size. Our sharing code strategy involved using the AWS Amplify UI parent component library, encompassing xstate and utility functions. We also had packages for Amplify UI View, UI React, and UI Angular that shared code. xstate is a finite state machine library that we found valuable.
So we'll talk a little bit more about that. We used Yarn workspaces with the monorepo. And then we used a different type of way of sharing code. And then we wanted to have some Vue 3 best practices. So this Vue 3 was extremely new when we started it. And the best practices were starting to be known but it wasn't as clear as it is today. And then, of course, we wanted our app to be tested. And we had a couple of testing strategies.
As I mentioned, accessibility was one of our number one requirements. We wanted to make sure that we were WCAG 2.1 AA tested against. We had a number of accessibility experts on our team and other teams that help test. We ran some accessibility tools against it. We made sure that anybody using different types of screen readers or different types of applications, different types of use would make sure that our component, that if they added into the application, would meet that high standard. And then for bundle size, we looked at Vue CLI, but we decided to go with V. Vue was pretty new a few years ago. We really like Vue and the roll up and how it was creating its bundle size and how small the bundle sizes were. We made sure that we had it all ESM compatible for our whole application, of course. And we reduced the number of dependencies. We looked at things like LODASH and Moment.js and some of those other libraries that are often common that you might add into your web application, we removed as many as we can. So that way that we wrote all those functions ourselves that we didn't have those libraries increasing the bundle size by however much.
Now the sharing code strategy that we had, you can see here at the top, we use this kind of this AWS Amplify UI parent component library that encompassed xstate and some utility functions. So we'll talk a little bit more about xstate, but that is our state management system that we went with. And then our packages for our Amplify UI View, UI React, and UI Angular also had a dependency on the Amplify UI internal package, so that way they all could share code. So as we were writing all three of these at the same time, we took different pieces of code that we found that were common between all three and then pulled them back up into the Amplify UI. And by the way, this is all open source. You can check out our repo if you want to get a little bit more details of what that looks like today. It has changed a little bit, but it's mostly the same way.
Now I talked about xstate. And xstate is a finite state machine library that allows you to share code in your application. So we really liked the idea of finite state machines.
5. Using Xstate for State Management
We decided to use Xstate instead of Vuex and Pina for our state machine in the authenticator project. Xstate provided libraries for Vue, React, and Angular, allowing us to share logic between different implementations. It also offered excellent package support and we received great support from David K. Piano. Using Xstate was an interesting and efficient approach compared to other options like Pina.
We looked at Vuex and Pina, which is really well-known in the Vue 3 ecosystem, but we thought that this state machine made more sense. It worked—it had libraries for Vue, React, and Angular. And we were able to share that logic between the same state machine between all the different implementations of the authenticator. It also had great package support when we ever had any questions or issues. We had really good support from, I believe, David K. Piano. And it looked just really interesting. So it's just an interesting project and an interesting way of handling it.
So if we went through something like Pina, we would have to figure out how to get that working with Vue or with Angular and React, and that'd probably be a lot more work than It was worth while Xstate just worked out of the box.
6. Vue 3 Best Practices
We went all in on the composition API with Vue 3. We used single file components with templates and TypeScript. We turned on strict mode in our TS config and did ES linting. We used slots, Vmodels, and emits to separate Vue idioms from React. We followed Vue 3 best practices and made sure to prevent compiling if there were any ES lint errors.
Now for the Vue 3 best practices, this wasn't as well known when we started, but we definitely went all in the composition API. We started with the composition API with the setup function if you remember that, but we quickly moved over to the script setup, and then Chris' scripts got set up with language equals TS.
We had single file components with templates. We looked at the JSX route, but since I just wanted to get started quickly and I thought it'd be easiest, I started with templates. We use TypeScript. We turn strict turned on. We have strict mode turned on in our TS config. We did have any turned aloud, so it wasn't as high of a TypeScript as we probably should have had, but we definitely kept it with that level.
We did ES linting. We did the Vue 3 essential. That's a level when you add in linting with Vue 3. That gave us some pretty good common recommendations when creating a Vue 3 app of how we should style it. We made sure that if there was any ES lint errors that it would prevent compiling. Then, we used a lot of stuff that for Vue apps you should probably use. We used slots, we used Vmodels, and we used emits. If you use the React application, you probably have seen where functions get passed down as props. This is part of Vue, why not just emit data back up? We used that pattern quite often. We wanted to make sure that we weren't just creating a Vue library that used React idioms. We wanted to make sure they were separate.
7. Testing, Canaries, and Community
We used Vue test utils with testing library for testing and Cypress end-to-end tests as our main way of testing. We also used Cucumber for behavior-driven development. We ran Canaries every 15 minutes to ensure our latest commits didn't break anything. Our testing approach involved human-readable Cypress Cucumber tests, covering various scenarios. In terms of community, we embraced open source tooling, such as Yarn workspaces for our Monorepo and Next with MDX for documentation.
For testing, we had a couple of different choices. We used Vue test utils with testing library. Testing library really promotes accessibility and ways of creating your app. We looked at Vue test, but it came a little bit too late into our process. And we really wanted to keep with Jest, so we stuck with Vue test utils.
We ended up using Cypress end-to-end tests throughout all our application, was our main way of testing. Then we used Cucumber, a library to use behavior-driven development, and I'll show you what that looks like. Then we went all in on GitHub Action. All our tests ran on every single PR. We created a bunch of Actions to help us publish each one of our packages. We also ran a Canary. We thought this was pretty novel for open-source work. We ran a Canary that takes Vue, CLI, Vite, V2, V3, and also the same for React and Angular. We ran this test every 15 minutes where it creates an application, uses our library, builds it, makes sure there aren't any errors, and then it runs a Cypress end-to-end test to make sure that you can log in. We ran this every 15 minutes that way and it runs on our latest. That way we know that if we push up a new commit to our repository that breaks anything, our Canaries will break first. The whole idea was to make sure that we're the first people to know if we broke anything in our library. And that's worked out really well.
So here's a little bit of how we did our Cypress Cucumber testing. So here's the scenario, sign in with unknown credentials. So this is a very human-readable way of writing your tests. These all get translated down to Cypress functions that run things. So when I type my email stats unknown, the email and unknown are like aliases that get passed into a Cypress function, and then it executes the code. And I type my password, I click the sign in button, then I see user doesn't exist. So we had this throughout our application, and we pretty much tested everything we could, that we could using these Cypress end-to-end tests, using this Cucumber.
So let's talk about community. So we wanted to, since we are an open source tool, we wanted to obviously use open source tooling in our app. We use Yarn workspaces. We had a debate between Yarn workspaces and NX, and a few others, and Yarn workspaces just won out, and we've really loved it with our Monorepo. And then we use Next with MDX, so for our documentation.
8. Open Sourcing and Community Feedback
We open sourced the repository from the beginning, creating an RFC for community feedback and doing developer previews on GitHub. The open source community has been great in trying out our project.
And so, as this talk suggests, we definitely open source the repository nearly from the beginning. We didn't really have any closed, private repository at any time, like basically the initial commit was an open source repository. We did, since there were older versions of this authenticator component, we did create an RFC, where we asked the community for feedback on this new approach and being able to rewrite our whole application, and we got some good feedback there. We did a developer preview, so as we kept moving forward, we did tagged releases on GitHub for developer preview that, so if anybody wanted to check it out, they could, and that really helped out at the beginning. And the whole open source community is awesome at trying these things out in our community, for sure.
9. Office Hours and Documentation Driven Development
We created office hours on Discord, allowing anyone to ask questions and providing updates on our progress. We also utilized documentation driven development, documenting features first and then implementing them while ensuring the documentation and tests were in sync.
And then we pulled every single GitHub issue that we had from the previous versions of the library to help us really understand what the problems were with the older versions of the libraries, what people liked, what people didn't like, and that helped really guide the requirements that I mentioned earlier, and also made sure that we knew when we were done, when we had those issues fixed.
This is an interesting idea we did through our development processes that we did documentation driven development. So what is documentation driven development? It's as we were going through our feature list, we documented the features first, so we would go in, we would document them inside our doc site, and then we would implement it. And then at the same time, as we were implementing it, we would add unit tests or more often end-to-end tests with a sample application that ran in Cypress that described the docs. So that way, as we were writing, we knew that the docs we wrote worked. We knew that the tests we wrote to those docs that wrote the specifications that worked as well. Really docs were a first-class citizen.
10. Documentation Platforms and Authenticator Features
We tested different documentation platforms and settled on Next. The React, Angular, and Vue versions of the authenticator had identical look and feel. The docs website emphasized accessibility and connected components. The authenticator automatically configured itself based on the backend system. We added customization options for different styles. The development process took about a year. The parody between Vue, React, and Angular was great. TypeScript sped up development. Testing with Cypress caught many issues.
We did a lot of time testing different documentation platforms. We tried Next, we tried some of the View platforms we decided to stick with Next, and then we made sure that inside our docs platform that we had embedded our authenticator so it was running in real time, but maybe it also mocks some of the backend so at least you can test it out and run it. And so that worked well.
We had to, since we use Next, we ended up using the React authenticator in the documentation for all the examples, but we found out at the end of the process the look and feel for customers between the React, Angular, and View versions were exact. They were identical, so it wasn't that big of an issue of using like the React authenticator in our docs to describe some View features because they were all in parity.
So this is what the docs website is. We looked at the docs from other Amplify and AWS sites and we decided to be a little different. So we wanted to emphasize our accessibility, our connected components, because the authenticator connects to our AWS services like Cognito, like I mentioned. So we kind of came up with this idea where Amplify use is collection accessible, theme will perform at React and more. And then we had a little code snippet since all our libraries were on NPM and then you can install it. And then to highlight how small the amount of code is to add this in. Here's an example of how you do this in a view. You just inside your template, you would add the authenticator and then this would produce this whole authenticator here with the sign in and create account. And we would give you a ways that if you created a different back-end system in Cognito, let's say you add a phone number or a username or you had certain signup questions, those are automatically be configured and taken from the authenticator so you wouldn't have to configure yourself. So that made this process much easier. And then we added in other abilities and definitely lots of different things from moving the create account tab for some people, allowing customizations of the top, the footers and headers of this. So we tried to add a lot of different customization for a lot of different styles and allow customers to override the look and feel of that. So this whole process took about one year to get the application, this authenticator written in Vue and out as a developer preview in a couple of months before we got it into general availability for everyone. So here's a few things we learned throughout that whole process. So as I alluded to before, the parody between Vue, React and Angular was awesome. We, at the end of the day, all three shared a lot of different code, we shared some styling and they looked nearly identical. And the TypeScript really ended up speeding up development. As I said before, we didn't have super... We had strict turn on, but we didn't allow any so that wasn't perfect. But just having TypeScript was a blessing and we were glad we did it. And we now even have higher levels of TypeScript and support now. And testing, we heavily went in on and end testing with Cypress and that caught so many issues, especially during development. We would be working really closely on a new feature. We would push it up. Our end to end tests would run, they would fail.
11. Issues, Feedback, and Improvements
We caught issues, received positive feedback, and enjoyed the native review, bundle size improvements, and speed increases. However, there are areas we could have improved, such as setting ESLint levels, increasing coverage, and implementing coverage reports.
We'd know we had an issue, we had to fix it. And I think we caught a lot of issues with that. And we had a lot of positive customer feedback. So a lot of people who use the older versions of our library and authenticator came to the new one, really enjoyed it. Glad it was native review and the bundle size improvements and the speed increases were all awesome.
But there, with every story, there's things that we could have done better. We did have that view 3 recommended, but we should have spent more time on setting ESLint levels, setting custom ESLint levels, probably having a more, we probably should have had more coverage in that area and maybe had coverage reports and a minimum amount of testing before things would pass. Something that we added in later, but during the development we didn't have in. We were okay with the ESLint level, but definitely we could have had it higher.
12. Challenges and Improvements
We considered supporting Vue 2 initially, but it would have been challenging due to the lack of popular Vue 2 libraries. We had a complex nesting system with slots and could have simplified it by using Provide and Inject or a state management system. We overemphasized End-to-End testing and should have had a better balance with Unit Tests. We are still working on improving community involvement and getting external contributors to participate.
At the beginning of the project, we were thinking about supporting view 2 because this was during the time that a lot of people were transitioning, maybe still is to this day, from view 2 to view 3, but libraries like Vue2Me and a few others weren't as popular as they are now. And I don't even think they existed. So it would be really difficult to keep the Vue 2 support in the new library. We still supported the older library for Vue 2 users. We do to as of today, even though it's not being maintained, it's being maintained but no updates are being added to it, but that's something we could have probably done different.
More JSX, so I found as I was working on the project, there's a lot of people on my team that were JSX experts. And this could have been something where we've added that more in and we're exploring that as we add updates to it, if we want to rewrite some of our templates in JSX, because it's just gotten really, really well. And an open-source library, it shouldn't matter. I mean, obviously, our end users are still gonna use this in templates. It's not gonna affect that at all. It's just the way the sausage is made, I suppose.
We had lots of slots within slots. We had a very complicated nesting system that the idea was you can override any function that from a deeply nested component. Looking back on that code, definitely could have been simpler. Maybe we should have used Provide and Inject. Maybe we should have used it in some kind of state management system. I think that could be improved. And as we've gone through, we have improved a lot of that.
We did overemphasize End-to-End. End-to-End is amazing because it's almost like as if the user is clicking on different parts of the app, typing things in, you're very thorough in your testing. The downside of it is it's really slow and it's hard to iterate quickly. And we didn't do as many Unit Tests as we could have. So I think we should have had a better balance between Unit Tests and End-to-End Tests. I think that would have covered more. At this point though, we do have a minimum amount of Unit Test coverage that we require now on every single feature. So that has been corrected, but during that process, we probably should have done more Unit Tests.
This is a problem that we're still trying to this day, we're still trying to get better at, is how do you get external contributors to go into your library, to be part of the community, to comment on RFCs and issues, be able to take up problems that the community's having, maybe working on features. That is something we're still working on. So if you go to our Amplify UI Components Library and look at our list of issues, there's a lot in there. Not a lot, but there's definitely ones in there that the community can work on, but trying to get the community to work on it, we've tried to add tags, it's just difficult.
13. Roadmap, Downloads, and Conclusion
We have a roadmap for the next year and are working on improving information sharing. Looking at npm downloads, the AWS Amplify/UI view library has seen significant growth, doubling its downloads in a year. The UiReact and UiAngular libraries have also shown similar trends. For more info, tweet me at EricCH. Check out the AWS Amplify U repo. Thank you for listening!
Plus we have a roadmap of things that we're working on for the next year. And that's something like the public doesn't have all the information on, so it's hard for someone to implement a feature that we're going to be implementing in three months. So we're still trying to get better at that. That's something that we're learning about.
So let's talk a little bit how it went. So we've looked at this whole process throughout the two years, what went well, what didn't, but what did we end up with? Well, a good indication of how any open source project is doing beyond looking at the issues, comments, and mentions on Twitter is just to look at the npm downloads. This is a graph from npm trends. And so we released the AWS Amplify slash UI view library. It was hovering around, probably between five to 10,000 for quite a while. But as we started putting more effort into it and we created this new library, you can see it's almost doubled in the amount of downloads in a year, which has been really awesome. And these are weekly downloads. So we went from 10,000 weekly to 20,000 weekly, and it's gone up from there. And it kind of goes up and down depending on the season and the time, but we're really happy that people are using it. And this same sort of graph you can see in the UiReact and UiAngular. React has definitely had more downloads, but it's also increased about the same amount.
So need more info, you can tweet me at EricCH. Feel free to check out the AWS Amplify U repo if you have a second. I appreciate everybody for listening in. I'll be available.