DEV Community

Peshang Hiwa
Peshang Hiwa

Posted on

Publishing a Vue 3 - Typescript Component Library to GitHub Packages

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

 
 

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.

npm create vite@latest
Enter fullscreen mode Exit fullscreen mode

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)

cd github-packages-ui-library
npm install
Enter fullscreen mode Exit fullscreen mode

 
If you open the project now you'll get to see a fully scaffolded project that looks like this:

Vue.js 3 project directory

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

 

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

<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>

Enter fullscreen mode Exit fullscreen mode

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.

// /src/index.ts 
export { default as BaseButton } from "./components/BaseButton.vue";
Enter fullscreen mode Exit fullscreen mode

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

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

<template>
  <BaseButton> Click me </BaseButton>
</template>
Enter fullscreen mode Exit fullscreen mode

And now our library is ready to be configured as a package in which we'll do in the next step

 

Configuring Vite to package the library for publishing

Next, we'll update the vite.config.ts file to implement the packaging process as follows:

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",
        },
      },
    },
  },
});


Enter fullscreen mode Exit fullscreen mode

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

npm install -D @types/node
Enter fullscreen mode Exit fullscreen mode

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

npm install -D vite-plugin-css-injected-by-js
Enter fullscreen mode Exit fullscreen mode

 

Next we need to update the tsconfig.json file as follows:

{
  "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"]
}

Enter fullscreen mode Exit fullscreen mode

 

And tsconfig.node.json as follows:

{
  "compilerOptions": {
    "composite": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "allowSyntheticDefaultImports": true
  },
  "include": ["vite.config.ts"]
}
Enter fullscreen mode Exit fullscreen mode

 

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

{
  "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"
  }
}

Enter fullscreen mode Exit fullscreen mode

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:

@peshanghiwa:registry=https://npm.pkg.github.com
Enter fullscreen mode Exit fullscreen mode

and replcae peshanghiwa with your github username

 

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

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}}

Enter fullscreen mode Exit fullscreen mode

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.

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
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

 

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

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:

@peshanghiwa:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=YOUR_GENERATED_GITHUB_TOKEN_HERE
Enter fullscreen mode Exit fullscreen mode

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:

npm install @peshanghiwa/github-packages-ui-library
Enter fullscreen mode Exit fullscreen mode

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

Usage

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

Top comments (6)

Collapse
 
saeedpanahi profile image
Saeed Panahi

Excellent Article !!!
The entire day I have been looking for a way to inject CSS styles into the module but nothing was found and suddenly I saw this great article and used "vite-plugin-css-injected-by-js" inside my app and it worked!

Collapse
 
rihan profile image
Rihan

This was a great read. Thanks to this article, I also learned something extra too - withDefaults in Vue!

Collapse
 
mohammadnazm profile image
Mohammad Nazm

Great Article 👍

Collapse
 
ali_a_koye profile image
Ali Amjad

great article

Collapse
 
justalittleheat profile image
Matt • Edited

This is a nice run through of setting up a library, however it would be helpful if you provided more detailed insights on the configuration for package JSON like the build scripts, ts.configs and the release-package.yml.

The setup feels a little bit too much just copy this code. Links to resources on how you figured out all the configs would be nice.

Collapse
 
benfk profile image
Ben Kissi

How do you handle conflicting vue packages