Recently I have been working on a project built with Vue.js and the amazing Vuetify Material UI library. I needed the ability to accept payments from users and decided to use Stripe Elements to accomplish this. While Stripe demonstrates that their Elements are highly customizable, actually integrating them with Vuetify was much easier said than done.
However, since it’s common for me to integrate Stripe into projects, this situation seemed tailor-made for creating a custom extension of Vuetify.
Complicating matters somewhat, Vuetify recently updated all of their components to be functional components built with TypeScript (the Vuetify 2.0 Arcadia release announcement is worth the read). I should say that I am not primarily a TypeScript developer, but I couldn’t resist the challenge of incorporating the full power of their upgrades. However, since the Vuetify core team was focused on releasing the update, they haven’t yet released fully functional type definitions for all of their components. It proved to be somewhat challenging to get all of benefits of TypeScript while creating this extension.
So, I’m writing this series of blog posts to document the process, for myself and others, of building a packageable Vuetify extension with TypeScript. I hope others will be able to use this guide to create amazing extensions that benefit all of us! This first post will demo the finished package and give a brief overview of the project structure.
Last Things First: A Demo
Here’s a screenshot of what the end result looked like, side-by-side with a regular Vuetify VTextField for comparison of how well I was able to achieve the same look and feel.
You can also see an interactive “playground” demo on CodePen that will let you experiment with adjusting all of the many available settings. As you will see, the VStripeCard extension supports nearly all of the styles and interaction details built into Vuetify’s other input elements. Vuetify’s components, in turn, are very carefully implemented to reflect all of the design wisdom packed into Material Design text fields.
What did I actually build? Vue and Vuetify components are designed to be used in a couple of different common scenarios:
- Direct inclusion in web pages via
- Being imported and used in SPAs (single-page apps) and PWAs (progressive web apps) built (usually) with Webpack
- Incorporated into projects that use SSR (server-side rendering), most notably Nuxt.js
This is a screenshot of the project structure that contains:
.vscode: VSCode editor settings
assets: the logo I used on my
build: webpack config and other scripts supporting various build targets
dev: a Vue app environment for visual inspection and manual testing while coding
dist: build targets for the browser; what gets served by services like unpkg and jsDelivr
lib: ESNext modules importable into any recent NodeJS or Babel-transpiled project
src: the actual extension code
test: unit tests (with Jest)
types: type definitions for use in TypeScript projects
- …and all of the other standard project configuration files
This is a modified version of the exact same structure used by Vuetify to build all of the components of their library. One thing that surprised me was that even though the resulting extension is reasonably small (browser target is ~14kb, gzipped), and has few dependencies (basically just Vue and Vuetify, and optionally
vue-plugin-load-script), there are a HUGE number of dev dependencies necessary to support the dev environment and all the various build targets. Take a look at
package.json to see what I mean.
Building a dev environment that can package an extension for almost any build target is way more complicated than I anticipated, and I easily spent more time understanding, configuring, tweaking, and debugging the dev environment than I did actually writing the code for this extension. That’s cool. A big part of the reason I do projects like this is to learn more stuff and deepen my skills.
I had to spend an enormous amount of time reading the docs for Webpack, and TypeScript, and Jest, and more than a dozen other packages that all contributed setting up an efficient and responsive coding environment. The cool thing is that now that I’ve done this, the next time I build an extension, it will all go much faster. During the process, I ended up reading about almost every single one of the dev dependencies, evaluating potential alternatives, and considering whether or not it was even necessary to keep.
Admittedly, I made the whole process much harder for myself by choosing to stick with TypeScript as the coding language. Juggling complex type dependencies across several packages proved to be quite time and energy consuming. I now have a much deeper understanding of, and appreciation for, the work done by the fantastic Vuetify team. Using TypeScript definitely improved my code, though, and I’m glad I stuck it out.
In Part 2 of this series, I’m going to take a deeper dive into the dev environment and describe what each of the pieces does, and why it is there.