JavaScript

Creating and Publishing Node.js Modules

You can split off reusable JavaScript into its own libraries called modules. A Node module is simply a JavaScript file. The export or module.exports statement is used when creating a Node module. You can convert functions, objects, or primitive values into module that might be useful to outside code to exports.

Let’s say you have a function that takes two or more arrays or strings, merge them and return a new array:

//using spread syntax
function arrayMerge(...arrays){
 let a = [];
 arrays.forEach(e=>{
  Array.isArray(e)
   ? Array.prototype.push.apply(a,e)
   : a.push(e);
 });
 return a;
}

To convert above function to module, we need to save it in a separate file and then export it using the exports statement, as shown in the following code:

//arrays.js
module.exports.arrayMerge = arrayMerge;

function arrayMerge(...arrays){
 let a = [];
 arrays.forEach(e=>{
  Array.isArray(e)
   ? Array.prototype.push.apply(a,e)
   : a.push(e);
 });
 return a;
}

To use arrayMerge in a Node app, we need to import the module as shown in following code:

//app.js
let arrays = require('./arrays');
let arrayMerge = arrays.arrayMerge;
/* or
let {arrayMerge} = require('./arrays');
*/
let str = 'hello';
let arr = ['a','b','c'];

let newArr = arrayMerge(str,arr);

console.log(newArr);
//[ 'hello', 'a', 'b', 'c' ]

Let’s make our arrays.js module a little bigger, growing it to three functions:

//arrays.js
module.exports.arrayMerge = arrayMerge;
module.exports.hasValue = hasValue;
module.exports.delValue = delValue;

function hasValue(val,arr){
 return 'hasValue function';
}

function delValue(val,arr){
 return 'delValue function';
}
function arrayMerge(...arrays){
 ...
}
//minimum code

Next, we’ll import the module. Destructuring assignment (curly braces around the variables) is a great way to assign many functions exported by a required module to their local variables in a single line.

//app.js
Destructuring assignment
let {arrayMerge,hasValue,delValue} = require('./arrays');

let str = 'hello';
let arr = ['a','b','c'];

let newArr = arrayMerge(str,arr);

console.log(hasValue(str,arr));
//hasValue function
console.log(delValue(str,arr));
//delValue function

exports vs module.exports

As you inspect the code of Node modules you might see that some modules export their functionality using module.exports, while others simply use exports:

//arrays.js
exports.arrayMerge = arrayMerge;
exports.hasValue = hasValue;
exports.delValue = delValue;
/* vs. */
module.exports.arrayMerge = arrayMerge;
module.exports.hasValue = hasValue;
module.exports.delValue = delValue;

Both methods given above will do the same thing. The exports is just a reference to module.exports. Node’s core modules are defined using the module.exports statement. So, to be safe, just use the module.exports.

Exporting our FileWalker class as a module

We’ll just add the module.exports = before the class keyword, as shown:

//walker.js
const fs = require('fs'),
path = require('path'),
{EventEmitter} = require('events');
//class FileWalker extends EventEmitter {
module.exports = class FileWalker extends EventEmitter {

constructor (entry){
 super();
 this.readTree(entry);
}

readTree (entry) {
 entry = entry || this._entry;
 fs.lstat(entry, (err,stat) => {
  if (err){
   this.emit('error',err,entry,stat);
   return;
  }

  if (stat.isFile()){
   this.emit('file',entry,stat);
  }
  else if (stat.isDirectory()){
   this.emit('dir',entry,stat);
   fs.readdir(entry, (err,files) => {
    if (err){
     this.emit('error',err,entry,stat);
     return;
    }
    files.forEach( file => {
     this.readTree(path.join(entry,file));
    });
   });
  }
 });
}
}

In next tutorial we’ll expand our FileWalker module by adding some methods and events for filtering dirs and files. For example, if (dir == 'some/path') //skip or if (file.extension == 'ext') //skip.