Showing posts with label nodejs. Show all posts
Showing posts with label nodejs. Show all posts

Thursday, June 16, 2016

Browserify Winston

1. The problem.

Winston 1.0 and still 2.0 uses fs.readdirSync() which is not supported by brfs and the other goto fs transform modules.

2. The solution.

Browserify lets us write our own transforms. Thus if we take the MVP path, we can write two files one, that substitutes require("mockLogger") in for require("winston") out of every src file, and the second file to define the "mockLogger".

Assume we have file called browserTest.js as follows:

// browserTest.js
var winston = require('winston')
console.log('I am browserified!')
winston.info('I am info!')
winston.debug('I am info!')
winston.silly('I am info!')
winston.error('I am info!')

If we have a mockLogger.js file as follows:

// mockLogger.js
module.exports = {
  error: function(e) {
    console.log(e)
  },
  warn: function(d) {console.log(d)},
  verbose: function(d) {console.log(d)},
  info: function(d) {console.log(d)},
  debug: function(d) {console.log(d)},
  silly: function(d) {console.log(d)},
};

Then we can define a transform file called wtc.js like this:

// wtc.js
var through = require('through2')

module.exports = function(file) {
  return through(function(buf, enc, next) {
    this.push(buf.toString('utf8').replace(/require\(.winston.\).*(;)?/g,'require("./mockLogger");'))
    next()
  })
}


And the final browserify cli command:

browserify browserTest.js -t ./wtc.js > bundle.js

If you like you can run the above command without specifying the "-t ./wtc.js" part and if you simply try to run "node bundle.js" you will see a "TypeError: fs.readdirSync is not a function" Error.

Of course you don't really want to rely on the cli for production software. In that case you can forgo the transformer and just map the module substitution in your package.json like so:

//package.json
{
  "browser": {
    "winston": "./mockLogger"
  },
  "scripts" {
    "browersify": "browserify -e browserTest.js -o bundle.js"
  }
}

Your command then becomes:

npm run browserify

Sunday, June 1, 2014

Asychronous NodeJs

I have been doing a lot of work with Cordova lately and struggled a little to get my head around some of the event driven concepts of NodeJs, Promises, and Callbacks. This became apparent when trying to test my NodeJs with Jasmine. I had written a method much like this one:
exports.doAsync = function(my_str) {
  fs.exists(fileName, function() {
    // fileName, callback declared elsewhere
    fs.writeFile(fileName, my_str, callback);
  });
}
Even though I knew somewhere deep down in my heart that Callbacks meant asychronous I still wrote my test like so:
it("should be an async method", function () {
  //setup
  doAsync(param1);
  expect(fs.writeFile).toBeCalled();
});
This test failed everytime because I had no mechanism in place to tell test code when that first callback had been executed. You'll notice my first mistake was writing the function prototype first, which did not include a callback parameter. This anti-BDD stunt influenced my presuppositions when writing the test. To make it work correctly, I needed the following changes:
// In my file.js
exports.doAsync = function(my_str, win) {
  fs.exists(fileName, function() {
    // fileName, callback declared elsewhere
    fs.writeFile(fileName, my_str, callback);
  });
  function callback(err) {
    win(err);
  }
}
// in file.spec.js
it("should be an async method", function (done) {
  //setup
  doAsync(param1, function() {
    expect(fs.writeFile).toBeCalled();
    done();
  });
});
This is a bit contrived because to make this work you'd need a bit more setup. (You can view the source from which this is derived in the file around here). However, in this post I simply want to share a mental tool that has helped me grasp these ideas and detect my conceptual errors quicker.
I worked in a restaurant for a few years and so I imagine our single-threaded Javascript engine as a kitchen with only one cook. The cook, Long John Silver, actually has to do many things quickly to handle all the orders his waiters are taking on the floor. If we just opened and I only have one table whose ordered our famous Eggs and Salsa, I can put in the order and go have a chat with Long John while the eggs cook. When the dish is done, I could end the chat and deliver them to the customer and everyone tips 20%, even though this has been synchronous and inefficient. Obviously, when we're at the peak dinner hours everyone works asynchronously. John will do many other things while waiting for the eggs to cook.
In my test case above, I was essentially saying, "Long John, can you make me Eggs and Salsa?" He gruffly responds, "Sure" and because he's forgotten the Salsa on previous eggs, I immediately begin to ask, "Did they come with Salsa?" (past tense). He's just gotten the eggs in the pan so it hardly makes sense to ask a past-tense question about a future state of the eggs. So then, I either have to check back every five minutes asking the past tense question expect(fs.writeFile).toBeCalled() or I can simply have him text me when the meal is ready. Using the Callback or the Promise is like saying, "Just text me when your done."
This analogy works in a few other respects. If you want to test the process of how he makes eggs, you can give him fake veggies so he doesn't have to go search for them in the fridge (module dependencies or data). Either way, you can only check his work when he's finished with it.
Just remember, one cook, one thread, so you'll need to be making sure you have ways for your cook to notify you when he's done. Otherwise everyone waits in line.