How to optimize your image to calm down Google
Top

A lazy developer is a good developer, or how I became acquainted with Node.js basics

For those people who can not wait Click here and you can see everything with your own eyes a live example

Every day each of us has to do repetitive actions, lose a lot of time without any skills improvement. So my Team Lead, gave me an idea about the automatization of this boring process. Better I try to explain at a specific example. Let’s pretend we have to optimise images so that Google Page Speed would have no pretension to them. How many steps should we pass through?

  1. Create of HTML document.
  2. Copy images to the same directory.
  3. Append an tag (a lot of tags) into document.
  4. Set an src value to each image.
  5. Send our document and images to server.
  6. Copy the document link on server.
  7. Enter Google Page Speed website.
  8. Paste copied images into the input for analysis.
  9. Download optimised images by clicking on special link.

In total 9, we do almost 9 actions every day. Maybe not quite hard, but how exhausting it is!
No more!

google optimizer

Setting Up the Project

The structure of our project will look like this:

  • node_modules/ Node.js dependencies
  • public/ styles and scripts for the front-end part
  • uploads/ folder for uploaded images and generated HTML document
  • views/ HTML templates
  • app.js logic for our server-side part
  • package.json information about the project and dependencies

Firstly we need to run npm init and create the package.json file in that way.
Let’s install our dependencies via NPM now:

$ npm install express --save
$ npm install fs-extra --save
$ npm install formidable --save

We’ll talk about mission of these modules later.

Client side

HTML and CSS parts are quite easy here, so I just focus on the input for uploaded files:

<input id=“upload-input” type=“file” name=“uploads&#91;&#93;” multiple=“multiple”>

All we need is to hide #upload-input (because I don’t like how it looks) and trigger it every time we click on the upload button. But it’s just appearance and not really necessary for our purposes.
In directory public/js/ create file upload.js. Here we will write simple code for button to trigger hidden input and for progress bar to reset it to 0% each time we attempt to select new files:

$('.upload-btn').on('click', function () {
     $('#upload-input').click();
     $('.progress-bar').width('0%');
});

Then add logic for uploading file process. We need to check if the user clicked the upload button and uploaded the files, or clicked the cancel button. So this is what we have:

$(‘#upload-input’).on(‘change’, function() {
    var files = $(this).get(0).files;

    if (files.length > 0) {
        // Code
    }
});

Where ‘files’ is an array that would contain uploaded files.
Next step is the creation an object that will be sent in AJAX request to server side.

var formData = new FormData();

Then we need to loop through selected files and add them to the formData object.

for (var i = 0; i < files.length; i++) {
     var file = files&#91;i&#93;;

     formData.append('uploads&#91;&#93;', file, file.name);
}&#91;/code&#93;</pre>
<p>Create an AJAX request that will POST the data:</p>
<pre>$.ajax({
    url: '/upload',
    type: 'POST',
    data: formData,
    processData: false,
    contentType: false,
    success: function(data) {
        console.log('upload successful!\n' + data);
    },
    xhr: function() {
        var xhr = new XMLHttpRequest();
        xhr.upload.addEventListener('progress', function(evt) {

            if (evt.lengthComputable) {
                var percentComplete = evt.loaded / evt.total;
                percentComplete = parseInt(percentComplete * 100);
                $('.progress-bar').width(percentComplete + '%');
                if (percentComplete === 100) {
                    $('.progress-bar').html('Done');
                }
            }
        }, false);

         return xhr;
     }
});

Our client-side code is almost ready. We’ll just add few more features later.

Server Side

Required modules we need for the uploader:

var express = require('express');
var app = express();
var path = require('path');
var formidable = require('formidable');
var fs = require('fs-extra');

The role of the express module is to provide small, robust tooling for HTTP servers.
The path module provides utilities for working with file and directory paths.
The formidable module is used for parsing form data, it’s especially convenient for file uploads.

The fs-extra adds file system methods that aren't included in the native fs (file system module). We’re going to use the emptyDir() method to delete directory contents if the directory is not empty (without deleting the directory). If the directory doesn’t exist, it is created.

Create server:

var port = 9040; // choose any free port
var server = app.listen(port, function() {
    console.log('Server listening on port ' + port);
});

Now you can start the server by command in console:

$ node app.js

If everything is ok you will see a message in the console:

Server listening on port 9040

To serve static files such as images, CSS files, and JavaScript files, we used the express.static built-in middleware function in Express and the path.join() method (joins all given path segments together using the platform specific separator as a delimiter, then normalizes the resulting path).

app.use(express.static(path.join(__dirname, 'public')));

Express module methods has next syntax: app.all(path, callback [, callback ...])

app.get() method routes HTTP GET requests to the specified path with the specified callback functions. In our case serving up the homepage and clearing uploads/ directory every time someone visits the page.

app.get('/', function(req, res) {
    res.sendFile(path.join(__dirname, 'views/index.html'));
    fs.emptyDir(ROOT);
});

app.post() method routes HTTP POST requests to the specified path with the specified callback functions. For our project this will be to respond to the POST request on the /upload route:

app.post('/upload', function(req, res) {
    // Code
});

Create an incoming object with the help of ‘formidable’ module:

var form = new formidable.IncomingForm();

Specify we want to allow user to upload multiple files in a single request:

form.multiples = true;

Execute the function whenever the file is received (execution happens for every individual file):

form.on('file', function(field, file) {
    // Code
}); 

Store all uploads in the /uploads directory:

form.uploadDir = path.join(__dirname, '/uploads');

Every time the file has been uploaded, rename it to its original name:

fs.rename(file.path, path.join(form.uploadDir, file.name));

And then append it to HTML file:

fs.appendFile(path.join(__dirname, "/uploads/opt-images.html"), imgHTML, function(err) {
   if (err) throw err;
   console.log('Created!');
});

Where

var imgHTML = "<img src=" + "http://" + path.join('blabla.com', '/uploads', file.name) +"></img>";

The fs.appendFile() method appends content to a file and if the file doesn’t exist, it will be created. And remember to output errors for easier debugging.
Parse the incoming request with the form data:

form.parse(req);

You can also log any errors that occur:

form.on('error', function(err) {
   console.log('An error has occured: \n' + err);
});

Or send a message to the client side when all files are uploaded:

form.on('end', function() {
   res.end(‘Some message in console for the client’);
});

Eventually the code will look like:

var express = require('express');
var app = express();
var path = require('path');
var formidable = require('formidable');
var fs = require('fs-extra');

var ROOT = __dirname + "/uploads";
var port = 9040;

app.use(express.static(path.join(__dirname, 'public')));

app.get('/', function(req, res) {
    res.sendFile(path.join(__dirname, 'views/index.html'));
    fs.emptyDir(ROOT);
});

app.post('/upload', function(req, res) {
    var form = new formidable.IncomingForm();

    form.multiples = true;

    form.on('file', function(field, file) {
        var imgHTML = "<img src=" + "http://" + path.join('blabla.com', '/uploads', file.name) +"></img>";

        form.uploadDir = path.join(__dirname, '/uploads');
        fs.rename(file.path, path.join(form.uploadDir, file.name));
        fs.appendFile(path.join(__dirname, "/uploads/opt-images.html"), imgHTML, function(err) {
            if (err) throw err;
            console.log('Created!');
        });
    });

    form.on('error', function(err) {
        console.log('An error has occured: \n' + err);
    });
    
    form.on('end', function() {
        res.end('Some message in console for the client');
    });

    form.parse(req);
});

var server = app.listen(port, function() {
    console.log('Server listening on port ' + port);
});

Now we have uploading images to server and generating HTML page with those appended images, so we have outer link we can paste to Google Page Speed.
On the client side in upload.js:

var URL_TO_GET_RESULTS_FOR = 'http://blabla.com/uploads/opt-images.html';

In AJAX on success event:

window.location.replace("https://developers.google.com/speed/pagespeed/insights/optimizeContents?url=" + URL_TO_GET_RESULTS_FOR);

Conclusion

I had deleted tons of code, so I wondered about quantity of code in the final result. Looks like too few for so much time I have expended. Sometimes I had done something I had no need to do, because I just wanted to see what would happen as a result. But in such way I got a little more understanding of how exactly code works.

That was great experience for me, front-end developer, to get to know server side. I used some code from tutorials so I improved my ability to understand someone’s code and to combine someone’s ideas and realisations with my own without reinventing a wheel.

In the future I want to provide some features like displaying the percentage of how much every image has been optimised, to make some folders private without access from the outer environment, to let several users to upload files simultaneously.

Click here and you can see everything with your own eyes a live example

Links:

up

Please turn your device