Mariusz Rajczakowski
Software Engineer
22 min read | 3 months ago

Create and publish your first npm package!

Npm
picture

In this tutorial you will learn how to write and publish your first npm package. Source code is available on github and package is published on npm registry.

Prequisites

  • Create an account on github
  • Install node.js and npm (comes with node)
  • Install/update your npm to the latest version
    npm install npm@latest -g
      //or
      npm update -g
      
    (you can read more about npm here)
  • Create an account on npm registry
  • Create an account on circleci (sign up with github)
  • Create an account on codecov (sign up with github)

Getting started

1. Create your github repo

adding new repository

2. Choose name and description, then tick add readme checkbox, choose .gitignore for node and MIT license (most popular)

repo_details

3. Then clone the repo on your local machine


  git clone https://github.com/mariocoski/rademenes.git
  

4. Set up the personalized init data for npm


  //set up your full name
  npm set init.author.name “Mariusz Rajczakowski”
  //set up your email
  npm set init.author.email “mariuszrajczakowski@gmail.com”
  //set up your website
  npm set init.author.url “http://mariuszrajczakowski.me”
  

You can control what has being set up by opening ~/.npmrc. It should look like:


  vi ~/.npmrc

  //registry.npmjs.org/:_authToken=xxxxxxxxxxxxxxxxxxxxx
  init.author.name=Mariusz Rajczakowski
  init.author.email=mariuszrajczakowski@gmail.com
  init.author.url=http://mariuszrajczakowski.me
  

5. Then you are ready for your package initialization


  //cd into your cloned repo
  cd rademenes
  //initialize
  npm init

  //then choose package name (default is fine)
  //remember about the rules for name in package.json
  //(up to 214 characters, can't start with dot or an underscore,
  //no uppercase, only URL-safe characters)
  package name: (rademenes)
  //choose version 0.1.0 (feature according to semantic versioning)
  version: (1.0.0) 0.1.0
  //choose package description - be descriptive as this is how people
  //will discover your package when searching on npmjs.com
  description: minimalistic java script utility library
  //choose entry point to your program (in our case is src/index.js)
  entry point: (index.js) src/index.js
  //set test command
  test command: jest --coverage
  //set git repo (default is fine)
  git repository: (https://github.com/mariocoski/rademenes.git)
  //choose keywords describing your package:
  keywords: javascript, utility library, functional programming
  //specify license (MIT will do)
  license: (ISC) MIT
  Is this ok? (yes)
  

After that you should see newly created package.json in root dir


  cat package.json

  //it should look like this
  {
    "name": "rademenes",
    "version": "0.1.0",
    "description": "minimalistic java script utility library",
    "main": "src/index.js",
    "scripts": {
      "test": "yarn test --coverage"
    },
    "repository": {
      "type": "git",
      "url": "git+https://github.com/mariocoski/rademenes.git"
    },
    "keywords": [
      "javascript",
      "utility",
      "library",
      "functional",
      "programming"
    ],
    "author": "Mariusz Rajczakowski <mariuszrajczakowski@gmail.com> (http://www.mariuszrajczakowski.me)",
    "license": "MIT",
    "bugs": {
      "url": "https://github.com/mariocoski/rademenes/issues"
    },
    "homepage": "https://github.com/mariocoski/rademenes#readme"
  }
  

6. Let's add engines property to package.json.

Npm treats engines.node as advisory and it will check node version when you install your package (not when you run it). We will use version equal or higher than 4.8.2.


  //package.json
  {
     //rest of config
     "engines" : {
        "node": ">=4.8.2"
     }
  }
  

7. Create your first npm module

Bear in mind, it has to adhere to CommonJS module spec. We can start by creating an entry point to our app


  mkdir src
  touch src/index.js
  

8. Let’s then install jest testing framework

To read more about jest visit their docs


  npm install jest --save-dev
  //or with yarn
  yarn add jest --dev

  //after that create tests folder (jest naming convention)
  mkdir __tests__
  

9. Let’s also install codecov (coverage report uploader)

You can read more about codecov here


  npm install codecov --save-dev
  //or
  yarn add codecov --dev
  

Module's Requirements

What are we going to build

  • Our library will contains two functions which are useful for working with arrays and objects: pluck and pick
  • We will implement them in separate files then you can use them individually, additionally you will see how to work with exports from more than one files
  • We will try to follow tdd (test driven development) approach to implement them
  • When we finished, we will plug in circleci and codecov to automatically run tests and check test coverage whenever someone will make some changes to our repo
  • Then we will add badges which will be visible in our repo on github (one for build and one for test coverage)

Let's build our pluck function


  touch __tests__/pluck.test.js

  //__tests/pluck.test.js
  const pluck = require('../src/pluck'); //we can skip an extension if it's js
  //bear in mind we haven't created this file yet..

  //then we will write our first test
  test('it can pluck values for a given key from array of objects', function(){
    const data = [ { id: 1, name: 'Thomas' }, { id: 2, name: 'Lukas' }, { id: 3, name: 'Ann'} ];
    expect(pluck(data, 'name')).toEqual(['Thomas','Lukas','Ann']);
  });
  

When you run your tests: (npm test or yarn test). You should see:

first_test_module_does_not_exist

So first we create new module


  touch src/pluck.js

  // src/pluck.js
  'use strict';
  module.exports = function(arrayOfObjects, key){
      //normally we would go with map method on array prototype
      //but is not available prior to IE9 hence we will use loops:
      const newArray = [];
      const length = arrayOfObjects.length;
      for(let i = 0; i < length; i++){
         newArray.push(arrayOfObjects[i][key]);
      }
      return newArray;
  }
  

When your run your tests you should see:

initial_pluck_test_passed

This works, however what will happen if we have objects which don't have given property or they have it but down the prototype chain?

We don’t want to pluck those values, let's then write a test for that cases


  test('it can pluck values for a given key only when object has it as own property', function(){
    const person = { id: 3, name: 'William'};
    const ann = Object.create(person);
    //second object has no property name, third one inherits those properties from person
    //hence doesn’t have own ones
    const data = [ { id: 1, name: 'Thomas' }, { id: 2 },ann];
    expect(pluck(data, 'name')).toEqual(['Thomas']);
  });
  

When we run our test suite we should see:

pluck_test_failure_own_property

To fix that we will add an extra check:


  // src/pluck.js
  'use strict';
  module.exports = function(arrayOfObjects, key){
      //normally we would go with map method on array prototype
      //but is not available prior to IE9 hence we will use loops:
      const newArray = [];
      const length = arrayOfObjects.length;
      for(let i = 0; i < length; i++){
         if(arrayOfObjects[i].hasOwnProperty(key)){
  newArray.push(arrayOfObjects[i][key]);
         }
      }
      return newArray;
  }
  

After running the tests we should see:

pluck_all_tests_passed

Let's now build our pick function


  touch __tests__/pick.test.js

  //and add following lines:
  'use strict';
  const pick = require('../src/pick');
  test('it could pick a single property from an object', function(){
    const testObj = {a:1, b:2, c: 3};
    expect(pick(testObj, b)).toEqual(2);
  });
  

When you run your tests you should see:

pick_not_found

Let’s implement a basic functionality


  touch src/pick.js
  //then we will start with the following functionality
  'use strict';
  module.exports = function(obj, key){
    const newObj = {};
    newObj[key] = obj[key];
    return newObj;
  }
  

When you run your test suite your should see:

pick_first_passed

However we will write more tests for edge cases when property does not exist or its inherited


  test('it could only pick a property which exist on object', function(){
    const parent = {z: 12};
    const testObj = Object.create(parent);
    expect(pick(testObj, 'z')).not.toHaveProperty('z');
  });
  

When you run your tests now:

pick_not_from_parent

To fix that you will add an extra check in pick.js


  // src/pick.js
  'use strict';
  module.exports = function(obj, key){
    const newObj = {};
    if(obj.hasOwnProperty(key)){
      newObj[key] = obj[key];
    }
    return newObj;
  }
  

Then when you run your tests:

pick_single_passes

Next step will be to change the API of pick function to accept array of keys as an second argument

We will start with test including the edge case with prototypical inheritance:


  test('it should pick many keys from an object if array given as an argument', function(){
    const parent = {z:3};
    const testObj = Object.create(parent);
    testObj['a'] = 12;
    expect(pick(testObj,['a','z'])).toHaveProperty('a');
    expect(pick(testObj,['a','z'])).not.toHaveProperty('z');
  });
  

When you run your tests now:

pick_multiple_fail

We will now write an implementation


  //src/pick.js
  'use strict';
  module.exports = function(obj, key){
    const newObj = {};

    if(typeof key === 'string' && obj.hasOwnProperty(key)){
      newObj[key] = obj[key];
    }

    if(typeof key === 'object' && key.constructor === Array){
      key.forEach(function(prop){
        if(obj.hasOwnProperty(prop)){
           newObj[prop] = obj[prop];
        }
      });
    }
    return newObj;
  }
  

Then when you run your tests:

all_tests_green

Wrapping up a module

Let's finish up the module with multiple export in main entry point


  // src/index.js
  const pluck = require('./pluck');
  const pick = require('./pick');

  module.exports = {
    pluck,
    pick
  };
  

Then we will modify our README.md file


  # rademenes

  minimalistic java script utility library providing pluck and pick methods

  ## Installation
  ```javascript
     yarn add rademenes
     //or
     npm install rademenes --save
  ```

  ## Usage
  ```javascript
    //require at the top of your script
    const R = require('rademenes');

    //PLUCK
    const arrayOfObjects = [
      { id: 1, name: 'Thomas' },
      { id: 2, name: 'Mariusz' },
      { id: 3, name: 'John' }
    ];

    const onlyNames = R.pluck(arrayOfObjects, 'name');
    console.log(onlyNames);  // prints ['Thomas', 'Mariusz', 'John']

    //PICK
    const input = { firstname: 'John', lastname: 'Doe', age: 25, isAdmin: true };
    const fillable = [ 'firstname', 'lastname', 'age' ];
    const onlyFillable = R.pick(input, fillable);

    console.log(onlyFillable); //prints { firstname: 'John', lastname: 'Doe', age: 25 };
  ```

  ## Tests
  ```javascript
    yarn test
    //or
    npm test
  ```

  ## Contributing
  When contributing to this repository, please first discuss the
  change you wish to make via issue, email, or any other method
  with the owners of this repository before making a change.

  ## Release History
  * 0.1.0 Initial release
  

Add, commit and push to github


  git add .
  git commit -m 'Initial release'
  git tag v0.1.0
  git push origin master --tags
  

Publishing on npm registry

Log in to npm registry with the same credentials you used on npmjs.com when you registered:


  npm login //then provide username, password and email

  //later when you checked that your package name is unique in searchbox
  //at npmjs.com you can run:
  npm publish
  

If everything goes well you should see:

publish_success

Adding continuous integration for your repo

We will use the account on circleci (if you have not registered yet - signup with github)

Then go to projects tab in circleci and add new project (choose from the github account)

Follow the instructions there:

circle_ci_setup


  //we will start by creating .circleci folder
  mkdir .circleci

  //then we will create a file there and edit it
  vi .circleci/config.yml

  //and we paste all below, then press esc, type :wq and press enter
  version: 2
  jobs:
    build:
      working_directory: ~/rademenes
      docker:
        - image: circleci/node:4.8.2
      steps:
        - checkout
        - run:
            name: update-npm
            command: 'sudo npm install -g npm@latest'
        - restore_cache:
            key: dependency-cache-{{ checksum 'package.json' }}
        - run:
            name: install-npm-wee
            command: npm install
        - save_cache:
            key: dependency-cache-{{ checksum 'package.json' }}
            paths:
              - ./node_modules
        - run:
            name: test
            command: npm test
  

The commit those changes to github repo:


  git add .
  git commit -m 'added circleci config'
  git push
  

Then go to circleci and wait until build will finish. If everything go well you should see:

successful_build

Adding coverage statistics to the repo

Start by adding following lines to package.json. That will tell jest to collect coverage metrics and store it in root dir in coverage folder.


  //package.json
  {
     //rest of the config
    "jest": {
      "coverageDirectory": "./coverage/",
      "collectCoverage": true
    }
  }
  //also change this lines in package.json
  "scripts": {
    "test": "jest --coverage"
  }
  //to
  "scripts": {
    "test": "jest --coverage && codecov"
  }
  //it will allow to send statistics to codecov servers
  

Then go to your codecov account and add new repo from github account:

codecov_add_new_repo

After you can commit changes you made to github repo:


  git add .
  git commit -m 'added coverage reporting'
  git push
  

Your build on circleci should be successful, moreover when you check your codecov account you will see test statistics.

codecov_all_covered

Adding badges to the repo

Circleci

Go to your repo settings, then click on settings icon, you should have an option 'status badges', click on it and copy the markdown.

getting_build_badge

Codecov

Go to your codecov account, and in project settings click on settings and then badge, copy the markdown.

getting_codecov_badge

Modify your README.md file and add those two badges like so:


  # rademenes

  [![CircleCI](https://circleci.com/gh/mariocoski/rademenes.svg?style=svg)](https://circleci.com/gh/mariocoski/rademenes)
  [![codecov](https://codecov.io/gh/mariocoski/rademenes/branch/master/graph/badge.svg)](https://codecov.io/gh/mariocoski/rademenes)
  

Commit those changes to github repo:


  git add .
  git commit -m 'added build and coverage badges'
  git tag v0.1.1
  git push origin master --tags
  

Then update your npm registry and publish:


  npm version patch -m 'Version %s - added build and coverage badges'
  //and then
  npm publish
  

When you check your npmjs account you will see v.0.1.1 there.

What next?

That's it! You made it! Congratulations! You created and published your first npm package. Now you are free to add more features, collaborate with other developers and share your code with others. Welcome to the open source world!

Source code is available here and your can also download a package from npm - more details here

References
  1. https://circleci.com/docs/2.0/
  2. https://codecov.io/
  3. https://docs.npmjs.com/getting-started/publishing-npm-packages

Share:



Warning! This site uses cookies
By continuing to browse the site, you are agreeing to our use of cookies. Read our privacy policy