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!