Creating a Vue component library
Published:
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="https://github.com/lancegliser/vue-display-components-library-template">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"], };