Better JavaScript Workflow With Jake
Jake is a javascript build tool that can dramatically improve your javascript development workflow. It helps you to easily maintain and automate the development tasks such as compiling sass files, typescript, coffeescript, etc, concatenate and minify source code, optimising images and validating your javascript code with JSHint. Unlike Grunt & Gulp, jake is not a plugin based tool. You can create jake tasks with any valid javascript code.
In this blog post we will use jake to speed up the front end development process. We will look breifely what jake can do, before jumping into real development walk through.
Setting up
In order to start with the jake, you need to have nodejs installed on your machine. If you know nothing about node js, visit the download page and grab the installer for your operating system.Once nodejs installed, run this command in your terminal to install the jake.
npm install -g jake
-g flag will install the jake glabally. To make sure jake has been installed properly, you can open the command line prompt and type jake --version
and it should output the current version of jake.
The Jakefile
Every jake project has a file, jakefile.js
which defines workflow for jake to execute. Jakefile is just executable javascript. You can include whatever the javascript you want in it. A task can be defined using task
function. It has one required argument, the task name, and three optional arguments.
task(name, [prerequirities], [action], opts);
The name
argument is a string with the name of the task. And prerequisites
is the optional array of the list of prerequisite to perform first. The action
is a function defining the action to be executed for task. The opts
is the normal javascript object for specifying any additional configurations to the task. If you want to make the jake task asynchronous, set the async property to true and you task must call complete()
to notify the jake that the task is done, and execution can proceed. By default async
property is false
.
You can use desc(str)
to add a description for task.
desc('This is the default task');
task('default', function() {
console.log('Jake is up and running...');
});
desc('This is the task with prerequisites.);
task('build', ['clean', 'copy', 'compile'], function() {
console.log('Clean, Copy & Compile tasks must be executed before entering to this task');
});
desc('This is an example of asyncronous task');
task('test', { async: true }, function(param) {
console.log(' You have passed ' + param);
setTimeout(function() {
complete();
}, 10000);
});
A Jake task can be executed as following:
jake [task][params]
For example, the test
task from above code sample can be executed as:
jake test[paramValue]
If you do not specify a task name, then the default task will be executed.
Setting up project.
Let’s kickstart our app with angular-seed project. I recommend the angular-seed as it provides a great skeleton for bootstrapping. It also contains bunch of development and testing tools. To get started simple clone the angular-seed repository and install the dependencies.
git clone https://github.com/angular/angular-seed.git
Install dependencies
We have two types of dependecies in this project angular framework code and tools which helps to manage and test the
application code. Tools can be downloaded via node package manager using the command npm install
.
And angular framework code can be downloaded via bower
using the command: bower install
.
Angular team has preconfigured the npm
to automatically install bower dependencies by running bower install
command.
So just need to install npm modules only which will install bower modules also.
After installing dependencies, you should find two new folders in your project.
node_modules
- npm packages for toolsapp\bower_components
- angular js framework files.
Angular-seed is preconfigured with simple webserver. And you can run the application with npm start
command. But it does not contain automated build system like Jake.
Jake In Action
Create a file called Jakefile.js
in your project root directory. This is where you will define and configure tasks that you want jake to run.
var jake= require('jake');
desc('default jake task');
task('default', [], function() {
console.log('Finished Building');
});
Clean previous build
We need to clean/remove the build artifacts from previous build. jake.rmRf()
is the utility method which recursively remove the directories and all its content.
desc('clean previous build files');
task('clean', function() {
console.log('cleaning the project....');
jake.rmRf('dist');
});
This will remove the contents of dist/
directory.
Linting JavaScript code with JSHint
Linting is the process of analysing code for potential errors whith the use of programs. JSHint is a static analysis tool for javascript. It analyzes JavaScript source code for common mistakes. Instead of using JSHint
directly we use simplebuild-jshint
, library that provides a simple interface to JSHint. Its convenient to use automation tools such as Grunt, Gulp or Jake.
Install JSHint and simplebuild-jshint npm modules using npm install
commands as following
npm install -g jshint
npm install --save-dev simplebuild-jshint
We need to validate source javascripts with JSHint, also we need to exclude the bower dependencies from validation.
JSHint Options
JSHint was designed to be very configurable. These configuration options can be found here. Define the JSHint rules in jakeConfig file like following:
this.jsHintOptions = {
curly: true,
camelCase: true,
window: false,
document: false,
setTimeout: false,
quotmark: 'single'
};
Our lint task is as follows:
var jshint = require("simplebuild-jshint");
task('jshint', function () {
jshint.checkFiles({
files: [
"app/**/*.js",
"!app/**/*_test.js",
"!app/bower_components/**/*.js"
],
options: {
curly: true,
quotmark: 'single',
eqeqeq:true
}
}, complete, fail);
}, { async: true });
Task will lint the all javascript inside app folder, it also excludes the bower_component folder from liniting.
Wiredep the bower dependencies
After bower packages has been installed, you could add them manually to your application’s HTML file. To avoid this manual intervention, you can use wiredep to automate this process. Wiredep enables you to wire Bower dependencies into your source code.
Install module with npm.
npm install wiredep --save-dev
Edit app/app.html and add placeholders for use by wiredep, as shown below:
<html>
<head>
<!-- bower:css -->
<!-- endbower -->
</head>
<body>
<!-- bower:js -->
<!-- endbower -->
</body>
</html>
Create the jake task as follows:
var wiredep = require('wiredep');
desc('Wiring bower dependencies...');
task('wiredep', function () {
console.log('Wiring bower dependencies...');
wiredep({
src: 'app/index.html',
bowerJson: require('./bower.json'),
directory: './bower_components'
});
});
How it works?
Wiredep reads the dependencies array from your bower.json file, then reads the bower.json from each dependencies folder in your bower_components folder. Based on these connection, it determines the order of scripts must be included before injecting them between the placeholders in your source code.
If one of your dependencies does not have main in its bower.json, then you may want to override the default behaviour in your bower.son file like following:
{
....
"dependencies": {
"package-without-main" : "0.0"
},
"overrides": {
"package-without-main" : {
"main" : "package-main.js"
}
}
}
You can read more details about wiredep library here.
Copy assets to build folder
Copy task will copy the assets and source code from source folder to build folder. Node’s built in file system api
is very low level and because of that often painful to use. We use fs-jetpack
module, which gives more convenient API to work with file system.
Visit github page for more details.
Installation
npm install fs-jetpack --save-dev
Our copy task will look like:
var jetpack = require('fs-jetpack');
task('copy', function () {
var source = 'app',
dest = 'dist',
filesToInclude = ['**/*.html', 'assets/*', '*.!(css|js|less)' ];
jetpack.copy(source, dest, { matching: filesToInclude });
});
Compiling the less files
We need to compile less files to standard css files. Less can be used on command line via npm, or as a script file for runtime
compilation inside browser itself, or via wide variety of third party tools. Here we use less via command line.
jake.exec
command is the best option with jake js for executing the shell commands.
Install less css via npm npm install -g less
. If you don’t want to install less as global module,
then you should change the less command relative to your node_modules folder.
task('less', function () {
var list = new jake.FileList();
list.include('app/**/!(_)*.less');
list.forEach(function (file) {
var dest = file.substring(file.indexOf("/"), file.lastIndexOf('.')) + '.css';
jake.exec('lessc ' + file + ' > ' + dest, { printStdout: true }, function () {
complete();
});
});
}, { async: true });
In above code jake.FileList
searches the filesystem and find files into arrray. It takes list of glob-patterns and filenames as parameter, and lazily
creates a list of files to include. More on FileList.
Optimizing the application
The best way to optimize our application is to reduce the number of requests by combining multiple files as few as possible and to reduce the size of files with the help of technique like minification. In this section we will check how we can combine our application source code into a single JavaScript file and also minify source files to reduce the size.
Let’s optimize our application with usemin-cli, which replaces reference from non-optimized scripts,stylesheet and other assets to their optimized version within a set of HTML files.
Usemin blocks can be expressed as following:
<!-- build<type>(alternate search path) <path> -->
... List of script/link tags goes here.
<!-- endbuild -->
Change our app/index.html like s following:
<!-- build:js scripts/vendor.js -->
<!-- bower:js -->
<!-- endbower -->
<!-- endbuild -->
<!-- build:js scripts/app.js -->
<!-- app:js -->
<script src="app.js"></script>
<script src="view1/view1.js"></script>
<script src="view2/view2.js"></script>
<script src="components/version/version.js"></script>
<script src="components/version/version-directive.js"></script>
<script src="components/version/interpolate-filter.js"></script>
<!-- endapp -->
<!-- endbuild -->
After running this task, all our bower dependencies will be concatenated and minified into a single file which will be place into scripts/vendor.js in dist folder. Also scripts/app.js will contain our application script.
Install usemin-cli:
npm install usemin-cli --save
Optimize task look like this:
task('optimize', { async: true }, function () {
jake.exec('node ./node_modules/usemin-cli/bin/usemin app/index.html -d dist -o dist/index.html',
{ stdout: true }, function () {
complete();
});
});
Configuring the Staging Server
This task allows you to create a http server. Once run this task, you can access the application on http://localhost:8080. We use http-server
module
which is very simple and powerful. Install http-server
module via npm.
task("server", ["build"], function () {
jake.exec("node ./node_modules/http-server/bin/http-server " + "dist", {
interactive: true
}, complete);
}, { async: true });
Watch the filesystem for changes
Watch task will watch your app directory, and if any file changes, jake will automatically rerun build task. Install nodemon
module via npm, which will
monitor for any changes.
var nodemon = require('nodemon');
task('watch', function () {
nodemon({
ext: "sh bat json js html css hbs less scss",
ignore: ["build", "dist"],
exec: "jake build",
execMap: {
sh: "/bin/sh",
bat: "cmd.exe /c",
cmd: "cmd.exe /c"
}
}).on("restart", function (files) {
console.log("*** Restarting due to", files);
});
});
Nodemon accepts several configuration options. ext
says the extensions of files thats should be watched, ignore
is the list directories or files
to be excluded from watching, and exec
is the command that should be executed when some changes occur in filesystem. You can find more about nodemon here.
Wrap up
Our build task put all the task togethere into one task. You can start building the application with jake watch
which will call the build task and
starts watching application.
task('build', [ 'clean', 'jshint', 'wiredep', 'copy', 'less', 'optimize'], function () {
console.log('Finished building successfully!');
});
It’s up to your choice, whether to use Jake over grunt, gulp or any other build libraries. Each of these build tools have their own pros and cons. But jake provides a clean JavaScript syntax which is more readable and easy to maintain. As jake is not a plugin centric tool, it can even be used without any other dependencies on it.
You can find the full source code here in Github.