Morphatic https://morphatic.com Connect...with a purpose. Fri, 16 Apr 2021 16:36:28 +0000 en-US hourly 1 https://wordpress.org/?v=6.7.1 https://morphatic.com/wp-content/uploads/2015/12/cropped-Morphatic-Logo-32x32.png Morphatic https://morphatic.com 32 32 Vuetify3 (Alpha) + Storybook https://morphatic.com/2021/04/11/vuetify3-alpha-storybook/?utm_source=rss&utm_medium=rss&utm_campaign=vuetify3-alpha-storybook https://morphatic.com/2021/04/11/vuetify3-alpha-storybook/#comments Mon, 12 Apr 2021 03:35:54 +0000 https://morphatic.com/?p=602

DISCLAIMER (2021-04-14): Vuetify v3 is still in an early alpha release phase. I was able to get Storybook working in the way I’ve described below but YMMV. The steps needed to make this work are likely to change a few times before the latest release. At the time of writing, I used [email protected] and [email protected]

Just a quick post to document how I was able to get Storybook working with the Vuetify 3. Here are some quick links:

Background

On 2021-03-03 the VuetifyJS project released it’s first alpha of version 3 which is designed to be compatible with and take advantage of the new features in VueJS v3. I’m a big fan of using Storybook to develop components for my projects, so I thought I’d use it to kick the tires of the updated framework. Storybook only very recently announced support for Vue 3, and while there are probably still a few kinks to be worked out, so far the process has been relatively pleasant.

The steps below are the ones that I followed. There are probably other (better) ways to accomplish this. Please leave a comment if you have any ideas for ways to improve this strategy. A huge caveat is that Vuetify 3 is still very much in development–a bunch of features have not been implemented, yet, and as with any alpha project, changes are quite likely.

Step 1: Create a New Vue 3 Project

Shell

I used the @vue/cli to generate a new project. Here are the options I selected in the menus:

  1. “Manually select all features”
  2. Selected all EXCEPT TypeScript (and haven’t tested this process with TS)
  3. Choose “3.x (Preview)”
  4. Use history mode: Yes
  5. Sass/SCSS (with dart-sass)
  6. ESLint + Standard config (my personal preference)
  7. Lint on save (only)
  8. Jest
  9. Cypress
  10. In dedicated config files

Step 2: Add Vuetify 3

Shell

I used the @vue/cli to add Vuetify to the project and chose the “V3 (alpha)” preset. At this point I also recommend committing any changes you’ve made to the project git repo in case you want to roll back to this point. I also recommend starting up the app once at this point (npm run serve) just to confirm that Vue 3 and Vuetify 3 are playing nicely together.

Step 3: Initialize Storybook

Although the Storybook Vue 3 support announcement touted their “zero-config setup”, I didn’t find that this worked out-of-the-box just yet. Upon digging deeper, I discovered that the Storybook init command checks for the presence of Vuetify in a project, and if it finds it, defaulted to a Vue 2 configuration, which won’t work for Vue3/Vuetify3. As such, you needed to specify the project type at setup like this:

Shell

I submitted a PR to update the Storybook CLI to auto-detect projects with Vuetify 3 installed. As of 2021-04-13 and [email protected], the auto-detect feature now works out of the box with Vue3 and Vuetify3:

Shell

At this point, you can actually run storybook (npm run storybook) and it should start without incident, but it won’t actually parse Vuetify components just yet.

Step 4: Update Storybook Config Files

In the .storybook/ directory, update the following two files:

JS
.storybook/main.js

This updates the main config to register the standard @ alias to the src/ directory used in Vue projects, and also instructs the Storybook webpack builder to parse Sass/Scss files correctly.

JS
.storybook/preview.js

This file pulls in the same src/plugins/vuetify.js config file that’s used by your app and adds it to the Vue 3 instance used by Storybook. It also adds a decorator that wraps all of your stories in a <v-app></v-app> element, which is required for many/most of the Vuetify 3 styling/JS to be applied correctly.

Step 5: Start Writing Stories!

You can see a sample component and story in the sample repo I set up for this project. You can see the built Storybook site, as well. In addition to the standard setup, I added the @storybook/addon-a11y (accessibility) and storybook-dark-mode plugins. You can also see how Storybook detects the component and property documentation (in the comments in src/components/Button.vue and automatically generates docs and controls for the different properties that can be submitted to the VBtn component.

Bonus: i18n

The sample repo also demonstrates how to use i18n (internationalization) for Vue 3 in both the actual app being developed as well as the Storybook stories. To set this up:

  1. Install the Vue-I18n package: npm i vue-i18n@next
  2. For the app itself:
    1. Instantiate i18n in the ./src/i18n/index.js file and place appropriate language/translation files in ./src/i18n/locales/, e.g. ./src/i18n/locales/en.json
    2. Import and use the i18n instance in ./src/main.js
  3. For Storybook, following the recommended setup from the storybook-addon-i18n package (NOTE: do NOT actually install this package as it is deprecated):
    1. Update .storybook/preview.js to import the useI18n() function from the vue-i18n package and the i18n instance we created in step 2.1 above
    2. Add a global locale variable and assign it a toolbar button/menu with your defined locales
    3. Update the setup() method of the decorator used to wrap all of your stories so that it can update your components’ locale to match that selected in the toolbar
    4. Update your actual components and/or their stories to use the string interpolation functions as directed by the vue-i18n documentation

NOTE: vue-i18n has several different methods for translating strings depending on whether or not you are injecting the component globally or locally, and whether you’re using their “Legacy API” or the newer composition API. My example uses the composition API instructions.

Conclusion

Although there are still aspects of this setup that I have yet to explore, and I haven’t used it in a “real” project, yet, I found this MUCH easier and cleaner to use than my previous example with Vue 2 + Vuetify 2. Please share a comment below if you found any ways to improve upon what I’ve started.

]]>
https://morphatic.com/2021/04/11/vuetify3-alpha-storybook/feed/ 2
Configuring Storybook 6 for Vue 2 + Vuetify 2.3 https://morphatic.com/2020/09/30/configuring-storybook-6-for-vue-2-vuetify-2-3/?utm_source=rss&utm_medium=rss&utm_campaign=configuring-storybook-6-for-vue-2-vuetify-2-3 https://morphatic.com/2020/09/30/configuring-storybook-6-for-vue-2-vuetify-2-3/#comments Thu, 01 Oct 2020 01:34:59 +0000 https://morphatic.com/?p=577

You probably know this story.

You go to start a brand new Vue + Vuetify project. You think,

Hmmm… I think I’d like to use Storybook to build and test my components in relative isolation.

–me, every time I start a new project

(BTW, if you don’t know what Storybook is, the 2-ish minute video below gives a pretty decent overview.) So you go to install it and you find that one or more of the following things has happened since the last time you did this:

  1. There’s a new version of Storybook
  2. There’s a new version of Vuetify
  3. There’s a new version of (node|ecmascript|vue|shiny-new-best-practice)

And of course, even if you have a perfectly valid working version of Storybook already configured in your last project–it doesn’t work anymore. 🙁

And of course, enough time has passed that you don’t really remember the details of why you configured it the way you did the last time, so you have to spend most of a day re-learning it to figure out how to make it work again. How many actual components could you actually have built in that same span of time? Who knows, but we <3 progress.

So, in the vain hope that it won’t change too much between this time and the next time, here’s my latest stab at configuring Storybook to work with Vue and Vuetify. The following assumes you’ve already got a working Vue+Vuetify app created.

<strong>AVOID NPM@7!</strong>

These instructions DO NOT WORK if you’ve updated npm to version 7. The reason is that npm@7 is stricter about how dependencies are handled and there are some known issues that prevent this from working correctly. I expect this will be fixed in time, but as of late December 2020, you’re better off sticking with npm v6.x.

Install Storybook + Addons

Shell

So far so good. This new installer automatically figures out I’m using Vue and sets me up for that. The a11y (accessibility) plugin is for reminding me to implement, well, accessibility features into my components. The other two are just so I can get the cool dark theme in the Storybook UI.

Go to the dark side

To get the Storybook UI to display in dark mode add this file to the config directory:

JS
.storybook/manager.js

This is totally unnecessary, but I’ve found I like dark themes in the apps I use a lot…

Get SASSy

These updates get made to the .storybook/main.js file:

JS
.storybook/main.js

The trickiest part is getting the SASS rule typed in correctly. Note that if I decide to switch to SCSS or to save the variables.sass file in a different directory (must be either sass|scss|styles), I need to update this rule to match. Also, if using SCSS, then I might not need the indentedSyntax rule. I dunno. I almost always have to look this up, and it always seems to take forever to find the docs.

Wrap It Up

This was new. Last time I followed John Leider’s approach and created a custom add-on with a decorator to wrap all my component stories in Vuetify clothing. However, this repo has been archived. Shit, now that I’m writing this, I realize there’s a new version of that plugin. No matter, it appears the plugin is designed for the old version of Storybook (5.x).

The newer version of Storybook (6.x) lets you put a “decorator” right in the .storybook/preview.js file. You could probably do that before. There’s probably a more elegant way to do this, but I’m slowly, slowly learning that working code is better than perfect code. Here’s my wrapper:

JS
.storybook/preview.js

The props and watch sections of my wrapper are there so you can toggle between languages (i18n) and the dark|light themes when viewing your components in Storybook. If you have other app-level settings you’d like to be able to mess with while building/testing your components, you should put them here.

“KNOBS” are now “CONTROLS”!!

In the last version of Storybook, the doohickeys you used to tweak your components at run-time were called “knobs.” They’ve renamed them “controls.” I’m embarrassed how long it took me to figure this out…

Export Options

In order for the above decorator to work, you have to set up your core Vuetify options so that they are exported as an object. Here’s how I do it:

JS
src/plugins/vuetify.js

Doing it this way allows you to re-use the options in your Storybook config. That way, if you change your app’s options, they will automatically be reflected in Storybook.

Help is on the way

I borrowed this style of creating stories from John’s plugin. I modified it this time to simplify and flatten the structure needed to generate docs from stories. This file could really go almost anywhere, but this seems like a decent location.

JS
.storybook/util/helpers.js

A First Story

With the above helper in place, your stories can look something like:

JS
src/components/MyComponent.stories.js

What the Font!?!

The fonts I specified in src/sass/variables.sass aren’t showing up! Here’s my SASS:

Sass
src/sass/variables.sass

Oh! Right, I need to create a preview-head.html so they’ll be imported:

HTML
.storybook/preview-head.html

This is basically the same as the old version of Storybook.

That’s about it!

God I hope this configuration info is still relevant by the time I start y next project!! Even though Vue 3 has just been launched, I suspect that won’t be too much of a problem. Of course, Storybook 7 is probably less than a year away. Not to mention that the Vuetify team is bearing down on Vuetify 3. I expect that in a couple of months.

Sigh.

Such is the life of a developer these days. Hope this will be useful to someone. Get it while it’s hot!

]]>
https://morphatic.com/2020/09/30/configuring-storybook-6-for-vue-2-vuetify-2-3/feed/ 16
My Google Cloud Sandbox https://morphatic.com/2020/09/05/my-google-cloud-sandbox/?utm_source=rss&utm_medium=rss&utm_campaign=my-google-cloud-sandbox https://morphatic.com/2020/09/05/my-google-cloud-sandbox/#respond Sat, 05 Sep 2020 18:55:10 +0000 https://morphatic.com/?p=574

In order to teach myself how to use Google Cloud services, I needed to create an OAuth Client ID. Part of the process requires setting up an “OAuth Consent Screen” that requests:

  • an authorized domain
  • an application homepage link
  • an application privacy policy link
  • an application terms of service (TOS) link

Any apps I create for my sandbox are NOT intended for general public consumption and are strictly for my own learning. I set this page up to be the endpoint for the above links.

Privacy Policy

The “My Google Cloud Sandbox” app is NOT intended for general public consumption. If you do stumble upon it and happen to use it, any and all information about you the app happens to gather will be completely ignored.

Terms of Service

The “My Google Cloud Sandbox” app offers no services to anyone. It is purely a vehicle for my (Morgan Benton) own personal learning. As such, anyone other than myself is not entitled to any rights, privileges, or guarantees of any kind in connection with any functionality that may or may not exist here.

Thanks!

]]>
https://morphatic.com/2020/09/05/my-google-cloud-sandbox/feed/ 0
High Performance Game Streaming Setup https://morphatic.com/2020/08/14/high-performance-game-streaming-setup/?utm_source=rss&utm_medium=rss&utm_campaign=high-performance-game-streaming-setup https://morphatic.com/2020/08/14/high-performance-game-streaming-setup/#respond Fri, 14 Aug 2020 22:05:17 +0000 https://morphatic.com/?p=567

I was asked to provide a recommendation for what computer to buy as part of a dedicated game streaming PC setup. “Sure!” I said, not realizing the sheer depth of the rabbit hole I was jumping into. During my exploration, one thing I did NOT find was one article that tried to explain everything to a total n00b in enough depth that I could understand both what to buy and why to buy it. This is that article–the one I wish I had found first.

This article describes everything you need to buy to get started as a semi-pro game streamer for platforms like Twitch. It explains what you need to buy, why you need to buy it, and most importantly how to choose from among the many, many, many different options available. This is not a rock-bottom “budget” setup, nor is it the best money could buy. It is targeted at people who are looking to break into the world of professional game streaming or at least do it as a serious hobby without totally breaking the bank. If that’s you, read on.

This article does NOT discuss your primary gaming PC or console. I assume you already have a top notch gaming PC. There are many entire YouTube channels dedicated to figuring out the answer to this question (Gamers Nexus, Hardware Unboxed, JayzTwoCents, and Linus Tech Tips are among my favorites–and I have watched them A LOT). This article discusses everything you need in addition to your gaming PC (or console) and assumes you are using a dedicated secondary PC for streaming, and that you currently own nothing else.

tl;dr

For the impatient, here’s a shopping list. Prices are for mid-August 2020 and some of them (particularly power supplies, cameras, microphones, and capture cards) may cost a bit more due to COVID-19-related shortages. If you bought everything, it would total around $2400. All the links below are Amazon affiliate links, but some items are cheaper elsewhere.

Disclaimer

I am not a streamer, nor have I ever personally created a streaming setup. My recommendations are pieced together from what I deemed to be the most credible and complete info I could find. If anything, I learned that there are LOTS of options and LOTS of variables. YMMV. That said, these components should give you a reasonably powerful and high performance system.

No Silver Bullets or Free Lunches

Below I’m going to dive into the why behind each of the components I’ve listed just now, but it’s important to start by pointing out that just having good equipment does NOT mean you will have a good stream. It’s really important that you take the time to:

  1. Learn to use each piece of equipment
    Read the manuals, watch tutorial videos, join online forums and discuss your settings and configurations with other people using the same or similar equipment.
  2. TEST, TEST, TEST
    Spend time tweaking and adjusting your setup, do some actual streaming, and ask your audience for feedback on what is working and what isn’t.

In the same way that buying a really expensive pair of basketball shoes won’t magically allow you to dunk, or getting a top-of-the-line guitar won’t instantly make you a rock star, just having good equipment won’t make you a famous streamer. There’s no substitute for time, study, and experimentation. Have fun with it and don’t be afraid to make epic fails.

What is Each Component For?

Let’s start by exploring what each piece of equipment contributes to your overall stream quality.

Audio

I’m starting with audio because good audio is frequently considered to be more important than good video (the video below gives an effective 3 minute demonstration). This is an incredibly complex topic! Some people make their entire career by being an audio engineer. I say this not to scare you but to encourage you to dive deeper and learn more about this topic, and also to let you know that the tips below only barely scratch the surface of what there is to know about high quality audio.

Audio Sources

All of the audio in your stream is important. Not all of them are desirable. Here are the most common sources of sound, where they come from, and how they get into your stream.

  • “Good” Sounds
    • Game Audio
      The audio coming from the game itself. You probably want at least some of the music and sound effects from your game in your stream. The main way it should get into your stream is via the HDMI cable connecting your gaming PC to your streaming PC. It should NOT enter your stream from any sort of external speakers which could be picked up by your microphone. Turn off any speakers from your gaming PC.
    • Your Voice
      Another desirable source of audio is your voice. (Duh. This may seem obvious but you’ll understand why I’m pointing it out in a moment.) Your voice enters your stream via a microphone, preferably connected directly to your streaming PC.
    • Sound Effects
      Many streamers like to spice up their streams by adding in their own sound effects–explosions, bells, whooshes, etc. You as the streamer will trigger these effects yourself while streaming, usually with macro keys or something like a Stream Deck. Your sound effects should be generated by your streaming PC as well.
    • Music
      You may want to add in your own music. This should be generated by a music player, again on your streaming PC.
  • “Bad” Sounds, i.e. Noise
    • Ambient or Room Noise
      Microphones (usually) can’t tell the difference between good sounds, like your voice, and bad sounds, like your barking dog, or other people in your house, or a garbage truck driving down your street. You need to think about how to reduce ambient noise as much as possible.
    • Interference or Electronic Hum
      All electrical devices generate electromagnetic fields made of waves. At certain frequencies, these waves may look to your computer just like the “good” sounds listed above. This gets into your stream via the circuitry of your computer itself, but it is mitigated by using high quality equipment and configuring it correctly.
    • Other Programs
      While streaming, don’t forget to mute your phone and turn off all unnecessary programs on your computers. Random alerts, alarms, and other program noise can get into your stream either directly via one of your computers or through your microphone.

Controlling Audio Sources

So how do you control these audio sources to maximize the good sounds and get rid of as much noise as possible? Here are the typical approaches.

“Soundproof” Your Room

There are many levels of “soundproofing.” Professional recording studios spend many thousands of dollars building rooms within rooms within rooms to isolate them from virtually all external sounds. This is mostly unnecessary for a streamer and you can get rid of a lot of sound by doing simple things like closing your door, filling your room with stuff, and covering walls and other flat (i.e. sound-reflective) surfaces with blankets. Here’s a good short video that describes a cheap vocal booth made with PVC pipe and moving blankets.

Use a Mixer

A “mixer” is a piece of hardware or software that takes multiple audio inputs and can control the level of output for each input. You will take all of your “good” sounds above and run them into a mixer which will allow you to control the relative levels of game audio, music, your voice, and other sound effects that your audience will hear. The GoXLR is an extremely popular hardware mixer for streamers, but fortunately, you can save the $500 it would cost you to get one because the Elgato Wave 3 I’ve recommended comes with a software mixer that appears to be really excellent.

Now is probably as good a time as any to recommend this video from EposVox that gives a deep dive into the Elgato Wave. This is an AMAZING YouTube channel and you’ll probably end up watching and re-watching many hours of EposVox videos as you progress toward becoming a professional streamer.

Use Pre/Post-processing

“Pre-processing” and “post-processing” are features of your microphone and streaming software that allow you to do things like control the sensitivity of your microphone (i.e. how much and what kinds of sound it picks up) and use noise cancellation, like with noise cancelling headphones. You can also add effects like reverb to give your voice a richer quality. There are many, many, many videos on YouTube with tutorials on how to tweak your audio this way.

Adjust Your Bitrate and Sampling Frequency

These are related and fairly technical topics. The “bitrate” is the amount of audio data being sent through your stream. The “sampling frequency” is the number of times per second your microphone samples the sound coming from your voice. The higher the bitrate and sampling frequency, the higher quality your audio. However, if these values are too high, your internet connection and the bandwidth of your audience can make your audio terrible. Your streaming software will likely provide you with some reasonable default values. You should absolutely check in with your audience to make sure they’re getting clean sound, but once you find good levels, you shouldn’t have to tweak these all that much.

About Microphones

There are MANY kinds of microphones. You should spend some time learning about what they are, how they work, and why some are better suited to streaming than others. In general, though, the debates in the streaming community tend to center around two issues: XLR vs. USB, and dynamic vs. condenser. Here are a couple of good videos that explain the debate.

Why did I recommend the Elgato Wave 3?

The Wave 3 is a USB condenser microphone, two things that EposVox says he specifically tells people to avoid. That said, EposVox’s review of the Elgato Wave that I linked to above addresses these issues and explains why I am passing on his recommendation to you. In short, for getting started with streaming without spending a fortune on audio equipment, the Wave 3 offers a TON of features, particularly via the software. However, you will have to invest significant time in tuning the Wave 3 to your setup. Like I said earlier, good equipment is not magic. You have to learn to use it like a pro. You will want to use your mic stand, pop filter and the general configuration to decrease things like keyboard noise, your squeaky chair, or deafening your audience by banging on your table.

Video

Let’s jump right in. There are typically three sources of video:

  1. Your Game
    Duh. The video feed from your gaming PC gets sent to your streaming PC via an HDMI cable that connects the graphics card on your gaming PC to the capture card on your streaming PC.
  2. Your Camera
    You’ll send video from your camera to your streaming PC via an HDMI cable that runs directly between the camera and the cam link device plugged into a USB port on your streaming PC.
  3. Overlays, Alerts, and Other Effects
    To spruce up your stream, you may want to have an overlay and/or use alerts and other effects for interacting with your audience. These things will be configured within your streaming software (i.e. OBS) and controlled via macro keys on your keyboard or using another device like a Stream Deck.

What are the factors that contribute to high quality video?

Compression

Video generates A LOT of data. You already know that if you want to play games at 4K with a high refresh rate like 240FPS, particularly with AAA games, you need a truly beefy, high-end graphics card in your gaming PC. There’s virtually no way that your audience would be able to watch you game at that level of quality without investing tens or hundreds of thousands of dollars in a professional broadcasting studio. One of the primary benefits of having a separate, dedicated streaming PC is that you can still play your games with high settings, but stream your gameplay at a much lower level of quality that won’t break the internet. In order to accomplish this, your streaming PC will compress your video into a smaller size before sending it to your audience. Video compression will be handled on your streaming PC and the way you do it depends on your hardware and software. The key concerns are how it is being compressed (the algorithm) and what is doing the compression (your CPU or your GPU).

Algorithms

You’ve probably heard algorithms talked about, but may not have understood exactly what they were. Algorithms for video encoding are sometimes referred to as “CODECs” which is short for COmpression/DECompression. The algorithms are usually created first by programmers at big software and/or hardware companies, and then over time they get adopted by other companies and people until they evolve into a “standard,” which is a generally agreed-upon way of doing something. Since the patent of a CODEC is typically owned by the company that creates it, when they are new, those companies charge a fee to other companies who want to use it. Those fees can be pretty expensive, which is why sometimes everyone uses an older standard when a newer, better one exists. CODECs go by names like H.264 or H.265, but confusingly, in addition to these names, companies will brand these algorithms with “fancier” names like MPEG-4 AVC (H.264) and HEVC (H.265).

Encoding Hardware

All of your video sources will get sent to your streaming PC on which your streaming software (i.e. OBS) will handle combining and compressing them into a streamable size. Within OBS you can configure where this compression takes place, either on your CPU or GPU. Until a couple years ago, most compression happened on the CPU which is why you may have seen recommendations for streaming PCs that didn’t even have a dedicated graphics card. (BTW, if you are gaming and streaming on the same PC–no separate streaming PC–you’ll want to use a CPU-based method to balance everything out.) Then Nvidia started a collaboration with the OBS studio team to create NVENC, which is a dedicated video compressor built into Nvidia graphics cards.

What’s the best Algorithm/Hardware Combo?

Again, it’s EposVox to the rescue. He did a fantastic benchmark comparing the compression quality of a variety of encoders for various bitrates. Fortunately for us, the results pretty clearly show that you can get the best quality by using any Turing-based Nvidia graphics card (that means a GTX 1650 Super and up). Even better, Nvidia clearly says that they use the exact same encoder in ALL of their Turing-based cards, so you don’t have to get the most expensive one–a GTX 1650 Super ($175) will compress your video just as well as an RTX 2080ti ($1400+). Unfortunately for us, Nvidia’s dominance in this area means there’s not enough competition to bring prices down. While AMD has fantastic cards for gaming you should not use an AMD card in your streaming PC. This video is well worth the watch.

What should my target resolution and bitrate be?

Nvidia has helpfully released a guide for using NVENC with OBS. There’s a table which dictates the best resolution and bitrate you can target based on the upload speed of your internet connection. First use SpeedTest.net to find your upload speed. Here’s a screenshot from my system. We pay a premium for the fastest consumer-grade internet we can get in a kind of rural area.

Once you know your upload speed, you can look up the max resolution and bitrate that your system could possibly handle, in my case it looks like I could handle streaming to YouTube at 1440p 60FPS. That said, knowing where you’re streaming to is really important. Most game streamers stream to Twitch. Twitch has three tiers of streamers: starters, “affiliates,” and “partners.” One of the key differences between these tiers is how Twitch handles incoming streams.

Unless you are a partner, Twitch basically just passes your stream directly on to your audience at the same bitrate that you send it. If you try to stream at a speed that is too fast for your audience, they will experience buffering or stuttering. This will make it extremely difficult for you to build up your followers. Once you are a partner, Twitch will adjust the bitrate of your stream to match the speed of your audience’s internet connection. That means that if you are a partner, you want to stream at the highest rate possible so that audience members with a fast internet connection can watch your stream in its full glory. If you are not a partner, though, it’s typically recommended that you stream at a much lower speed, basically capped at 6mbps or roughly 900p 30FPS. You may even want to reduce your video stream to 720p.

The Components

Okay, let’s finally get down to the individual components I’ve recommended any why.

GPU–EVGA GeForce RTX 2060 KO Ultra Gaming

Earlier I made the claim that ALL Turing-based GPUs can compress video using the latest NVENC encoder so there was no need to buy a GPU more expensive than the GTX 1650 Super which currently costs around $175. While this is true, the newer RTX cards will be able to take advantage of Nvidia’s new RTX Broadcast Experience which can enable some cool newer features like an AI-based real-time background removal that you can accomplish without actually having a greenscreen. This was announced in September 2019.

CPU–AMD Ryzen 5 3600

While it’s important to have a multi-core processor, Alpha Gaming (another excellent YouTube channel for learning how to become a pro streamer) did a convincing benchmark that showed that if you are using NVENC, you don’t actually need a very powerful processor to add all kinds of overlays and effects without dropping frames. The R5 3600 is well known for providing huge value at a lower price.

Motherboard–MSI B450 Tomahawk Max

The B450 Tomahawk is generally known as the “go to” motherboard for balancing reliability with features. Since the streaming PC has no hardware that takes advantage of PCIe 4.0, there’s no need to buy a newer X570 or B550 board. You could probably get away with a cheaper B450 board (which go for under $100), but I picked the Tomahawk not only for its reputation but also its support for USB-C. While none of the current components require USB-C, camera companies like Canon have already begun to release software that will allow a direct streaming connection to a computer without the use of Cam Link.

Memory–Team T-Force Vulcan Z DDR4 16GB 3200 CL16

Honestly, you would probably do just fine with 8GB of RAM. OBS does not use a lot of memory, and NVENC does all of its encoding directly on the GPU so doesn’t use a lot of system memory either. There was only about a $20 difference between 8GB and 16GB, though, which is why I went with 16. It never hurts to have more memory.

PSU–Thermaltake Toughpower GX1 500W ATX 80+ Gold Certified

Really, any 500W power supply will do. I chose an 80+ Gold certified supply because its higher efficiency will produce less waste heat. While there’s not a lot of risk that this computer will get too hot, it’s always a good idea to opt for more power efficiency.

Capture Card–Elgato 4K60 Pro and Cam Link–Elgato Cam Link 4K

These products seemed far and away to be the “go to” choices in this category. They got high reviews from every reviewer and compared to every competitor I could find. Since the Wave 3 microphone and the Stream Deck are also Elgato products, I reasoned that there may be benefits to having a suite of components all from the same company. There’s also going to be plenty of places to get tutorials and support in configuring these products.

Case–Lian Li Lancool II Mesh or Phanteks P400A

Since this is an air-cooled build and we are not putting in any sort of after-market cooling solution, I went with the cases that scored at the top of the list for thermal performance and value as rated by Gamers Nexus. While not the cheapest on their list, these cases come with included fans that will prevent having to buy extra ones later.

Drives: XPG SX8200 Pro 256GB SSD and 4TB WD Blue or Seagate

The SSD is for the OS and will ensure fast boot times. I added a large HDD for storing large uncompressed video files. While it’s not necessary to actually record your stream to disk, if you want to go back to a gaming session later and turn it into a highlights video or something else, you’ll need storage space for that.

Stream Monitor

I made a couple of recommendations–one flat, one curved–but really any monitor that can connect to the GPU on your streaming PC will do. Basically, you’re going to want a 2nd monitor in addition to your gaming monitor on which to be able to host chat and otherwise monitor your audience.

You probably do NOT need a second keyboard and mouse for your streaming PC although it’s always good to have a backup. You can use software like Synergy ($30) or InputDirector (free) to control both computers with one keyboard and mouse. I don’t have any experience with this but one comment I saw said that once the stream has started they have almost no reason to switch to the streaming PC and you can use the Stream Deck to program macros for common operations on the streaming PC.

Camera–Sony A5100 or Canon M200

These recommendations came from the Alpha Gaming video below. For purely streaming, where the video of your face is a small thumbnail in the corner of your screen, any good quality webcam will do. However, if you want to do anything full screen, like a YouTube video, you’ll want a better camera for that. The key feature you need from a DSLR for streaming is that it has to output “clean HDMI.” DSLR cameras can run into the thousands of dollars, so $500 is relatively affordable compared to that. These cameras can supposedly be found used on places like eBay for considerably less if you’re on the lookout.

If you tend to be a gamer who bangs on the desk a lot, you might want to get the tripod instead of the stand that clamps to the desk. The cameras have built-in stabilization but if you want to keep your camera stream smooth, the tripod means it won’t be mechanically attached. The downside is you need more room behind your desk to use a tripod.

Microphone–Elgato Wave 3

I think I already talked enough about this in the audio section above. As far as audio quality is concerned the Elgato Wave 1 is supposed to be equivalent and a bit cheaper than the Wave 3. The key feature I liked about the Wave 3 is the ability to mute it with a tap.

The other thing I like about the Elgato Wave software is that it allows you to have a different mix of audio sources going out to your stream and to your headphones at the same time. You can switch back and forth between them to fine tune what your audience hears.

Elgato Stream Deck

The last piece of hardware to discuss is the Stream Deck. I’m not going to say much here since I’ve never personally used one of these, but this review makes it sound like it would become pretty indispensable pretty quickly if you got used to using one and it could add a lot of interactivity to your stream.

Odds and Ends and Conclusion

There is A LOT you need to learn to become a professional level streamer. You don’t actually need to buy any hardware at all to get started (see LTT video below). However, if you’ve gotten to the point where you think you’d like to make it as a professional or even as a serious hobby, the list in this article is designed to allow you to stream on Twitch at the highest quality recommended for non-partners. Chances are, if you get to partner status on Twitch, you’ll already be generating enough income and have enough experience to know which components you’ll want/need to upgrade and you’ll have enough money to do so.

Two areas that I have basically not addressed at all here are what goes behind you in your room or how to soundproof your gaming space. These are serious considerations in themselves and you’ll want to address them pretty soon in the process. There are lots of videos and articles out there that describe DIY solutions to a lot of the

]]>
https://morphatic.com/2020/08/14/high-performance-game-streaming-setup/feed/ 0
Building Packageable Components to Extend Vuetify with TypeScript–Part 6 https://morphatic.com/2019/09/09/building-packageable-components-to-extend-vuetify-with-typescript-part-6/?utm_source=rss&utm_medium=rss&utm_campaign=building-packageable-components-to-extend-vuetify-with-typescript-part-6 https://morphatic.com/2019/09/09/building-packageable-components-to-extend-vuetify-with-typescript-part-6/#respond Tue, 10 Sep 2019 00:02:52 +0000 https://morphatic.com/?p=542

Welcome to the 6th (and final?) installment of this series. Now that I’m nearing the end I’ve started thinking of this as more of a “short course” than a “tutorial.” If you’d like to follow along from the beginning, please head on over to Part 1. When we left off in Part 5, we had just finished adding functionality and tests. In this installment, we will:

  • Add thorough documentation to the README file
  • Create a “playground” demo over on CodePen
  • Merge the code on our “dev” branch back into “master”
  • Trigger our first release to NPM

Writing the Docs

The quality of the documentation of a package can make or break its popularity on NPM. Even if your package delivers amazing functionality, if your docs are lacking, developers may not be able to figure out how to use it. Yes, some of them may already have enough experience to use your component without too much help, and others may be willing to dive into the source code to figure it out, but most people would just like clear and straightforward instructions on what to do. If you’ve gone to the trouble to thoroughly test your package and make it usable in as many contexts as possible, you owe it to yourself to provide excellent docs.

Here’s the outline that I recommend for your README.md file:

  • Title/Name of your package
  • Badges (see Part 4)
  • 1-sentence Description of what it does
  • Link to Demo
    I recommend using CodePen or something similar (see below).
  • Installation (and Configuration if necessary)
    Most people don’t really need this, but it’s customary
  • Basic/Common Usage Instructions
    The simplest example possible, for impatient people
  • Detailed Usage Instructions
    Thorough documentation of all available options and defaults, as well as guides on how to handle edge cases and other more common scenarios, e.g. SSR
  • Contributing (if desired)
    How can people help you improve your package, if they are so inclined?
  • Gratitude
    Thank sponsors, supporters, the community, and anyone else who has helped bring your package to production. This is also a good place to let others know how they can thank you! (Patreon, Ko-fi, etc.)
  • Contact
    How should people submit questions, bug reports, or any other requests for help?

Hopefully most of this stuff is self-explanatory. If you’d like to see an example, here are the docs for my VStripeElements package. You’ll notice that I wasn’t super strict about following this outline. It’s more of a checklist of topics you may want to cover than a rigid structure that must be followed.

A “Playground” Demo on CodePen

Many of Vuetify’s components have a “playground” example (like this one) that allows you to easily toggle a component’s settings on and off, or otherwise edit them. This allows would-be users of your package the opportunity to see the full breadth of what it can do, and to imagine what it would look like in their own apps. In fact, since we’re extending Vuetify components here, the easiest way to create your own is to go to the playground section for the component you’re extending and click the “Edit in CodePen” button in the toolbar above the demo.

screenshot highlighting the "Edit in CodePen" button for Vuetify playgrounds
Click the “Edit in CodePen” button to start your demo (click to enlarge)

Naming and Saving Your Pen

screenshot showing how to rename a pen on CodePen
Give your pen a good name (click to enlarge)

You’ll need to create an account on CodePen and/or log in. Once you’ve done that, click on the pencil (edit) icon in the upper left of the pen, next to its title and give it a good name. I like to specify the version of Vuetify, the name of the component being shown, and then some other descriptive words to let people know what this pen is supposed to communicate. Don’t forget to hit the “Save” button. Once you do, it should auto-save your changes once a minute or so after that.

Linking to Your Custom Component’s Code

If you’re modifying one of the existing playground demos from the Vuetify docs, all of the basic JavaScript and CSS necessary to display a Vuetify pen will already be available (Vue, Vuetify, Material Design Icons, etc.). You’ll have to provide a link to the JS and CSS for your custom component in addition to these. The easiest way to do this is to use a service called jsDelivr.

jsDelivr logo

You don’t need to create an account on jsDelivr. It is a free, open-source CDN (content-delivery network) that can serve the latest version of your component’s JS and CSS safely and reliably to your pen. If you’ve already published your package on NPM, you can link directly to the JS and CSS by constructing a link as follows–assuming you’re using the template provided in this short course (hint: you can go ahead and try out any of these links by copying and pasting them into the address bar of your browser):

Plain Text

If you have NOT released your package yet, you can still link to your built files directly on GitHub:

Plain Text

Note

Although one would think you could just use the “raw” GitHub URL (e.g. like this one), it turns out that CORS restrictions prevent this from working as you might expect. Well, how I expected anyway…

Once you’ve got the URLs that point to where your code can be retrieved, you need to add them to the settings on CodePen. Click the “Settings” button at the top of the CodePen window. Then in the window that pops up, click the “JavaScript” tab, and then the “+ add another resource” button at the bottom of the screen. Paste the URL to your JS code into the text field that appears. Do the same for your CSS (if you have any–we don’t for the VAddressFields package), but use the URL to your built CSS file. Finally click the green “Save & Close” button to close the popup.

Now you’re ready to create your demo. Your custom components should already be loaded and registered, so all you have to do is just use them!

What to Put in Your Demo

I like to throw some Vuetify “chrome” into my demos (i.e. an app bar) and wrap them in a VCard to make them look nice. Here’s the demo from my VStripeElements package. It’s also a good idea to provide links back to your repo and/or your package on NPM and also back to Vuetify. That way, if someone finds your demo before they find your package on NPM or GitHub, they have an easy way to get to it. I also like to provide some minimal text on what people are looking at. Document both the settings that are unique to your custom component, as well as any settings inherited from the Vuetify component(s) upon which it is based. It’s a good idea to segment them so people know which is which.

A great demo is an excellent way to convince people that your Vuetify extension is of high quality and worth using in their projects. Of course, that may not be super important to you, but if you’ve come this far, you might as well go all the way!

Merge “dev” Into “master”

Okay. Your code is done. Your tests are written. Your coverage is >80%. You have robust documentation, as well as a super sharp demo on CodePen. It’s time to take the final steps necessary to publish our first release to NPM.

Here’s the series of steps to take:

  1. Check the status and make sure everything has been committed and pushed
  2. Checkout the “master” branch
  3. Check the status again to make sure “master” is clean
  4. Merge “dev” into “master”
  5. Push the changes to the remote repo
  6. Watch the magic happen

In your terminal this looks like (lines where we type stuff start with ➜):

Shell

Once you’ve pushed the master branch to your remote GitHub repo, this will:

  • Trigger TravisCI to pull your code, build it, run your tests, and check coverage
  • After that succeeds, it will build your library (yarn build)
  • After that is done it will run Semantic release

Semantic Release analyzes your commit history. If you have any “feat” or “fix” commits, that will trigger a new release being sent to NPM. You may not realize it, but this is, in fact, the first time we’ve pushed any changes to master that have a feature or fix commit included. Up until now we’ve been using other kinds of commits.

Congratulations!!!

You’ve now got a package on NPM that extends Vuetify and was built with TypeScript!

It was a lot of work to get this far, but as Isaac Newton said, “We stand but on the shoulders of giants.” A lot of other people put in a lot of effort so that we could do what we’ve done. The magic of open source is the cumulative effects of thousands of people working independently and together to create a fabulous array of tools to make our world better.

In Closing

If you do end up building and publishing something, I hope you’ll tweet about it and mention me (@morphatic). I’d love to see what you’ve done. Thanks for following along this far.

]]>
https://morphatic.com/2019/09/09/building-packageable-components-to-extend-vuetify-with-typescript-part-6/feed/ 0
Building Packageable Components to Extend Vuetify with TypeScript–Part 5 https://morphatic.com/2019/09/09/building-packageable-components-to-extend-vuetify-with-typescript-part-5/?utm_source=rss&utm_medium=rss&utm_campaign=building-packageable-components-to-extend-vuetify-with-typescript-part-5 https://morphatic.com/2019/09/09/building-packageable-components-to-extend-vuetify-with-typescript-part-5/#respond Mon, 09 Sep 2019 04:23:07 +0000 https://morphatic.com/?p=531

Welcome back! If you’re just joining us, it would behoove you to go back and read the earlier installments of this series: Part 1, Part 2, Part 3, and Part 4. In this installment, we’re going to pick up where we left off. More specifically, we want to:

  • Deal with the [Vue warn] message we’ve been getting in the console
  • Add some tests
  • Improve upon the dev user experience of our component
  • Maybe even publish our first official release to NPM!

Dealing with the Warning

Vue has been wagging it’s virtual finger at us with the following message:

[Vue warn]

Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop’s value. Prop being mutated: “items”

This has been popping up in the developer console of our web browser for the demo app that uses our component. It’s referring to this block of code we wrote in our mounted() function:

TS
src/VStateSelect.ts

This is indeed a problem. As the author of that article puts it:

Changing the value in a child component won’t change it in the parent component, but it’s a symptom of not having thought out your component design clearly enough.

Michael ThiessenVue Error: Avoid Mutating a Prop Directly, November 2018

Guilty. Let’s think about our design a bit more clearly so nobody can accuse us of implementing an “anti-pattern.”

The Right Way

The first thing we should do is try to figure out how the parent component, i.e. VAutocomplete handles this task. After all, VAutocomplete inherits from VSelect and the items property is first declared there. Michael Thiessen’s article indicates that the way to handle this is with computed properties. In examining the code of VAutocomplete and VSelect, I came across the computedItems() function in VSelect:

TS
vuetify/src/components/VSelect/VSelect.ts

Sure enough, if you inspect the code for VAutocomplete you can see that this is how they update the list of items. VAutocomplete‘s computedItems() function returns the result of another computed property filteredItems() which in turn is a filtered copy of the computed property this.allItems(), which in returns an array of objects made by filtering duplicates out of the concatenation of this.items and this.cachedItems. Whew! You have to read through the code a few times before you can piece all of this together, but eventually, what we need to do becomes clear.

We need to override one of these computed functions, but which one? We could do something like:

TS
src/VStateSelect.ts

If we try it that way, we still get our list of states, but we no longer get items filtered as we type, and even clicking on an item fails to select it. Similarly, if we override filteredItems(), unless we duplicate its functionality, we’ll lose the ability to filter. It appears that our best bet is to override the computed allItems() function. In the grandparent VSelect component, allItems() is defined like this:

TS
vuetify/src/components/VSelect/VSelect.ts

We need to make sure we understand what this function is doing so we don’t unintentionally lose some key functionality. filterDuplicates() is easy enough–it filters out any duplicate items. What are cachedItems? This is a mechanism that the Vuetify developers put in to handle situations in which the list of items in the VSelect might have changed after it was mounted. cachedItems consists of the original list of items that were passed in via the items prop on the VSelect (or [] if caching is not turned on, which is the default).

In our component, that would translate to keeping a copy of the original list of states that were passed in, but since we’re generating our list of states internally (using the usa-states package), we shouldn’t need to worry about this. As such, we can define our computed property like this:

TS
src/VStateSelect.ts

Go ahead and update your component to match. Once you save, you’ll notice a few things:

  1. We still get our list of states popping up in the dropdown as expected
  2. We are still able to filter them when we type
  3. We are still able to select one of them
  4. No more [Vue warn] in the console!

Also, since we no longer are referencing this.items directly, we can get rid of it from the list of base props in the extended interface type up on around line 30.

Mission accomplished. This is a significant milestone so let’s commit our changes. Our commit type should be “fix” since this fixes a bug. The scope is “VStateSelect”, and the messages should be “avoid mutating ‘items’ prop.”

Adding Tests

If you hadn’t figured it out by now, I’m kind of a purist. And as a purist, I feel guilty that I haven’t been following a strict test-driven development (TDD) approach. I mean, we started out the project with some passing tests, but then we just dove right into implementing new functionality without any thought for how we should be testing it. What can I say? I guess my purism (purity?) has limits. That said, I am committed to making sure any code I plan to release to the world at large is thoroughly tested.

Running Tests in “watch” Mode

The extension template I provided comes ready to run tests in “watch” mode. Watch mode is kind of like the dev server, in that every time you make a change to your source code files and save it, the testing engine (Jest in this case) recompiles your code and runs the tests again. The output of the latest test run will show up in your browser. To start the tests, type the following in your terminal:

Shell

It won’t be surprising if the tests fail the first time. This is probably because there is a “snapshot” in the src/__tests__/__snapshots__/ directory that no longer matches the output from your component. To update the snapshot, type the “u” key in the terminal where your tests are running. This will re-run the tests and update the snapshots. The first time this happens, the snapshot is pretty much guaranteed to match because the first snapshot is created from whatever output comes from your component. When successful, your terminal screen should look something like this:

screenshot of successful unit tests
Successful tests! (click to enlarge)

Although they’re designed to be mostly self-explanatory, let’s run through the tests just to make sure.

VStateSelect installer should register the v-state-select component

This tests the code in src/index.ts which is invoked by the Vue.use(VAddressFields) statement on about line 31 in src/__tests__/VStateSelect.spec.ts. In plain English, if the installer works as intended, then there should be a globally registered component with the name “v-state-select.” This will be a property on the global Vue.options.components object. This is pretty much the only test we have that directly checks the code in src/index.ts.

VStateSelect component initialization should render component and match snapshot

Jest supports a form of testing against “snapshots.” Basically, when your code generates a complex HTML structure, Jest can save a copy of that HTML structure as plain text and then compare that to the output of your component every time it gets initialized during tests. You actually have to look at the snapshot the first time after it is generated! Your first snapshot is not guaranteed to be correct. In fact, it is very likely to be wrong. In our case, when I inspected the snapshot, something immediately jumped out at me. The first line of the rendered component in the snapshot looks like:

HTML
src/__tests__/__snapshots__/VStateSelect.spec.ts.snap

You’ll notice that the rendered component has a class name for every type in the class hierarchy. VAutocomplete extends VSelect extends VTextField extends VInput, and the kebab-case name of all of these shows up in the class property of the outermost <div>. If we are going to continue with this convention, v-state-select should be among the class names listed here.

Manually edit the snapshot to add “v-state-select” to the list of class names:

HTML
src/__tests__/__snapshots__/VStateSelect.spec.ts.snap

Now save it, switch to the terminal and hit “a” to re-run all the tests. Your tests should now fail and produce output that looks like this:

screenshot of v-state-select snapshot test failing
A failed snapshot test (click to enlarge)

At the bottom of the screenshot, you can see in green the text that Jest expected to see based on the snapshot, and in red the text that it actually saw. This is because at present, our component does not add its own name to the list of classes. Let’s fix this. If you look at the source code for VAutocomplete, you’ll see that among its computed properties, it outputs first the classes of its parent (VSelect) and then adds some of its own. Let’s do the same in our component. In our component, update the computed section to look like this:

TS
src/VStateSelect.ts

Save this code, and watch what happens in the terminal. The tests should automatically re-run, and this time, they all pass because now “v-state-select” is among the classnames on the outermost <div> of the rendered component. Now we, or anyone else, can add custom styles to this component by referencing .v-state-select or one of its children in their CSS code.

The lesson here is that although auto-generated snapshots are a huge timesaver, you have to inspect and edit them manually to use them correctly as intended.

VStateSelect component initialization should have a property called ‘foo’

The third test checks for the existence of a property called “foo” on our component. The “foo” property, while it does exist on our component, probably shouldn’t. I just included it in the template as a dummy property to show how to add and test for the existence of custom properties. Although we could (and should) safely remove it from both the component and our tests, I’m going to leave it for now because later on I want to replace it with an actual custom property.

VStateSelect component internal functions and events bar() should return “baz”

Like “foo,” bar() is a dummy method that I added to the template as an example of how to add and test custom methods to our component. We will remove or replace it later.

Examples of More Complex Tests

The tests included in the extension are pretty simple. Writing tests can be hard, and if you’d like some examples of more complex tests, you can look at the tests I wrote for the VStripeElements package, or look inside of any of the components directories in the Vuetify source code for inspiration and guidance.

What NOT to Test

You should only ever test your own code. You should NOT write any tests to confirm that an Vuetify component, or Vue, or any 3rd party library works as it is supposed to. That is their responsibility.

Our First Test

Let’s write our own test. So far, we really haven’t added that much functionality. We’ve already tested whether or not our component’s class name will appear in the HTML generated. The only other thing we’ve really done is override the allItems computed property. Let’s test that it works as expected. The allItems property is supposed to return an array of objects, one for each of the states (and the District of Columbia) that will populate the dropdown menu. Why don’t we test to make sure that this is, in fact, what is getting returned. Add the following test right after the “foo” test:

TS
src/__tests__/VStateSelect.spec.ts

With any luck, this test already passes, but it gives us a bit more confidence that the component is behaving as expected. Let’s move on to adding more functionality.

Improving the Developer Experience

In the process of going through the tests, it occurs to me that there are some things our component doesn’t do. First of all, there is no way to customize the list of states that are presented to the user. If someone wanted to say they live in Guam or Puerto Rico, they are out of luck. Fortunately, the usa-states package supports adding US territories to the list. All we have to do is pass { includeTerritories: true } to the UsaStates() constructor.

Let’s finally get rid of the foo property and replace it with something more meaningful. Let’s replace it with a boolean property called includeTerritories that will default to false, but when set to true will include the US territories in the menu. Replace the foo property code with the following:

TS
src/VStateSelect.ts

Technically, this is a bit overkill since boolean variables will naturally default to false, but the Vue style guide recommends this explicit, more verbose style. Now we need to use our includeTerritories prop as part of the UsaStates() initialization. Replace the current line with this one:

TS
src/VStateSelect.ts

When you do this, you’ll immediately get a new red squiggly with the following explanation:

screenshot showing IConfiguration parameter is missing properties
IConfiguration is missing properties! (click to enlarge)

Drats. Foiled again by bad TypeScript type declarations. If you look at our type declaration (remember, the one we created in src/node_modules/usa-states/index.d.ts), you’ll see that indeed, the type declaration for the IConfiguration type makes all of its properties required, even though the usa-states docs would seem to suggest that all of the properties are optional. We have two obvious options:

  1. Update the type declaration to make the props optional
  2. Include all of them in our constructor call

Like includeTerritories, developers may also want control over the contiguousOnly and exclude properties. The ignoreCharacter property is not one that is likely to be needed, however. Let’s do a combination of these strategies. First, we’ll make all the properties optional in the type declaration like this (we’ve added the question marks to signify each property is optional):

TS

Next, we’ll add in props for contiguousOnly and exclude. As we’re doing this, you may notice that we have a new red squiggly underneath this.includeTerritories which says, “Element implicitly has an ‘any’ type because type ‘typeof globalThis’ has no index signature.” But wait! Haven’t we explicitly given it a boolean type when we declared it? Actually, there’s one more place it must be specified, which is in the base options instance type near the top of the file. Let’s go ahead and add all of the props we’re about to add.

In addition, I’m going to go ahead and make one more change. I’m going to move the initialization of the usaStates class into the allItems computed property, since that is really the only place it is used, and because this will allow the list of states to be modified on the fly if the prop values get changed by the parent context.

TS
src/VStateSelect.ts

So here is our updated component. I’ve removed all of the unused sections (data, watch, and methods which contained the unused bar() function). Updating our tests to match yields:

TS
src/__tests__/VStateSelect.spec.ts

We should also test our new properties in the browser by updating our component in dev/App.vue like so:

HTML
dev/App.vue

You’ll see now that Alaska and Hawaii no longer appear in the list because they are not part of the “lower 48.” If we change it to:

HTML
dev/App.vue

You’ll see that Guam, Puerto Rico, and the Federated States of Micronesia (all US protectorates or territories) now show up in the list. Finally:

HTML
dev/App.vue

will exclude both Alabama and Arizona from this list, because we can do so by passing a list either of state names or abbreviations. I added a paragraph above the input that will show the currently selected state value. Of course, these props can also be combined, as well as mixing in props from the parent components like a hint and a label:

HTML
dev/App.vue

The initial value can even be set by giving state a value other than null, e.g.:

JS
dev/App.vue

Lastly, and this will be left as an exercise for the reader, it would be nice if the developer using this component could specify what values will be used both as the text (that displays in the dropdown) and also the value that gets stored in the variable assigned to v-model.

Checking Coverage

Okay, now that we’re done adding functionality and getting rid of extraneous properties and such, it’s time to check our coverage. From the terminal run:

Shell

You should see output that looks something like this:

v-address-fields screenshot of coverage output
96% ain’t bad!! (click to enlarge)

You should be striving for a coverage value >80%. Unless it’s a very simple component, if your coverage is 100%, you’re likely working too hard. One thing to note in the output is that it tells you exactly which lines remain “uncovered” by unit tests. In this case, the only line I’ve apparently missed is line 14 in src/index.ts. I can live with that.

Commit!

It’s been a while since we committed anything and we’ve made a bunch of changes. I realized that the custom type declaration we did for usa-states was not being checked into the repo because the .gitignore file ignores any folder called node_modules by default. You can override that by adding a line like this to the bottom of the file:

Plain Text
.gitignore

Now when you check git status it will show you the src/node_modules folder is untracked and give the opportunity to add it to source control.

I broke this batch of changes up into three separate commits. A “chore” commit that involved adding the missing/incorrect type definitions for usa-states, a “test” commit for the tests and snapshots we added, and a “feat” commit for the new functionality we gave to the developer.

After you’ve made your commits, when you go to git push your changes to the remote repository, git will notify you that “The current branch dev has no upstream branch.” That’s because we’ve never pushed anything on the “dev” branch to the remote repo since we created it. Git will also tell you how to do it. Enter this in the terminal:

Shell

This will create the dev branch on the remote repo as well.

Let’s Call it a Night

At the beginning of this post I teased that we might get as far as making our first release, but haven’t we done enough for one sitting? In the next (final?) installment, we’ll merge our dev branch back into master, push it to the remote repo and do the things we’ve gotta do to go live. This will include adding great documentation to the README file and creating a demo on CodePen. See you next time!

]]>
https://morphatic.com/2019/09/09/building-packageable-components-to-extend-vuetify-with-typescript-part-5/feed/ 0
Building Packageable Components to Extend Vuetify with TypeScript–Part 4 https://morphatic.com/2019/09/08/building-packageable-components-to-extend-vuetify-with-typescript-part-4/?utm_source=rss&utm_medium=rss&utm_campaign=building-packageable-components-to-extend-vuetify-with-typescript-part-4 https://morphatic.com/2019/09/08/building-packageable-components-to-extend-vuetify-with-typescript-part-4/#respond Sun, 08 Sep 2019 21:41:29 +0000 https://morphatic.com/?p=492

Okay! Down to business. Before we get started writing code, let’s recap how we got here.

In this installment, we’re going to build out the custom component we began in the last post, a Vuetify extension called VAddressFields, which will provide input for collecting user address information, specifically VStateSelect, a dropdown for allowing a user to specify what US state they live in. If you haven’t read the other posts, I recommend you at least look over them before coming back here to follow along.

Claim Your Badges!

VAddressFields Build Status VAddressFields Coverage Status VAddressFields is released under an MIT license VAddressFields is Commitizen Friendly VAddressFields uses Semantic Release VAddressFields on NPM VAddressFields downloads from NPM

But first things first. If you made it through the marathon that was part 3, you have a well-earned reward coming to you: badges! Badges have become a stable part of the open-source ecosystem, and they play a role much more important than just making package developers feel good, and allowing them to show off a little. Badges are typically displayed at the top of the README file, and when used correctly, they convey key information about a package that can help people decide if they’d like to use it or not.

Of course, take the badges you see on people’s packages with a grain of salt. Just because a build is “passing” and coverage is >80% doesn’t necessarily mean anything special. For example, right now as I write this post, the package we’re about to write (which as of yet has ZERO code other than the boilerplate we downloaded with the template) boasts that its tests are passing and that it has coverage of 89%! That’s pretty good for a package that hasn’t even been written yet!

Still, some badges are better than none, and experienced devs are smart enough to dig deeper to find out if your package is worth its salt. Some services, like Travis and Coveralls, will provide you with the Markdown markup to add their badges to your README. You might also like to explore Shields.io, that will allow you to generate a wide range of informative and even custom badges.

Getting Started

So let’s get into it. Start by firing up your dev server, i.e. run yarn dev from the command line. Open up a browser window so that you can monitor changes as we make them, and go ahead and open up the browser’s developer console (I use Chrome) so you can see any errors that get output in real time. I have 4 monitors (yeah, probably overkill, but they’re old, and I got them basically for free as salvage), and I fill each one with key info: terminal output, my code editor, the output of the dev server running in a browser, and the developer console output. When I have to google stuff (which is constantly), I use the “browser” monitor. There’s usually somewhere between 20 and 50 tabs open at any given time while I’m working.

panoramic photo of 4-monitor development setup
A really bad panoramic photo of my 4-monitor setup.

The first file we’re going to edit is dev/App.vue. This is where we’ll add our custom component, just as if we were using it in a project. Replace the text that says “YOUR CUSTOM COMPONENT SHOULD GO HERE” with the following:

HTML
dev/App.vue

You’ll also need to add state: null to your data property at the bottom of the component. This is the-simplest-thing-that-could-possibly-work. All we really know at this point is that we want a component that will provide a dropdown that allows people to select their state. The only attribute or piece of data that we absolutely have to track is the result, i.e. the state that the user picks, and we’ll store this in a variable called state assigned to the v-model prop of the component. Go ahead and save this file, and watch as the dev server re-compiles it and the content in the browser window and dev console updates. The content in the browser should now look like this:

first dev render of v-state-select
First dev render of <v-state-select> (click to enlarge)

The good thing about this is that it appears our component has successfully inherited from <v-text-field> and is rendering a Vuetify text field in the place where we put our custom element. You should be able to click on the field, add text, and interact with it exactly as you would a <v-text-field> because, at this point, that is exactly what it is. You should be able to add and modify all of the same props of a <v-text-field>, change the theme, and have all of the benefits of using one of Vuetify’s components. By default, text fields fill up 100% of the width of their parent element, have no visible label, hint, or placeholder, and have an underline where the text goes. That’s why all we see is a thin line extending the width of the window. Excellent.

The bad thing about this is that I realize we’re extending the wrong component! The goal of our custom component is to allow people to select the state they live in. Instead of VTextField, we should instead be inheriting from VSelect, or even better, VAutocomplete. Let’s fix this now.

I’m going to open up the VStateSelect.ts file and change the component that it extends:

TS
src/VStateSelect.ts

NOTE: you may have noticed the // @ts-ignore comment on line 16 above. Because Vuetify doesn’t yet export the TypeScript type definitions for all of their components, tslint and tsc (the TypeScript compiler) will complain that they “Could not find a declaration file for module ‘vuetify/lib’.” The // @ts-ignore directive will tell tslint and tsc to ignore the fact that we don’t have a type definition for VAutocomplete. If you take this directive out, it will prevent your component from compiling correctly. At some point soon, I expect the Vuetify team will publish type definitions for all of their components and this workaround will no longer be necessary.

You’ll also note that in the options interface specification (which starts on line 21), in the example code provided by the template, a bunch of the properties inherited from the parent component (VTextField in the default template) have been re-declared as part of the component type. That’s also because without a type definition for VTextField, tslint and tsc are not able to infer the existence of the properties that should be inherited from the parent component. In the future, when the type declarations have been published, you will also be able to avoid these re-declarations. You only have to re-declare the properties that you actually use in your extended component. I’ve removed them all for now in VStateSelect.ts but will re-add them when and if they become necessary as we continue.

If you save these changes, you should see that the dev server will recompile your component and update the output shown in the browser. If you click into that component you should now see something like this:

v-state-select renders as a v-autocomplete with no data
<v-state-select> renders as a <v-autocomplete> with no data. (click to enlarge)

Yay! VStateSelect now extends the VAutocomplete component, and because we have not yet provided it with the list of states to select from, it warns us that there is “No data available.” Now we have an excellent baseline from which to start adding the features and functionality to our custom component that will make it useful to the world.

Now would be a good time to commit the changes we’ve made so far to our git repo. Go ahead and run git add . from the command line and then enter git cz to begin the commit process. When commitizen asks you what type of change you’re committing, select “refactor: A code change that neither fixes a bug nor adds a feature” since essentially, this is what we’ve done so far. I’m going to use “set dev baseline” as my commit messages.

Switch to the dev Branch

“Branching” is one of the core capabilities of most version control systems, like git. (If you’re not familiar with git branches, I suggest that you read up on it now.) Semantic Release, which we’re using to automatically publish our package to NPM, will possibly trigger a release every time you push changes to the “master” branch. While technically we don’t have any releases yet, we want to safeguard against accidentally releasing our package to NPM before we’re ready. The Semantic Release docs suggest doing all of your development on a “dev” branch. It’s a very good idea to get into the habit of developing this way. If you type git checkout -b dev into your terminal, this is what you should see:

Shell
VSCode indicates that we are on the dev branch
git branch indication in VSCode

In the event the “dev” branch didn’t exist yet (as in our case) it will be created. Otherwise it will just tell git that all of the changes we’re about to make should be tracked on the “dev” branch, and NOT on the “master” branch. In the event that we decide we made a wrong turn, we can delete the entire “dev” branch and our package will revert to the baseline we just established in the last step. If you’re using VSCode, you’ll notice that the git branch indicator in the lower left of your editor window will have switched to say “dev.”

Add the State List

Since our component is supposed to allow people to select from a list of US states, we need to provide that list to the component. To do this we’re going to:

  1. Create a new states prop for our component
  2. Populate it with a default list of state names and values
  3. Use the states prop to populate the items prop inherited from VAutocomplete
  4. Disallow users of our component from accidentally overriding states by specifying their own additional items prop.

Getting the List of States

Where should we get our state data? Perhaps the most straightforward thing to do would be to head on over to Wikipedia and manually compile our list. However, if you think about it, this is such a common task, it’s almost guaranteed that some programmer out there has already solved this problem for us. Sure enough, a brief Google search yields several packages that look promising:

But how do you pick? There’s no kind of quality control on NPM. Literally anyone at any time can publish a package, and it doesn’t even have to have any running code in it. So it is up to you, on your own, to determine whether or not any of these packages will meet your needs. Here are the features of a package that I typically examine when making this kind of decision:

  • When was it published and how often has it been updated?
    The version number of the package and when it was last published are listed at the top of the package page on NPM. In general, anything that hasn’t been updated in over a year (or never) should be treated with suspicion. (With USA state data, however, the actual list of states hasn’t really changed in a while, so this might not be a problem in this particular case.)
  • How many other people are using it?
    Every NPM page shows a sparkline chart of the package’s download history. If a package is being consistently downloaded hundreds or thousands of times a week, it’s a pretty good bet that many other developers have found it robust and reliable enough to include in their packages. I’ll only consider a package with few downloads if it is very new, i.e. published for the very first time within the last couple of months.
  • How good is the documentation?
    Good packages have great documentation. The NPM page (which mirrors the README from the GitHub repo where the packages usually live) typically explains what the package is, how to install it, how to use it, and gives clear examples of common usage scenarios. Sometimes I’ll use a package that forces me to dig into the source code to learn how to use it, but not often.
  • How responsive are the maintainers to support requests?
    Finally, I’ll head over to the GitHub repo where the package is maintained and look at the “Issues” and “Pull requests.” How is the ratio of open to closed issues? How quickly do the maintainers respond to questions or bug reports? How quickly do they respond to pull requests from the community? Are they courteous to the people who post questions and PRs?

None of these criteria are absolute. You have to take them all into account before reaching a decision. For a simple package, like providing the list of US states, you don’t really expect there will be many issues. Likewise, the code is likely so simple that it’s not an issue if it hasn’t been upgraded in a while. In the case of us-state-codes their sparkline shows a period of time when they were getting about 20,000+ downloads a week (this is A LOT)! This started suddenly in October of 2018 and ended just as suddenly in February of 2019. I couldn’t really figure out why. Since then they’ve been getting a consistent 3,000 downloads per week. This is still quite respectable and more than the roughly 2,000 dpw that usa-states has been getting. On the other hand, state-list is getting only about 3 downloads per week and never got above the mid-20s. That’s not good.

All of that said, usa-states has clearer documentation, and provides something much closer to what we actually need than does us-state-codes. Plus, usa-states provides TypeScript type declarations, which is great since we’re developing with TypeScript. If a package is getting a couple thousand downloads per week, you can have a certain confidence that even if the original developer/maintainer decides to abandon the package, it will be important enough to someone to take over maintenance and keep it alive. And that’s probably the most important thing to look for. In this case, anyway, it’s enough for me to overlook the fact that usa-states’ current version number is a very low v0.0.5 and it hasn’t been updated in nearly 2 years.

One last thing to consider is package weight. How big (in kb) is the package you’re planning to use? Are you planning to use enough of it’s functionality to justify making people who use your package download their package as well? In the case of usa-states, the estimated download size is only 1.8kb (minified, gzipped), and that’s pretty darn small. If a package gets up into the tens or hundreds of kb, you might want to consider re-implementing just the parts you need manually in your own package.

Installing usa-states

There’s a bit of confusion around whether a package should be installed as a “dependency” or a “dev dependency” (at least, it confuses me). The way I solve this is to ask the question, “In production, would this dependency be necessary to make my package operate correctly?” If the answer is “yes,” it’s a dependency. If the answer is “no,” it’s a dev dependency. In this case, the list of states will absolutely be required in production environments, so I consider it a dependency, which means we should install it like this:

Shell

After you do this, you should see that usa-states has been added as a dependency in your package.json file.

Import usa-states Into Our Component

In src/VStateSelect.ts up near the top of our component is a section for importing 3rd party libraries. Add an import statement for usa-states here:

TS
src/VStateSelect.ts

Then in the data section of our component, we’ll instantiate it as demonstrated in the usa-states docs:

TS
src/VStateSelect.ts

Assign a List of States to items

Now add the following code to the mounted() function in your component:

TS
src/VStateSelect.ts

Save what you’ve got so far. Go to the browser tab where our demo is running and click on the VStateSelect element there, and…

v-state-select populated with state values
Woohoo! We have states! (click to enlarge)

We now have an autocomplete component that is populated with US states! You can play around with it, and watch how the state names are automatically filtered if you type a state name while the select is highlighted. You can use the arrow keys to move up and down through the selections.

Uh-oh! Dealing with the Red Squigglies

Okay, so although the component is doing what we expect, we’re getting some troubling errors, both in the terminal output (where our dev server is running) and in our code editor. You might be tempted to ignore them since, hey, it’s working, right? I’m the kind of dev who just can’t sleep until all of these issues have been resolved, so let’s resolve them.

usa-states has no exported member 'UsaStates'

usa-states has no exported member 'UsaStates'
No exported member?!?! (click to enlarge)

The first error shows up on the line where we imported the usa-states library. There’s a red squiggly line under the UsaStates class that we’ve imported. The TypeScript compiler seems to believe that no such class exists, even though we know that it does: if you open up node_modules/usa-states/index.js, you can see the export on the very last line:

JS
node_modules/usa-states/index.js

The first place I went to investigate was the repo for usa-states. As it turns out, which frequently happens, this is a known bug. There was even a PR submitted to fix it, and this PR has even been merged into the master branch! Unfortunately, it appears that the package maintainer failed to take the last step of updating the package in NPM to reflect the changes. Sigh. That means it’s up to us to fix the type declaration in our own package.

One way to do it would be to modify the node_modules/usa-states/typings/index.d.ts file (where the package’s types are defined) directly. This is a Bad Idea™. The node_modules folder contains other people’s code and you should never modify it directly. Even if you do, other people who download and want to use your package will not get your modifications.

I found a couple of options for better ways to do it in this Stack Overflow thread, and ended up choosing this one. Essentially, create a node_modules/ folder inside your src/ folder, and inside of that, create a folder for the module whose type declarations you’d like to override, i.e. usa-states/, and then inside of that you put an index.d.ts file that contains your custom type declarations. You can get the correct contents of this file directly from the usa-states repo. Boom. Error neutralized.

Property 'items' does not exist on type 'CombinedVueInstance'

The next error is one that I encountered when developing VStripeElements. Because Vuetify doesn’t export type declarations for their components, TypeScript is unable to recognize that our VStateSelect indeed actually already has an items prop which it inherited from VAutocomplete which in turn inherited it from VSelect. The workaround for this is to re-declare this property in the extension of the base instance type near the top of our file. Modify the section that begins with interface options extends InterfaceType up around line 20 to look like this:

TS

One thing to note here is that we gave the items prop the type Object[]. In reality, a more accurate type definition would be something like { text: string, value: string }[], but in our case we need to make it conform to the same type that is output by the UsaStates.format() function, which is Object[]. This is okay. Both of them are arrays of objects, but one is more specific, and the error is resolved, both in the code editor and in the terminal.

Commit Your Changes

We’re still not done yet, but we’ve made some significant progress. Go ahead and type git add . && git cz on the command line to add and commit your changes. Since we’ve added a substantial new feature to our component, this time select “feat” as the commit type. Also, since now we’re focused on a specific component (VStateSelect) within our larger package (VAddressFields) let’s specify that the scope of our change is “VStateSelect”. For my commit messages, I entered “added states list” and “Added the list of US states to VStateSelect”, and accepted the defaults for the rest.

Up Next

If you’ve been watching carefully, you’ve seen that we’ve been receiving a [Vue warn] message in the browser console from our dev server–something about avoiding mutating props directly. In my experience, it’s a good idea to pay attention to such warnings. They usually signal that there’s a better way to do what you’re trying to do. In the next installment of this tutorial, we’ll address that warning, add some tests, think more about the dev experience of those who will use our component, and maybe even get as far as shipping our first release. See you there!

]]>
https://morphatic.com/2019/09/08/building-packageable-components-to-extend-vuetify-with-typescript-part-4/feed/ 0
Building Packageable Components to Extend Vuetify with TypeScript–Part 3 https://morphatic.com/2019/09/06/building-packageable-components-to-extend-vuetify-with-typescript-part-3/?utm_source=rss&utm_medium=rss&utm_campaign=building-packageable-components-to-extend-vuetify-with-typescript-part-3 https://morphatic.com/2019/09/06/building-packageable-components-to-extend-vuetify-with-typescript-part-3/#respond Fri, 06 Sep 2019 07:23:46 +0000 https://morphatic.com/?p=472

In this post, I’ll walk you through the steps required to build your own custom component that extends Vuetify with TypeScript. Part 1 of this series gave an overview of VStripeElements, a component I built to apply Vuetify styling to the credit card inputs provided by Stripe Elements. In Part 2, we took a deep dive into the development environment necessary to do this on your own.

Create Accounts

Before we begin coding anything, you’ll need to make sure you have accounts for key services and have installed the necessary software. If you haven’t got them already, please create accounts at the following sites. Start with GitHub. For the other sites, I strongly encourage you to select the “Sign Up with GitHub” option to link your accounts on those sites to your GitHub account. The places you’ll need accounts (all of which are FREE!) are:

  • GitHub
    GitHub is where the source code for your extension will live. It will be the primary source of truth about what your extension does, and does not do.
  • NPM
    NPM is the primary repository for public NodeJS extensions.
  • Travis CI
    “CI” stands for “continuous integration” and is a service that will handle running your unit tests. Unit tests are absolutely critical to getting people to trust that your package is well-built. As a general rule, if there are no unit tests, many, if not most, experienced developers will not even consider installing your package.
  • Coveralls
    Unit tests are not enough. “Coverage” refers to the percentage of your codebase that has been tested with unit tests. If 100% of your unit tests pass, but that 100% only represents a coverage of 5% of your codebase, that means 95% of it still untested. A good rule of thumb is that you should strive to have at least 80% coverage for any project you want people to actively download and use. Coveralls is a service that will provide 3rd party verification of your unit test coverage.

While technically you could release a package to NPM that neither has unit tests nor tracks coverage, no serious developer would ever consider installing such a package. There is absolutely no indication that it is of good quality. In the world of open source, we need a way to decide whether or not it is worth it to use other people’s code. For lack of any other better metrics, passing unit tests and at least 80% coverage is what passes for “good enough.”

(Of course, there are alternatives to Travis, e.g. CircleCI, and Coveralls, e.g. Codecov, and tools that do even more than that like CodeClimate. Feel free to use any of these. I’m not specifically endorsing Travis and Coveralls–they’re just the ones I know and they’re the ones that will work out-of-the-box with this template.)

Install Necessary Software

Install Yarn. Also, if you haven’t already, you’ll need to install Git. (Note for newbies, Git !== GitHub. Git is version control software. GitHub is a company that hosts repositories. If you prefer an analogy: git is to GitHub as email is to Gmail.)

The “official” package manager for NodeJS is NPM, which is also the name of the repository where Node packages are usually hosted. Yarn is a 3rd party alternative to the command-line npm software. I’ve chosen to use yarn for this tutorial and my template because that’s what the folks at Vuetify use. Of course, you are free to use npm, but it won’t be the supported way to use this template.

Once you’ve installed yarn, you’ll need to use it to install two global packages: Commitizen and Semantic Release. You can do this from the command line with one command:

Shell

Do Some Planning

Before we do any coding, it would behoove us to give some thought to what we’re going to build.

There are 2 hard problems in computer science: cache invalidation, naming things, and off-by-1 errors.

Leon Bambrick’s adaptation of Phil Karlton’s famous quote, via Martin Fowler

It’s a pain in the ass to have to go back later an rename files and variables and GitHub repos, so it pays to spend some time up front deciding what you want to call things. For example, the original name of VStripeElements was VStripeCard. That was until I realized that Stripe Elements specifies a handful of components, only one of which is called a “card.” I also realized that while my first priority was implementing just the “card,” I might later want to go back and add some of the other elements. So I had to go back and rename my repo, and a bunch of files, and a bunch of variables. And yes, it was a pain in the ass.

For this tutorial, I’m going to create a new set of Vuetify components to accomplish a relatively simple, but common, task: implementing form fields for collecting and validating United States postal addresses. Specifically, I’m going to focus on a dropdown menu for picking a person’s state of residence, but as I think about it, I’ll probably also eventually want at least a zip code field, and maybe some way to validate an entire address. That means that the package should have a more generic name having to do with addresses, and the individual components can be specific about what they do.

Vue.js provides a style guide with recommendations for how to name things. I’m a big advocate for following naming conventions, but I’ll spare you a treatise on the reasons behind my philosophy (for now…). I’ve decided to name my package VAddressFields, from which the first component will be VStateSelect. The “V” is for “Vuetify” and I picked it to create an explicit association with other Vuetify components, whose names all start with V. Since this set of components will focus specifically on United States addresses, I wrestled with whether or not to add “US” to the name, e.g. VUSAddressFields or VAddressFieldsUS, and respectively VUSStateSelect or VStateSelectUS. Ultimately those options felt clunky, and I decided that I’ll leave room for my package to expand to cover addresses for other countries in the future. (Yeah, that was it. I’m definitely NOT being lazy.)

Create the Repo

Now that we have a name, it’s time to start on the package. I’m going to go over to GitHub and create a new empty repository like so:

creating a GitHub repo for v-address-fields
Creating the Repo (click for larger view)

Some things to note:

  • It has a short, clear description that contains key words that will help others find it
  • It is Public
  • Do not check the box to create a README file (one is included in the template)
  • Likewise, do not add a .gitignore file nor license as those are also provided.

Once you click “Create Repository,” the next screen you see should look like this one:

v-address-fields repo created screen
Repo created! (click for larger view)

Leave this browser tab open because we will want to come back to it at the very end of this tutorial. Now that our repo has been created, we can go on to the next step.

Download a Zipped Template Copy

download zipped copy of Vuetify extension template

Go to the GitHub repo for the Vuetify extension template. Click the green “Clone or Download” button and select “Download ZIP.” You might be asking, “Why don’t we just fork the repo in our own account?” For sure, that is a legit way to accomplish the task, but I think the way I’ve describe gives you a little more control, and avoids your having to rename your repo (which as I said before, is a pain in the ass).

Unzip in a Local Directory

Unzip the downloaded template and rename the folder with the name you picked earlier. In my case, I renamed the folder to “v-address-fields”. While the folder doesn’t have to have the same name as your repo, it’s a good idea so you don’t forget what you called it.

Rename Stuff

pristine directory structure of VAddressFields project

Remember what I said about renaming stuff? Yeah, then you’ll know how I feel about this step. Open the project folder in your code editor (I use VSCode). There are 9 files that we will need to customize BEFORE we install or do anything else. The files (pictured at right) are:

  • package.json
  • LICENSE
  • README.md
  • src/
    • index.ts
    • YourComponent.ts
    • YourComponent.sass
    • __tests__/YourComponent.spec.ts
  • dev/
    • index.js
    • App.vue

Efficiency Tip: If you’re using an editor (like VSCode) that has global find/replace, you can use that to edit all of the files at once, since many of the changes are similar from file-to-file.

Specifically, here are the things you need to change:

package.json

The name field should match the name of your repo (in my case v-address-fields). I’ve set it up so that you can do a “find and replace” for the text REPLACE_WITH_YOUR_COMPONENT_NAME. All of the instances of this text should be replaced with the exact same thing.

Update the description. I made mine match the description I wrote when creating the repo itself. Update the author field with your own information. You do not have to include a website, but it’s a really good idea to include an email address where people can contact you if they have questions about your code. Use the same email address you used to create your NPM account.

Find and replace the text YOUR_GITHUB_USERNAME so that the package will be associated with your GitHub repo.

The license field is set to “MIT” by default. I’ve also included a LICENSE file that is is pre-populated with the MIT license and only requires you to insert your name. You are free to change the license to whatever you would like, but I strongly encourage you to adopt one of the open source licenses.

You may now save and close package.json.

LICENSE

Open the file named LICENSE. If you choose to use the MIT license, then all you need to do is update the year to the current year, and replace the text YOUR NAME with your actual name. Then you can save and close this file.

README.md

Eventually, this file will contain full, detailed instructions to people on how they can make use of your custom components. For now, you should delete everything that’s in there, and just add the Pascal-cased name of your package, and a short description of what it is/does in Markdown format. (If you don’t know Markdown, you should learn it. It will only take a few minutes.) Here’s mine:

Markdown

I kept the “Questions” section at the end and modified the link to point to my new repo.

Actually, it’s not a bad idea to start your project by writing the documentation. Starting the project by explaining to the end user how they should use it will force you to think carefully through all phases of development. Not only that, but it’s a lot easier to finish the docs before you do any coding. If you wait until afterwards, there’s a chance you’ll have run out of steam and be tempted to blow it off. If you want your package to become popular and widely-used, good documentation is critical, so whatever you can do to make yourself put significant energy into this is a good idea.

src/index.ts

In src/index.ts replace all instances of YourComponent with the name of your first component, i.e. in my case VStateSelect. Replace YourPackageName with the package name, i.e. VAddressFields. Don’t forget to use the kebab case of your component on line 6 where you’re registering the component with Vue.

(BTW, earlier I referred to “Pascal case” and just now to “kebab-case.” If you don’t know the various variable naming conventions/case types, you should take a few minutes to learn them.)

Here’s what my file looked like when I was done:

TS

src/YourComponent.ts

Rename the YourComponent.ts file to the name of the component you’re going to create, e.g. VStateSelect.ts. Inside the file, find and replace all instances of YourComponent with, e.g., VStateSelect, and all instances of your-component with v-state-select. Don’t worry about the other stuff in that file right now. We’ll get to editing this file in the next installment of this tutorial.

src/YourComponent.sass

All you have to do is rename this file like you did with YourComponent.ts. There’s no content in the file to be edited right now.

src/__tests__/YourComponent.spec.ts

Rename this file to match your component name, e.g. VStateSelect.spec.ts. Inside the file, replace YourPackageName with, e.g. VAddressFields, and YourComponent with, e.g. VStateSelect.

dev/index.js

Almost done. In this file, replace YourPackageName with, e.g. VAddressFields.

dev/App.vue

Actually, you might not want/need/be able to do anything in this file right now. This is eventually where you’ll put a “demo” version of your new component that you’ll use during development to monitor progress. For the purposes of this tutorial, I’m not going to make any changes to this file right now.

Yay!!! Done. Wasn’t that kind of a PITA?

Install Default Dependencies

Okay, now from the command line, run yarn to install all of the dependencies (i.e. on the command line, type the word yarn and hit [Enter]). This will install local copies of all of the dependencies listed in package.json. It will take a few minutes depending on the speed of your internet connection. There are quite a lot of packages to be installed, but none of these will end up being deployed with your package, so there’s no need to worry that they’ll add weight to the package.

When it’s all done, there will be a node_modules folder added to your project, and also a file called yarn.lock. The folder is where all of the dependencies get downloaded to and will not get uploaded to your repo. The second file keeps track of every single package that was downloaded into node_modules and should become a part of your repo.

Initialize Local Git Repo

Go back to the command line and type: git init and hit [Enter]. You now have initialized an empty git repository on your local machine. To add the files of the project to it, type: git add . and hit [Enter]. (Yes, there is a dot in that command. The dot means “all of the files in this directory.”) But we’re not going to commit these changes just yet. First we need to make our repo “commitizen friendly.”

Make Repo “Commitizen Friendly”

Type the following command on the command line:

Shell

This will (re)install the dependencies required by commitizen and setup our repo to override normal git commit commands and force us to follow best practices and always include informational commit messages.

Make Your First Commit

I know. Commitment is scary. Overcome your fear and enter git cz on the command line. This will start up an interactive session with commitizen to gather info about what changes have been made to your repo. The first question will ask you what type of change you’re making. Here is a list of descriptions of all the different types of changes. In this case, it really could be any or all of these different types. Since choosing feat is one of the choices that will trigger Semantic Release to create and publish a new build to NPM, and I don’t think we’re quite ready for that yet, I’m going to recommend that you choose chore. Use the down arrow on your keyboard to scroll through the choices until chore is highlighted and hit enter.

For the second question (scope), this really applies to the entire project, so just hit enter to skip this one.

For the short commit message, type the traditional “first commit” and hit enter. You can type the same thing for the “longer” description as well. There’s really not too much to say here that’s informative. Breaking changes? No. Open issues? Again, no. After that, your first commit will have been applied and we’re ready to move on.

Run Your First Tests

From the command line run yarn test. This will fire up Jest and run your unit tests for the first time. Assuming you’ve followed all of the instructions heretofore perfectly, you should have four passing tests. We’ll discuss the tests in more detail in our next installment. For now, let’s move on.

Do Your First Build

Now from the command line run yarn build. Again, assuming you’ve done everything so far perfectly, you should now have three additional directories in your project (dist, es5, and lib). These contain built and exported versions of the component you are building. While these don’t really have any additional functionality in them right now, the fact that this process completed successfully gives us a baseline to know that our build process works as it should.

Start the Dev Server

Now type yarn dev from the command line. This will spin up the dev server. If all has gone smoothly, there should be no errors or warnings in the console and you should be able to see your “demo” site by opening your web browser and navigating to http://localhost:8080/. Here is what you should see there:

screen shot of the dev server output
The default “Demo” page (click to enlarge)

This is the Vue + Vuetify app that resides in the dev/ folder in your project. Open up the dev/App.vue file and make a change to the text that says “YOUR CUSTOM COMPONENT SHOULD GO HERE”. Save the changes and then check your browser. The dev server should have automatically rebuilt the project based on the changes you just made and reloaded the changed app in the browser. There’s a toggle button in the upper right to toggle Vuetify’s dark theme. This comes in handy when testing because you can make sure your new component will look fantastic in either of the default themes.

To stop the dev server, go back to the terminal and hit [ctrl] + [c] (hold down the “control” button and then hit the “c” key).

Commit Our Changes

If you want to see a list of which files have changed, type git status from the command line. Since our last commit, we’ve:

  • built the project,
  • run tests (which generated a snapshot–more on this in the next tutorial), and
  • made some small changes to dev/App.vue.

Thinking ahead, if we were to commit all of these changes at once, how would we choose which category/type of commit to select when commitizen asks us? One of the great things about commitizen is that it forces you to think in terms of very small, incremental changes. With what we’ve done, the output of running yarn build should probably be committed as a build type. The snapshot generated by running the tests should be committed as test, and the change to App.vue would be a refactor.

But we only get to choose ONE commit type per commit. So in this case what we want to do is add these changed files in batches. Let’s start with the refactor. Type git add dev/App.vue and hit enter from the command line, then run git status again. Now you’ll see that dev/App.vue has been added to the list of files to be committed. All of the other files are still listed as either “not staged for commit” or “untracked.”

Let’s commit the refactor. Type git cz and hit enter. Choose “refactor” as the commit type. Skip the second question, enter “confirm dev server refresh” for the short and long commit messages, and hit enter twice to accept the default choices for the last two questions. Now run git status again. You’ll see that dev/App.vue is no longer on the list. That’s because we just committed those changes to the repo.

Now let’s add the testing snapshot that was created by Jest (when we ran yarn test). Type git add src/__tests__/__snapshots__/* and then run git cz. Choose “test” as the commit type and use “add first snapshot” as the commit messages. Accept the defaults for the last two questions. Now check git status again. All that is left are the files that were created as part of the first build.

Type git add . to add ALL of the remaining files to our next commit. Then run git cz and choose “build” as the commit type. Type “first build” for the commit messages and accept the defaults for all of the other questions. Now when you run git status you get the lovely message “nothing to commit, working tree clean.”

Congratulations, you just observed one of the “best practices” when it comes to managing revisions in a project. By dividing up a number of changes into clear and distinct categories, and attaching a meaningful message to all of them, you’ve taken a great step toward building a super-well organized, high-quality, maintainable package.

Push to the Remote Repo

At this point, we still have not connected our local repo to our remote repo. Remember that screen we saw on GitHub, way back at the beginning of this tutorial, right after we created our repo there? Go back to that tab in your browser. About halfway down the page, there’s a line that begins git remote add origin... Copy that line and paste it into your command line, and hit enter. It may look like it didn’t do anything but if you type the command git remote -v, you’ll see that your GitHub repo has been added as a “remote origin” for your local repo.

Now enter the command git push -u origin master. You may be prompted for your username and password for your GitHub account. This is to ensure you have appropriate permissions to write changes to the remote repo. In the future, your credentials should be remembered on your computer (learn about git credential storage so you don’t have to type them every time!).

Once you have authenticated successfully, your changes will be pushed from your local repo to your remote repo. If you refresh the browser tab that is open to GitHub, you should now see something like this:

v-address-fields first successful push
Your first successful push! (click to enlarge)

Yay! Now all of our changes so far are safe and sound in our remote repository. We’ve only got a couple of steps left to finish the setup.

Setup TravisCI

Travis CI button to add a repository to the list of those that are being tracked

Log into your TravisCI account. On the main screen near the top left you should see a tab that says “My Repositories” that has a small plus sign to the right of it. Click the plus sign. On the page that comes up you should see a list of your available GitHub repositories (assuming you’ve already given Travis permission to access your GitHub account). However, since we’ve only just added our new repo, it might not be in the list yet. Click the button in the upper left that says “Sync Account.” It will take a little while for Travis to send a message to GitHub and retrieve the latest list of repositories in your account there. Once it’s done, search for the name of your new custom Vuetify component in the search bar to the right. Click the toggle button indicated in the screenshot below:

Travis CI enable repo screen

This will instruct Travis CI to keep an eye on this repo. Whenever you push changes to GitHub, Travis will immediately pull down the latest version of your code, spin up a testing server, install your package and run the unit tests. If the tests complete successfully, it will then collect coverage information and attempt to send it to Coveralls, which is what we’ll set up next.

Tracking Coverage with Coveralls

By this time, hopefully you’ve setup an account at Coveralls.io and linked it to your GitHub account in the same way you did for Travis. As we did with travis, we need to instruct Coveralls to watch our repo.

screenshot of the "add repos" button on the Your Repositories page of Coveralls

In the upper-lefthand side of the screen, if you mouse over the little plus sign inside of a circle, a menu will pop open with the choice for you to “Add Repos.” Again, you will likely need to tell Coveralls to “sync repos” with your GitHub account. The button to do this is in the upper right of the screen (it’s dark blue). Once this is finished you will be able to see your new repo in the list of repos that comes up. There will be a toggle button (pictured below) that you need to press in order to turn on coverage tracking with Coveralls. The next time you push changes to GitHub, and Travis successfully runs all of your tests, the coverage info for that test run will be sent automatically to Coveralls. You can go there and click through the results to see exactly what lines of code have (and have not!) been tested (yet). This is extremely useful information for figuring out what you need to pay attention to in your coding.

screenshot of the coveralls Add Repo screen

Setup Semantic Release

The final step in this part of the tutorial will be to setup Semantic Release. Earlier you installed semantic release globally on your local machine. Now from the command line, in the root folder of your project type the command:

Shell

You will be asked a number of questions. When asked what your npm registry is, accept the default. Select “Token based” authentication. Provide your npm username/password as well as your GitHub username/password, and select Travis CI as your CI. Once this is done, you may notice that your package.json file has been updated slightly. The Semantic Release package will have been updated in the list of dev dependencies, and the version field will have been changed to 0.0.0-development. This is the version number that will exist until we make our first official release.

Behind the scenes, Semantic Release will use your GitHub credentials to request a token that will be stored in your Travis CI account associated with your repo. Semantic Release is a pretty amazing tool and you should take the time necessary to read their user manual.

The last thing you should do is git add package.json to stage the changes to be committed. Then run git cz and select “chore” as the commit type and “configure semantic release” as the commit messages. Don’t forget to git push your changes to the remote repo after you commit. This final push should trigger Travis to run your tests and also send coverage info to Coveralls.

Congratulations!!!

You are now completely setup and ready to begin developing your custom component to extend Vuetify. I don’t know about you, but it feels like we’ve already done a TON of work and we haven’t even really written any code yet. You may recall that I commented earlier that managing a high-quality package is extraordinarily involved and complex.

At this point it would be a good idea to go back and read up on the background and user manuals of any of the pieces of this setup process that you still feel shaky on. Don’t worry, the longer you do this, the more comfortable and natural it will begin to feel. Hang in there!

In the next installment of this tutorial, we will build out our VStateSelect component, make sure it is thoroughly tested, add meaningful documentation, and make the changes that will trigger Semantic Release to publish the first version of our component to NPM!

]]>
https://morphatic.com/2019/09/06/building-packageable-components-to-extend-vuetify-with-typescript-part-3/feed/ 0
Building Packageable Components to Extend Vuetify with TypeScript–Part 2 https://morphatic.com/2019/09/05/building-packageable-components-to-extend-vuetify-with-typescript-part-2/?utm_source=rss&utm_medium=rss&utm_campaign=building-packageable-components-to-extend-vuetify-with-typescript-part-2 https://morphatic.com/2019/09/05/building-packageable-components-to-extend-vuetify-with-typescript-part-2/#respond Thu, 05 Sep 2019 23:18:53 +0000 https://morphatic.com/?p=456

In Part 1 of this series, I introduced VStripeElements, an NPM package that I wrote to be able to use functionality of Stripe Elements with the power and beauty of the Vuetify UI library. Part 1 described the goals and end product of the project, and gave an overview of the project structure. In Part 2, I will dive more deeply into the elements of the dev environment.

Organization

To organize this part, I’m going to use the scripts section of the package.json file for this project, which looks like this:

JSON

Almost all of the dev dependencies included in this package are required by these scripts in some fashion, so this seems like a pretty good way to break down this analysis. I’ll try to cover them in an order that builds from the simpler scripts to the more complex ones.

commit

I had never heard of Commitizen before I started contributing to the Vuetify project. Basically, commitizen hijacks your git commit commands and forces you to use a consistent and informative commit message style. These commit messages then get automatically compiled into your release log so you can keep people up to speed on the evolution of your package without much extra effort. This is a pretty genius idea, and I intend to use it on pretty much every project I do from now on.

tsc

The three TypeScript config files (tsconfig.*.json) for this project are a simplification and amalgamation of the five tsconfig files in the Vuetify project. These files control how TypeScript source code gets transpiled into JavaScript. There are three different transpilation contexts that have to be considered:

  1. Testing
    This produces the JavaScript required by Jest to run unit tests. Jest’s testing engine is the only client to consider here, and for that we want to output our code in the CommonJS module format.
  2. Development
    This produces the JavaScript required by the webpack dev server that is running constantly in the background during coding. Since you’ll want to be able to see and read your code in order to debug it, it’s not necessary to minify or create source maps in this context.
  3. Building
    This produces the JavaScript that will be minified, bundled, and exported for other people people to use, i.e. the people who will eventually consume this package.

In order to handle transpiling for these contexts, all of the scripts start with the core tsconfig.json file. This file is used “as-is” for dev builds. The tsconfig.dist.json and tsconfig.test.json files import the base config file and overwrite/add the settings necessary for those contexts.

build:lib

The build:lib script process

The build:lib script converts the TypeScript code in your /src directory into JavaScript modules suitable for being imported into other projects. More specifically, it runs a shell script (/build/lib.js) that uses the TypeScript compiler (tsc) to first transpile the TypeScript into ESNext code that gets stored in /lib-temp. Then it uses Babel to convert the ESNext code into ES5 (/es5) and ES6 (/lib) code. It does not process the SASS files at all. These files remain untouched in the /src folder to be imported and processed by whatever build system is used by the people who will eventually use your component. Note that this script does not use webpack, and it is rare that you’d need to invoke this script on its own. It gets invoked by the more general build script that builds all of the assets required for distribution by your completed project. The config files that are used by this script are noted in the diagram.

test

The test script process

The test script uses Jest to run unit tests for your extension. It first converts all of your TypeScript code into CommonJS format using the TypeScript compiler and then passes that through Babel using your babel.config.js settings. The output of your tests shows up in the terminal, and if you run test:coverage it also outputs coverage info for all the files in your src/ folder, as well as saving coverage data in the coverage/ folder.

All of the config files used in this process are noted in the diagram. The test/index.ts file deserves special mention. If you follow the same approach to component development as Vuetify, the components you generate are what are known as “functional” components, i.e. the component is generated by a render() function rather than being patterned off of a template property. In production, it is possible that you may need to generate a tree of components this way, but in testing this is not necessary. The test/index.ts file exports a utility functionalContext() function that makes it easier to mock components when you test.

The jest.config.js file makes use of the ts-jest plugin. This plugin makes it possible to test TypeScript files with Jest, which is really designed just for testing JavaScript. Our tests also make use of the @vue/test-utils, a package that helps to mount our components in memory during testing. You can run the tests in “watch” mode by running npm run test:watch.

Running tests also produces what are known as “snapshots,” or snippets of HTML that are generated by your custom component. Jest will test the HTML generated by your component against the HTML in the snapshot to determine whether your test passed or not.

Scripts that use webpack

The dev and build:dist scripts both use webpack to process both the TypeScript source code files and the *.sass files that you write. Webpack is an extremely powerful and complex package that coordinates all of the steps necessary to convert your source files into production-ready code, including (but not limited to):

  • Handling CSS-related tasks like:
    • finding style files that have been included via require() or import statements from JavaScript
    • extracting style rules from .vue files
    • converting SASS/SCSS into regular CSS
    • adding prefixes to various style rules to make sure that CSS will be applied consistently across various browsers
    • combining all of the CSS files into one file to reduce the number of downloads on the client side
    • minifying and zipping these files to reduce their size
    • renaming the combined, minified, zipped files appropriately
  • Copying static assets (images, fonts, etc.) into the appropriate folders for distribution
  • Handling TypeScript/JavaScript-related tasks like:
    • extracting TypeScript/JavaScript from .vue files
    • transpiling TypeScript into JavaScript
    • importing and including code from 3rd party libraries
    • “tree shaking” i.e. only including the minimum amount of 3rd party dependencies necessary to make your package work
    • converting JavaScript into build targets (e.g. ES5, ES6, etc.) suitable for the specified contexts
    • combining JavaScript files into one file to reduce downloads
    • adding in appropriate polyfills (e.g. using core-js) to ensure consistency and compatibility across different web browsers
    • minifying, obfuscating, and zipping the combined JavaScript files to reduce file size
    • renaming the files appropriately

In addition to all of these things, webpack has a built-in web server that can be spun up for use during development. It can also watch your source files for changes, compiles, build and “hot swap” them in real time so that the impacts of changes to your code can be observed in a browser in real time.

It’s important to note that webpack does not actually do all of these things by itself. Rather, almost all of the above functionality is actually effected by plugins and 3rd party libraries (like tsc, and Babel), each of which has to be configured to do it’s particular task. Webpack’s main role is in coordinating all of this activity. Even coordination can be supported by 3rd party code–in our project we use a package called happypack to make all of the above tasks run in parallel threads, which speeds up build times and makes the whole development effort much more pleasant and efficient.

With a package as complex as the one we’re building, you end up with multiple nested configuration layers, and it is enormously confusing to try to understand and keep track of them all. The next three sections will try to explain how all of the pieces fit together.

dev

The dev script process

The dev script runs a local dev server that is designed to be used by the extension/package developer during development. When you run the script, it:

  1. fires up the webpack dev server,
  2. transpiles the TS code of your extension,
  3. combines it with the JS in dev/index.js and dev/App.vue,
  4. runs it through the Babel processor and pulls in the core-js polyfill,
  5. gets your SASS code from your extension,
  6. processes it and combines it with any styles in dev/App.vue,
  7. runs it through the postcss processor, and
  8. spits out two files: dev/dev/main.js and dev/dev/main.css

This is basically the same thing that happens when you run npm run serve from the root of a Vue project. The happypack package speeds up this process by making all of the above processes happen in parallel, and the resulting files are updated on the fly every time you make changes, which is known as HMR or “hot module replacement.” All of the config files that are involved in the process are noted in the image above.

To see the result in your browser, simply open it to the page that is indicated on the console when webpack dev server starts. It’s important to note that the CSS and JS files produced by this process do not get saved in your project and are destroyed when you shut down the server. As you work, you’ll get helpful debugging information in the terminal, as well as in the browser’s developer console.

build:dist

The build:dist script process

The build:dist script is what packages the component for use directly in web browsers. All of the JavaScript is bundled into a single JS file, and all of the styles are bundled into a single CSS file. Minified and mapped versions of both of these files are also created. The way I set it up, the [name] given to these files is the name of the package as listed in the name property of package.json. The goal here is to get the files as small as possible and to make it so that people who want to use your component can do so by importing a single JS file and a single CSS file. You can see a demo of this in the CodePen playground I created for my component.

Other Scripts

In addition to the scripts we’ve described so far there are a few others. Some of them are just shortcuts to run other scripts or combinations of other scripts. The build script runs build:lib and build:dist and is designed to be run after any changes have been made, and before a new version of the component is to be published to the NPM repository. The various test:* scripts use slightly different configurations for running tests in different operating systems. The lint:* scripts use tslint and eslint to check the style and syntax of the code.

The release script bears special mention. I decided to use the Semantic Release package to manage releases of my component and enforce best practices in that area. To be honest, I’m still pretty new to this package, so still am not fully comfortable with its workflow, but it forces me to stick to the best practices of semantic versioning. Up until this point, I had been a bit haphazard in my approach to assigning version numbers, but I was persuaded by the SR docs that I really shouldn’t be that way. If you decide to build and release a package, I very much encourage you to use SR.

Conclusion

Honestly, it took me well over a week to really understand how all of the puzzle pieces I’ve described in this post fit together. That all had to happen before I was actually able to do any coding! I’m absolutely sure that there’s still room for more improvement. As just a guy working by himself in his spare time, it’s a daunting task to build and release a high-quality, broadly compatible extension of Vuetify and do it in TypeScript with the same care and attention to detail as John Leider and the Vuetify team have done.

I hope to make this easier for more people to do. In the next installment of this series, I’m going to introduce my Vuetify extension template and walk you through the steps of getting it set up, and starting writing your own code. I hope you’ll follow it and make some amazing stuff for all of us to use!

]]>
https://morphatic.com/2019/09/05/building-packageable-components-to-extend-vuetify-with-typescript-part-2/feed/ 0
Building Packageable Components to Extend Vuetify with TypeScript–Part 1 https://morphatic.com/2019/09/04/building-packageable-components-to-extend-vuetify-with-typescript-part-1/?utm_source=rss&utm_medium=rss&utm_campaign=building-packageable-components-to-extend-vuetify-with-typescript-part-1 https://morphatic.com/2019/09/04/building-packageable-components-to-extend-vuetify-with-typescript-part-1/#comments Thu, 05 Sep 2019 03:06:49 +0000 https://morphatic.com/?p=443

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.

A VStripeCard component side-by-side with a Vuetify VTextField component
A VStripeCard component side-by-side with a Vuetify VTextField component

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.

Build Targets

What did I actually build? Vue and Vuetify components are designed to be used in a couple of different common scenarios:

  1. Direct inclusion in web pages via <script> tags
  2. Being imported and used in SPAs (single-page apps) and PWAs (progressive web apps) built (usually) with Webpack
  3. Incorporated into projects that use SSR (server-side rendering), most notably Nuxt.js

Vue and Vuetify are flexible enough to be used with well-established, and nearly universally-supported variants of JavaScript like ES5, as well as incorporated into projects built with ESNext (bleeding-edge JavaScript) and TypeScript. My goal was for my extension to be usable in all of the same environments where Vue and Vuetify can be used. I think I was mostly successful except for SSR. Because of the way that Stripe Elements are instantiated on a page, I’m not sure my solution is compatible with SSR environments. (I’m not sure though because I haven’t tried it.)

Project Structure

This is a screenshot of the project structure that contains:

  • .vscode: VSCode editor settings
  • assets: the logo I used on my README
  • 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
  • es5: ES5-compatible modules importable into most any modular JavaScript project
  • 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.

Some Thoughts

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.

Up Next…

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.

]]>
https://morphatic.com/2019/09/04/building-packageable-components-to-extend-vuetify-with-typescript-part-1/feed/ 1