Thursday, June 6, 2019

Creating NPM packages with @dizmo/generator-module

NPM has become a hugely important part of the JavaScript ecosystem. It allows the easy packaging and publication of various projects and libraries, which can be based on JavaScript or other languages like for example TypeScript (or CoffeeScript).

At dizmo, we have developed the @dizmo/generator-module NPM module generator, which allows us to quickly create an NPM based library project with support for the following features:

  • Generation: the process of generating an NPM based module (based upon a template);

  • Transpilation: the process of translating from e.g. (modern) JavaScript or TypeScript to (older) JavaScript;

  • Bundling: the process of bundling various separate JavaScript modules into a single one;

  • Minification: the process of compressing a large module into a smaller one;

  • Linting: the process of checking that the coding style meets certain standards;

  • Testing: the process of testing code for expected behavior;

  • Coverage analysis: the process of checking how much of the code is covered by tests;

  • Continuous integration: the process of automatically running tests (on a remote server).

Generation

Our module generator is based on Yeoman. After installing Yeoman and the @dizmo/generator-module package, you can create an NPM project with:

yo @dizmo/module [--git] [--typescript|--coffeescript]

As you see we support pure JavaScript, TypeScript and CoffeeScript projects. Further – provided the git command is available – it is possible to initialized a project as a GIT repository. Let’s have a closer look at a JavaScript project:

JavaScript module

yo @dizmo/module --git
     _-----_     
    |       |    ╭──────────────────────────╮
    |--(o)--|    │  Welcome to the awesome  │
   `---------´   │  dizmo module generator! │
    ( _´U`_ )    ╰──────────────────────────╯
    /___A___\   /
     |  ~  |     
   __'.___.'__   
 ´   `  |° ´ Y ` 

? Name your module: @dizmo/my-module
? Describe it: library module
? And your URL? https://github.com/hsk81
   create package.json
   create cli/run-build.js
   create cli/run-lint.js
   create cli/run-prepack.js
   create cli/run-test.js
   create cli/run-utils.js
   create lib/index.js
   create test/test.js
   create LICENSE
   create README.md
   create .travis.yml
   create .eslintrc.json
   create .gitignore
   create dist/.gitignore
   create dist/.npmignore

Setting the project root at: /home/hsk81/my-module.git

Directory structure

tree my-module.git/ -a
my-module.git/
├── cli
│   ├── run-build.js
│   ├── run-lint.js
│   ├── run-prepack.js
│   ├── run-test.js
│   └── run-utils.js
├── dist
│   ├── .gitignore
│   └── .npmignore
├── .eslintrc.json
├── .gitignore
├── lib
│   └── index.js
├── LICENSE
├── package.json
├── README.md
├── test
│   └── test.js
├── .travis.yml
└── .yo-rc.json

4 directories, 16 files

where we have omitted above the .git sub-directory. Let’s have an even closer look at the various sub-directories:

./clithe embedded build system

tree my-module.git/cli/ -a
my-module.git/cli/
├── run-build.js
├── run-lint.js
├── run-prepack.js
├── run-test.js
└── run-utils.js

0 directories, 5 files

These scripts beneath the ./cli folder provide support for all the features listed above: building (transpilation), linting (code standard adherence), testing (code correctness with coverage analysis), and prepackaging (bundling with minification).

The totality of this embedded build systems is less than 100 lines of code, and thanks to it being directly embedded into the project, it allows for specific adjustments of the build process.

./distthe distribution folder

tree my-module.git/dist/ -a
my-module.git/dist/
├── .gitignore
└── .npmignore

0 directories, 2 files

The ./dist folder is the target location of the transpilation, bundling and minification processes. The .gitignore file here ensures that no bundle (which can be rather large) is committed to the GIT repository, while the (empty) .npmignore file ensures that the bundle is published to the NPM repository.

Hence, these two files should never be removed: Sometimes, it is necessary to clean-up the remaining files under ./dist, upon which great care should be taken to not also accidentally remove these .gitignore and .npmignore files.

./libthe source folder

tree my-module.git/lib/ -a
my-module.git/lib/
└── index.js

0 directories, 1 file

The ./lib folder contains all the source files, which collectively make up the library; it is also possible to create sub-folders within it. By default a single index.js is given, which with its export statements provides the API of this library.

./testthe test folder

tree my-module.git/test/ -a
my-module.git/test/
└── test.js

0 directories, 1 file

The ./test folder contains test scripts (beginning with a test prefix), which together test the code under ./lib for expected behavior. By default a single test.js is given containing test cases for ./lib/index.js.

Development

Build

npm run build

without linting:

npm run -- build --no-lint

with UMD support (incl. minimization):

npm run -- build --prepack

with UMD support (excl. minimization):

npm run -- build --prepack --no-minify

By invoking npm run build all scripts under ./lib (and ./test) are by default transpiled and linted. Optionally, they are bundled and then minified. The transpilation is performed with Babel, linting with ESLint, (UMD) bundling with Browserify and minification with Terser.js.

Lint

npm run lint

with auto-fixing:

npm run -- lint --fix

By invoking npm run lint the linting process can also be triggered separately (outside of a build process), and it is performed with ESLint, whereas the .eslintrc.json file is used for configuring the set of linting rules.

Test

npm run test

without (re-)building:

npm run -- test --no-build

By invoking npm run test by default a build step is performed, after which the (transpiled) test scripts are executed – using the Mocha framework.

Cover

npm run cover

without (re-)building:

npm run -- cover --no-build

By invoking npm run cover by default a build step is performed, after which the (transpiled) test scripts are executed – using Mocha in combination with the Istanbul.js coverage framework (to provide corresponding statistics).

Publish

npm publish

initially (if public):

npm publish --access=public

By invoking npm publish the transpiled scripts under ./dist are prepackaged by bundling and minifying them, after which the bundle is then published to the NPM registry. While for bundling Browserify is used, for minification Terser.js is facilitated.

Continuous integration

$ cat .travis.yml 
language : node_js
node_js :
 - stable
install:
 - npm install
script:
 - npm run cover

after_script: "npx nyc report --reporter=text-lcov | npx coveralls"

When the source code of the NPM library is pushed to a GIT repository (for example on GitHub.com), it can automatically be tested using the Travis CI service. For this to work a .travis.yml configuration (as shown above) needs to be provided (which is generated by @dizmo/generator-module). Further, the Travis CI needs to have access to the corresponding GIT repository – to be able to pull and test the most recent commits.

Finally, the results of the tests is reported to the Coveralls service, which offers a user interface to interactively investigate the portions of the code base, which are (or are not) covered by the provided test cases (under ./test).