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
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
If you open the project now you'll get to see a fully scaffolded project that looks like this:
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>
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";
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>
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",
},
},
},
},
});
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
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
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"]
}
And tsconfig.node.json
as follows:
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
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"
}
}
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
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}}
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.
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.
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.
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
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
And you can now import and use the package just like any other dependency in your project as shown below:
The build includes complete typescript support and the props are properly typed.
Top comments (6)
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!
This was a great read. Thanks to this article, I also learned something extra too - withDefaults in Vue!
Great Article 👍
great article
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.
How do you handle conflicting vue packages