Skip to content

Instantly share code, notes, and snippets.

@Warry
Last active August 29, 2015 14:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Warry/24585d6a0c0c0735bd87 to your computer and use it in GitHub Desktop.
Save Warry/24585d6a0c0c0735bd87 to your computer and use it in GitHub Desktop.
sbt-we plugin workflow

We'll take the stylus plugin as an exemple.

Making it work with Node.js

To make our plugin to work with node, we are going to use Christopher's js-transpiler. It allows to develop a shell script that will work independtly on both node and the jvm. You can call a shell script from the cli like this:

    node my-shell.js file.extension '{"flag":true}'

You can use this to debug your processor until it works!

Anatomy of a jstranspiler shell script

You will find in attached, stylus-shell.js which will be our example. There are 4 parts in the file:

imports/require

Nothing hard here. You can use npm (package.json) to define dependencies if you want. Note that sbt-web works natively with package.json, but cannot ensure that your code will work on Rhino, and node_modules/ files won't be accessible from other projects (such as the ones who will have the currently developed plugin). So a better practice is to use npm only the early stages of the developement workflow, then use webjars.

processor

This is the code the logic of the build. We use when.js promises here to manage the callbacks. What you have to do is to define a function that will take in argument an input and an output file locations, and that returns a when promise.

The processor uses node apis such as mkdirp or fs.readFile, those apis are readable from Rhino, so you don't have to worry about this.

jstranspiler call

One line:

    jst.process({processor: processor, inExt: ".styl", outExt: (args.options.compress? ".min.css" : ".css")}, args);

You send the processor and some informations about the extentions to watch and to output. The last parametter is the arguments sent from sbt or the cli, basically the files locations.

error parsing

The biggest advantage of using sbt-web, especially with Playframework, is to get nice error reporting (in the browser or the IDE). In order to do that, you need to transform errors from your processor to this json object structure:

    {
    message: "ERROR MESSAGE",
    severity: "error",
    lineNumber: parseInt(lineNumber),
    characterOffset: 0,
    lineContent: "x + z...",
    source: input
    }

Making it work with sbt-web

TODO

/*global process, require */
var fs = require("fs"),
jst = require("jstranspiler"),
nodefn = require("when/node"),
mkdirp = require("mkdirp"),
path = require("path"),
stylus = require("stylus"),
nib = require("nib");
var promised = {
mkdirp: nodefn.lift(mkdirp),
readFile: nodefn.lift(fs.readFile),
writeFile: nodefn.lift(fs.writeFile)
};
var args = jst.args(process.argv);
function processor(input, output) {
return promised.readFile(input, "utf8").then(function(contents) {
var result = null;
var options = args.options;
options.filename = input;
var style = stylus(contents, options)
if (options.useNib) style.use(nib());
style.render(function (err, css) {
if (err) {
throw parseError(input, contents, err);
} else {
result = {
css: css,
style: style
};
}
});
return result;
}).then(function(result) {
return promised.mkdirp(path.dirname(output)).yield(result);
}).then(function(result) {
return promised.writeFile(output, result.css, "utf8").yield(result);
}).then(function(result) {
return {
source: input,
result: {
filesRead: [input].concat(result.style.deps()),
filesWritten: [output]
}
};
}).catch(function(e) {
if (jst.isProblem(e)) return e; else throw e;
});
}
jst.process({processor: processor, inExt: ".styl", outExt: (args.options.compress? ".min.css" : ".css")}, args);
/**
* Utility to take a stylus error object and coerce it into a Problem object.
*/
function parseError(input, contents, err) {
var errLines = err.message.split("\n");
var lineNumber = (errLines.length > 0? errLines[0].substring(errLines[0].indexOf(":") + 1) : 0);
var lines = contents.split("\n", lineNumber);
return {
message: err.name + ": " + (errLines.length > 2? errLines[errLines.length - 2] : err.message),
severity: "error",
lineNumber: parseInt(lineNumber),
characterOffset: 0,
lineContent: (lineNumber > 0 && lines.length >= lineNumber? lines[lineNumber - 1] : "Unknown line"),
source: input
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment