Top of page

Blog

Turning Vue components into reusable npm packages

A guide to publishing your Vue components

When working on multiple projects with Vue.js as your framework of choice, you often find yourself writing the same components over and over again. Is there a better way?

Workflow using Vue, Vuepress, GitHub and npm

A way to solve this is to separate them from your projects and publish them to npm. This way you can make your components reusable, open source and available across teams. Here’s how we automated our process to bundle, test, document and publish Vue components.

Do you want to become a Vue master? During our two day hands-on workshop we’ll teach you everything you need to know to build large performant web apps with Vue.

Join our Vue masterclass

Bundling a Vue component

One way to share your component is by directly publishing the .vue file to npm. This will work in cases where you can import .vue files in your project, but what if someone wanted to use it directly in the browser? That will not work. To prepare for these types of situations you should bundle them into .js files.

As a base for the project we’ll need a package.json file (run npm init to create one) and a src folder.  The structure of it will roughly look like this:

src/
  index.js
  your-vue-component.vue
  your-vue-component.test.js
package.json
rollup.config.js

For unit testing we’re using vue-test-utils, the official test library for Vue components, combined with Jest. Tests are located in .test.js files and are used to make sure components work the way they are supposed to work. It improves maintainability, since it will indicate if you broke something when making changes to your code.

A commonly used tool for bundling Vue components is Rollup, a JavaScript module bundler. It compiles the Vue components into different formats to use in ES, common JS or directly in the browser. The base configuration for Rollup in this situation looks like this:

// import necessary dependencies
import vue from 'rollup-plugin-vue'
import buble from 'rollup-plugin-buble'
import commonjs from 'rollup-plugin-commonjs'

export default {
  input: 'src/index.js', // entry file for our components
  plugins: {
    preVue: [
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      commonjs(), // add support for CommonJS modules
    ],
    vue: {
      css: true, // include CSS in the output
      template: {
        isProduction: true,
      },
    },
    postVue: [
      buble(), // use buble to transpile ES2015
    ],
  },
}

This base configuration can now be extended for the module types we want to export. For a full example, see the rollup.config.js of our vue-lazy-load component.

In our package.json we can specify where we want our transpiled bundles to end up:

{
  "name": "your-component-name",
  "main": "dist/your-component-name.ssr.js",
  "module": "dist/your-component-name.esm.js",
  "unpkg": "dist/your-component-name.min.js",
  "style": "dist/your-component-name.css",
  // ...
}

All the different fields serve their purpose:

  • Modern bundlers will use the module build
  • Legacy bundlers and Node.js will use the main build
  • The unpkg bundle can be used directly in the browser via UNPKG

You’ll also notice a style property. Typically, you’ll want to separate your styles from the JS bundle. This gives freedom for users to apply their styles or use their loaders/plugins of choice to process them.

This is an example of how you would import the component and styles in your application:

import YourComponentName from 'your-component-name'
import 'your-component-name/dist/your-component-name.css'

Adding documentation with VuePress

Writing your documentation in your readme.md is a convenient way to document components. But what if you want to create an interactive demo or add a nice theme to it? Markdown processing tools like VuePress or Docz are a perfect option for that. These tools convert markdown files into a static site to host as your documentation. In our case, we’re using VuePress, since it suits the Vue ecosystem.

Component documented with VuePress

To create our documentation we need a folder to build it from. The structure for this folder should look something like this:

docs/
  readme.md
  .vuepress/
    config.js

The readme.md file is used for generating the HTML. There are plugins available for VuePress to extend its functionality. In our case we’re using plugin-register-components combined with vuepress-plugin-demo-code to be able to demonstrate our components:

const pkg = require('../../package.json')

module.exports = {
  title: `${pkg.name} (${pkg.version})`,
  description: pkg.description,
  dest: `www`,
  plugins: [
    ['@vuepress/register-components', {
        componentsDir: `${__dirname}/../../src/`
    }],
    'demo-code'
  ]
}

Documenting Vue props, events and slots

To give the user information about our component, we should document props, events and slots. Documenting those is not fun to do by hand and seems like something we can automate. A tool named @vuedoc/md can do just this. It scans through the Vue component looking for props, slots, and events. It converts these into a markdown structure and will append it to a specified markdown file.

Output of generated docs

Below are the scripts to generate our documentation. First, we extract the props, events, and slots and append to our markdown file. After that, we generate our static site using VuePress.

"scripts": {
  // ...
  "docs": "run-s docs:*",
  "docs:api": "vuedoc.md src/your-component-name.vue --section 'API' --output docs/v1/readme.md --ignore-data --ignore-methods --ignore-computed",
  "docs:vuepress": "vuepress build docs",
  // ...
},

The generated static site can now be hosted wherever you want, for example on Netlify or GitHub pages.

Publishing to npm

The bundles created by Rollup are ready to be published to npm. After creating a build, use npm publish.

To make things easier, we can automate this by using a GitHub action. This will automatically create a new version of your npm package when a new tag is pushed. We do this by adding a .github/workflows/npm.yml file:

on:
  push:
    tags: v*.*.*
jobs:
  npm:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@master
    - uses: actions/setup-node@v1
      with:
        node-version: '10.x'
        registry-url: 'https://registry.npmjs.org'
    - name: Build package
      run: npm run build
    - name: Publish tag to npm
      if: contains(github.ref, 'tags')
      run: npm publish
      env:
        NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}

Bonus: publish to GitHub

It’s also possible to publish your package to GitHub packages registry

- uses: actions/setup-node@v1
  with:
    registry-url: 'https://npm.pkg.github.com'

- name: Publish tag to GPR
  run: npm publish
  env:
    NODE_AUTH_TOKEN: ${{ secrets.GITHUB_AUTH_TOKEN }}k
Package on GitHub

You now have an npm package with (partly) automated documentation and publishing to npm on every new tag! To see an example of how this is implemented in real-life, take a look at the repo of @voorhoede/vue-lazy-load. We have created a couple of components this way, those can be found on our open-source page. If you publish a Vue component yourself, please do share it with us!

Resources

← All blog posts

Also in love with the web?

For us, that’s about technology and user experience. Fast, available for all, enjoyable to use. And fun to build. This is how our team bands together, adhering to the same values, to make sure we achieve a solid result for clients both large and small. Does that fit you?

Join our team