Why build-steps?
Certain tasks must be repeated every time you push your app to couchdb. Build-steps let you automate these steps to ensure they're executed with the right parameters and in the proper order.
Things you should do with build-steps:
- Load: Need to get data from the filesystem into your app? build-steps have access to both environments
- Compile: Using coffeescript? Or less? Compile with in a build-step
- Process: Want to save a few bytes? Minify your javascript and css.
In fact, build-steps are a fundamental component of Kanso. Without them your design doc would be empty. Every part of the build process is implemented using packages that expose build-steps. Check the build-tools category on the package repository to see the wide range of applications.
Before writing a build-step
Check the package repository to see if someone's already shared a package that does what you need.
Writing a build-step
Implement a build-step in a Kanso package. Here's our example directory structure:
coffee-script-precompiler/
|-- build
|-- compile.js
|-- kanso.json
in kanso.json
Start like you would for any Kanso package, add name, version, categories (to help others discover your module in the package repository), description, and dependencies.
{
"name": "coffee-script-precompiler",
"version": "0.0.4",
"categories": ["build-tools"],
"description": "compiles coffeescript to javascript on each push",
"dependencies": {
"couchtypes": null,
"modules": null
},
}
Build-steps also specify preprocessors and/or postprocessors. These contain a dictionary of each build step name mapped to the module path to the file that should be excecuted during that build step.
Preprocessors are executued on each individual package before it is combined with the other packages to form the complete application. Postprocessors are invoked after all packages have been combined and modify the final design doc. The general rule is, if you can make it a preprocessor, do so.
{
...
"preprocessors": {
"compile": "build/compile"
}
}
In compile.js
The compile.js file in our example exports a dictionary that must include a run property. The run function will be executed when the build-step is called. It is passed these variables:
- root:
{Boolean}whether the package is the main (top) package being built (always true for postprocessors) - path:
{String}the fs path of the package directory - settings:
{Object}the values in kanso.json - doc:
{Object}the design doc being built - callback:
{Function}function to call after the processor is complete or on error. the first argument of the callback is an optional error, the second is the udpated doc object.
You must make sure you call the callback with either an error as the first argument or the updated doc object as the second argument, otherwise processing will stop.
Inside this run function, you are in a node.js environment. You have access to the normal node.js modules fs, child_process etc.
Defining prerequisites and dependants
If your build step must occur before another build-step and/or after another build-step, you may set the before and after attributes to the name of the package whose build-steps they should preceed of follow.
module.exports = {
before: "properties",
run: function (root, path, settings, doc, callback) {
...
callback(err, doc);
}
};
The before and after properties can also be an array of required packages:
before: ["properties", "modules"]
You can also be even more specific with these settings and define specific build-steps from a package:
before: "properties/build"
The format is "<package name>/<build-step name>", where the build-step name is the property in kanso.json:
{
"preprocessors": {
"BUILD-STEP NAME": "path/to/module"
}
}
Usage
Simply adding a package that includes a build-step to an app's dependencies will invoke the included steps every time the app pushed with kanso push.
To run a preprocessor, the package must require it directly. Assume C defines a preprocessor, B defines C as a dependency, and A defines B as a dependency:
A --> B --> C
In this arrangement, the preprocessor from C will only be run on package B before it is merged with A. This is so depending on a coffee-script compiler (for example) does not affect the other packages in the hierarchy.
Postprocessors always operate at the final level. So if C defined a postprocessor, it would not run on A or B, but on the combined design doc of A, B and C.