RequireJS provides an effective means of bundling an application for production deployment by way of the RequireJS Optimizer; which allows for optimizing, concatenating and minifying modules via Node or Rhino.
The Optimizer is quite powerful insofar that fine-grained control over various aspects of the optimization process can be implemented with relative ease. One such example is the ability to specify a callback function to be invoked for each module in the build prior to optimization. This essentially provides the necessary hooks needed to implement a preprocessor.
The onBuildWrite option
The optimize
method of the RequireJS Optimizer accepts an onBuildWrite
option which allows for a callback to be specified. The callback will be invoked prior to serialization of each module within the optimized bundle. Callbacks receive the name, path and contents of the module; and, are always expected to return a value.
For example, consider the following build configuration which demonstrates a basic onBuildWrite
callback that simply logs the name of each module processed by the build to the console and, returns the module’s content unmodified.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | var config = { baseUrl : 'src', appDir : '../', dir : '../deploy', mainConfigFile : '../src/main.js', onBuildWrite : function(name, path, contents) { console.log('Writing: ' + name); return contents }, modules: [{ name: 'main' }] }; requirejs.optimize(config, function(results) {...}); |
Using the above configuration, when executed against a (contrived) example consisting of just a single module, “ModuleA”, in Node, it would output the following to the console:
1 2 3 4 5 6 7 8 9 10 | $ node build.js Writing: main Writing: ModuleA src/main.js ---------------- src/app/module.a.js src/main.js |
If we were to print out the contents of the files we would see something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | $ node build.js Writing: ModuleA define(function() { return { name: "Module A" } }); Writing: main require.config({ paths: { ModuleA: 'app/module.a' } }); require(['ModuleA'], function(moduleA) {...}); src/main.js ---------------- src/app/module.a.js src/main.js |
With this in mind, a basic preprocessor can be implemented quite easily using the onBuildWrite
option. Assume the main.js
script has a token placeholder for the build version like so:
1 2 3 4 5 6 7 8 9 10 11 | /*! * #version */ require.config({ paths: { ModuleA: 'app/module.a', } }); require(['ModuleA'], function(moduleA) {...}); |
We can implement a simple preprocessor which replaces the #version
token with a build date as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | var config = { baseUrl : 'src', appDir : '../', dir : '../deploy', mainConfigFile : '../src/main.js', onBuildWrite : function(name, path, contents) { console.log('Writing: ' + name); if (name === 'main') { // output the original source contents console.log(contents); // perform transformations on the original source contents = contents.replace(/#version/i, new Date().toString()); // output the processed contents console.log(contents); } // return contents return contents; }, modules: [{ name: 'main' }], }; requirejs.optimize(config, function(results) {...}); |
The above onBuildWrite
callback logs the original contents, replaces the #version
token with the current date, logs the result and returns the processed content. The output of which would be similar to the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | $ node build.js Writing: main /*! * #version */ require.config({ paths: { ModuleA: 'app/module.a' } }); require(['ModuleA'], function(moduleA) {...}); /*! * Fri Mar 23 2012 23:35:17 GMT-0400 (EDT) */ require.config({ paths: { ModuleA: 'app/module.a' } }); require(['ModuleA'], function(moduleA) {...}); src/main.js ---------------- src/app/module.a.js src/main.js |
As can be seen in the above examples, implementing a basic preprocessor is rather simple to accomplish with the RequireJS Optimizer. The ability to do so allows for some interesting possibilities and is certainly something worth checking out if you are leveraging AMD via RequireJS.
You can fork an example implementation at requirejs-preprocessor-example.
found new about reuquireJs in your article, thank you!
Hello, Eric. Thanks for your article. Do you know there’s something similar to this but for the files loaded via the text! plugin??
What I want to to is to preprocess the template files before the r.js runs. Specifically, I want to run them against htmlcompressor.
Any ideas of how to accomplish this? Thank you very much.
Oops, forgot to subcribes for comments follow up.
Thanks Eric, this helps me to remove custom logging from built output.