While working at Ionic I’ve been focused on the Ionic CLI.
My first big refactor of the CLI was pulling out most of the 21 commands it offers into an external library (ionic-app-lib) that could be consumed by both the Ionic CLI and our GUI – Ionic Lab.
The refactor went rather smoothly.
However, one thing happened that was not expected – now that the ionic-app-lib bundled all the commands together, whenever you required the app-lib module, it was rather slower than expected.
For example, whenever you ran: var IonicAppLib = require('ionic-app-lib'); – it would take a wee bit longer.
Here’s the code for the included moduled ionic-app-lib:
As you can see, whenever this module is require’d in, it require’s even more modules. This means, more file read requests and fulfilling those just to get this module working.
Also to note – anytime a new command was added in, it must be exported by adding in another annoying require statement.
Lazy loading via JavaScript getters
While looking through other open source projects, I came across the idea of lazy loading your modules on demand.
One way to do this is with JavaScript getters being defined. We wont require the module until it is requested.
For example, the code snippet:
12345
varIonicAppLib=require('ionic-app-lib');varoptions={port:8100,liveReloadPort:35729};//Do not load the serve command until it is requested as below:IonicAppLib.serve.start(options);
What’s happening above – require('ionic-app-lib') is called, which sets up the getters for start, serve, run, etc. Then, when the command is called, the require for the module then happens, thereby getting the module loaded, and returning it to the caller.
Here’s that code to enforce the lazy loading of modules:
varfs=require('fs'),IonicAppLib=module.exports,path=require('path');varcamelCase=functioncamelCase(input){returninput.toLowerCase().replace(/-(.)/g,function(match,group1){returngroup1.toUpperCase();});};//// Setup all modules as lazy-loaded getters.//fs.readdirSync(path.join(__dirname,'lib')).forEach(function(file){file=file.replace('.js','');varcommand;if(file.indexOf('-')>0){// console.log('file', file);command=camelCase(file);}else{command=file;}IonicAppLib.__defineGetter__(command,function(){returnrequire('./lib/'+file);});});IonicAppLib.__defineGetter__('semver',function(){returnrequire('semver');});
Testing
I threw together a quick test to ensure that all of the modules were still correctly being accessible:
123456789101112131415161718192021222324252627
varindex=require('../index');describe('index',function(){it('should have index defined',function(){expect(index).toBeDefined();});functiontestForProperty(input){it('should have '+input+' available',function(){expect(index[input]).toBeDefined();});}varobjs=['browser','configXml','cordova','events','hooks','info','ioConfig','login','logging','multibar','opbeat','project','share','semver','serve','settings','setup','start','state','stats','upload','utils'];// Doing it this way to give better failure messages. // Ensures all commands are available currently fromobjs.forEach(function(obj){// expect(index[obj], obj).toBeDefined();testForProperty(obj);});});
Gotchas
For one – you’ll need to ensure your files adhere to some naming conventions. For our commands, we had some with hyphens (-) that we had to account for, as you can see above if (file.indexOf('-') > 0).
Also – if you want to export other modules you can set up other getters, as I did with semver above.
If you want to short circuit lazy loading, go ahead and just export them as normal.
Performance
We say about a 8x performance increase by lazy loading the modules.