Fragmented Thought

Creating a Vue component library



Lance Gliser

Heads up! This content is more than six months old. Take some time to verify everything still works as expected.

It's fairly common to see React components in NPM modules. There's libraries out there you can import and play with and the support is great.

Vue, is not that world. There's a few issues along the way, but there are examples of great projects that already provide a method for doing it. See: Bootstrap Vue or Vuetify for examples. In fact, working with them is what got me started wanting to produce a library for my organization. Having travelled in their well worn shoes, I now have a great deal of respect for those projects. You should use them, love them, and contribute to them. But, like myself you might need your own version. So, here's the outcome of a couple weeks work, and a glass to more authors doing the same. Let's get the Vue ecosystem a little more friendly.

Primary components:

  • Storybook: Used for providing visual realtime feedback on the designed components and their interplay
  • Jest: For testing anything you might care to
  • Rollup: For packaging our work into consumable distributions
  • Bootstrap Vue: Because I didn't want to hand make all the styles, you could swap this out or remove it.

The full working example can be found on my <a href="">github repository</a>. This post will include only gists and a breakdown of why some patterns were chosen.

Building - Rollup

I owe another thank you to the Vue team. Their work on a Vue Rollup plugin made the Rollup portion of this a breeze.

export default { input: "src/index.ts", output: [ { file: pkg.module, format: "es", exports: "named", sourcemap: false, }, ], plugins: [ image(), // This will allow you to use svg, img, etc in component imports typescript({ rollupCommonJSResolveHack: true, clean: true, }), vue(), // That's all there is to it. Wonderful magic happens ], external, };

If this were React, the exports might have just worked. But, Vue needs a little more meta data exposed for IDE's to enable auto-completion features. JetBrains (Webstorm, et. all) require web-types.json and VS Code requires vetur-tags.json and vetur-attributes.json. This is where Bootstrap Vue and Vuetify have so generously provided a pair of solutions. I've used Bootstrap Vue's build method. You'll find the files at /build and /src/utils. We just use the Node esm package to injest the /dist after it's built, and loop over the package.json files we made for each plugin. The package.json are used to signal not only that we have components, but their slots available, and property descriptions if the generic types don't already cover that.

{ "name": "@my-organization/placeholder", "version": "1.0.0", "meta": { "title": "Placeholder", "description": "", "components": [ { "component": "Shimmer", "slots": [ { "name": "default", "description": "Optional. Card contents of the main text." }, { "name": "header-image", "description": "Optional. Header image." } ] } ] } }

Developing - Storybook

Storybook Vue does take a little time to customize. It works right out of the box, until you run into a host of very specific concerns. To that end, I'll document some of the choices I made and the configs that get it running.

  • preview.ts - This file is used to wrap the stories, so you can ensure that all the globals you need are loaded into Vue.
  • manager.ts - This file is used to manage Storybook itself. Mostly, I just wanted a dark theme.
  • decorators.ts - This is a file of helpers. In trying to create an app story in itself, I found you of course couldn't wrap the app inside itself. Sometimes you need the fine grain control offered by this, if not, use the default.
  • preview-body.html - This file allows you to inject some base html and css. In my case the goal was for my <App /> to take 100% VH and VW, so I had to remove storybook and the dom's internal elements.
import { addons } from "@storybook/addons"; import { themes } from "@storybook/theming"; addons.setConfig({ theme: themes.dark, });

Testing - Jest

Jest requires a bit more configuration than default. It's simple once it's in front of you, but make sure you have all the right transformers to handle whatever your components might import:

module.exports = { moduleFileExtensions: ["js", "ts", "vue", "json"], transform: { "^.+\\.(js|jsx)$": "babel-jest", "^.+\\.ts$": "ts-jest", ".*\\.vue$": "vue-jest", ".+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$": "jest-transform-stub", }, collectCoverage: false, // See package.json script collectCoverageFrom: [ "<rootDir>/src/**/*.{ts,js,vue}", "!<rootDir>/src/**.stories.{ts.js}", "!<rootDir>/node_modules/", ], // transformIgnorePatterns: [], coverageReporters: ["text-summary"], };