Ludochaordic
Fantaisies programatico-ludiques

Generating SRI hashes with grunt-usemin

I'm not really crazy about Yeoman's grunt-usemin : I find painful the way it enforces a unique pipeline, with its preliminary useminPrepare task and :generated targets.

But on the project I'm working on, we made the choice to use it early on, and we're sticking with it for now. And this time we wanted to add subresource integrity (SRI) checks to our project. Here is a quick and easy way to do so, assuming you already use grunt-usemin.

This was tested with NodeJs version 0.12.2 (versions inferior to 0.12 won't have execSync), grunt version 0.4.5 and grunt-usemin version 3.1.1. We also use load-grunt-config (v0.19.0), so the following is the exact content of grunt/usemin.js :

var execSync = require('child_process').execSync;

var compute_sri_hash = function (filename, algo) {
    algo = algo || 'sha256';
    return algo + '-' + execSync('openssl dgst -' + algo + ' -binary ' + filename
            + ' | openssl enc -base64 -A');
};

module.exports = function (grunt) {
    return {
        html: ['grunt-target/index.html'],
        options: {
            assetsDirs: ['grunt-target'],
            blockReplacements: {
                css: function (block) {
                    var media = block.media ? ' media="' + block.media + '"' : '';
                    grunt.log.writeln('Generating SRI hash for ' + block.dest);
                    var sri_hash = compute_sri_hash('grunt-target/' + block.dest);
                    return '<link rel="stylesheet" href="' + block.dest + '"' + media
                        + ' integrity="' + sri_hash + '" crossorigin="anonymous">';
                },
                js: function (block) {
                    var defer = block.defer ? 'defer ' : '';
                    var async = block.async ? 'async ' : '';
                    grunt.log.writeln('Generating SRI hash for ' + block.dest);
                    var sri_hash = compute_sri_hash('grunt-target/' + block.dest);
                    return '<script ' + defer + async + 'src="' + block.dest + '"'
                        + ' integrity="' + sri_hash + '"'
                        + ' crossorigin="anonymous"><\/script>';
                },
                nominjs: function (block) {
                    var defer = block.defer ? 'defer ' : '';
                    var async = block.async ? 'async ' : '';
                    grunt.log.writeln('Generating SRI hash for ' + block.dest);
                    var sri_hash = compute_sri_hash('grunt-target/' + block.dest);
                    return '<script ' + defer + async + 'src="' + block.dest + '"'
                        + ' integrity="' + sri_hash + '"'
                        + ' crossorigin="anonymous"><\/script>';
                }
            }
        }
    };
};

With load-grunt-tasks, module.exports can be either an object hash or a function. I use the later form here to gain access to grunt.log. Then I redefine grunt-usemin default block replacements so that they include the SRI hash. Note that I also define a nominjs target : this is a useful trick so that I can define different usemin flow steps for JS files I don't want to uglify. I then use it like this in my index.html :

&lt;!-- build:nominjs compile/scripts/app.js --&gt;
&lt;script src="../../grunt-target/scripts/app.js"&gt;&lt;/script&gt;
&lt;script src="../../grunt-target/scripts/ngtemplates.js"&gt;&lt;/script&gt;
&lt;!-- endbuild --&gt;

In thgis example, grunt-target/scripts/app.js & grunt-target/scripts/ngtemplates.js have been generated by an earlier grunt target when the usemin piepline kicks in.

That's it !