Last Updated: October 07, 2020
·
13.68K
· 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