Optimization

Mimosa utilizes RequireJS and Uglify to make your code production ready when you need it to be

In the normal course of development assets are best loaded individually rather than in one merged file. And assets definitely should not be minified or mangled. This all makes for easier debugging and for speedier compile and load times. But when an application is taken to production, all the performance improvements that come with merging, optimizing, and minifying assets should be included.

For both the build and watch commands, Mimosa provides --optimize and --minify flags that combine to provide both the ability to fully optimize an application, and to have a more control over what gets optimized.

Via the mimosa-require module that comes with Mimosa by default, Mimosa uses the r.js optimizer which is perfect for optimizing RequireJS applications. Underneath the hood, that optimizer is using Uglify2 to shrink a codebase down as tiny as possible. Mimosa bundles Uglify2, and can use it to individually shrink assets. And Mimosa's Uglify and the r.js optimizer can be combined to provide more fine-grained control over a minification strategy.

Out-of-the-box Mimosa supports RequireJS based optimization. But if browserify is your favorite build tool, then check out the mimosa-browserify module. The GitHub page has details on its use and the author has put an example app up show how to use it.

But if RequireJS is your thing...

Mimosa's RequireJS optimization support comes courtesy of the mimosa-require module, a default Mimosa module that comes with Mimosa when it is installed.

For both the build and watch commands, Mimosa provides an --optimize flag that will turn on optimization. Mimosa wraps the r.js optimizer and runs it with a default configuration gleaned from the setup of the application.

When a codebase is optimized and pulled together into one file, the RequireJS library is no longer necessary. Mimosa will wrap r.js optimized code in Almond, a replacement AMD loader for RequireJS that provides the minimal functionality necessary for optimized files. (If you are loading modules/resources from a CDN, Almond does not support that. See this Mimosa issue for help dealing with CDN resources.)

Optimizer Default Settings

Mimosa will infer the following default settings for the r.js optimizer.

  • baseUrl: set by combining the mimosa-config watch.compiledDir with the compilers.javascript.directory
  • mainConfigFile: set to the file path of the main module unless a common config (require.commonConfig setting) is detected. If a common config is found, mainConfigFile is set to that.
  • findNestedDependencies: true
  • wrap: true
  • logLevel: set to 3 which is the r.js error logging level
  • optimize: set to uglify2 unless both the --optimize and --minify flags are used, in which case it is set to none

For single file runs without modules, the following will also be inferred.

  • out: optimized files are output into the watch.compiled + compilers.javascript.directory in a file that is the main module name + -built.js
  • include: set to the name of the module being compiled
  • insertRequire: set to the name of the module being compiled
  • name: set to almond

For runs that involve the optimize.modules config, the following in inferred.

  • keepBuildDir: Keeps the build directory between builds.
  • modules: This isn't inferred, rather it is taken without modification from the require.optimize.modules config. It is the presence of optimize.modules that triggers the different group of inferred properties.
  • dir: This is set to the relative path from the root of the project to the root of the javascript directory as defined in the watch< config. This is where all the outputs from the run will be deposited.

These settings will package up each individual module into its own optimized file wrapped with Almond.

Overriding and Including Additional Optimizer Settings

Any of the other RequireJS optimizer configuration options can be included in the mimosa-config. Just uncomment the require.optimize.overrides setting and include those settings there. Settings can be both overridden and removed. To override a Mimosa setting, put the override in overrides. To remove a default setting, set it to null.

require.optimize.overrides can also be configured as a function. That function is passed the full inferred r.js config for the module being optimized. This provides the opportunity to amplify the inferred config rather than just replace it.

Mimosa can also be configured to not infer anything and to go entirely with a custom config. Set require.optimize.inferConfig to false and Mimosa will run r.js with only the settings provided in require.optimize.overrides.

Also use require.optimize.inferConfig:false if configuration settings are in script tags in an HTML file, or in any other file that does not compile to JavaScript. For now, Mimosa is only able to make inferences for configs in JavaScript files. If a config (and require/requirejs method calls) are in script tags on an HTML page, Mimosa will not find any modules to compile for optimization and therefore will not run optimization, so a custom configuration will need to be provided in overrides with inferConfig set to false.

Regarding "modules" based builds

As noted above, Mimosa has specific support in place for configuring and running require.js builds involving a modules config. As of Mimosa version 1.0.8 source maps are disabled by default for r.js builds that involve the modules config. Source maps are only generated when using mimosa watch, so when running watch with the optimize flag and a modules config, Mimosa turns off source maps. Source maps do not cause any issues with the first r.js optimize run, but they do cause trouble with subsequent ones. Source maps can be forced on by adding generateSourceMaps: true to the require.optimize.overrides. Just know the 2nd+ runs will not output proper files.

This should be remedied in future Mimosa releases.

Programmatically Take Control of the Optimizer with Mimosa Modules

Mimosa can't know all the intricacies of a project. It can make a lot of educated guesses and put together a really good base r.js config, but there are times when complicated alterations must be made to the r.js config. overrides allows static changes to the r.js configuration, but that isn't always ideal. require.optimize.inferConfig:false loses all of the smarts Mimosa puts into building a r.js config. Ideally a project can take advantage of the work Mimosa puts into building the r.js config, and dynamically alter it as well.

Mimosa's building of the inferred r.js config, and the execution of the r.js optimization are pulled apart in two separate steps in Mimosa's workflows. Config building executes during the beforeOptimize step, and execution during the optimize step. This means a custom module can programmatically and dynamically alter the Mimosa-prepared r.js configs before r.js execution occurs.

For instance, maybe there are files to include in the r.js execution that are not pulled in as dependencies by r.js, maybe all .html files via the requirejs text plugin, and rather than listing them one by one in the overrides, they could be dynamically added so a list need not be maintained. A module could do this, executing during the beforeOptimize step, but after the configs have been built. In that module the codebase could be scanned for .html files and push them onto the r.js config include array.

Doing this provides both Mimosa's smarts, and the intelligence Mimosa can't provide by way of a custom module. To get started building such a module, check out the long-named example mimosa-requirebuild-textplugin-include which performs just the task mentioned above.

Source Maps During Development

When mimosa watch is used with --optimize (and not also --minify which does minification outside of requirejs), Mimosa will generate source maps for easy debugging in browser dev tools. mimosa build does not generate source maps. All cleans performed by various Mimosa tasks will clean up the generated .src and .map files. Because generation of source maps is configured via the r.js config, which can already be overridden in the require config, no additional config settings have been added to change the default behaviors.

Mimosa's Minification support comes courtesy of the mimosa-minify-js and mimosa-minify-css modules which are both default modules that come with Mimosa when it is installed.

For both the build and watch commands, Mimosa provides an --minify flag that will run Uglify2 over JavaScript assets. The mimosa-config provides the ability to exclude certain files from this minification using the minifyJS.exclude and minifyCSS.exclude settings. By default any file already possessing .min. in the path name will be excluded as it is assumed to already be minified.

If minifying a file that has a sourceMap the minifier will detect the source map and use it to create a new map. So, for example, CoffeeScript compiled to JavaScript and then minified will have source maps that map the minified code back to the CoffeeScript source.

For either the build or watch commands, using the --minify and --optimize flags together provides increased control over the optimization workflow.

The r.js optimizer by itself is often good enough to handle minifying, compressing and pulling modules into single files; however, the occasional file does not take kindly to being run through Uglify and will be broken when compressed. The r.js optimizer is all or none. It does not allow omitting a file from compression if it is not compressing correctly.

When both minifying and optimizing, r.js minification is turned off. Specifically the optimize setting of r.js is set to none. The optimizer is still used to pull all of an application's files together into the same file and wrap it in Almond. But Mimosa will Uglify files first, before calling the optimizer, and Mimosa provides the opportunity to not Uglify whatever files are not making it through Uglifying in working order.

If a project has many pages, and therefore many optimized files that need to be written, using both flags at the same time will also speed up the build. If Mimosa writes minified files, then the files are minified once and the minified versions are used to build the many optimized files. If Mimosa writes un-minified files, then those files have to be minified by the r.js optimizer. The optimizer is going to minify each file every time it is used. So if the unminified jQuery source is in a project's codebase, and it is used on 10 pages and therefore is bundled into 10 optimized files, then r.js will minify it 10 times, which can slow the build. So, if a build is running slow, try using both flags at once to speed it up.

The AngularFunMimosa demo project is a perfect example of a project that benefits from this functionality. This commit from that project shows the effect of the minify+optimize feature being added to Mimosa. Previously optimization was turned off via Mimosa's r.js overrides, because when r.js Uglified the code, it also broke the application. When this Mimosa feature was added, the r.js override was removed, and the file that was not surviving Uglifying, main.js, was simply omitted from minification. What comes out the other side is a fully r.js optimized and zipped up file that is fully compressed with the exception of the file that cannot be minified.

When the --optimize flag is used, Mimosa will minify CSS files too.

The external mimosa-combine module can be used to do things like pull compiled or vendor CSS files together...or anything really. All mimosa-combine wants is a folder to merge and a place to put the result, but it also allows assets to be excluded and an order to be provided.

Just tack the string "combine" onto the mimosa-config modules array and Mimosa will fetch the module for immediate use.