Modules

Extend Mimosa's default functionality, and publish your own extensions

So maybe Mimosa doesn't come with exactly what you need out of the box. Maybe you want to use a different linter, or you want to kick off your testing suite on every file save. A module can probably help.

A Mimosa module is a node.js project that, when installed inside Mimosa, can be used as part of a Mimosa workflow or introduce a new Mimosa command. Mimosa is able to use a module because modules implement a simple interface Mimosa can access.

There's a strong chance what you need can be created as a new module. In most cases all that is required to install a module is to include the module's name in the Mimosa configuration. Mimosa self-installs modules it comes across but does not already know about.

Included Modules

The table below lists the modules that come baked into Mimosa. These do not need to be installed separately.


Name Description
lint CSS and JS linting/hinting
require AMD/RequireJS support including optimization
server Mimosa-based hosting and managing user hosting
live-reload Application reloads when the code does
minify Minification of JavaScript assets

External Modules

The table below lists the modules that do not come with Mimosa, but can be installed.


Name Description
web-package Packaging of node-based user server for deployment
require-commonjs CommonJS support via AMD/RequireJS
combine Combine all non-binary files in a folder into a single file
karma Karma (formerly Testacular) test runner integration
testem-simple Simple Testem test runner integration.
server-reload Restart a node server when server assets change
import-source Copy other files into a project to be included as part of the project.
server-template-compile Copy your server templates into static HTML as part of a build.
requirebuild-textplugin-include Both an example module for modifying the r.js configuration dynamically, and a functional module that will include files attached to the text plugin to r.js runs.
volo Introduces a import command that wraps Volo functionality. Comprises the import command that came bundled with Mimosa pre 0.7.2.
skeleton Adds three new commands to interact with Mimosa skeletons. This module will eventually be a default included Mimosa module once more skeletons have been developed. For more information, visit the github page.
client-jade-static Compiles Jade files with a `.html.jade` extension statically and writes them out as plain HTML.

There are four ways to install a module.

The easiest way: just update the mimosa-config modules array

One way to install a module is to just add it to the modules array in the mimosa-config for a project. This is a shortcut for the project level npm install (covered next). The next time Mimosa is started for that project it will install the module into the Mimosa project from NPM. For example, to add the mimosa-web-package module, include web-package in the uncommented modules config. Run, for instance, mimosa watch, and the first thing Mimosa will do is go fetch mimosa-web-package from NPM.

To use a module it needs to be added to the modules array anyway, so this shortcut saves the installation step.

Project npm install

Place and install Mimosa modules in the project itself and Mimosa will prioritize those modules above all others. Run npm install mimosa-nameOfModule and the module will be installed. Modules installed inside a project take precedence over any modules installed into other scopes.

mimosa mod:install

One way to install a module is the mod:install command discussed further below. mimosa mod:install mimosa-web-package will install mimosa-web-package from NPM. The module will still need to be added to any projects that need to use it. This command adds the module right into the Mimosa install as if it was installed with Mimosa itself. This allows Mimosa to draw from it when building a new mimosa-config. If a module is installed inside Mimosa, Mimosa will include that module's configuration snippet inside any generated mimosa-config.

npm install

Use npm install -g to install any modules into the global node scope. Modules installed with mod:install take priority, so if any modules are installed with both npm install -g and mod:install, the npm install -g module will be ignored.

Install specific module version

To install a specific version of a module, to, for instance, ensure everyone on a project stays on the same version, add a @ followed by the version after the module name. Ex: web-package@0.1.0. This is also accomplished using by managing versions in a project's package.json.

mod:list

Execute mod:list to see a list of all the installed Mimosa modules. The displayed list includes the name, version, and URL of the website of the module.

$ mimosa mod:list

Get more details (-v/--verbose)

Add a --verbose flag and Mimosa will including a short description and the list of dependencies the module has on other node.js modules.

$ mimosa mod:list --verbose
$ mimosa mod:list -v

Davids-MacBook-Pro:webapp dbashford$ mimosa mod:list

The following is a list of the Mimosa modules you have installed.

Name              Version   Website
mimosa-server     0.2.1     http://www.mimosajs.com
mimosa-require    0.2.0     http://www.mimosajs.com
mimosa-minify     0.2.0     http://www.mimosajs.com
mimosa-lint       0.2.0     http://www.mimosajs.com

To view more module details, execute 'mimosa mod:list -v' for 'verbose' logging.

Davids-MacBook-Pro:webapp dbashford$

mod:search

mod:search will search NPM to find all the available Mimosa modules. This command can take some time as it talks over HTTP to NPM, finding all available modules and then narrowing down the list to just those prefixed with 'mimosa-'.

$ mimosa mod:search

Included in the list of modules is the name, current version, last time it was updated, whether or not it is already installed, and a website for the module.

Get more details (-v/--verbose)

Add a --verbose flag and Mimosa will include additional information including a short description and the list of dependencies the module has on other node.js modules.

$ mimosa mod:search --verbose
$ mimosa mod:search -v

Davids-MacBook-Pro:webapp dbashford$ mimosa mod:search

Searching NPM for Mimosa modules, this might take a few seconds...

The following is a list of the Mimosa modules in NPM.

Name           Version Updated    Installed? Website
mimosa-logger  0.1.0   2012-10-21 no         http://www.mimosajs.com
mimosa-server  0.2.1   2012-10-25 yes        http://www.mimosajs.com
mimosa-lint    0.2.0   2012-10-24 yes        http://www.mimosajs.com
mimosa-require 0.2.0   2012-10-24 yes        http://www.mimosajs.com
mimosa-minify  0.2.0   2012-10-24 yes        http://www.mimosajs.com

To view more module details, execute 'mimosa mod:search -v' for 'verbose' logging.

Install modules by executing 'mimosa mod:install [name of module]'

Davids-MacBook-Pro:webapp dbashford$

mod:install

To install a module execute mimosa mod:install and provide the name of the module.

$ mimosa mod:install mimosa-server

If the module has already been installed, this will update it to the latest version. To install a specific version of a module, add the version after a @.

$ mimosa mod:install mimosa-server@0.2.0

To use and test a module under local development that isn't in NPM execute mod:install without providing a name for the module. For this to work properly, the command must be executed from the same directory as the module's package.json, which should be the root of the module. Once the module is installed, Mimosa treats it the same as a module it installed from NPM.

Davids-MacBook-Pro:webapp dbashford$ mimosa mod:install mimosa-server@0.2.0

09:57:43 - Install of 'mimosa-server@0.2.0' successful

Davids-MacBook-Pro:webapp dbashford$

mod:uninstall

Execute mod:uninstall to remove a module from Mimosa.

$ mimosa mod:uninstall mimosa-server

Davids-MacBook-Pro:webapp dbashford$ mimosa mod:uninstall mimosa-server

09:57:43 - Uninstall of 'mimosa-server' successful

Davids-MacBook-Pro:webapp dbashford$

mod:init

Execute mod:init and provide the command with a 'mimosa-' prefixed module name and Mimosa will create a directory using the name (or use an existing directory), and place a module skeleton inside. The skeleton contains heavily documented JavaScript code to get started, docco-generated documentation for that code, and a starter package.json for deploying the module to NPM.

All of the package.json placeholders need to be changed before publishing to NPM. The following sections go into detail about what steps to take with the new module skeleton.

$ mimosa mod:init mimosa-foo

Prefer CoffeeScript? (-c/--coffee)

Add a --coffeescript flag and Mimosa will provide a CoffeeScript skeleton instead of JavaScript.

$ mimosa mod:init -c
$ mimosa mod:init --coffee

Davids-MacBook-Pro:mimosaplay dbashford$ mimosa mod:init mimosa-foo

10:06:59 - Module skeleton successfully placed in mimosa-foo directory. The first thing you'll want to do is go into mimosa-foo/package.json and replace the placeholders.

Davids-MacBook-Pro:mimosaplay dbashford$

mod:config

Execute mod:config to have Mimosa write to the console the configuration placeholder for a module. This is useful when needing to add the configuration for a newly installed module to the mimosa-config.

$ mimosa mod:config server

Davids-MacBook-Pro:foo dbashford$ mimosa mod:config live-reload


# liveReload:                  # Configuration for live-reload
  # enabled:true               # Whether or not live-reload is enabled
  # additionalDirs:["views"]   # Additional directories outside the watch.compiledDir
                               # that you would like to have trigger a page refresh,
                               # like, by default, static views



Davids-MacBook-Pro:foo dbashford$

Where to start?

Mimosa can help module development get off the ground with a module skeleton. Discussed above, the mod:init command will create a module skeleton. The only hard and fast rule with Mimosa modules is that when they are created they must be prefixed with 'mimosa-'. This makes them easy to find if someone else wants to use them, but also differentiates Mimosa's modules from all of the libraries both in NPM and inside Mimosa.

One great place to start, if you want to skip reading and get right to code, is to check out some existing modules, like mimosa-lint and mimosa-minify.

What does mod:init give me?

mod:init provides a group of files to get module development started.

Mimosa module interface

At different times, Mimosa will attempt to execute five different functions in a module. All of the functions are optional.

registerCommand(program, retrieveConfig)

The registerCommand function is self-explanatory. This function is used to register new Mimosa commands.

The arguments passed in are:

The mimosa-volo module is a good example module for how to add a command to Mimosa. It adds an import command and makes use of the mimosa-config during execution.

defaults()

A module's defaults function is called when Mimosa starts up and is used to assemble the configuration used by Mimosa. defaults should return a JavaScript object that is the default configuration for the module. That default configuration is merged with the user provided configuration to establish the full mimosa-config.

exports.defaults = function() {
  return {
    minify: {
      exclude: [/\.min\./]
    }
  };
};

If a module doesn't have its own configuration, then this function does not need to be implemented.

placeholder()

A module's placeholder function is called when Mimosa is building the mimosa-config.coffee file that is delivered to a user during both the mimosa new and mimosa config commands. Use this function to return a string that shows what the module's configuration is and then explains each setting.

exports.placeholder = function() {
  return "\t\n\n"+
         "  # minify:                    # Configuration for non-require minification/compression via uglify\n" +
         "                               # using the --minify flag.\n" +
         "    # exclude:[/\\.min\\./]     # List of regexes to exclude files when running minification.\n" +
         "                               # Any path with \".min.\" in its name, like jquery.min.js, is assumed to\n" +
         "                               # already be minified and is ignored by default. Override this property\n" +
         "                               # if you have other files that you'd like to exempt from minification.";
};

If a module doesn't have its own configuration, then this function does not need to be implemented.

validate(mimosaConfig, validators)

A module's validate function is called when Mimosa starts up and after Mimosa has put together the full mimosa-config. That full config is passed to the validate method of all modules and each module has the opportunity to ensure that the config settings are valid. This method should return an array of strings. Each string represents an error that the module has found with the config. If a module return any errors, Mimosa will print the errors and exit to give the user a chance to update the config.

The validate function is also passed, as the second parameter, an object containing a collection of validation and utility functions. The best way to learn what those functions are is to check the source.

If there are no errors, return an empty array. If there is no config to validate, then do not implement the method.

This method is also the last opportunity to edit the module's configuration. After this method is called, the configuration is completely locked down and cannot be edited. For example, a module may take a list of regexs and turn them into a single RegExp object.

exports.validate = function(config, validators) {
  var errors = [];
  if validators.ifExistsIsObject(errors, "minify config", config.minify) {
    if validators.ifExistsIsArray(errors, "minify.exclude", config.minify.exclude) {
      var exls = config.minify.exclude;
      for (var _i = 0, _len = exls.length; _i < _len; _i++) {
        var ex = exls[_i];
        if (typeof ex !== "string") {
          errors.push("minify.exclude must be an array of strings");
          break;
        }
      }
    }
  }

  if (errors.length === 0 && config.minify.exclude && config.minify.exclude.length > 0) {
    config.minify.exclude = new RegExp(config.minify.exclude.join("|"), "i");
  }

  return errors;
};

registration(mimosaConfig, register)

The registration function is the most important function of a module. This function is called during Mimosa's startup and it allows a module to bind to one or many steps in a Mimosa workflow. Inside this function is where a module tells Mimosa to call its functions at certain times.

The arguments passed in are:

registration = (mimosaConfig, register) ->
  if mimosaConfig.isMinify
    e = mimosaConfig.extensions
    register ['add','update','buildFile'],      'afterCompile', _minifyJS,  [e.javascript...]
    register ['add','update','buildExtension'], 'beforeWrite',  _minifyJS,  [e.template...]

As mentioned in a previous section, a module should implement a registration function that Mimosa calls when it starts up. When Mimosa calls the module's registration function, Mimosa passes the enriched mimosa-config and a register callback. The register callback is what a module uses to inform Mimosa what function in the module to call and under what circumstances to call it. The register function takes the following four parameters:

  1. A list of workflows, an array of strings. Pick one-to-many workflows depending on the sort of task the module accomplishes. Possible values: buildFile, buildExtension, postBuild, add, update, remove, preClean, cleanFile, postClean. These are explained below.
  2. A workflow step, a string. A workflow step for the selected workflows. For example, for the 'update' workflow, a module's code might be executed 'afterCompile'.The list of steps for each workflow is explained below.
  3. Module callback, a function. This is the function Mimosa will call when processing reaches the workflow(s) and step called out in the previous parameters.
  4. An optional array of extensions upon which to execute the callback. If the file or extension being processed doesn't match one of these extensions, the callback will not be executed. The extensions refer to the original extension of the file being processed, so in the case of a CoffeeScript file it would be 'coffee' and not 'js'. The passed in mimosaConfig object has an extensions object a module can use to cover all of the desired extensions. The extensions object has 4 properties: javascript, css, template, and copy. If no extensions are provided, Mimosa will send all files/extensions through the module.
register ['add','update','buildFile'],      'afterCompile', _minifyJS,  [e.javascript...]
register ['add','update','buildExtension'], 'beforeWrite',  _minifyJS,  [e.template...]

Notice that the register method is executed multiple times. There is nothing stopping a module from registering multiple times for multiple workflows.

Workflows

A workflow is a set of steps an asset, a group of assets, or an application can go through. There are three groups of workflows: build, watch and clean.

Build Workflows

Modules registered to take place during the build workflows perform project builds. The build workflows are all prefixed with build and they all occur in order during Mimosa's startup. preBuild occurs first, followed by buildFile, buildExtension, and postBuild.

preBuild

preBuild is processed before any asset is handled. This is the workflow for preparing the file system and moving files around in preparation for individual file processing.

buildFile

During buildFile each file in the configured watch.sourceDir is individually processed. This step is when, for instance, CoffeeScript files are compiled to JavaScript and written to watch.compiledDir. It is also when images are copied to the watch.compiledDir.

Some assets, like micro-template files (Handlebars or Dust) and CSS preprocessor files (SASS or LESS) are not processed during this step as they, during startup, need to be dealt with as a group rather than individually.

buildExtension

After all of the assets are processed individually, buildExtension makes another pass through all the assets, but this time to handle extension-wide processing. Now is when Mimosa handles the file types that need to be managed as a group like micro-templates and CSS preprocessors.

For instance, if a project uses SASS, then it might have 20 includes/partials that result in two top-level files. Mimosa will process all 20 files at once and recognize that only two files need to be compiled

postBuild

After the extensions are finished, individual asset handling is finished. postBuild is when post asset completion tasks, like asset optimization, take place. postBuild is also when servers are started. If a module was being built for something like installing the app to Heroku, then postBuild is a candidate workflow.

Watch Workflows

If mimosa build was executed, then after postBuild is done, Mimosa exits. If mimosa virgin or mimosa watch is executed, then Mimosa keeps running and monitors the project's codebase for changes. When changes occur, the watch workflows are executed. The watch types are very straight forward.

For watch types no delineation exists between file and extension. For each type only a single file is processed at a time. So modules like the CSS and micro-templates compilers perform extension-wide tasks based on the single file that was updated.

add

The add workflow is kicked off when a new file is added to the watch.sourceDir directory.

update

The update workflow is run when an existing file in the watch.sourceDir directory is saved.

remove

The remove workflow is executed when a file in the watch.sourceDir directory is deleted.

Clean Workflows

The clean workflows execute when using mimosa clean (but not currently when --force is used), and when the --clean flag is used with mimosa watch. These workflows are responsible for removing compiled, copied and created assets and to generally return a project to a pre-Mimosa state. The clean workflows are always executed together and in order.

Modules can create and deposit files during the other workflows. A good module will use the clean workflows to tidy up after itself.

preClean

The preClean workflow is executed first and is an opportunity to handle files before cleanFile comes through and deals with each file individually.

cleanFile

Each file in the watch.sourceDir is passed through the cleanFile workflow one at a time.

postClean

The postClean workflow is the final opportunity to tidy up files and directories. This is when, for example, a module might make a pass to remove any empty directories the module might be responsible for that remain after the files have been handled.

Workflow Steps a list of processing steps for each workflow

The previous section covered each of the workflows. Each workflow has many steps within it. An example is the best way to illustrate how steps relate to workflows.

The simplest workflow is update. If a CoffeeScript file is saved inside the watch.sourceDir while Mimosa is running, here is what happens:

  1. The first step executed is init. During the init step, Mimosa will generate some information about the asset to be used by future steps. For instance it will set a flag into the options object (discussed in the next section) to indicate that the file is a JavaScript file, and it will set flags to indicate if the file is a vendor file or not.
  2. During a step named read the modified CoffeeScript file is read in.
  3. The compile step is next. Here the CoffeeScript gets compiled to JavaScript.
  4. Next is afterCompile. Here the compiled JavaScript is linted to get feedback regarding code quality.
  5. If minification was selected, beforeWrite is where the compiled JavaScript would be minified.
  6. During the write step the compiled JavaScript is written to the watch.compiledDir
  7. You'd think we'd be done now, but not so fast! The afterWrite step is where optimization takes place if that is selected.

And there are many steps in this example that have no functionality registered against them. The beforeRead, afterRead and beforeCompile steps, for example, have no modules that registered to execute against them, but they are there in the event a module comes along that logically has tasks to perform at that point in the workflow.

Also, many modules can register to be executed for the same workflow and step, they'll just be executed in order that they get registered which is determined by their order in the modules array in the mimosa-config.

When building a module, a developer needs to decide which step is the right place to execute a module's functionality. For instance, if building a module that will run CoffeeLint over CoffeeScript to determine CoffeeScript code quality, then afterRead is probably the best step. If, for instance, beforeRead is used, then Mimosa will have not yet read in the file, and there will be nothing on which to run CoffeeLint.

Workflow Type & Step Breakdown

Below is the exhaustive list of all of the steps by workflow. Use the radio buttons to toggle between the different default Mimosa modules.



preBuild

  • init
  • complete

buildFile

  • init
  • beforeRead
  • read
  • afterRead
  • betweenReadCompile
  • beforeCompile
  • compile
  • afterCompile
  • betweenCompileWrite
  • beforeWrite
  • write
  • afterWrite
  • complete

buildExtension

  • init
  • beforeRead
  • read
  • afterRead
  • betweenReadCompile
  • beforeCompile
  • compile
  • afterCompile
  • betweenCompileWrite
  • beforeWrite
  • write
  • afterWrite
  • complete

postBuild

  • init
  • beforeOptimize
  • optimize
  • afterOptimize
  • beforeServer
  • server
  • afterServer
  • beforePackage
  • package
  • afterPackage
  • beforeInstall
  • install
  • afterInstall
  • complete

add

  • init
  • beforeRead
  • read
  • afterRead
  • betweenReadCompile
  • beforeCompile
  • compile
  • afterCompile
  • betweenCompileWrite
  • beforeWrite
  • write
  • afterWrite
  • betweenWriteOptimize
  • beforeOptimize
  • optimize
  • afterOptimize
  • complete

update

  • init
  • beforeRead
  • read
  • afterRead
  • betweenReadCompile
  • beforeCompile
  • compile
  • afterCompile
  • betweenCompileWrite
  • beforeWrite
  • write
  • afterWrite
  • betweenWriteOptimize
  • beforeOptimize
  • optimize
  • afterOptimize
  • complete

remove

  • init
  • beforeRead
  • read
  • afterRead
  • beforeDelete
  • delete
  • afterDelete
  • beforeCompile
  • compile
  • afterCompile
  • betweenCompileWrite
  • beforeWrite
  • write
  • afterWrite
  • betweenWriteOptimize
  • beforeOptimize
  • optimize
  • afterOptimize
  • complete

preClean

  • init
  • complete

cleanFile

  • init
  • beforeRead
  • read
  • afterRead
  • beforeDelete
  • delete
  • afterDelete
  • complete

postClean

  • init
  • complete

So a module's code is registered to be called in the right spot in the workflow. What happens when Mimosa calls the function that has been registered?

The executed function is handed three arguments.

  1. config: The full mimosa-config enriched with all sorts of useful data beyond the default mimosa-config, and including the configuration for the module being developed. The config object is immutable. It cannot be changed. This prevents modules from breaking one another inadvertently.
  2. options: An object containing information about the asset(s)/extension currently being processed. At different steps of the a Mimosa workflow the options object will contain various important pieces of information. One of the first steps a module developer should take when developing a new module is to console.log the content of the options object to see what sort of information is available at their desired workflow and workflow step.
  3. next: a workflow callback. This callback must be called when a module has finished processing. It tells Mimosa to execute the next step in the workflow. If for some reason a module decides that processing for the current asset/build step needs to stop, the callback can be called passing false. Ex: next(false). In most cases the workflow should not be ended prematurely.

A command module has a very simple interface. A module that intends to implement a new command needs to implement and export a registerCommand function. That function takes a commander program object for registering the command, the flags, the help text and a callback.

If a function needs access to execute a build before running, or needs the mimosa-config, it can get accomplish those things using the second parameter passed to registerCommand. The second parameter, a retrieveConfig function, takes a flag indicating if a build is needed, and a callback. That callback should contain the command module's code, and it is passed the full mimosa-config.

During Development

The mimosa mod:install command handles installing local modules to Mimosa. From inside the root module directory, the directory with the package.json file, execute mimosa mod:install. Then modify the modules array of a project's mimosa-config to use the module. Fire up Mimosa and test your module out!

Publishing to NPM

If you are familiar with NPM, this part is not new. A simple npm publish from the root of your module will install a module to NPM so that anyone can get access to it. Once it is in NPM, commands like mod:install and mod:search will be able to find it.

If you have never published to NPM before, then spend a few minutes reading the developer docs. Just a few simple steps and you are ready to publish.

With upcoming Mimosa releases, Mimosa will no longer recognize CoffeeScript modules. The CoffeeScript module skeleton delivered with mod:init -c accounts for this by using Mimosa to build the CoffeeScript module when npm install and publish are used.

If you do create and publish a module, let us know!!!