Managing assets in ASPAX vs Grunt
A couple of weeks ago I published a protip about ASPAX - a simple Node.js asset packager that enables you to manage your client-side assets based on a single human-readable file in YML format.
Some people were asking for a comparison between ASPAX and Grunt.js, so...

Here's a sample aspax.yml file:
# Main application file
js/app.js|fp|min:
- lib/bootstrap/js/bootstrap.js
- lib/moment.js
- lib/jade/runtime.js
- scripts/namespaces.coffee|bare
- templates/now.jade
- scripts/index.ls|bare
# Main CSS
css/app.css|fp|min:
- lib/bootstrap/css/bootstrap.css
- lib/bootstrap/css/bootstrap-theme.css
- styles/index.styl|nib
# Images
favicon.png: images/favicon.png
# Font icons
fonts/bs-glyphs.eot|fp: lib/bootstrap/fonts/glyphicons-halflings-regular.eot
fonts/bs-glyphs.svg|fp: lib/bootstrap/fonts/glyphicons-halflings-regular.svg
fonts/bs-glyphs.ttf|fp: lib/bootstrap/fonts/glyphicons-halflings-regular.ttf
fonts/bs-glyphs.woff: lib/bootstrap/fonts/glyphicons-halflings-regular.woff
...and here's a Gruntfile.js you'd have to write in order to achieve the same results:
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
// CoffeeScript files compilation
coffee: {
compile: {
options: { bare: true },
files: {
'../tmp/namespaces.js': '../client/scripts/namespaces.coffee'
}
}
},
// LiveScript files compilation
livescript: {
compile: {
options: { bare: true },
files: {
'../tmp/index.js': '../client/scripts/index.ls'
}
}
},
// Jade client-side templates compilation
jade: {
compile: {
options: {
compileDebug: false,
client: true
},
files: {
'../tmp/now.js': '../client/templates/now.jade'
}
}
},
// Stylus files compilation
stylus: {
compile: {
options: {
compress: false
},
files: {
'../tmp/index.css': '../client/styles/index.styl'
}
}
},
// Concatenate CSS and JS files
concat: {
js: {
files: {
'public/js/app.js': [
'../client/lib/bootstrap/js/bootstrap.js',
'../client/lib/moment.js',
'../client/lib/jade/runtime.js',
'../tmp/namespaces.js',
'../tmp/now.js',
'../tmp/index.js'
]
}
},
css: {
files: {
'../tmp/app.css': [
'../client/lib/bootstrap/css/bootstrap.css',
'../client/lib/bootstrap/css/bootstrap-theme.css',
'../tmp/index.css'
]
}
}
},
// Copy miscellaneous files and process asset URLs in CSS files
copy: {
css: {
src: '../tmp/app.css',
dest: 'public/css/app.css',
options: {
process: function(content, srcPath) {
// Should replace asset URLs in CSS file
console.log('Should process asset URLs in', srcPath);
return content;
}
}
},
misc: {
files: [
{ src: '../client/images/favicon.png', dest: 'public/favicon.png' },
{ src: '../client/lib/bootstrap/fonts/glyphicons-halflings-regular.eot', dest: 'public/fonts/bs-glyphs.eot' },
{ src: '../client/lib/bootstrap/fonts/glyphicons-halflings-regular.svg', dest: 'public/fonts/bs-glyphs.svg' },
{ src: '../client/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf', dest: 'public/fonts/bs-glyphs.ttf' },
{ src: '../client/lib/bootstrap/fonts/glyphicons-halflings-regular.woff', dest: 'public/fonts/bs-glyphs.woff' }
]
}
},
// Watch and trigger compilation, concatenation, ...
watch: {
js: {
files: [
'../client/lib/bootstrap/js/bootstrap.js',
'../client/lib/moment.js',
'../client/lib/jade/runtime.js',
'../client/scripts/namespaces.coffee',
'../client/templates/now.jade',
'../client/scripts/index.ls'
],
tasks: ['coffee', 'jade', 'livescript', 'concat:js']
},
css: {
files: [
'../client/lib/bootstrap/css/bootstrap.css',
'../client/lib/bootstrap/css/bootstrap-theme.css',
'../client/styles/**/*'
],
tasks: ['stylus', 'concat:css']
},
misc: {
files: [
'../client/images/favicon.png',
'../client/lib/bootstrap/fonts/glyphicons-halflings-regular.eot',
'../client/lib/bootstrap/fonts/glyphicons-halflings-regular.svg',
'../client/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf',
'../client/lib/bootstrap/fonts/glyphicons-halflings-regular.woff',
],
tasks: ['copy']
}
}
});
// Load plugins
grunt.loadNpmTasks('grunt-contrib-coffee');
grunt.loadNpmTasks('grunt-livescript');
grunt.loadNpmTasks('grunt-contrib-jade');
grunt.loadNpmTasks('grunt-contrib-stylus');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-csso');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-watch');
// Tasks
grunt.registerTask('default', [
'coffee', 'jade', 'livescript', 'concat:js',
'stylus', 'concat:css',
'copy',
'watch'
]);
};
As you can see, aspax.yml is just 23 lines long, including comments - and quite easy to comprehend, I might say - while Gruntfile.js got to more than 150 lines, without even addressing fingerprinting and URLs adjustment in CSS files (I got bored and finally gave up, I admit :-).
Don't get me wrong, I am a fan of Grunt, I think it's an excellent general-purpose tool, I like it and use it when necessary, but I'm also a fan of using the right tool for the right job, and obviously writing an asset management Gruntfile can easily become a burden.
So, why not giving ASPAX a try?... It's still under development, of course, so we may still change/refactor a few things during the next couple of weeks, but the main functionality is there.
Written by Ionut-Cristian Florescu
Related protips
4 Responses
My opinion is:
- Grunt configuration syntax is suitable when you need to perform one action for all files. For example: compile all coffee files (src/*.coffee) or lint all js files
- Aspax configuration syntax is suitable when you need to perform multiple different actions for single file or group of concatenated files. For example: concatinate js files, minify result, copy result for aplication and just minify and copy for libraries
@mahnunchik - my thoughts exactly, but I usually have to perform multiple/mixed actions on my assets.
I was trying to switch to Grunt when I first came across it a few months ago, but didn't really like the idea of having to write so much code. Here's a "real-life" sample aspax.yml:
js/index.js:
- lib/swiper/idangerous.swiper-2.3.js
- lib/moment/moment.js
- lib/moment/lang/ro.js
- lib/jquery.unveil.js
- scripts/public/jquery.lsh.coffee | bare
- scripts/public/common.coffee | bare
- scripts/public/main-gallery.coffee | bare
- scripts/public/product.coffee | bare
- scripts/public/search-form.coffee | bare
- scripts/public/contact-form.coffee | bare
js/admin.js:
- lib/underscore.string.js
- lib/jquery.ui.core.js
- lib/jquery.ui.widget.js
- lib/jquery.ui.mouse.js
- lib/jquery.ui.sortable.js
- lib/jquery.iframe-transport.js
- lib/jquery.fileupload.js
- lib/jquery.autosize.js
- scripts/admin/jquery.lsh.admin.coffee | bare
- scripts/admin/user-list.coffee | bare
- scripts/admin/dealer-list.coffee | bare
- scripts/admin/make-list.coffee | bare
- scripts/admin/meta-model-list.coffee | bare
- scripts/admin/model-list.coffee | bare
- scripts/admin/attribute-list.coffee | bare
- scripts/admin/product-edit.coffee | bare
- lib/jade-runtime.js
- templates/namespace.coffee | bare
- templates/user-edit.jade
- templates/dealer-edit.jade
- templates/make-edit.jade
- templates/meta-model-edit.jade
- templates/model-edit.jade
- templates/attribute-edit.jade
css/index.css:
- lib/icomoon/style.css
- lib/swiper/idangerous.swiper.css
- styles/index.styl | nib
css/admin.css:
- styles/admin.styl | nib
css/product-print.css:
- styles/product-print.styl | nib
images/logo.png | fp : images/logo.png
images/logo@2x.png | fp : images/logo@2x.png
images/carbon-texture.png | fp : images/carbon-texture.png
images/carbon-texture@2x.png | fp : images/carbon-texture@2x.png
images/carbon-texture-light.png | fp : images/carbon-texture-light.png
images/carbon-texture-light@2x.png | fp : images/carbon-texture-light@2x.png
images/barcode.png | fp : images/barcode.png
images/barcode@2x.png | fp : images/barcode@2x.png
images/breadcrumbs-separator.png | fp : images/breadcrumbs-separator.png
images/breadcrumbs-separator@2x.png | fp : images/breadcrumbs-separator@2x.png
images/spinner-140x105.gif | fp : images/spinner-140x105.gif
favicon.png : icons/favicon.png
apple-touch-icon-57x57-precomposed.png : icons/apple-touch-icon-57x57-precomposed.png
apple-touch-icon-72x72-precomposed.png : icons/apple-touch-icon-72x72-precomposed.png
apple-touch-icon-114x114-precomposed.png : icons/apple-touch-icon-114x114-precomposed.png
apple-touch-icon-144x144-precomposed.png : icons/apple-touch-icon-144x144-precomposed.png
fonts/im.eot | fp : lib/icomoon/fonts/icomoon.eot
fonts/im.svg | fp : lib/icomoon/fonts/icomoon.svg
fonts/im.ttf | fp : lib/icomoon/fonts/icomoon.ttf
fonts/im.woff | fp : lib/icomoon/fontsfonts/icomoon.woff
Can you imagine the trouble of writing a Grunt scenario for that thing?...
@icflorescu in my project I have divided tasks between grunt and "asset tool" for example like this:
config.yml
js/index.js:
- lib/swiper/idangerous.swiper-2.3.js
- lib/moment/moment.js
- lib/moment/lang/ro.js
- lib/jquery.unveil.js
- scripts/public/index.js
js/admin.js:
- lib/underscore.string.js
- lib/jquery.ui.core.js
- lib/jquery.ui.widget.js
- lib/jquery.ui.mouse.js
- lib/jquery.ui.sortable.js
- lib/jquery.iframe-transport.js
- lib/jquery.fileupload.js
- lib/jquery.autosize.js
- scripts/admin/admin.js
- lib/jade-runtime.js
- templates/templates.js
css/index.css:
- lib/icomoon/style.css
- lib/swiper/idangerous.swiper.css
- styles/index.css
css/admin.css:
- styles/admin.css
css/product-print.css:
- styles/product-print.css
images/logo.png | fp : images/logo.png
images/logo@2x.png | fp : images/logo@2x.png
images/carbon-texture.png | fp : images/carbon-texture.png
images/carbon-texture@2x.png | fp : images/carbon-texture@2x.png
images/carbon-texture-light.png | fp : images/carbon-texture-light.png
images/carbon-texture-light@2x.png | fp : images/carbon-texture-light@2x.png
images/barcode.png | fp : images/barcode.png
images/barcode@2x.png | fp : images/barcode@2x.png
images/breadcrumbs-separator.png | fp : images/breadcrumbs-separator.png
images/breadcrumbs-separator@2x.png | fp : images/breadcrumbs-separator@2x.png
images/spinner-140x105.gif | fp : images/spinner-140x105.gif
favicon.png : icons/favicon.png
apple-touch-icon-57x57-precomposed.png : icons/apple-touch-icon-57x57-precomposed.png
apple-touch-icon-72x72-precomposed.png : icons/apple-touch-icon-72x72-precomposed.png
apple-touch-icon-114x114-precomposed.png : icons/apple-touch-icon-114x114-precomposed.png
apple-touch-icon-144x144-precomposed.png : icons/apple-touch-icon-144x144-precomposed.png
fonts/im.eot | fp : lib/icomoon/fonts/icomoon.eot
fonts/im.svg | fp : lib/icomoon/fonts/icomoon.svg
fonts/im.ttf | fp : lib/icomoon/fonts/icomoon.ttf
fonts/im.woff | fp : lib/icomoon/fontsfonts/icomoon.woff
Gruntfile.coffee
module.exports = (grunt)->
'use strict'
_ = grunt.util._
path = require 'path'
grunt.initConfig
pkg: grunt.file.readJSON('package.json')
coffeelint:
gruntfile:
src: 'Gruntfile.coffee'
application:
src: ['scripts/**/*.coffee']
options: grunt.file.readJSON('../coffeelint.json')
coffee:
options:
sourceMap: true
join: true
index:
src: [
'scripts/public/*.coffee'
]
dest: 'scripts/public/index.js'
admin:
src: [
'scripts/admin/*.coffee'
]
dest: 'scripts/admin/admin.js'
recess:
options:
compile: true
index:
src: ['styles/index.less']
dest: 'styles/index.css'
admin:
src: ['styles/admin.less']
dest: 'styles/admin.css'
productprint:
src: ['styles/product-print.less']
dest: 'styles/product-print.css'
jade:
options:
client: true
templates:
src: ["templates/*.jade"]
dest: "templates/templates.js"
watch:
options:
atBegin: true
scripts:
files: 'scripts/**/*.coffee'
tasks: ['coffeelint:application', 'coffee']
templates:
files: 'templates/*.jade'
tasks: ['jade:templates']
styles:
files: 'styles/*.less'
tasks: ['recess']
grunt.loadNpmTasks 'grunt-coffeelint'
grunt.loadNpmTasks 'grunt-contrib-coffee'
grunt.loadNpmTasks 'grunt-contrib-clean'
grunt.loadNpmTasks 'grunt-contrib-watch'
grunt.loadNpmTasks 'grunt-contrib-jade'
grunt.loadNpmTasks 'grunt-recess'
# tasks.
grunt.registerTask 'compile', [
'coffeelint'
'coffee'
]
grunt.registerTask 'default', [
'compile'
'jade:templates'
'recess'
]
It gave me the full power of Grunt for compiling stage (coffee, less, jade etc). And simple and predictable syntax to prepare assets for "client usage".
@mahnunchik - combining "full power of Grunt" with the "simple and predictable syntax" of ASPAX is an excellent idea.
Don't forget that you can also grunt.util.spawn aspax CLI in your scenario!
Have a fresh tip? Share with Coderwall community!
JavaScript on a page runs with the privileges of that origin. One altered library, tag manager, or snippet can read inputs, change what people see, and call your APIs as the user.
Security insights from Cside