Driving Phantom from Grunt – A Checklist Aside

Article Continues Beneath

Whereas constructing web sites at Filament Group, there are a pair instruments that constantly discover their means into our workflow:

  • GruntJS is a JavaScript Process Runner. It runs on NodeJS and permits the consumer to simply concatenate and minify information, run unit assessments, and carry out many different duties, from linting to minification of pictures.
  • PhantomJS is a headless (Webkit-based) net browser. A headless net browser renders a web page with out having a visual window. Utilizing this performance, we are able to write code that you’d wish to run in a browser, however see its ends in the command line. This enables us to run scripts and even render snapshots of pages with out having to open a browser and do it manually.

Collectively, these instruments enable us to get constant suggestions for our code, by additional automating checks that will usually require opening a browser.

For this instance, we’re going to construct a Grunt job that takes a display shot of the pages we’re constructing (much like Wraith, however far much less superior). There are a number of components to make this work, so let’s break it down. First, we are going to write a PhantomJS script that renders every web page. Second, we make a NodeJS perform that calls this script. Lastly, we make a GruntJS job that calls that Node perform. Enjoyable!

To get began, we have to guarantee that PhantomJS is put in. Since we’re utilizing Phantom from the context of a NodeJS utility, an easy solution to set up it’s by utilizing the NPM PhantomJS installer bundle. Putting in Phantom on this method permits us to ensure we now have easy accessibility to the trail for the Phantom command whereas concurrently having an area, project-specific model of it put in.

To put in domestically: npm set up phantomjs.

Now, we have to write a script to provide to PhantomJS that may render a given web page. This script will take two arguments. The primary is the URL of the web page that must be opened. The second is the file identify for the output. PhantomJS will open the web page, and when the web page has opened efficiently, it would render the web page as a PNG after which exit.

var web page = require( "webpage" ).create();
var web site = phantom.args[0],
    output = phantom.args[1];

web page.open( web site, perform( standing ){
    if( standing !== "success" ){
        phantom.exit( 1 );
    }
    web page.render( output + ".png" );
    phantom.exit( 0 );
  });

Let’s create a lib listing and save this file in it. We’ll name it screenshotter.js. We will check this shortly by operating this command from our command line (in the identical listing we put in phantom): ./node_modules/.bin/phantomjs lib/screenshotter.js https://www.google.com google. This could create a file in the identical listing named google.png.

Now that we now have a PhantomJS script, let’s work on making this run from Node. PhantomJS is a very totally different runtime than Node, so we’d like a solution to talk. Fortunately, Node provides us a superb library named child_process and particularly, a way from that library known as execFile.

If we have a look at the documentation for the execFile technique, we are able to see that it takes as much as 4 arguments. One is necessary, the opposite three are non-compulsory. The primary argument is the file or, in our case, the trail to PhantomJS. For the opposite arguments, we’ll have to go PhantomJS args (the URL and output from above), and we’ll additionally wish to embody our callback perform—so we are able to ensure we seize any output or errors from operating Phantom.

var path = require( "path" );
var execFile = require( "child_process" ).execFile;
var phantomPath = require( "phantomjs" ).path;
var phantomscript = path.resolve( path.be a part of( __dirname, "screenshotter.js" ) );

exports.takeShot = perform( url, output, cb ){
    execFile( phantomPath, [
            phantomscript,
            url,
            output
    ],
    perform( err, stdout, stderr ){
        if( err ){
            throw err;
        }

        if( stderr ){
            console.error( stderr );
        }

        if( stdout ){
            console.log( stdout );
        }
        if( cb ){
            cb();
        }
    });
};

Our instance code from above is written as a Node.js module. It has a perform that takes three parameters. These parameters are the identical parameters which might be used within the PhantomJS script from above and a callback perform to run when the duty has accomplished. It then calls execFile and passes it three arguments. The primary is the trail to PhantomJS. The second is an Array with the our handed in parameters. The third is our callback perform. This callback perform is named with three arguments: err, stdout, and stderr. err is the error thrown by Phantom if one thing unhealthy occurs inside that script. stderr and stdout are the usual error and normal output streams. This could give us the whole lot we have to name our script as if it’s a daily NodeJS perform, which is able to make it good for a Grunt job. Let’s put it aside in lib/shot-wrapper.js.

Now, for the Grunt job:

var screenshot = require( "../lib/shot-wrapper" );

grunt.registerMultiTask( 'screenshots', 'Use Grunt and PhantomJS to generate Screenshots of pages', perform(){
    var achieved = this.async();
    // Merge task-specific and/or target-specific choices with these defaults.
    var choices = this.choices({
        url: '',
        output: ''
    });

    screenshot.takeShot( choices.url, choices.output, perform(){
        achieved();
    });
});

Let’s check out this piece by piece. First, we require the shot-wrapper library we constructed above. Then, we create the duty screenshots by utilizing grunt.registerMultiTask. Because the takeShot technique is asynchronous, we have to create a achieved callback perform that lets Grunt know when to finish the duty. The choices object units defaults for url and output in case they aren’t handed in (on this case, they’re empty strings, which received’t work). Lastly, go the choices and the achieved callback into the takeShot technique. Now, when someone calls this Grunt job, your code will run.

Let’s give it a strive. Right here’s an excerpt from my Gruntfile:

screenshots: {
  default_options: {
    choices: {
      url: 'http://www.alistapart.com/',
      output: 'ala'
    }
  }
}
An animated gif running screenshots tasks

The duty has run, so we’ll open the file produced:

open ala.png

And voilà: as you possibly can see from this relatively giant picture, we now have a full-page screenshot of A Checklist Aside’s homepage. (Word: chances are you’ll discover that the net fonts are lacking within the rendered picture. That’s at the moment a recognized problem with PhantomJS.)

Simply think about what you are able to do along with your newfound energy. Phantom and Grunt offer you ample freedom to discover all kinds of latest methods to reinforce your improvement workflow. Go forth and discover!

For extra in-depth code and to see the best way this works when constructing a challenge, take a look at the repository.

Leave a Comment