While the site has a lot of documentation on how modules are built with a small walk-through, I wanted to dedicate some serious attention to how easy it is to build something and get it linked into your workflow.
Example Module
A great way to learn how a module is built is to look at a real simple existing module. We'll look at the handlebars-on-window module. The goal of that module is detect a handlebars.js
file as it flows through Mimosa's build steps and transform the text of the file to make the Handlebars
object visible on the window
. If you are wondering why such a module would exist, you can check the README.
File Structure
This is the file structure for the handlebars-on-window module.
/src
config.js
index.js
.gitignore
.npmignore
README.md
mimosa-config.coffee
package.json
Non-Mimosa Files
Lets knock out the simple stuff first. These files are more about Git, NPM and node and not about Mimosa, but they are a common part of any Mimosa module.
mimosa-config.coffee
If you are interested in building a module, you are likely using Mimosa. This file configures Mimosa. But, don't let the presence of this file confuse you, the mimosa-config
file has nothing to do with the Mimosa module. A Mimosa module does not need a mimosa-config
to be a module.
When I built this module I wanted to make sure I ran JSHint over the project's source code to make sure the JavaScript code is idiomatic and to catch simple syntax bugs quickly. It just so happens Mimosa can do that, so I use Mimosa to run JSHint.
README.md
README.md
contains the module's documentation. How to install it, how to configure it and what its purpose is. More documentation the better! Many of the Mimosa modules have a common pattern for documentation if you care to follow it.
The handlebars-on-window has documentation that covers usage, functionality and configuration.
.gitignore
This indicates what local files you do not want to push to your git repository. Because Mimosa modules are node projects, using .gitignore
to make sure you do not push the node_modules
directory is common.
.npmignore
NPM is where you will deploy your module when you are done with it and it is how other people will get your module. As with .gitignore
and git, .npmignore
determines what files are not sent to NPM when you publish your module. The files that get published to NPM are the ones that get delivered when someone uses your module.
The mimosa-config
is a good example of something to add to the .npmignore
. It is only there to run JSHint during development. It isn't useful for anything else.
package.json
The package.json
configures node projects. If your module needs any other node modules (like wrench for directory manipulation or request for some HTTP goodness), you would declare those as dependencies
in your package.json
. This file is also where you declare the name of your module, in this case mimosa-handlebars-on-window
.
Nodejitsu has a great guide on the package.json
.
Module Code
While some of the previous files are necessary, they don't really have anything to do with Mimosa. The files inside src
are where the module's code exists.
The first thing to identify is where the module's main code is. When Mimosa uses the module, what does it use?
The package.json
has a property in it named main
.
"main": "./src"
This points anything using this module to module's main entry point. In this case it is pointed at the directory ./src
. By default, when a directory is the name
rather than a file it means that the file to use is index.js
inside that directory.
And that is where we'll start.
src/index.js
Because the index.js
is the main
for this NPM module, that means it is responsible for defining the interface Mimosa will use. At the bottom of the index.js file is a module.exports
.
module.exports = {
registration: registration,
defaults: config.defaults,
placeholder: config.placeholder,
validate: config.validate
};
Each property in this object is a reference to a function. These four functions are the functions that Mimosa will use in order to access the module's functionality.
Not only is module.exports
the interface for this module, is also contains the interface Mimosa expects from its modules. For every module plugged into Mimosa, Mimosa will look to execute these specific functions and various times during a build or during mimosa new
.
The defaults
, placeholder
and validate
functions are all inside the config.js
file that is require
d (imported) into index.js
. We'll cover those after we touch on the most important of the interface functions.
registration
The registration
function is how a Mimosa module hooks itself into a Mimosa build. It is the most important of all the functions, and it is only one line of code!
var registration = function (mc, register) {
register(
['add','update','buildFile'],
'afterCompile',
_attach,
mc.extensions.javascript );
};
Before digging into this code, lets refresh what this module does. It's purpose is to add the text "window.Handlebars
" to the handlebars.js
file. That's all. That means this module needs to process JavaScript files, capture the handlebars.js
and update the text that is going to be written.
This line of code register
s files with JavaScript extensions (mc.extensions.javascript
) to be processed by the _attach
function (which is higher up in the code) after compilation has been executed (afterCompile
) whenever a file is add
ed, update
d or built (buildFile
).
Lets break that down.
Callback
_attach
is the callback for this registration. This is where the module's code lives. It is this code that will look for handlebars.js
and update the text.
Extensions
mc.extensions.javascript
is a reference to an array of JavaScript extensions being processed by the application. By register
ing this array of extensions, it ensures the _attach
callback gets called only when a JavaScript file is being processed. This would include CoffeeScript files as well as JavaScript files. But it would ensure that, for instance, CSS files or images being processed by Mimosa would not trigger the _attach
callback.
mc
is the fully evaluated and amplified mimosa-confg
and it is passed to the registration
function. It includes all the configuration for all the modules, but it also includes information like the extensions
object.
But when does the processing of JavaScript files in the _attach
function occur?
Workflows
A Mimosa workflow is a set of steps that get executed during a Mimosa command.
The buildFile
workflow runs during mimosa build
and when mimosa watch
first starts. When it first starts up, mimosa watch
builds all the assets, but it keeps watching. add
and update
are executed during mimosa watch
after initial startup when a file is added or updated. In all of these scenarios -- when building, adding or updating files -- we want the module to be checking the JavaScript files being processed to see if any of the files are handlebars.js
.
Workflow Steps
When a file is processed through a workflow it goes through several workflow "steps". Steps include read
when the file is read, compile
when the file's input text is transformed into the desired output text, and write
when it is written. This module wants to process files during the afterCompile
step. This ensures that the module is working with JavaScript. If this module ran beforeCompile
, then the file's output text may not have been determined and it is the output text we want to tweak.
When the callback gets called
When _attach
gets called by Mimosa at the configured time, it gets passed some information.
var _attach = function ( mimosaConfig, options, next ){ ... }
mimosaConfig
The first parameter is again the full mimosa-config
with all of the application's configuration and plenty of other goodies. What we care about is that the mimosaConfig
contains the configuration for this module: mimosaConfig.handlebarsOnWindows
.
options
The options
object has information about the file being processed. It has a files
array, options.files
, that contains any files being processed. Each entry in the files
array has an inputFileText
and an outputFileText
. As mentioned above, this module is concerned with transforming the outputFileText
.
next
next
is Mimosa's lifecycle callback. When the module has finished doing whatever it needs to do, it must call this callback or else Mimosa will stop processing this file.
Read the code!
Armed with the information above, you should be able to read the 19 lines of code that comprise the _attach
callback and understand what is happening. The key part is this one line...
file.outputFileText = file.outputFileText.replace(
mimosaConfig.handlebarsOnWindow.replace,
"window.Handlebars = " + mimosaConfig.handlebarsOnWindow.replace );
This line rewrites the outputFileText
with window.Handlebars
inside of it.
Regarding mimosaConfig and options
These two objects have lots of useful stuff in them. Just for curiosity's sake, you may want to console.log
these objects just to see what is inside them.
src/config.js
The functions in this file are really straight forward. They are also all optional. If your module has no configuration, it wouldn't need this file at all.
Remember the index.js
looks into the config.js
file to expose some of its functions.
module.exports = {
registration: registration,
defaults: config.defaults,
placeholder: config.placeholder,
validate: config.validate
};
defaults
This function returns the module's default configuration. Pretty simple!
exports.defaults = function() {
return {
handlebarsOnWindow: {
libName: 'handlebars.js',
replace: "__exports__ = Handlebars"
}
};
};
placeholder
This function returns a string snippet that explains the default config that is placed in the mimosa-config-documented.coffee
file during mimosa new
and mimosa config
. The string it returns is commented CoffeeScript.
exports.placeholder = function() {
return "\t\n\n"+
" # handlebarsOnWindow:\n" +
" # libName: 'handlebars.js' # file name of handlebars library\n" +
" # replace: '__exports__ = Handlebars' # code to prepend 'window.Handlebars = ' to\n";
};
validate
This function is called by Mimosa to give the module a chance to validate its own config.
exports.validate = function(config, validators) {
var errors = [];
if ( validators.ifExistsIsObject( errors, "handlebarsOnWindow config", config.handlebarsOnWindow ) ) {
validators.ifExistsIsString( errors, "handlebarsOnWindow.libName", config.handlebarsOnWindow.libName );
validators.ifExistsIsString( errors, "handlebarsOnWindow.replace", config.handlebarsOnWindow.replace );
}
return errors;
};
If this function returns an array of strings (error messages), Mimosa will error out after printing the errors. So, if, for instance, the libName
property wasn't a string, the validate
function would return something like:
["handlebarsOnWindow.libName must be a string"]
The validators
parameter passed in contains a set of helpful validation methods like ifExistsIsString
. The full set can be found in the Mimosa source.
So you wrote a module, now what?
Testing Locally
Run mimosa mod:install
from the root of your module's directory and your Mimosa will install your module inside itself. Now you can use your module on local projects.
Put it in NPM
As long as your package.json
is in working order, npm publish
your module! And then tell the world about it (and let me know via @mimosajs).