Creating a Vue.js Component Library

August 21st 2020 Vue.js NuxtJS TypeScript

Although Vue CLI has built-in support for building component libraries, there's still some work in creating one, especially if you want it to be TypeScript and SSR compatible.

To specify what gets included in the library, an entry file should be created which exports each component like that:

export { default as Button } from "./components/Button.vue";

The file is then referenced from the build script:

{
  "scripts": {
    "build-lib": "vue-cli-service build --target lib src/index.ts"
  }
}

The command puts the generated files in the dist folder. To include them in the NPM package, they must be declared in the package.json file:

{
  "files": ["dist/*"],
  "main": "./dist/vue-component.umd.js"
}

For testing purposes, you can create a local NPM package with an extended build script:

{
  "scripts": {
    "build-lib": "vue-cli-service build --target lib src/index.ts && npm pack && shx mv ./vue-component-0.1.0.tgz .."
  }
}

I'm using shx for moving the generated package file.

The package can then be installed in another Vue.js project:

npm i ../vue-component-0.1.0.tgz

That's enough to import the components in a .vue file:

import { Button } from "vue-component";

In a JavaScript file, this will just work. But the TypeScript compiler will complain:

Could not find a declaration file for module 'vue-component'.

This can be fixed by creating a placeholder type declaration file vue-component/index.d.ts somewhere in the project, e.g. in the types folder:

declare module "vue-component";

The file must be referenced in tsconfig.json, either by adding the root folder to the typeRoots setting:

{
  "typeRoots": ["./node_modules/@types", "types"]
}

Or by adding the folder itself to the types setting:

{
  "types": ["@types/node", "@nuxt/types", "types/vue-component"]
}

However, this will only prevent the error. It won't provide the types.

A better solution is to include the type definitions in the component library. This can be done in a types/index.d.ts file:

import { VueConstructor } from "vue";

export const Button: VueConstructor;

It's enough to just declare them as components, as Vue.js can't take advantage of more type information. Other Vue.js component libraries do that as well.

The file must also be declared in package.json:

{
  "files": ["dist/*", "types/*"],
  "types": "./types/index.d.ts"
}

With this change in the component library, the TypeScript error will go away even without the placeholder types in the application code.

The component library includes the styles in a separate CSS file. This means that it must be included globally in the consuming project. In NuxtJS, this can be done in nuxt.config.js:

export default {
  // ...
  css: ["node_modules/vue-component/dist/vue-component.css"],
  // ...
};

Component libraries typically solve this with a module.

Vue CLI allows you to inline the styles in the generated JavaScript file by including a vue.config.js file in the component project:

module.exports = {
  css: {
    extract: false,
  },
};

However, this approach doesn't work with server-side rendering (SSR). The project will still build, but will fail at runtime:

document is not defined

You can find a working sample in my repository on GitHub. It consists of a component library and a NuxtJS application using it.

Even when using Vue CLI to create a component library, you still need to manually edit the package.json file to configure and generate the NPM package. To make the components work with TypeScript, you should also include the type definitions in the package.

Get notified when a new blog post is published (usually every Friday):

Copyright
Creative Commons License