Last Updated: October 07, 2020
·
14.31K
· Ionut-Cristian Florescu

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...

Managing assets in ASPAX vs Grunt

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.

4 Responses
Add your response

My opinion is:

  1. 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
  2. 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
over 1 year ago ·

@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?...

over 1 year ago ·

@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".

over 1 year ago ·

@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!

over 1 year ago ·

Have a fresh tip? Share with Coderwall community!

Post
Post a tip