Publishing a NPM Typescript package to Gitlab Package registry

A step-by-step guide to publish a npm package to Gitlab

Bharat Tiwari
15 min readJun 11, 2021
Photo by Pixabay from Pexels

Introduction

In this post, I will provide a step-by-step guide on how to publish a private NPM package to your Gitlab project’s Package Registry, that can be then reused/imported in your other Gitlab projects installing it as a npm dependency.

Using Typescript

When developing NPM packages, using Typescript and providing type definitions of the public modules and interfaces of your package is a good practice. With type-definitions, the users of the npm package get to see the available modules properties, methods easily in code editor’s intellisense and also can use those type-definitions in their code as needed.

We will also use tools like ESLint(@typescript-eslint) and Prettier for code quality.

What are we developing

For our example purpose, we will develop a very basic npm package library app-themes, to define two simple color scheme/themes - dark and light, that other apps can import to use.

Pre-requisites:

  • node and (npm or yarn) latest versions
  • Gitlab account
  • VSCode or your code editor of choice

Let’s get started.

🆃🅻;🅳🆁 This would be a bit long post as we perform below tasks:

▪️ Gitlab project setup

▪️ Development setup
- add & configure Typescript,
- ESLint /Prettier settings,
- Lint-staged & Husky

▪️ Code development

▪️ Prepare to publish the package - addgitlab-ci.yml, .npmrc

▪ ️Publishing the package

▪️ Using the published npm package in a test app

Gitlab Package Registry

Like NPM, Gitlab’s Package Registry allows you to register your package libraries, which can be used/imported as dependencies in other projects.

With Gitlab Package Registry, you can keep the package library as private (to be used by projects inside your Gitlab account) or can be shared as public.
It supports most of the common package managers like npm, Go, Maven, Ruby Gems etc.

Scoped package name

Gitlab requires that your package must be scoped, i.e. the package-name must be in the format @scope/package-name.
Gitlab allows two options to register a package - project-level or instance-level.

project-level: In Gitlab, a project usually corresponds to a single code repository. Gitlab also allows to register multiple packages within a single project.
This option could be useful when you have multiple package libraries that you want to register at one place in a single Gitlab project and import/use those from there in other projects of various other groups/subgroups of various departments of your organization.
When registering a package at project-level, you can use/append your own unique scope to your package name.

A package registered at project-level can be made public if needed.

instance-level: here you create a project for your package’s code repository within a Gitlab group/sub-group and register the package in that group/sub-group. The package should be named in the format @scope/package-name, where scope is the root namespace(root group name) of the project.
instance-level packages cannot be made public

(For more details, check Gitlab Endpoints for packages and Gitlab’s Package Naming conventions.)

In this post, we will create a private npm package at project-level in our Gitlab account.

Gitlab setup

We will create two Gitlab projects:

  1. common-package-library : This project would act as a common project at organization level, where we would register all the npm (or other) package libraries that would be common for the org. Any app in the organization can import and use the package libraries registered here.
  2. app-theme: This project would be for the code repository of app-themes npm package library’s code. To this project, we would also add a Gitlab CI pipeline to register this package in the above common-package-library

Let’s start:

Login to your Gitlab account and create a new project:

1. Creating 𝚌𝚘𝚖𝚖𝚘𝚗-𝚙𝚊𝚌𝚔𝚊𝚐𝚎-𝚕𝚒𝚋𝚛𝚊𝚛𝚢 project

The above project repository will remain empty as we are going to use this project only to register npm(or others) library packages.

📌 ⓃⓄⓉⒺ ① Note down the Project-ID of this project. We will need it later

2. Creating 𝚊𝚙𝚙-𝚝𝚑𝚎𝚖𝚎𝚜 project for code repository of the package library’s code:

The project we created above is intended to register all common packages that would be used in our org. It will have an empty code repository.

Additionally, we will need project(s) to store the code repositories of our package(s).
Let’s create a Gitlab group named org-packages, under which we will group the individual project(s) for each of the package’s source code repository.

Now, inside this org-packages group, create a project app-themes.

Note down the repository’s git URL 👆, we’ll need this to set up git for our package’s folder.

📌 ⓃⓄⓉⒺ ② Note the URL of app-themes projects's GIT repository:
https://gitlab.com/org-packages/app-themes.git

✔️ That’s it with Gitlab setup for now, we will come back to Gitlab later.
Let’s now move on with the development setup and coding part of our npm package.

Development setup

📂 Create a folder for the npm package

mkdir app-themes
cd app-themes

Initialize npm

Initialize the folder with npm.

npm init (or yarn init) 
(or npm/yarn init -y to init with defaults and change values later)

👆 Notice the scope @btiwari-gitlab provided for the package name.

Initialize git

Next, initialize Git for our project’s folder

git init
git remote add origin https://gitlab.com/org-packages/app-themes.git
echo "Apps Themes Package" >> README.md
echo "node_modules" >> .gitignore

👆For remote origin, use the Gitlab project’s repository URL that we noted at ⓃⓄⓉⒺ ② above.

Open the folder in your code editor. Edit .gitignore as below:

.DS_Store
# node modules
node_modules/
# Log files
npm-debug.log*
# Editor settings
.vscode

Stage, commit and push to git remote repository:

git add .
git commit -m "Initial commit"
git push -u origin master

The initial code should now be available in the Gitlab project app-themes.

Adding Typescript

As discussed before, we will use Typescript for developing our package.

Add TS and other related packages as dev dependencies

npm install typescript jest ts-jest @types/jest -D

ts-jest allows to write our jest test-cases in typescript.

Initialize Typescript configuration

Set up your Typescript config file (tsconfig.json) using the tsc command, or simply create the file manually within the project folder.

tsc --init

The command would create tsconfig.json file in the project folder

Edit tsconfig.json as below to have minimum configuration required:

Github Gist

👆 Line # 5: “declaration”: true This would tell typescript compiler to create and output a type definition file (index.d.ts) for the exported modules of the app.
(We want this so that the client apps importing our package can get the package’s exported modules, their methods and properties in code-editor’s intellisense and can also use these type-defs if needed.)

Update package.json

With above config, our transpiled javascript code would output to ./dist folder. Hence update the path of main index.js in package.json as below:

Above, to package.json, we also added:

"types": "./dist/index.d.ts": With this setting, a file index.d.ts would also get added to the output dist folder.
Because we specified declarations:true in tsconfig.json, Typescript compiler would generate and add type-defs for our code to this file.

"files": ["dist"]: With this setting, only the files/folders from the dist folder will be included in the published package.

Update .gitignore

Make sure to add dist folder to .gitignore

Code formatting and linting configuration

In 2019, Typescript team announced their plan to eventually deprecate TSLint and support ESLint as a common linter for both javascript and typescript. typescript-eslint project from ESLint provides packages to support for linting typescript.

Install dependencies

The detailed installation and configuration guide for typescript-eslint can be found here, or follow along as below:

npm install eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-jest -D

As described on its Github page:

  • @typescript-eslint/parser - is an ESLint-specific parser to lint typescript code.
  • @typescript-eslint/eslint-plugin - an ESLint-specific plugin which, when used in conjunction with @typescript-eslint/parser, allows for TypeScript-specific linting rules to run.
  • eslint-plugin-jest ESLint plugin for Jest

Add .eslintrc.js config file in the root of project folder with below configuration:

Github Gist

Files to be ignored for linting

Add .eslintignore file under the root of project folder to tell ESLint which files and folders it should ignore to lint.

Github Gist

👆 line#6:.prettierrc.js is for Prettier config file that we install next.

Prettier for code formatting

Install Prettier and required packages for it to work with eslint

npm i prettier eslint-config-prettier eslint-plugin-prettier -D

Add .prettierrc.js config file in the root of project folder with the code formatting rules you prefer:

module.exports = {
singleQuote: true,
trailingComma: "all",
printWidth: 120,
tabWidth: 4
};

and .prettierignore

Update .eslintrc.js to add prettier

Github Gist

If you are using VSCode, its ESlint extension can help to auto-fix lint and formatting errors on file save. Install/update latest version of VSCode’ ESLint extension if you don’t have it already, and add below to your VSCode settings.

"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},

In package.json, add the lint script command to have linting run from CI/CD script or Git Hooks.

{
...
...
"scripts": {
...
...
"lint": "eslint 'src/**/*.{js,ts,jsx,tsx}' --quiet --fix"
...
...
}
...
...
}

lint-staged and husky

To make sure files committed to git don’t have any linting or formatting errors, we can use lint-staged along with husky.

lint-staged would allow us to run a linting command on a given set of files and stage any auto-formatted/linted files for commit. Then, using husky, we can define a pre-commit hook to run lint-stage before commit to git.

npm install husky lint-staged -D

Finally, update package.json to add husky and lint-staged configuration.

Github Gist

👆line#9–19 : lint-stage and husky settings

✔️ That completes our configuration for code formatting and linting. To check our code formatting/linting configuration is working, create a temp file inside <<project root>>/src folder, with some code contradicting to our eslint/prettier rules; on save those should be auto-corrected. 👇

Next, let’s move on to coding part

Code development

It’s time now to create some actual code. As mentioned in the introduction of this post, in this example package library we’ll define two basic themes dark and light that other applications of the org can use.

Create src folder and add files/subfolders under it to define our light and dark theme’s color schemes, something basic like below screenshot:

👆 We defined an interface Theme and implementing it, defined our light and dark themes. Finally, we export the two themes wrapped in orgThemes object from index.ts

Next, we need to run typescript compiler(tsc) to transpile our ts code to js. As per our tsconfig.json settings, tsc would output the transpiled js code to a folder dist.

𝚛𝚒𝚖𝚛𝚊𝚏 to clean dist folder before building again:
Its a good practice to clean the dist folder before running the tsc build command. This would remove any no-longer-used/deleted file, that may be there in the dist folder, from previous version of code.

To clean the dist folder, install rimraf

npm install rimraf -D

In package.json, add a clean command script and update build script as below:

{
...
"scripts": {
...
"clean": "rimraf dist",
"tscBuild" : "tsc --build",
"build": "npm run clean && npm run tscBuild"

...
},
...
}

Run the build command

npm run build

This should output the transpiled code with type definitions to dist folder 👇

Preparing to publish the package

Update package.json again to add prepare script (a npm life-cycle script ) that runs before npm publish. Our prepare script would simply run the build command that we added earlier above.

{
...
"scripts": {
...
"prepare": "npm run build"
...
},
...
}

.𝚐𝚒𝚝𝚕𝚊𝚋-𝚌𝚒.𝚢𝚖𝚕 to configure Gitlab CI job

Add a .gitlab-ci.yml file under the project’s root folder to define/configure the Gitlab CI job for publishing the package.

Github Gist

👆 line #6–15: defines our job’s script and other settings.

.𝚗𝚙𝚖𝚛𝚌

Add a .npmrc file under the project’s root folder to configure npm package’s registry settings:

Github Gist

👆 line #2: scope @btiwari-gitlab is same that’s used in the package’s name in package.json

line #8: example url to specify token for uploading to a project’s registry.
We specified this on line#10.

✱ The project id here is the id of common-package-library project that we noted in ⓃⓄⓉⒺ ① above.(not the id of app-themesproject)
👈

${CI_JOB_TOKEN}above is a Gitlab CI-CD variable, but we don’t need to set it in the Gitlab project’s CI-CD variables. (I guess) it gets its value assigned during the CI job by Gitlab runtime. (If anyone has better understanding of how CI_JOB_TOKEN gets its value, please add to comments).

✱ Using ${CI_JOB_TOKEN} in the project’s .npmrc file would throw an error if you run commands like npm version or npm install locally.
For this, add export CI_JOB_TOKEN='' to your ~/.bash_profile file and restart VSCode (you may have to run npm.load() , escape from the editor and relaunch VSCode)

Update 𝚙𝚊𝚌𝚔𝚊𝚐𝚎.𝚓𝚜𝚘𝚗
Next, add publishConfig to specify the package registry URL to package.json in below format:

<@your-package-scope-name>:registry" : "https://gitlab.com/api/v4/projects/<your-project-id>/packages/npm/"

like below for our example:

Commit and push your changes to the repository.

Publishing package to Gitlab Package Registry

Go to the app-themes repository on Gitlab.
Merge the changes to master or your release branch as needed 👇

Once the changes are merged, go to CI/CD pipeline from the project’s left side menu.
You should now see the pipeline job. This would be the build job that we defined above in .gitlab-ci.yml

Trigger the job to build and publish the package:

If all settings has been specified/configured correctly, the job should successfully register our package to the target project’s (common-org-packages) registry.

Go to the target project org-common-packages and we should now see our app-themes npm package registered there:

Using the published package in an app

Obtain Access token for you Gitlab account

📌 ⓃⓄⓉⒺ ③ Note the access token created above

We will need this access token to configure Gitlab authentication for npm locally from the test app project.

2. Create a basic bare bone ES6 project to test our npm package

Next, we need a very basic ES6 project where in we can import and test our above created app-themes npm package

For quick setup, we will clone some ES6 boilerplate repo. A quick google search yielded me this repo https://github.com/metagrover/ES6-boilerplate

Clone the ES6 boilerplate repo to your folder of choice and rename the cloned repository folder to test-app-themes or anything as per your requirements.

Open the folder in VSCode

Install npm packages included in the boilerplate

Next, update index.html as below:

Github Gist

👆 We added two buttons(line# 19–20) to index.html to apply dark or light themes.
Next, in src/index.js, we will import our app-themes package and implement the onclick handlers for these two buttons to apply the selected theme, reading the selected theme colors from the package.

For this, lets first install the npm package app-themes in our test project. The install command can be found from Gitlab’s package registry details 👇

Try running the install command from terminal:

npm i @btiwari-gitlab/app-themes

However, first time below error will be thrown:

👆 Its trying to find our Gitlab npm package at npmjs.org. For it to check our Gitlab package registry url as well, we need to set the registry url in the local npm config.

For this , create .npmrc file in the root of the project with below config

# set url for the scoped package@btiwari-gitlab:registry=https://gitlab.com/api/v4/projects/26671151/packages/npm/# Add auth token for the Gitlab scoped package
# //gitlab.com/api/v4/projects/<<<your project id>>>/packages/npm/:_authToken=<<<access token>>>
//gitlab.com/api/v4/projects/26671151/packages/npm/:_authToken=<<<access token>>>

👆 replace <<access_token>> with the token we created and noted down at ⓃⓄⓉⒺ ③ above.

(Alternatively, you can set these config globally on you system using the npm config command 👇)

npm config set @btiwari-gitlab:registry https://gitlab.com/api/v4/projects/<<your project id>>/packages/npm/npm config set -- '//gitlab.com/api/v4/projects/26671151/packages/npm/:_authToken'  "<<<access token>>>"

Once the URL and authentication is configured for our Gitlab package’s registry, try installing the package again. This time the package should install successfully:

👆 Our Gitlab npm package app-themes got installed successfully. Now we can use the theme colors from the package in our test app.

Update the src/index.js file as below:

👆 line#1: we imported orgThemes from our app-themes npm package and use it in the applyTheme function (line#6-7)to set the selected theme.

You would also get the supported package’s types, methods and properties in the editor’s intellisense window while coding 👇

Run the application and we should be able to change the main div’s background color to dark or light theme’s color by clicking the respective button:

There you have it! Your step-by-step guide for publishing a private NPM package to Gitlab registry. I hope you would find this helpful.

Credits, References and Resources:

That’s all. Thank you!!! Feedback, comments and suggestions are welcome.

--

--