Creating Node.js modules with ES2015
Excited about ES2015? Not sure how to start creating Node.js modules using it? Here's a quick tutorial to get you started.
We're going to create a hello-world package containing a single module written in ES2015. At the end we will be able to install and use the package as follows:
Note: I recommend using nvm to manage your Node.js versions. I explain how to set it up here.
$ nvm use node $ npm install path/to/hello-world $ node > greet = require('hello-world').default > greet() 'Hello, world!' > greet('Dwayne') 'Hello, Dwayne!'
Note: I don't cover publishing the package but if you're interested in that then here is where you can learn to do it.
In the process you will learn how to structure your package and how to use Babel 6 to transpile your ES2015 (formerly ES6) code into ES5 code that uses the CommonJS module format.
Set up a directory to contain your package, a package.json file and a src/index.js file.
Note: I go into more detail about package.json here. If you follow that post then a simple npm init or npm init -y will get you a really sweet package.json with much less typing.
$ mkdir -p hello-world/src $ cd hello-world $ touch package.json src/index.js
Serves as documentation for your dependencies.
Allows you to use semantic versioning rules to specify the versions of your dependencies.
Makes your build reproducible and easier to share with other developers.
Keeps track of useful metadata about your project.
Now, in your package.json file add the following:
{ "name": "hello-world", "version": "0.0.0", "description": "A hello world package for creating hello world applications", "keywords": ["hello", "world", "hello-world", "helloworld"], "author": { "name": "Dwayne R. Crooks", "email": "[email protected]", "url": "http://dwaynecrooks.com/" }, "license": "MIT" }
These are required as your package won't install without them. The name and version together form an identifier that is assumed to be completely unique. The name is what your package is called. The version should follow semver.
Since we're in initial development and may change anything at anytime I chose to start the version number at 0.0.0. As we continue to develop and make releases we can increment the minor version number each time giving a sequence of releases as 0.1.0, 0.2.0, 0.3.0 etc. Once we've reached the point where we feel the public API is stable we can make a 1.0.0 release. The way in which the version number is incremented after this release is dependent on this public API and how it changes.
It's recommended to add these since they help people to discover your package.
Lets everyone know who wrote the package and how to contact you. If you got help along way then there's a contributors field just for that.
I'm told you should have one. Which makes sense, since it allows people to know how they are permitted to use your code and any restrictions you're placing on it. Even if you think you don't need one because you don't care how people use you're code, you should still have one because not having one doesn't mean you're opting out of copyright law.
In summary, be explicit and choose a license.
Note: Learn more about these (name, version, description, keywords, author and license) and the rest of the options here.
The src/index.js file will contain our ES2015 code.
export default function(who = 'world') { return `Hello, ${who}!` }
This module has a default export. The exported function takes a default value for the who parameter and returns a string constructed from who using a template string. Hence, if the function is called with no arguments it returns Hello, world! but if it is called with an argument, say 'Dwayne', then it returns Hello, Dwayne!.
Babel is a JavaScript compiler that we can use to transpile our ES2015 code into ES5 code. We need to do this since Node.js won't understand our ES2015 code.
Let's install babel-cli and run it on our module.
$ npm install --save-dev babel-cli $ ./node_modules/.bin/babel src/index.js
Nothing happens. The output is our original module. The problem is that Babel 6.x does not ship with any transformations enabled. You need to tell it explicitly what transformations to run. The simplest way to do this is by using a preset, such as the ES2015 Preset. Presets are sharable configurations that bundle a collection of plugins. Plugins are extensions to Babel that allow it to parse specific types of syntax or apply transformations to your code.
Let's install and use the ES2015 Preset.
$ npm install --save-dev babel-preset-es2015 $ ./node_modules/.bin/babel --presets=es2015 src/index.js
This outputs the following ES5 code in CommonJS module format:
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = function () { var who = arguments.length
Rather than display the transformed code on standard output let's save it to a file and test with node.
$ ./node_modules/.bin/babel --presets=es2015 src -d lib $ node > greet = require('./lib/index.js').default > greet() 'Hello, world!' > greet('Dwayne') 'Hello, Dwayne'
I hate having to type ./node_modules/.bin/babel all the time to compile the code. Luckily the package.json file provides a scripts property we can use to run various commands. Learn more about the scripts property here.
{ ... "scripts": { "build": "babel --presets=es2015 src -d lib" }, ... }
We can now simply execute npm run build.
Better Babel configuration
Passing options to Babel via the command-line is fine when there aren't that many options to pass. But things can start to get unwieldly rather quickly. A better practice will be to either use the .babelrc file (see here) or the babel property inside of the package.json file (see here).
{ "presets": ["es2015"] }
{ ... "babel": { "presets": ["es2015"] }, ... }
Set the entry point of the package
{ ... "main": "lib/index.js", ... }
Remember, we use the ES5 build because that's what Node.js understands.
Point ES2015-aware tools like Rollup to the entry point of the package
{ ... "jsnext:main": "src/index.js", ... }
Specify the files we want included in our package
{ ... "files": [ "lib", "src" ], ... }
$ npm install --save-dev rimraf
{ ... "scripts": [ "clean": "rimraf lib", ... ], ... }
Ensure lib is built before publishing the package
{ ... "scripts": [ ... "prepublish": "npm run clean && npm run build" ] ... }
This hook is also run on local npm install which we will take advantage of in order to test install the package and use it.
Test install the package and use it
$ cd /tmp $ npm install path/to/hello-world $ node > greet = require('hello-world').default > greet('Keisha') 'Hello, Keisha!'
Note: If you need help installing Git, I did an extensive write-up here.
$ git init $ echo -e 'lib\nnode_modules' > .gitignore $ git add . $ git commit -m "Initial development release"
Here are some other things you can do.
Publish your package to npm.
Push your repository to GitHub so that others can contribute.
Email me if you need assistance. Happy hacking :).
Packages and Modules - Learn the vocabulary
Choosing an open source license doesn't need to be scary