Create and Publish a Universal JavaScript Module

Written by vladys_lav | Published 2019/11/28
Tech Story Tags: javascript | nodejs | npm-modules | karmajs | webpack | latest-tech-stories | javascript-modules | tutorial

TLDR There are a lot of inconsistency in the JavaScript world. Since 1997 (ECMAScript 1) and before 2015 there were no standards for JavaScript modules. In ES 2015 (ES6) modules were added to the specification and new export and import statements introduced. To make it compatible with browsers we will use webpack and it’s umd library. To test the module, use karma, chrome-launcher and jasmine to run tests in Chrome browser. After publishing your first version, update your module version from 0001 to 1 to 1.via the TL;DR App

Why Universal NPM Modules?

There are a lot of inconsistency in the JavaScript world. Since 1997 (ECMAScript 1) and before 2015 (ECMAScript 6) there were no standards for JavaScript modules.
ECMAScript specification is a standardised specification of the JavaScript language.
In ES 2015 (ES6) modules were added to the specification and new export and import statements introduced.
Before ECMAScript 2015, there were a few modules standards proposals, some of them are:
  • CommonJS
  • AMD
  • RequireJS
  • OSGI JavaScript Bundles and others
On the way to universal modules, UMD wrapper has being proposed.
Universal Module Definition (UMD) is an attempt to offer compatibility with the most popular scripts loaders (AMD and CommonJS).
UMD builds are available in both, browsers and NodeJS.

Open-Source

Open-source approach will help your project to accept improvements and fixes from the community.
In this example we will use a public GitHub repository async-assets-loader.

Project Initialisation

Working dir

Create a directory:
mkdir async-assets-loader
Change directory:
cd async-assets-loader/

Package config

Create a package.json file for scoped package.
npm init for unscoped or npm init --scope=@scope-name
A scope allows you to create a package with the same name as a package created by another user or organisation without conflict. Scoped packages are preceded by their scope name.
It will ask questions about package name, version, description, entry point, test command, git repository, keywords, author, license. As a result package.json file will be generated, see example below:

Source files

Main module source file is configured in package.json file, main property. In our case it will look for an async-assets-loader.js file in the dist folder.
In the file, add a function as a property of the exports object. This will make the function available to others code. Example:
exports.hello = function () {return "Hello World";}
exports
 is a reference to the 
module.exports
 that is shorter to type. However, be aware that like any variable, if a new value is assigned to exports, it is no longer bound to module.exports
module.exports.hello = true;
// OK
exports.hello = true;
// OK (shortcut)
exports = { hello: false };
// Not OK, exports is re-assigned
You can find the original module source code of index.js file here.

Build and Test

First of all we are using module.exports to export module as NodeJS module. This way is not compatible with browsers by default. To make it compatible with browsers we will use webpack and it’s umd library. To add libraries, run
npm i webpack webpack-cli --save-dev
After installation is done, lets create webpack config, see example below:
Check source code for webpack.config.js here. Now, we can run build command:
./node_modules/.bin/webpack
As result it will create async-assets-loader.js and place it into the dist folder:
Because of production mode in the webpack config file, it will also minify the resulting code of the module.
Now we are ready to test the module. First of all lets ensure it will be working in the browser. For test in browser let’s use karma, chrome-launcher and jasmine.
npm i karma karma-chrome-launcher karma-jasmine jasmine-core --save-dev
then
npm i jasmine --save-dev
Now we are ready to create karma config:
./node_modules/.bin/karma init karma.conf.js
See how it might looks like:
Here is the link to the karma.conf.js file. Note, singleRun is set as true.
Let’s add some tests to the test/loadAssetsSpec.js file.
It’s time to start testing our code in Chrome browser. If you want to run tests in another browser, check available browsers launchers page.
npm run
 runs an arbitrary command from a package’s "scripts" object. It is used by the test, start, restart, and stop commands, but can be called directly, as well.
npm run
 commands will automatically include local node_modules/.bin to the PATH.
Lets update npm test command in scripts.test property of package.json file to webpack && karma start karma.conf.js. This will make a new build and run tests:
npm run test
And it’s shortcut:
npm test
Awesome!
Time to test in NodeJS environment. Generate basic jasmine config:
./node_modules/.bin/jasmine init
It will create a file spec/support/jasmine.json, we will not change it. Lets create spec/testNodeSpec.js file.
And update test command in package.json:
"test": "webpack && karma start karma.conf.js && jasmine"
Now 
npm test
 will make a build, run browser tests and run basic NodeJS package test.

Publish to the Registry

After all tests have being passed, we are ready to update module version and publish to the NPM repository. If you didn’t make a login yet run the login command.
npm login
All files in the package directory are included if no local .gitignore or .npmignore file exists. If both files exist and a file is ignored by .gitignore but not by .npmignore then it will be included.
When you are ready to launch your first version, update your module version from 0.0.1 to 1.0.0:
npm version 1.0.0
And publish to the registry with command:
npm publish
All published packages are available on the npmjs website, example: https://www.npmjs.com/package/async-assets-loader

Add Info Badges

After publishing to the NPM registry we can add info badges about status of our open-source project with https://shields.io/. It could be build status, code coverage, size, downloads, version, etc. Status is updating automatically.
Add such lines to the top of README.md file in your project root:
# async-assets-loader
[![NPM Version](https://img.shields.io/npm/v/async-assets-loader.svg?style=flat-square)](https://www.npmjs.com/package/async-assets-loader)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](LICENSE)
[![NPM Downloads](https://img.shields.io/npm/dt/async-assets-loader.svg?style=flat-square)](https://www.npmjs.com/package/async-assets-loader)
The README file will be shown on the package page.
An npm package README file must be in the root-level directory of the package.
Badges example:

Setup CI

When you are contributing with a team, a good option is to setup a continuous integration (CI). Popular tool for open-source projects is Travis CI. It has support of NodeJS and integration with GitHub and NPM. They have deploying to npm article with a short description of how to setup tests against a few NodeJS versions and deploy the package to the NPM.
Source code I have being used in this article is available on the GitHub

Written by vladys_lav | Software Engineer
Published by HackerNoon on 2019/11/28