Skip to content

Build a typescript component library with Vite

Published on
Back to home page

Why Vite

Vite ships with a pre-configured build command that has many performance optimizations out of the box. It uses Rollup under the hood and provides an abstraction over the default rollup configuration. This makes it way easier to set up the build step without knowing all the internals of rollup.

Components

For the sake of the example, I'm going to create a simple button component, but the build step remains the same for any number of components. In this example, I'm using the <script setup> syntax.

Component code:

vue
<script setup lang="ts">
import { reactive } from 'vue';

interface Props {
  primary?: boolean;
}

const props = withDefaults(defineProps<Props>(), {
  primary: false,
});

const { primary } = reactive(props);
</script>

<template>
  <button class="btn" :class="{ primary }">
    <slot />
  </button>
</template>

<style scoped>
.btn {
  padding: 0.5rem 1rem;
}
.btn.primary {
  background: hsl(239, 100%, 27%);
  color: #fff;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

Next, I'm going to create the entry file for the library where all the components will be exported.

ts
// src/index.ts
export { default as MyButton } from './MyButton';
1
2

Vite config

Vite is a fast new build tool that is intended for modern web projects. It uses native ES modules and provides a blazing-fast dev server and hot module replacement. Learn more about it on the official website.

Vite is framework agnostic which means you can use it with most frontend frameworks, and the build config is pretty much the same. There is a section on the official website which describes different build modes, our interest is in the library mode build.

So we need to add the following to our vite.config.ts (or .js).

js
// vite.config.ts
const path = require('path');
const { defineConfig } = require('vite');

module.exports = defineConfig({
  build: {
    lib: {
      entry: path.resolve(__dirname, 'src/index.ts'),
      name: 'MyComponentLib',
      fileName: (format) => `my-component-lib.${format}.js`,
    },
    rollupOptions: {
      // make sure to externalize deps that shouldn't be bundled
      // into your library
      external: ['vue'],
      output: {
        // Provide global variables to use in the UMD build
        // for externalized deps
        globals: {
          vue: 'Vue',
        },
      },
    },
  },
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

If you now run npm run build you should get the output in the dist folder.

If you're getting an error regarding __dirname or path you need to install node type declarations

bash
npm i -D @types/node
1

Package.json config

Before testing our build output we need to configure the package.json to point at correct built files. We are defining the entry point to the library. You can learn more about what each option does by hovering the properties in VS Code.

json
// "my-component-lib" should match the "name" field in your package.json
{
  "files": ["dist"],
  "main": "./dist/my-component-lib.umd.js",
  "module": "./dist/my-component-lib.es.js",
  "exports": {
    ".": {
      "import": "./dist/my-component-lib.es.js",
      "require": "./dist/my-component-lib.umd.js"
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

Test build output

To test the library locally we can zip the dist file, before running the below change the name field in your package.json to your preferred library name.

Run the following:

bash
npm pack
1

This command provides us with a zipped file that we can map to the library dependency in the package.json. After running this add the following to the dependencies field:

json
 "dependencies": {
 "my-component-lib": "my-component-lib-0.0.0.tgz"
  },
1
2
3

After importing the library it should now work.

If you run into dependency errors try deleting the zipped file and packing it again, or deleting the package.lock.json and node_modules and running npm install again.

vue
<script setup lang="ts">
import { MyButton } from 'my-component-lib';
import '/node_modules/my-component-lib/dist/style.css';
</script>
1
2
3
4

However, you'll notice we get an error regarding missing declaration files, we will solve this in the next step.

Typescript declarations

To solve the typescript declarations error, we need to generate type declaration files for the components. We will do this using the vue-tsc package.

Make sure to update vue-tsc to a newer version since the create-vue CLI defaults to an older version.

You need to add the following to tsconfig.json:

json
{
  "outDir": "dist",
  "declaration": true
}
1
2
3
4

Change the build script to the following:

json
{
  "build": "vite build && vue-tsc --emitDeclarationOnly && mv dist/src dist/types"
}
1
2
3

Lastly, set the path for the type declarations inside package.json

json
{
  "types": "./dist/types/index.d.ts"
}
1
2
3

The last command in the build script moves the types to a types directory, by default they would be in a src directory since that is our target inside the tsconfig.

If you now repeat the build and pack process, the type error should not be there.

If it's still there try deleting your node_modules and package.lock.json and installing the packages again.

Publish to npm

Everything is already set up for publishing, you can just authenticate with npm and publish.

bash
npm login # authenticate

npm publish # publish
1
2
3

Conclusion

Vite makes it incredibly easy to build a component library with a very slim config. Coupled with the amazing development experience it provides I can conclude it is the next big thing in frontend development.