Skip to content

Instantly share code, notes, and snippets.

@0gust1
Last active November 17, 2016 15:58
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 0gust1/7683132 to your computer and use it in GitHub Desktop.
Save 0gust1/7683132 to your computer and use it in GitHub Desktop.
Usable and flexible Gruntfile modularization ?

I'm currently working on a large ecommerce website, full of legacy code, sometimes very dirty and buggy.

I was searching a solution to progressively refactor the codebase, with the help of grunt and its tooling.

I wanted something :

  • able to modularize grunt tasks and configuration, based on functionnalities (big UX/UI sections of the website) rather than technical-based (task based) modularization.
  • simple (readability, maintainability)
  • open and flexible

Thanks to @oncletom (see : https://gist.github.com/0gust1/7543330), I ditched the grunt-plugin scenario, to code a simple solution.

Here is my current "prototype" so far (still lack some error handling and other things...) :

/*global module:false*/

module.exports = function(grunt) {

  var env = process.env.NODE_ENV || 'dev';
  var _ = require('lodash');

  /*** External config & tasks filepaths ***/
  
  //we have 1 base config, and possibly many module-specific config
  var configLocations = ['./grunt-config/default_config.js', './grunt-config/**/config.js'];
  
  //we have 1 base tasks definition, and possibly many module-specific config
  var tasksLocations = ['./grunt-config/default_tasks.js', './grunt-config/**/tasks.js'];
  
  
  /* Typical project layout (matching with the globbing pattern above - adapt to your project structure) :

  ├── Gruntfile.js 
  ├── package.json
  ├── grunt-config
  │   ├── homepage
  │   │   └── config.js
  │   ├── navigation
  │   │   └── config.js
  │   ├── module1
  │   │   ├── config.js
  │   │   └── tasks.js
  │   ├── default_config.js
  │   ├── default_tasks.js
  │   └── template_module_grunt.txt
  ├── website_directory1
  │   ├── mdp
  │   ├── multimedia-storage
  │   ├── mv-commit.sh
  │   ├── query
  │   ├── temp
  │   └── tmp
  └── website_directory2
      ├── crossdomain.xml
      ├── css
      ├── favicon.ico
      ├── fonts
      :
      :
      :

  */

  /***************** External configuration management ***********************************/

  var configFiles = grunt.file.expand({
    filter: "isFile"
  }, configLocations );

  grunt.log.writeln('Gathering external configuration files'.underline.green);
  grunt.log.writeln("configFiles : " + grunt.log.wordlist(configFiles, {
    separator: ', ',
    color: 'cyan'
  }));

  var configArray = configFiles.map(function(file) {
    grunt.log.writeln("=> importing : " + file);
    return require(file)(grunt, env);
  });

  var config = {};

  configArray.forEach(function(element) {
    config = _.merge(config, element);
  });

  grunt.initConfig(config);

  /***************** Task loading & registering *******************************************/
  // We load grunt tasks listed in package.json file
  require('load-grunt-tasks')(grunt);

  /****** External tasks registering ****************/
  grunt.log.writeln('Gathering external task files'.underline.green);

  var taskFiles = grunt.file.expand({
    filter: "isFile"
  }, tasksLocations);

  grunt.log.writeln("task files : " + grunt.log.wordlist(taskFiles, {
    separator: ', ',
    color: 'cyan'
  }));

  taskFiles.forEach(function(path) {
    grunt.log.writeln("=> loading & registering : " + path);
    require(path)(grunt);
  });
  
  grunt.registerTask('default', ['attention:gruntfile', 'jshint:gruntfile', 'logConfig']);

  grunt.registerTask('checkGruntFile', 'Tâche par défaut - aide et vérification du gruntfile', function() {
    grunt.log.subhead('* Tâche par défaut - aide et vérification du gruntfile *');
    grunt.log.writeln('Exécutez "grunt -h" pour avoir plus d\'informations sur les tâches disponibles');
    grunt.log.writeln('...');
    grunt.log.subhead('Vérification du gruntfile...');
    grunt.task.run(['jshint:gruntfile']);
  });

  //write the generated configuration (for debug)
  grunt.registerTask('logConfig', 'Ecrire la configuration générée', function() {
    //grunt.task.run(['attention:gruntfile']);
    grunt.log.subhead('* Configuration générée : *');
    grunt.log.writeln(JSON.stringify(config, undefined, 2));
  });

};
module.exports = function(grunt, env) {
var defaults = config = {
//Write here your configuration , grunt style, in the standard way
//Extract informations from package.json
pkg: grunt.file.readJSON('package.json'),
//Define a global banner
banner: '/*! test - v<%= meta.version %> - ' + 'generated at : <%= grunt.template.today("isoDateTime") %>\n' + '* http://www.foo.bar - GENERATED FILE : ** DO NOT EDIT THIS FILE ! **\n' + '*<%= grunt.template.today("yyyy") %> ' + 'Foo Bar; all rights reserved */\n',
//Define default jshint options and gruntfile target
jshint: {
options: {
//http://www.jshint.com/docs/options/
//
//Enforcing options :
curly: true, //forcer l'utilisation des accolades
eqeqeq: true, //forcer l'uttilisation de l'égalité stricte
es3: true, //compat IE8 pour le JS
immed: true, //Style des IIFE : parenthèses autour
latedef: true, //This option prohibits the use of a variable before it was defined.
newcap: true, //This option requires you to capitalize names of constructor function
noarg: true, //This option prohibits the use of arguments.caller and arguments.callee
undef: true, //This option prohibits the use of explicitly undeclared variables.
unused: true, //This option warns when you define and never use your variables.
//Relaxing options :
asi: true, //This option suppresses warnings about missing semicolons.
boss: true, //This option suppresses warnings about the use of assignments in cases where comparisons are expected.
eqnull: true, //This option suppresses warnings about == null comparisons.
sub: true, //This option suppresses warnings about using [] notation when it can be expressed in dot notation
//environnement options :
browser: true, //This option defines globals exposed by modern browser
jquery: true, //This option defines globals exposed by the jQuery JavaScript library.
globals: {
jQuery: true
},
reporter: require('jshint-stylish') //pretty reporter
},
gruntfile: {
src: ['Gruntfile.js'],
options: {
globals: {
require: true,
process: true,
jQuery: false
}
}
}
}
}; //end config
//if (env === 'prod'){
//special configuration for production
//}
return config;
};
module.exports = function(grunt) {
grunt.registerTask('helloWorld', 'Say hello!', function() {
grunt.log.writeln("Hello world!");
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment