Create an npm package easily in JavaScript/TypeScript

Software Engineer • TypeScript • JavaScript • Angular • VueJS • WordPress • Ex-founder of ng-enious
Search for a command to run...

Software Engineer • TypeScript • JavaScript • Angular • VueJS • WordPress • Ex-founder of ng-enious
Hello there, I'm not sure I understand your first point, the repo is available here and evey step in the guide has the commit id + link to commit reflecting that step.
As for your second point, correct, TSC does not build and bundle scss. I didn't mention CSS/SCSS bundling for a package to keep things really simple. If you think I need to address it, I can add a bonus section for sure 😊 .
Wade Wilson I get what you mean. This is more a general guide to help someone new to the world of npm packages to get started.
In any case I like what you're suggesting, I think this deserves a part 2 with something more advanced 😃 !
Very happy you liked it, Rodrigo! I hope it'll come helpful to you yes! 🙌
Happy to read that, thank you so much 🙌 !
That was a really great article Tarek Jellali. As someone who has never self-published a package before I was curious to know how it was done and this explained things really well!
Thanks you so much Kieran! So happy you liked it 😀 !
Awesome Article!, I did code along until it was time to deploy to npm. I didn't want to do that.
Thanks for sharing though, it's good knowing how packages are made.
Very glad you've enjoyed it!
Honestly, my goal was to demystify how npm packages are made. I used to think that it was complicated and cumbersome. All the articles I found were talking about tools and other things that can make beginners just give up.
I glad you took a different approach then 😊Tarek Jellali
I found the article easy to follow along.
Hello good people! In this short article, we're going to convert KML files to the GeoJSON format. The main takeaway aside from discovering something new is that I'll tell you about the popular solution, and then I'll show you the new solution I had t...

By the time I'm writing this article, you surely may have heard of ChatGPT. This "new AI" made so many waves that I couldn't resist digging through it and writing something about it. But rather than jumping into the hype train of ChatGPT and OpenAI, ...

Hello good people! In this article, I want to shed light on a very interesting console function. It's called console.timer() and might help you understand how your code runs and optimize it. This will be a rather short article. We'll go through two s...

Hello good people! In this article, I want to deep dive with you into a usually overlooked file in a project. The package.json is crucial to a lot of frontend/backend NodeJS projects. Yet a lot of developers don't really know what it can really do. W...

Tarek's /dev/log
10 posts
Everyday blogs about software engineering, Frontend Web development and everything in between.
Hello good people! In this article, we're going to create an npm package step by step. By the end of this article you will be able to:
ℹ Note: Be sure to read my previous article "The anatomy of a package.json" as we'll be reusing some subjects from it.
Before we begin, I've added a commit hash that links to a demo repository here to help you understand what's happening.
npm packages are reusable JavaScript packages meant to help you reuse less code. You've surely heard of lodash, Moment, express and so many others!
Every time you npm install some-awesome-package you're asking npm to download and extract the package to the node_modules/some-awesome-package folder. You can then import/require it to use it in your JavaScript/TypeScript code.
Packages help you make your code modular by providing you reusable code. It's in a way like custom lego blocks that save you the hassle of creating everything from scratch.
What's interesting is that a lot of packages also use other packages called dependencies. This is why you usually find a lot of folders inside your project's node_modules. If well done, this lets you save a lot of time and provides you with robust code that's been tested and validated by a lot of other developers, just like you!
Just before we carry on if you're very curious about how npm deals with dependencies and want to deep dive into its complex inner-workings, check out this excellent article.
To build our npm package, we'll have to follow few simple steps:
If you want to follow along with this guide, start by creating a base folder, I'll name mine as build-npm-package-example/. I'll try to show you the current file tree on every step change.
Every npm package starts with a package.json file. Let's create a scratch folder named my-awesome-package/ under our base folder. Next, open your favorite command tool into that folder and type the following:
npm init -y
The -y flag tells npm to not bother prompting us with any questions, especially that this will be just a scratch package for now.
Next, let's create a file called index.js in the same folder as our package.json and add the following code:
// my-awesome-package/index.js
exports.printName = function(name) {
console.log('Hello ' + name + ' 👋');
};
Our package, for now, will have a single module that takes a name and greets it with a simple console log.
Now type the following command:
npm pack
You'll see a generated file named my-awesome-package-1.0.0.tgz this is effectively our npm package ready to be consumed by other developers! We won't use it for this guide, but you can open it using for example 7zip and view its contents.
By the end of this step, you should have the following file tree:
├📂build-npm-package-example/
└─📂my-awesome-package/
├🟨index.js
├🟩package.json
└📚my-awesome-package-1.0.0.tgz
Let's now create a folder called sample-app/. Inside this folder, you'll need to run another npm init -y.
Once your package.json is generated, let's link our package to this sample app as follows:
cd my-awesome-package
npm link
cd ../sample-app
npm link my-awesome-package
If we check the node_modules/ folder of our sample-app/ we'll find that there is a new folder called my-awesome-package/. It's not a copy though, behind the scenes, npm made a system link between our package folder and our sample app.
In my-awesome-package/ folder you'll notice a package-lock.json generated once you input npm link. #cfea861
Now that our package is linked, we can consume it. In your sample-app/ folder. Create an index.js file with this code:
// sample-app/index.js
const myModule = require('my-awesome-package')
myModule.printName('Hashnode');
If you next run node ./index.js you should see Hello Hashnode 👋. Because "my-awesome-package" is linked to our sample app, changes in the package are instantly reflected in our sample app. Below a GIF where I change the console.log content to:
console.log('Hello ' + name + ' 🔥');
And you can see that by running node ./index.js in our sample-app/ the greeting message changed to `Hello Hashnode 🔥'. #fa9e104

By the end of this step, you should have the following file tree:
├📂build-npm-package-example/
├─📂my-awesome-package/
│ ├🟨index.js
│ ├🟩package.json
│ └🟩package-lock.json
└─📂sample-app/
├🟨index.js
└🟩package.json
To publish your package to the npm repository, you'll first need to create an npm account. Once this is done, you should head to the folder of your package and input the following command:
npm adduser
npm will prompt you for your username, password and, email. If your next run:
npm publish --dry-run
The flag --dry-run tells npm to do everything as usual but not actually publish the package to the npm repository. You should see the following output:
npm notice
npm notice package: @tarkant/my-awesome-package@1.0.0
npm notice === Tarball Contents ===
npm notice 87B index.js
npm notice 241B package.json
npm notice 194B .vscode/settings.json
npm notice 498B my-awesome-package-1.0.0.tgz
npm notice === Tarball Details ===
npm notice name: @tarkant/my-awesome-package
npm notice version: 1.0.0
npm notice package size: 1.1 kB
npm notice unpacked size: 1.0 kB
npm notice shasum: 4232f6b7993c06be9431b95d3596effe4740cd47
npm notice integrity: sha512-99fO8veOFv2YH[...]k+78Pwa9LzyUw==
npm notice total files: 4
npm notice
+ my-awesome-package@1.0.0
Warning ⚠: You won't be able to publish "my-awesome-package" as it's an already reserved namespace. You'll have to be creative in naming your package.
Suppose you didn't set the flag --dry-run and accidentally pushed your test package to npm. You'll want to unpublish your package. Please take into consideration the following points:
Please read more here in the npm Unpublish Policy.
Now that you've been warned, here's the single command you need to unpublish your package:
npm unpublish <package_name> --force
You can also unpublish a specific version of your package using the following command:
npm unpublish <package-name>@<version>
If you'd like to read more, check out the official npm documentation here.
In our previous example, "my-awesome-package" was a name already in use. In most situations, you'll have to be creative with package names. This is not your only resort though as you can prefix the name of a package with your username. For example:
You can replace my-awesome-package to @<yourusername>\my-awesome-package or more generally @<yourusername>\<your-package-name> format. In my case it would be:
@tarkant/my-awesome-package
To publish your package using your username, you will need to add a specific flag to avoid a HTTP 402 error.
npm publish --access public
This will tell npm that you're trying to publish a public package as private packages are for paid members only.
ℹ By the end of this step, you should have the following file tree:
├📂build-npm-package-example/
├─📂my-awesome-package/
│ ├🟨index.js
│ ├🟩package.json
│ └🟩package-lock.json
└─📂sample-app/
├🟨index.js
└🟩package.json
Writing your own npm package can become a little bit complicated if you don't use a module bundler. Besides, the more your package will grow and become complex, the more it will be hard to maintain it without a module bundler. Lastly, using a module bundler will help you for example use TypeScript and seamlessly build your package to JavaScript.
Let's get back to our my-awesome-package and run the following command: #7a945d6
npm i -D css-loader sass sass-loader style-loader ts-loader typescript webpack webpack-cli webpack-dev-server
This will install all the development dependencies we need to build our package. Next, we'll have to create a file called webpack.config.js with the following content: #5077d24
// my-awesome-package/webpack.config.js
const path = require('path');
const PACKAGE_NAME = 'MyAwesomePackage';
const config = {
context: __dirname,
entry: {
app: './src/index.js',
},
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
libraryTarget: 'umd',
library: PACKAGE_NAME,
umdNamedDefine: true,
globalObject: 'this',
},
module: {
rules: [
{
test: /\.ts?$/,
loader: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.s[ac]ss$/i,
use: [
'style-loader',
'css-loader',
'sass-loader',
],
},
]
},
resolve: {
extensions: ['.ts', '.js']
},
devServer: {
contentBase: path.join(__dirname, 'dist'),
}
};
module.exports = (env, argv) => {
return config;
};
This file tells Webpack to:
./src/index.js, I moved the file accordingly #3bb5610 and changed the emoji for test purposes 😁!main.js and copy it to the dist folder.PACKAGE_NAME.Now let's add those two files: #c062ae2
// my-awesome-package/tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
},
"exclude": [
"node_modules"
]
}
// my-awesome-package/tsconfig.build.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "dist/types"
},
"exclude": [
"./src/app.ts"
]
}
Those two files tell the TypeScript compiler how to behave when building our package. Nothing too fancy.
For the final touches, we need to change our package.json to the following: #aca186b
// my-awesome-package/package.json
{
[...]
"main": "dist/main.js",
"scripts": {
"build": "webpack --mode production",
[...]
},
[...]
}
The main field tells npm what file to look for as the entry point of our package. And the build field just runs Webpack in production mode to build our package.
If everything went as planned, running an npm run build you should see a main.js under the dist folder.
Back to our sample-app I've just corrected the namespace for my package (#36fd773) and redid the npm link and npm link @tarkant/my-awesome-package accordingly. Be sure to do the same if you're following along!
If we now run a node ./index.js in our sample app, we should see Hello Hashnode 😁 as an output, success! Now since Webpack does the heavy lifting for us, we can for example use the export statement without worrying if it will work on every NodeJS/Browser version! Example: (22b3f2b)
// my-awesome-package/src/index.js
export const PrintName = (name) => {
console.log('Hello ' + name + ' 🎉');
};
ℹ By the end of this step, you should have the following file tree:
├📂build-npm-package-example/
├─📂my-awesome-package/
├──├─📂src/
│ │ └🟨index.js
│ ├🟩package.json
│ ├🟩package-lock.json
│ ├🔵tsconfig.build.json
│ ├🔵tsconfig.json
│ └📦webpack.config.json
└─📂sample-app/
├🟨index.js
└🟩package.json
Finally, the biggest bonus with this configuration is that it supports TypeScript and SCSS out of the box. You can just go ahead and rename index.js to index.ts and change the webpack.config.js config.entry.app = './src/index.ts'. #d359a1e
ℹ By the end of this step, you should have the following file tree:
├📂build-npm-package-example/
├─📂my-awesome-package/
├──├─📂src/
│ │ └🟦index.ts
│ ├🟩package.json
│ ├🟩package-lock.json
│ ├🔵tsconfig.build.json
│ ├🔵tsconfig.json
│ └📦webpack.config.json
└─📂sample-app/
├🟨index.js
└🟩package.json
If you don't know what we're talking about, it's ok. But I really advise you to learn TypeScript as it will help you greatly improve your code overall! One good thing about making a TS package is that you can also ship the type definitions of your modules in the package. This lets other ts developers easily work with your package and save them the hassle of figuring out what does what in your package.
Side note ℹ: Type definitions can also be used in JS, check the last sample of code in this section.
To build the types and point them to npm, you just need to slightly alter the build script and add a "types" entry as follows: (#83f6df0)
// my-awesome-package/package.json
{
[...]
"types": "dist/types/index.d.ts",
"scripts": {
"build": "tsc --build ./tsconfig.build.json && webpack --mode production",
[...]
},
[...]
}
If you then run an npm run build in my-awesome-package/ you'll notice a new folder called types with and index.d.ts with the following content:
// my-awesome-package/dist/types/index.d.ts
export declare const PrintName: (name: any) => void;
Let's adjust our code so we can have good type definitions: (#209403c)
// my-awesome-package/src/index.ts
export const PrintName = (name: string): void => {
console.log('Hello ' + name + ' 🎉');
};
If you run again npm run build you'll see that our types/index.d.ts has been updated accordingly:
// my-awesome-package/dist/types/index.d.ts
export declare const PrintName: (name: string) => void;
Your types will be working flawlessly on any TypeScript project. But I still got a trick under my sleeve if you've followed all along until here! You can use those typings in JavaScript too! Let's head back to our sample app and add this line to our package.json file: (#e486259)
// sample-app/package.json
{
"name": "sample-app",
"type": "module",
[...]
[...]
}
Now in index.js, let's replace the code with the following:
// sample-app/index.js
/// <reference types="@tarkant/my-awesome-package" />
import MyAwesomePackage from '@tarkant/my-awesome-package';
MyAwesomePackage.PrintName('Hashnode');
The first line tells VSCode to use the types located in our package.
The second line loads the package as a module, notice its name as "MyAwesomePackage" just like we defined it in our my-awesome-package/webpack.config.js in const PACKAGE_NAME. And finally, the last line calls our PrintName() function. If you hover on it with your cursor, you should see its type definition just like this screenshot:

And that's it! We've discovered what's an npm package. Then we tried to make our own simple package and consume it. We also saw how to publish our package. Next, we tried to use Webpack to bundle our package and optimize it. Finally, we've backed in TypeScript typings into our package for better reusability.
There are still many things to talk about but I feel that we should keep it for part 2. Especially that there is a lot of information to retain. I really hope you'll find this article useful! Again, you can find this repository with each step as a simple commit.
If you feel that I've forgotten a detail or didn't explain something well, please let me know.
Cheers!
Photo by @image4you from Pixabay.com.