Search code examples
javascriptbuildbuild-automationgruntjs

Have Grunt generate index.html for different setups


I'm trying to use Grunt as a build tool for my webapp.

I want to have at least two setups:

I. Development setup - load scripts from separate files, without concatenation,

so my index.html would look something like:

<!DOCTYPE html>
<html>
    <head>
        <script src="js/module1.js" />
        <script src="js/module2.js" />
        <script src="js/module3.js" />
        ...
    </head>
    <body></body>
</html>

II. Production setup - load my scripts minified & concatenated in one file,

with index.html accordingly:

<!DOCTYPE html>
<html>
    <head>
        <script src="js/MyApp-all.min.js" />
    </head>
    <body></body>
</html>

The question is, how can I make grunt make these index.html's depending on the configuration when I run grunt dev or grunt prod?

Or maybe I'm digging in the wrong direction and it would be easier to always generate MyApp-all.min.js but put inside it either all my scripts (concatenated) or a loader script that asynchronously loads those scripts from separate files?

How do you do it, guys?


Solution

  • I've come up with my own solution. Not polished yet but I think I'm going to move in that direction.

    In essense, I'm using grunt.template.process() to generate my index.html from a template that analyzes current configuration and produces either a list of my original source files or links to a single file with minified code. The below example is for js files but the same approach can be extended to css and any other possible text files.

    grunt.js:

    /*global module:false*/
    module.exports = function(grunt) {
        var   // js files
            jsFiles = [
                  'src/module1.js',
                  'src/module2.js',
                  'src/module3.js',
                  'src/awesome.js'
                ];
    
        // Import custom tasks (see index task below)
        grunt.loadTasks( "build/tasks" );
    
        // Project configuration.
        grunt.initConfig({
          pkg: '<json:package.json>',
          meta: {
            banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
              '<%= grunt.template.today("yyyy-mm-dd") %> */'
          },
    
          jsFiles: jsFiles,
    
          // file name for concatenated js
          concatJsFile: '<%= pkg.name %>-all.js',
    
          // file name for concatenated & minified js
          concatJsMinFile: '<%= pkg.name %>-all.min.js',
    
          concat: {
            dist: {
                src: ['<banner:meta.banner>'].concat(jsFiles),
                dest: 'dist/<%= concatJsFile %>'
            }
          },
          min: {
            dist: {
            src: ['<banner:meta.banner>', '<config:concat.dist.dest>'],
            dest: 'dist/<%= concatJsMinFile %>'
            }
          },
          lint: {
            files: ['grunt.js'].concat(jsFiles)
          },
          // options for index.html builder task
          index: {
            src: 'index.tmpl',  // source template file
            dest: 'index.html'  // destination file (usually index.html)
          }
        });
    
    
        // Development setup
        grunt.registerTask('dev', 'Development build', function() {
            // set some global flags that all tasks can access
            grunt.config('isDebug', true);
            grunt.config('isConcat', false);
            grunt.config('isMin', false);
    
            // run tasks
            grunt.task.run('lint index');
        });
    
        // Production setup
        grunt.registerTask('prod', 'Production build', function() {
            // set some global flags that all tasks can access
            grunt.config('isDebug', false);
            grunt.config('isConcat', true);
            grunt.config('isMin', true);
    
            // run tasks
            grunt.task.run('lint concat min index');
        });
    
        // Default task
        grunt.registerTask('default', 'dev');
    };
    

    index.js (the index task):

    module.exports = function( grunt ) {
        grunt.registerTask( "index", "Generate index.html depending on configuration", function() {
            var conf = grunt.config('index'),
                tmpl = grunt.file.read(conf.src);
    
            grunt.file.write(conf.dest, grunt.template.process(tmpl));
    
            grunt.log.writeln('Generated \'' + conf.dest + '\' from \'' + conf.src + '\'');
        });
    }
    

    Finally, index.tmpl, with generation logic baked in:

    <doctype html>
    <head>
    <%
        var jsFiles = grunt.config('jsFiles'),
            isConcat = grunt.config('isConcat');
    
        if(isConcat) {
            print('<script type="text/javascript" src="' + grunt.config('concat.dist.dest') + '"></script>\n');
        } else {
            for(var i = 0, len = jsFiles.length; i < len; i++) {
                print('<script type="text/javascript" src="' + jsFiles[i] + '"></script>\n');
            }
        }
    %>
    </head>
    <html>
    </html>
    

    UPD. Found out that Yeoman, which is based on grunt, has a built-in usemin task that integrates with Yeoman's build system. It generates a production version of index.html from information in development version of index.html as well as other environment settings. A bit sophisticated but interesting to look at.