15 Feb 2016
We’ll discuss libraries that solves different needs for software projects including libraries, modularization, automated building, linter and finally testing frameworks.
To work with npm, we need to write a configuration file, called
package.json. In this file, which is a JSON, we can define metadata when building a library, including title, version and the dependencies of other libraries. A sample configuration looks like this:
In the dependencies, we have to specify the versions. A version (more specifically semantic version or semver) consists of three parts numbers separated by
'.'. The last number should be bumped on small changes, like bug fixes, without change on functionality. The middle number, aka minor version, should be bumped whenever new features are added, but that are back-compatible. Finally, the first number, aka major version, should be bumped whenever back-incompatible changes are made .
package.json, you can specify a hard-coded version number or be more relaxed. If we use the
'~' in front of the version, for example
~5.11.0, it means we accept the most recent version of form
5.11.x. On the other hand, if we use the
'^', for example
^2.5.0, we accept the most recent version of the form
The dependencies of a package can be either production or development dependencies. In our case,
uglify are only used for building our package and not a dependency our code has, so it doesn’t make sense to ship those to the user of the library.
To parse the configuration in
package.json, we can run:
This will download the dependencies listed under devDependencies locally in the directory node_modules (created in the same directory the
package.json is). To run the production dependencies, we can do:
I had been working with browserify, but it seems better to adopt the ES6 standards, so I’ve switched to SystemJS. Another advantage of SystemJS is that is also allows ES6 syntax by transpiling the code using BabelJS.
To use SystemJS we need to define a configuration file (analogous to
package.json), named config.js (don’t worry about it for now).
Named exports. We can have multiple export statements within a single file or provide all exports within a single statement . Example:
Default exports. We can export default items in a module (the reason will be clear when we talk about importing next). We show the syntax for both the inline and the named exports:
We have 3 basic ways to import from a module.
To be able to import NPM packages, we have to download them first and for that we can use the jspm.io tool. For example, I was interested in the point-in-polygon package. Instead of running the npm command, we can use jspm:
Running jspm will write to the
config.js file (it creates one if it doesn’t exist). This will write a map from where the module got installed and the name you can use in code to import it. Since npm packages use the CommonJS syntax and SystemJS understands it, in code we can simply do:
To configure a build, we need to provide another configuration file, called
Gruntfile.js (should live in the same directory as the
package.json). You provide an object to
grunt.initConfig(), which contains tasks configurations.
grunt.registerTask('default', ['systemjs']) we’re telling grunt to run the systemjs task whenever we run
grunt from the command line.
It’s possible to run grunt automatically upon changes to JS files via the watch task. First, we need to install the plugin:
Then we configure it in
taskList is an array of task names. It can be the same one provided to the
default task. Make sure to blacklist some directories like
dist, which is the output directory of the
systemjs task (otherwise we’ll get an infinite loop). Finally we run:
Now, whenever we perform a change to any JS file it will run the task.
min.js to differentiate from the unminified version).
The source code can be compressed by removing extra spaces, renaming variables, etc, without changing the program. One popular tool to achieve this is UglifyJS.
To use it with Grunt, we can install the
And in our
The basic configuration here makes sure to blacklist “production” directories like
dist. Also, since we’ve been adopting ES6, we can set the
esnext flag to tell jshint to account for the new syntax.
We probably don’t want to run the lint every time we update the JS file. We can run it less often, for example before sending code for review. Thus, we can create a separate registry for it using
grunt.registerTask('lint', ['jshint']). We can now run jshint via the command line:
Another practice to avoid bugs is testing, including unit tests. Again, there are several libraries and frameworks that makes the job of unit testing less painful, for example easy ways to mock dependencies so we can test isolated functionality. In this case, I’ve picked Jest, which has a grunt task available in npm, which we can install via:
(NOTE: this will also install the
jest-cli binary which depends on a Node.js version
>= 4, so you might need to update your Node.js).
We can configure the grunt task with default configs in the following way:
With this setup we can run the following command to run jest tests:
Unfortunately, jest uses the CommonJS require syntax. It used to be possible to use
babel-jest but after version 5.0 this setup doesn’t work anymore.
To make things worse, for every task like module system, linting, testing, there are many alternatives and none of them is a clear best choice.
I’m happy that there’s an effort of standardization with ES6. I think the more we stick to one convention the more we reduce re-inventing the wheel, the less syntax differences to learn, etc.