bower/bower — pull request #1134 by geigerzaehler (2014-02-24 17:57:31+00:00)
Title: Loading commands on demand increases performance

I am not the first one to notice that requiring bower is quite slow (see #958). The problem seems to be that bower in turn requires a lot of modules, and somewhere deep down the stack it does some expensive work. I could not get to the bottom of this. Unfortunately [theseus](https://github.com/adobe-research/node-theseus) was not much help, as I didn't manage to profile `require` calls. However, I figured out that loading `lib/commands/index.js` is quite expensive.

### Implementation

This commit tries to alleviate the problem in `lib/commands/index.js` by wrapping `require` into a `lazyRequire` function. This means that `commands/list.js`, for example, is only required when calling `bower.commands.list()`. In addition executes the `require` call asynchronously so that `list()` returns almost immediately.

But the performance gains from this are almost none if we require `lib/core/PackageRepository` in the index file. Therefore, I moved this call into the `clearRuntimeCache` function of `lib/index.js`. This, however, will be called synchronously and might thus block for a while.

### Benchmark

I created a simple [benchmark script](https://gist.github.com/geigerzaehler/9192495) to compare my approach with the current implementation. It times the statements `require('bower')`, `bower.commands.list()`, and measure how long it takes for the command to emit 'end'. For my machine the results are

require
lazyRequire

require bower
1.54s
0.63s

call command
0.01s
0.00s

command finished
0.01s
0.75s

total
~1.6s
~1.4s

I also timed running `bower list` and got 2s for default `require` versus 1.4s for `lazyRequire`

This shows that the top-level require is significantly faster but it takes most of the saved time to complete the command. But because calling the command still returns quickly, this is in line with the asynchronous command API.

There is also a net increase in speed of about 0.2s. This is due to the fact that we load one command. Since the bower cli script only uses one command per invocation, this explains its boost in performance.

### Conclusion

Lazy-loading significantly increases performance for both API consumers and the command line scripts. It decreases time to call blocking functions while increasing time for asynchronous tasks, which is the way to go on node. Although this approach suffers from the blocking call to `bower.clearRuntimeCache()`, the documentation indicates that this is not supposed to be used frequently. It seems to be a good tradeoff.

### Future changes

The current implementation, though unobtrusive, is a bit messy. It creates proxy loggers and duplicates code already in the command functions. Maybe it would be a good idea to remove the logger code from the command functions, let them return a promise and hook in the logger in the `lazyRequire` function. This would also reduce code duplication in the commands.