Modular JavaScript in the Browser with CommonJS Compiler
Join the DZone community and get the full member experience.
Join For FreeJavaScript was designed as a script language that is easy to embed in a larger host system and meant to manipulate the objects of the host system. With the advance of HTML5 formerly mostly static web-pages are turning into sophisticated web-applications. Now we expect JavaScript code to be scalable and modular. But how when JavaScript has no built-in facilities to combine distinct scripts?
Surely, you can insert a script element into the DOM and therefore have outer scripts loaded dynamically. Then you will have to deal with asynchronously loaded scripts and resolve all the required dependencies prior to running any depended code. In fact there are plenty of libraries that can do it for you (http://wiki.commonjs.org/wiki/Implementations). While going AMD you will quickly realize that every module makes a separate HTTP request that badly affect the application performance. Well you can use the tools like r.js to combine modules in a single build. Still it brings an entire library into the resulting code. With compiled the modules we hardly need so much of extra complexity. After the compiler enclosures the modules in unique scopes, we only need a function to manage access to these contexts. Let’s take CommonJS module format. I do love how it is implemented in NodeJS – pretty concise, but incredibly capable. It introduces module object representing a module and available in every module scope. So the module scope can look like that:
require.def( "module-id", function( require, exports, module ){ // Module constructor var moduleObj = {}; module.exports = moduleObj; return module; });
Bow we need the require function to give access from module to module. Following NodeJS when require is called the first time the module is being constructed. Every second call takes the exported object from the cache. That can be implemented with the following code:
/** Define scope for `require` */ var require = (function(){ var /** * Store modules (types assigned to module.exports) * @type {module[]} */ imports = [], /** * Store the code that constructs a module (and assigns to exports) * @type {*[]} */ factories = [], /** * @type {module} */ module = {}, /** * Implement CommonJS `require` * @param {string} filename * @returns {*} */ _require = function( filename ) { if ( typeof imports[ filename ] !== "undefined" ) { return imports[ filename ].exports; } module = { id: filename, filename: filename, parent: module, children: [], exports: {}, loaded: false }; // Called first time, so let's run code constructing (exporting) the module imports[ filename ] = factories[ filename ]( require, module.exports, module ); imports[ filename ].loaded = true; if ( imports[ filename ].parent.children ) { imports[ filename ].parent.children.push( imports[ filename ] ); } return imports[ filename ].exports; }; /** * Register module * @param {string} filename * @param {function(module, *)} moduleFactory */ _require.def = function( filename, moduleFactory ) { factories[ filename ] = moduleFactory; }; return _require; }());
This design expects the compiler to replace ids given in require calls with the fully resolved filenames. So the filenames become unique identifiers for the modules. Besides, compiler must detect the require calls combinations causing infinite loops.
Using RequireJS Compiler
I released the compiler on GitHub https://github.com/dsheiko/cjsc. It is a NodeJS package that can be used as easy as it:
./cjsc main-module.js build.js
Let’s write a few modules to see what it does.
./main.js
onsole.log( "main.js running..." ); console.log( "Imported name in main.js is `%s`", require( "./lib/dep1" ).name ); console.log( "Getting imported object from the cache:" ); console.log( " imported name in main.js is still `%s`", require( "./lib/dep1" ).name );
./lib/dep1.js
console.log( "dep1.js running..." ); console.log( "Imported name in dep1.js is `%s`", require( "./dep2" ).name ); module.exports.name = "dep1";
./lib/dep2.js
console.log( "dep2.js running..." ); module.exports.name = "dep2";
After we compile the main.js module and fire up the derived build in the browser, we get the following output:
main.js running... dep1.js running... dep2.js running... Imported name in dep1.js is `dep2` Imported name in main.js is `dep1` Getting imported object from the cache: imported name in main.js is still `dep1`
Well, the dependencies resolved by given ids based on relative paths, module constructors ran when required and only once – everything went as under NodeJS.
Supporting RequireJS modules
What if we call for a UMD module? Let’s try:
./main.js
console.log( "%s is running...", module.id ); console.log( "%s imports %s", module.id, require( "./umd/module1.js" ).id );
./umd/module1.js
// UMD boilerplate according to https://github.com/umdjs/umd if ( typeof module === "object" && typeof define !== "function" ) { /** * Override AMD `define` function for RequireJS * @param {function( function, Object, Object )} factory */ var define = function ( factory ) { module.exports = factory( require, exports, module ); }; } define(function( require, exports, module ) { console.log( "%s is running...", module.id ); return { id: module.id }; });
The build output:
./umd.js is running... ./umd/module1.js is running... ./umd.js imports ./umd/module1.js
Everything is fine.
Automating build process
On the development environment we can have the fooling Grunt configuration:
Gruntfile.js
module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-cjsc'); grunt.loadNpmTasks('grunt-contrib-watch'); grunt.initConfig({ cjsc: { development: { options: { minify: true }, files: { "js/build.js" : "./js/modules/main.js" } } }, watch: { options: { livereload: false }, build: { files: ['./js/modules/**/**/*.js'], tasks: ['cjsc'] } } }); grunt.registerTask( "default", [ "cjsc" ]); };
And development dependencies in package.json
"devDependencies": { //.. "grunt-contrib-watch": "~0.4.4", "grunt-contrib-cjsc": "*" }
Now we can make Grunt compiling the build.js automatically every time any of modules changes:
grunt watch
So as you see you can write CommonJS modules and have them running in the browser without any additional library. If you ask me about Browserify – it looks awesome. Honestly I wasn’t simply aware about that solution while building RequireJS compiler.
Published at DZone with permission of Dmitry Sheiko, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
How To Scan and Validate Image Uploads in Java
-
Simplifying SAP Data Integration With Google Cloud
-
8 Data Anonymization Techniques to Safeguard User PII Data
-
File Upload Security and Malware Protection
Comments