Have Grunt generate index.html for different setups

JavascriptBuildBuild AutomationGruntjs

Javascript Problem Overview


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?

Javascript Solutions


Solution 1 - Javascript

I recently discovered these Grunt v0.4.0 compatible tasks:

  • grunt-preprocess
    > Grunt task around preprocess npm module.

  • grunt-env
    > Grunt task to automate environment configuration for future tasks.

Below are snippets from my Gruntfile.js.

ENV setup:

env : {
	
	options : {
		
		/* Shared Options Hash */
		//globalOption : 'foo'
		
	},
	
	dev: {
		
		NODE_ENV : 'DEVELOPMENT'
		
	},
	
	prod : {
		
		NODE_ENV : 'PRODUCTION'
		
	}
	
},

Preprocess:

preprocess : {
	
	dev : {
		
		src : './src/tmpl/index.html',
		dest : './dev/index.html'
		
	},
	
	prod : {
		
		src : './src/tmpl/index.html',
		dest : '../<%= pkg.version %>/<%= now %>/<%= ver %>/index.html',
		options : {
			
			context : {
				name : '<%= pkg.name %>',
				version : '<%= pkg.version %>',
				now : '<%= now %>',
				ver : '<%= ver %>'
			}
			
		}
		
	}
	
}

Tasks:

grunt.registerTask('default', ['jshint']);

grunt.registerTask('dev', ['jshint', 'env:dev', 'clean:dev', 'preprocess:dev']);

grunt.registerTask('prod', ['jshint', 'env:prod', 'clean:prod', 'uglify:prod', 'cssmin:prod', 'copy:prod', 'preprocess:prod']);

And in the /src/tmpl/index.html template file (for example):

<!-- @if NODE_ENV == 'DEVELOPMENT' -->
	
	<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.js"></script>
	<script src="../src/js/foo1.js"></script>
	<script src="../src/js/foo2.js"></script>
	<script src="../src/js/jquery.blah.js"></script>
	<script src="../src/js/jquery.billy.js"></script>
	<script src="../src/js/jquery.jenkins.js"></script>
	
<!-- @endif -->

<!-- @if NODE_ENV == 'PRODUCTION' -->
	
	<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
	
	<script src="http://cdn.foo.com/<!-- @echo name -->/<!-- @echo version -->/<!-- @echo now -->/<!-- @echo ver -->/js/<!-- @echo name -->.min.js"></script>
	
<!-- @endif -->

I'm sure my setup is different than most people, and the usefulness of the above will depend on your situation. For me, while it's an awesome bit of code, the Yeoman grunt-usemin is a more robust than I personally need.

NOTE: I just discovered the above listed tasks today, so I might be missing a feature and/or my process may change down the road. For now, I'm loving the simplicity and features that grunt-preprocess and grunt-env have to offer. :)


Jan 2014 update:

Motivated by a down vote ...

When I posted this answer there weren't many options for Grunt 0.4.x that offered a solution that worked for my needs. Now, months later, I would guess that there are more options out there that could be better than what I have posted here. While I still personally use, and enjoy using, this technique for my builds, I ask that future readers take the time to read the other answers given and to research all the options. If you find a better solution, please post your answer here.

Feb 2014 update:

I'm not sure if it will be of any help to anyone, but I've created this demo repository on GitHub that shows a complete (and more complex setup) using the technique(s) I've outlined above.

Solution 2 - Javascript

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.

Solution 3 - Javascript

I dislike the solutions here (including the one I previously gave) and here's why:

  • The problem with the highest voted answer is that you have to manually sync the list of script tags when you add/rename/delete a JS file.
  • The problem with the accepted answer is that your list of JS files can't have pattern matching. This means you've got to update it by hand in the Gruntfile.

I've figured out how to solve both of these issues. I've set up my grunt task so that every time a file is added or deleted, the script tags automatically get generated to reflect that. This way, you don't need to modify your html file or your grunt file when you add/remove/rename your JS files.

To summarize how that works, I have a html template with a variable for the script tags. I use https://github.com/alanshaw/grunt-include-replace to populate that variable. In dev mode, that variable comes from a globbing pattern of all my JS files. The watch task recalculates this value when a JS file is added or removed.

Now, to get different results in dev or prod mode, you simply populate that variable with a different value. Here's some code:

var jsSrcFileArray = [
    'src/main/scripts/app/js/Constants.js',
    'src/main/scripts/app/js/Random.js',
    'src/main/scripts/app/js/Vector.js',
    'src/main/scripts/app/js/scripts.js',
    'src/main/scripts/app/js/StatsData.js',
    'src/main/scripts/app/js/Dialog.js',
    'src/main/scripts/app/**/*.js',
    '!src/main/scripts/app/js/AuditingReport.js'
];

var jsScriptTags = function (srcPattern, destPath) {
    if (srcPattern === undefined) {
        throw new Error("srcPattern undefined");
    }
    if (destPath === undefined) {
        throw new Error("destPath undefined");
    }
    return grunt.util._.reduce(
        grunt.file.expandMapping(srcPattern, destPath, {
            filter: 'isFile',
            flatten: true,
            expand: true,
            cwd: '.'
        }),
        function (sum, file) {
            return sum + '\n<script src="' + file.dest + '" type="text/javascript"></script>';
        },
        ''
    );
};

...

grunt.initConfig({

    includereplace: {
        dev: {
            options: {
                globals: {
                    scriptsTags: '<%= jsScriptTags(jsSrcFileArray, "../../main/scripts/app/js")%>'
                }
            },
            src: [
                'src/**/html-template.html'
            ],
            dest: 'src/main/generated/',
            flatten: true,
            cwd: '.',
            expand: true
        },
        prod: {
            options: {
                globals: {
                    scriptsTags: '<script src="app.min.js" type="text/javascript"></script>'
                }
            },
            src: [
                'src/**/html-template.html'
            ],
            dest: 'src/main/generatedprod/',
            flatten: true,
            cwd: '.',
            expand: true
        }

...

    jsScriptTags: jsScriptTags

jsSrcFileArray is your typical grunt file-globbing pattern. jsScriptTags takes the jsSrcFileArray and concatenates them together with script tags on both sides. destPath is the prefix I want on each file.

And here's what the HTML looks like:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>Example</title>

</head>

<body>    
@@scriptsTags
</body>
</html>

Now, as you can see in the config, I generate the value of that variable as a hard coded script tag when it's run in prod mode. In dev mode, this variable will expand to a value like this:

<script src="../../main/scripts/app/js/Constants.js" type="text/javascript"></script>
<script src="../../main/scripts/app/js/Random.js" type="text/javascript"></script>
<script src="../../main/scripts/app/js/Vector.js" type="text/javascript"></script>
<script src="../../main/scripts/app/js/StatsData.js" type="text/javascript"></script>
<script src="../../main/scripts/app/js/Dialog.js" type="text/javascript"></script>

Let me know if you have any questions.

PS: This is a crazy amount of code for something I'd want to do in every client-side JS app. I hope someone can turn this into a reusable plugin. Maybe I will some day.

Solution 4 - Javascript

I have been asking myself the same question for a while, and I think this grunt plugin could be configured to do what you want: https://npmjs.org/package/grunt-targethtml. It implements conditional html tags, that depend on the grunt target.

Solution 5 - Javascript

I was looking for a more simple, straight forward solution so I combined the answer from this question:

https://stackoverflow.com/questions/19010621/how-to-place-if-else-block-in-gruntfile-js

and came up with following simple steps:

  1. Keep two versions of your index files as you listed and name them index-development.html and index-prodoction.html.

  2. Use the following logic in your Gruntfile.js's concat/copy block for your index.html file:

     concat: {
         index: {
             src : [ (function() {
                 if (grunt.option('Release')) {
                   return 'views/index-production.html';
                 } else {
                   return 'views/index-development.html';
                 }
               }()) ],
            dest: '<%= distdir %>/index.html',
            ...
         },
         ...
     },
    
  3. run 'grunt --Release' to choose the index-production.html file and leave off the flag to have the development version.

No new plugins to add or configure and no new grunt tasks.

Solution 6 - Javascript

This grunt task named scriptlinker looks like an easy way to add the scripts in dev mode. You could probably run a concat task first and then point it to your concatenated file in prod mode.

Solution 7 - Javascript

grunt-dom-munger reads and manipulates HTML with CSS selectors. Ex. read

Solution 8 - Javascript

I found a grunt plugin called grunt-dev-prod-switch. All it does is comment out certain blocks it looks for based on an --env option you pass to grunt (although it limits you to dev, prod, and test).

Once you set it up as it explains here, you can run for example:

grunt serve --env=dev, and all it does is comment out the blocks which are wrapped by

    <!-- env:test/prod -->
    your code here
    <!-- env:test/prod:end -->

and it will uncomment out blocks which are wrapped by

    <!-- env:dev -->
    your code here
    <!-- env:dev:end -->

It also works on javascript, I use it for setting up the right IP address to connect to for my backend API. The blocks just change to

    /* env:dev */
    your code here
    /* env:dev:end */

In your case, it would be as simple as this:

<!DOCTYPE html>
<html>
    <head>
        <!-- env:dev -->
        <script src="js/module1.js" />
        <script src="js/module2.js" />
        <script src="js/module3.js" />
        ...
        <!-- env:dev:end -->
        <!-- env:prod -->
        <script src="js/MyApp-all.min.js" />
        ...
        <!-- env:prod:end -->
    </head>
    <body></body>
</html>

Solution 9 - Javascript

grunt-bake is a fantastic grunt script that would work great here. I use it in my JQM auto build script.

https://github.com/imaginethepoet/autojqmphonegap

Take a look at my grunt.coffee file:

bake:
    resources: 
      files: "index.html":"resources/custom/components/base.html"

This looks at all the files in base.html and sucks them in to create index.html works fantastic for multipage apps (phonegap). This allows for easier development as all devs are not working on one long single page app (preventing lots of conflict checkins). Instead you can break up the pages and work on smaller chunks of code and compile to the full page using a watch command.

Bake reads the template from base.html and injects the component html pages on watch.

<!DOCTYPE html>

jQuery Mobile Demos

<body>
    <!--(bake /resources/custom/components/page1.html)-->
    <!--(bake /resources/custom/components/page2.html)-->
    <!--(bake /resources/custom/components/page3.html)-->
</body>

You can take this a step further and add injections in your pages for "menus" "popups" etc so you can really break pages into smaller manageable components.

Solution 10 - Javascript

Use a combination of wiredep https://github.com/taptapship/wiredep and usemin https://github.com/yeoman/grunt-usemin in order to have grunt take care of these tasks. Wiredep will add your dependencies one script file at a time, and usemin will concatenate them all into a single file for production. This can then be accomplished with just some html comments. For instance, my bower packages are automatically included and added to the html when I run bower install && grunt bowerInstall:

<!-- build:js /scripts/vendor.js -->
<!-- bower:js -->
<!-- endbower -->
<!-- endbuild -->

Solution 11 - Javascript

This answer is not for noobs!

Use Jade templating ... passing variables to a Jade template is a bog standard use case

I am using grunt (grunt-contrib-jade) but you don't have to use grunt. Just use the standard npm jade module.

If using grunt then your gruntfile would like something like ...

jade: {
    options: {
      // TODO - Define options here
    },
    dev: {
      options: {
        data: {
          pageTitle: '<%= grunt.file.name %>',
          homePage: '/app',
          liveReloadServer: liveReloadServer,
          cssGruntClassesForHtmlHead: 'grunt-' + '<%= grunt.task.current.target %>'
        },
        pretty: true
      },
      files: [
        {
          expand: true,
          cwd: "src/app",
          src: ["index.jade", "404.jade"],
          dest: "lib/app",
          ext: ".html"
        },
        {
          expand: true,
          flatten: true,
          cwd: "src/app",
          src: ["directives/partials/*.jade"],
          dest: "lib/app/directives/partials",
          ext: ".html"
        }
      ]
    }
  },

We can now easily access the data passed by grunt in the Jade template.

Much like the approach used by Modernizr, I set a CSS class on the HTML tag according to the value of the variable passed and can use JavaScript logic from there based on whether the CSS class is present or not.

This is great if using Angular since you can do ng-if's to include elements in the page based on whether the class is present.

For example, I might include a script if the class is present ...

(For example, I might include the live reload script in dev but not in production)

<script ng-if="controller.isClassPresent()" src="//localhost:35729/livereload.js"></script> 

Solution 12 - Javascript

Consider processhtml. It allows definition of multiple "targets" for builds. Comments are used to conditionally include or exclude material from the HTML:

<!-- build:js:production js/app.js -->
...
<!-- /build -->

becomes

<script src="js/app.js"></script>

It even purports to do nifty stuff like this (see the README):

<!-- build:[class]:dist production -->
<html class="debug_mode">
<!-- /build -->

<!-- class is changed to 'production' only when the 'dist' build is executed -->
<html class="production">

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDmitry PashkevichView Question on Stackoverflow
Solution 1 - JavascriptmhulseView Answer on Stackoverflow
Solution 2 - JavascriptDmitry PashkevichView Answer on Stackoverflow
Solution 3 - JavascriptDaniel KaplanView Answer on Stackoverflow
Solution 4 - JavascriptPer Quested AronssonView Answer on Stackoverflow
Solution 5 - JavascriptEdward TanView Answer on Stackoverflow
Solution 6 - JavascriptDaniel KaplanView Answer on Stackoverflow
Solution 7 - JavascriptbrilloutView Answer on Stackoverflow
Solution 8 - JavascriptanonymousView Answer on Stackoverflow
Solution 9 - JavascriptimaginethepoetView Answer on Stackoverflow
Solution 10 - JavascriptScottuxView Answer on Stackoverflow
Solution 11 - Javascriptdanday74View Answer on Stackoverflow
Solution 12 - JavascriptdatView Answer on Stackoverflow