Grunt PageSpeed with ngrok for local testing

Since releasing the original version of grunt-pagespeed a common question / issue that has been raise is the lack of support for running the Grunt task against local development environments. Yesterday Paul Kinlan tweeted a solution to using secure tunnels to local development environments.

As outlined in Paul’s tweet a tool to solve this problem is ngrok. Ngrok allows you to securely expose local development enviroments to the internet. For the remainder of this post, we’ll explore how ngrok and grunt-pagespeed can be integrated into your build process.

Setting up ngrok

Ngrok is available as binary download from it’s own site https://ngrok.com. However, it is also available as a Node module which means we can easily integrate it into our Grunt workflow. To get started lets include ngrok into our project.

npm install ngrok --save-dev

Next we need to setup Grunt and our Gruntfile as follows:

npm install -g grunt-cli
npm install --save-dev grunt
touch Gruntfile.js

We need to now add the Grunt plugins to the project:

npm install grunt-pagespeed --save-dev
npm install load-grunt-tasks --save-dev

Now that the project has all it’s dependencies, lets focus on the Grunt configuration to automate the testing of our local developement environment.

For this example, we’ll assume we have our application around running on http://localhost:8000. Lets start with a very simple skelton Gruntfile.

'use strict'

var ngrok = require('ngrok');

module.exports = function(grunt) {

  // Load grunt tasks
  require('load-grunt-tasks')(grunt);

};

In the above Gruntfile we load the ngrok node module and setup our Gruntfile to export our tasks. Finally the load-grunt-tasks module executed to automatically load all our Grunt plugins. Next we’ll configure grunt-pagespeed:

'use strict'

var ngrok = require('ngrok');

module.exports = function(grunt) {

  // Load grunt tasks
  require('load-grunt-tasks')(grunt);

  // Grunt configuration
  grunt.initConfig({
    pagespeed: {
      options: {
        nokey: true,
        locale: "en_GB",
        threshold: 40
      },
      local: {
        options: {
          strategy: "desktop"
        }
      },
      mobile: {
        options: {
          strategy: "mobile"
        }
      }
    }
  });
};

The above defines configuration for the pagespeed task and defines two Grunt targets, one that will run our local dev environment via the the Desktop strategy and another via the mobile strategy.

As you may have noticed, we haven’t declared a URL in our above configuration. To get the URL, we will use ngrok and then pass the url back to our pagespeed targets. The configuration for this custom task is shown below:

'use strict'

var ngrok = require('ngrok');

module.exports = function(grunt) {

  // Load grunt tasks
  require('load-grunt-tasks')(grunt);

  // Grunt configuration
  grunt.initConfig({
    pagespeed: {
      options: {
        nokey: true,
        locale: "en_G  B",
        threshold: 40
      },
      local: {
        options: {
          strategy: "desktop"
        }
      },
      mobile: {
        options: {
          strategy: "mobile"
        }
      }
    }
  });

  // Register customer task for ngrok
  grunt.registerTask('psi-ngrok', 'Run pagespeed with ngrok', function() {
    var done = this.async();
    var port = 8000;

    ngrok.connect(port, function(err, url) {
      if (err !== null) {
        grunt.fail.fatal(err);
        return done();
      }
      grunt.config.set('pagespeed.options.url', url);
      grunt.task.run('pagespeed');
      done();
    });
  });

  // Register default tasks
  grunt.registerTask('default', ['psi-ngrok']);
};

In the above configuration, we introduce a new custom task known as psi-ngrok. The next couple of lines declare the local port that will be mapped to ngrok. As our application is running on port 8000 (e.g. http://localhost:8000), we set this to 8000. Next we call ngrok.connect, passing our port in and a callback. The callback defines two parameters, err and url. The important parameter is url which provides us with the public URL that maps to our local development environment.

We check whether we have any errors connecting to ngrok but updating our pagespeed task configuration by using grunt.config.set and passing the url in. Finally we call the pagespeed task with grunt.task.run.

That’s a wrap…

Hopefully this will be a helpful starting point in your adventures to automate performance testing with Grunt and PageSpeed Insights against local environments. I’d encourage you to explore the ngrok and Grunt configuration to handle more sophisticated scenarios.

I’ve setup a sample project for people to fork as a starting point: https://github.com/jrcryer/grunt-pagespeed-ngrok