Publishing a Vue 3 - Typescript Component Library to GitHub Packages
Tags: vue, github, typescript, vite
Published: 2023-01-29 20:09:48

When it comes to working in a team or an organization it often involves reusing a collection of repositories or code-bases(components) in multiple projects again and again.
To make it easier for every developer out there to access and utilize these components, it's usually recommended to publish these components to the NPM registry as public open source projects that can be reused.
**However, if the components are private** and only used within the scope of the organization, the best option is to use Github packages.

In this article, I'll guide you through the process of publishing a Vue 3 component library built with typescript to Github Packages, which includes the following steps:

- Creating a Vue.js 3 project using Vite
- Building a component of the library
- Configuring Vite to package the library for publishing
- Setting up Github Actions for automated publishing to Github Packages
- Testing the component as a dependency in another project

&nbsp;
&nbsp;

## Creating a Vue.js 3 project using Vite
To create a Vue 3 project using Vite, you need to initialize a Vite project in your desired directory by running this script in your command line.

``` bash
npm create vite@latest
```
Make sure to complete the next couple steps to generate your project.
I'll name my project ``github-packages-ui-library`` and select Vue and typescript as the technologies.

Then go to the project directory and install the project dependencies using NPM (or your preferred package manager)
``` bash
cd github-packages-ui-library
npm install
```
&nbsp;
If you open the project now you'll get to see a fully scaffolded project that looks like this:

![Vue.js 3 project directory](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gt9ee598h3gd6vmycjsb.png)

Now clean up the project by removing all the unnecessary files and extra codes like style files, prebuilt HelloWorld.vue component and etc...

&nbsp;

## Building a component of the library
Next I am going to build one of the components of the library as a test component and export it, whereas you can built as many components as you want.

The component needs to be in the ``/src/components`` directory and I am going to name it ``BaseButton.vue`` as shown below

``` HTML
<script setup lang="ts">
import { computed } from "vue";
type BaseButtonProps = {
  size?: number;
  color?: string;
};

const props = withDefaults(defineProps<BaseButtonProps>(), {
  size: 16,
  color: "skyblue",
});

const fontSize = computed(() => `${props.size}px`);
</script>

<template>
  <button id="baseButton">
    <slot />
  </button>
</template>

<style>
#baseButton {
  padding: 1rem 2rem;
  cursor: pointer;
  border: none;
  background-color: v-bind(color);
  font-size: v-bind(fontSize);
}
</style>

```
The component is a simple button that accepts two props, both fully typed with TypeScript: ``color``, which must be a string, and ``size``, which must be a number. Additionally, it has a default slot.

Next, we'll create an ``index.ts`` file in the ``/src`` directory. 
This file will act as a central location to export all the library components from, as shown below.

``` TS
// /src/index.ts 
export { default as BaseButton } from "./components/BaseButton.vue";
```

Now we can import the components from ``index.ts`` and use them like this example in ``/src/App.vue``

``` HTML
<script setup lang="ts">
import { BaseButton } from "./index";
</script>

<template>
  <BaseButton> Click me </BaseButton>
</template>
```
And now our library is ready to be configured as a package in which we'll do in the next step

&nbsp;

## Configuring Vite to package the library for publishing
Next, we'll update the vite.config.ts file to implement the packaging process as follows:

``` TS
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";

import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js";

export default defineConfig({
  plugins: [vue(), cssInjectedByJsPlugin()],
  resolve: {
    alias: {
      "@/": new URL("./src/", import.meta.url).pathname,
    },
  },

  build: {
    cssCodeSplit: true,
    target: "esnext",
    lib: {
      entry: path.resolve(__dirname, "src/index.ts"),
      name: "GithubPackagesUiLibrary",
      fileName: (format) => `github-packages-ui-library.${format}.js`,
    },

    rollupOptions: {
      external: ["vue"],
      output: {
        globals: {
          vue: "Vue",
        },
      },
    },
  },
});


```
Notice that both name(written in PascalCase) and fileName(written in kebab-case) are the actual name of our project, so you should replace it with your project name too

If you encountered ``Cannot find path`` or ``Cannot find __diranme`` error you will need to install ``@types/node`` as dev dependency to solve the issue

``` BASH
npm install -D @types/node
```

and you also need to install ``vite-plugin-css-injected-by-js`` as dev dependency in order to include all the CSS codes of the project in to the build

``` BASH
npm install -D vite-plugin-css-injected-by-js
```

&nbsp;

Next we need to update the ``tsconfig.json`` file as follows:
``` JSON
{
  "compilerOptions": {
    "baseUrl": "./",
    "target": "esnext",
    "useDefineForClassFields": true,
    "module": "esnext",
    "moduleResolution": "node",
    "isolatedModules": true,
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "paths": {
      "@/*": ["src/*"]
    },
    "lib": ["esnext", "dom", "dom.iterable", "scripthost"],
    "skipLibCheck": true,
    "outDir": "dist",
    "declaration": true
  },
  "include": ["vite.config.*", "env.d.ts", "src/**/*", "src/**/*.vue"]
}

```
&nbsp;

And ``tsconfig.node.json`` as follows:
```JSON
{
  "compilerOptions": {
    "composite": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "allowSyntheticDefaultImports": true
  },
  "include": ["vite.config.ts"]
}
```


&nbsp;

Next We need to update our ``package.json`` file to support our packaging as follows:

``` JSON
{
  "files": [
    "dist"
  ],
  "main": "./dist/github-packages-ui-library.umd.js",
  "module": "./dist/github-packages-ui-library.es.js",
  "exports": {
    ".": {
      "import": "./dist/github-packages-ui-library.es.js",
      "require": "./dist/github-packages-ui-library.umd.js"
    }
  },
  "types": "./dist/types/index.d.ts",
  "publishConfig": {
    "registry": "https://npm.pkg.github.com/@peshanghiwa"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/peshanghiwa/github-packages-ui-library"
  },
  "scripts": {
    "dev": "vite",
    "build": "vite build && vue-tsc --emitDeclarationOnly && mv dist/src dist/types",
    "preserve": "vite build",
    "serve": "vite preview --port 5050",
    "typecheck": "vue-tsc --noEmit",
    "preview": "vite preview",
    "test": "exit 0"
  },
  "name": "@peshanghiwa/github-packages-ui-library",
  "version": "0.0.0",
  "type": "module",
  "dependencies": {
    "vue": "^3.2.45"
  },
  "devDependencies": {
    "@types/node": "^18.11.18",
    "@vitejs/plugin-vue": "^4.0.0",
    "typescript": "^4.9.3",
    "vite": "^4.0.0",
    "vue-tsc": "^1.0.11"
  }
}

```

You must replace all the ``github-packages-ui-library`` with the name of your library and ``peshanghiwa`` with your own github username in the above file (You must include the @ character too in the username: ``@YOURUSERNAME``)

and lastly create a ``.npmrc`` in the root directory as follows:

``` Properties
@peshanghiwa:registry=https://npm.pkg.github.com
```
and replcae ``peshanghiwa`` with your github username 

&nbsp;

## Setting up Github Actions for automated publishing to Github Packages

Next we need to create a github actions to generate and automate the building and publishing of our package into github packages registry

Create a ``.github/workflows/release-package.yml`` in the root directory as below

``` YML
name: Github Packages UI Library Actions

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 16
      - run: npm ci
      - run: npm test

  publish-gpr:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      packages: write
      contents: read
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 16
          registry-url: https://npm.pkg.github.com/
      - run: npm ci
      - run: npm run build
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}

```

This is a sample of Github Actions workflow that will run two jobs for us: ``build`` and ``publish-gpr`` to build and publish the package to Github Packages Registry. 

The action will trigger each time code is pushed to the ``main`` branch, but you can modify this behavior by referring to [Github Actions Workflow Syntax.](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions)

After pushing the final changes to the ``main`` branch, the Github Actions workflow will automatically start. You can monitor the workflow's progress in the Actions section of the Github page.


![Github actions page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ev5ryqln60je7uf86e3x.png)
As shown in the image above, both jobs ran successfully and the library is now registered in Github Packages. To verify, you can check the main repository page and see that a "Packages" section has been added to the sidebar, as shown in the below image.


![Github repository page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/om289fabn4vup9fotfq3.png)

&nbsp;

## Testing the component as a dependency in another project

Our package is now on Github Packages and ready to be used privately in another project or repository, accessible only by us. To use it, we need to generate a Github token for this purpose. 
Go to the Developer Settings and generate a token with only the ``read:packages`` permission, as shown in the following image.

![Github tokens](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vrcjronoji6drehfvg90.png)


Now create another vue.js project so that we can install our package at.
Next create a ``.npmrc`` file in the root directory of the newly created vue.js project and paste the follwoing code:

``` Properties 
@peshanghiwa:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=YOUR_GENERATED_GITHUB_TOKEN_HERE
```

change the ``peshanghiwa`` to your github username and paste your secret github token too

Next you can install your package using npm in the terminal as below:

``` BASH
npm install @peshanghiwa/github-packages-ui-library
```

And you can now import and use the package just like any other dependency in your project as shown below:


![Usage](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/irie589d87b6bjcb4c4a.png)

The build includes complete typescript support and the props are properly typed.

#### And you have successfully published your private package in to github packages registry.

#### The above repository is public and available in [this link](https://github.com/peshanghiwa/github-packages-ui-library)