Một số Grunt task giúp cải thiện hiệu suất trang Web của bạn

Bài viết gốc: https://manhhomienbienthuy.bitbucket.io/2015/Nov/15/grunt-and-5-tasks-to-improve-web-performance.html (đã xin phép tác giả 😄)

Hiệu suất và hoạt động mượt mà là một yếu tố rất quan trọng với 1 trang Web. Trong bài viết này, tôi sẽ hướng dẫn 1 số task của Gruntjs giúp cải thiệu hiệu suất của trang Web mà cụ thể ở đây là tốc độ tải trang.

Tốc độ tải trang nhanh hay chậm phụ thuộc rất lớn vào lượng dữ liệu cần tải về của trang Web đó. Vì vậy trong bài viết này, tôi sẽ tập trung vào cách giảm dung lượng tải về của trang.

Trong bài viết này, tôi sẽ giới thiệu một số task của Grunt nó giúp bạn cải thiện tốc độ tải trang.

Bài này chỉ giới thiệu các task của Grunt nhằm giúp cải thiện trang Web của bạn, chứ không giới thiệu về Grunt. Nếu bạn chưa biết về Grunt hay muốn tìm hiểu về nó, bạn có thể đọc bài viết của tôi về Grunt.

grunt-contrib-imagemin

Đây là task đầu tiên tôi muốn nói tới. Lý do rất đơn giản, hình ảnh chính là những dữ liệu nặng nhất trên trang Web của chúng ta.

Hãy nhìn vào thống kê của HTTParchive.org và bạn dễ dàng nhận ra rằng, trên 63% lưu lượng truy cập vào trang Web là để tải hình ảnh. Vì lý do đó, nên việc giảm dung lượng hình ảnh có nghĩa rất lớn trong việc cải thiện tốc độ tải trang Web của bạn. Và grunt-contrib-imagemin là một task giúp làm việc đó.

Task này sẽ dụng các tools sau để giúp nén ảnh và qua đó, giảm dung lượng file ảnh cần tải.

  • gifsicle để nén các ảnh GIF
  • jpegtran để nén các ảnh JPEG
  • optipng để nén các ảnh PNG
  • svgo để nén các ảnh SVG

Một config đơn giản cho task này như sau:

imagemin: {
  dist: {
    options: {
      optimizationLevel: 5
    },
    files: [{
      expand: true,
      cwd: "src/images",
      src: ["**/*.{png,jpg,gif}"],
      dest: "dist/"
    }]
  }
}

Config này cho phép thiết lập mức độ tối ưu hóa thông qua setting của key optimizationLevel. Giá trị có thể thay đổi trong khoảng từ 0 đến 7, nếu bạn không thiết lập cho nó, giá trị default là 3 sẽ được sử dụng. Trong config trên, tất cả các file có phần mở rộng là png, jpg, gif và nằm trong thư mục src/images sẽ được nén và lưu các file kết quả vào thư mục dist.

Ngoài ra còn một số task khác cũng có tác dụng giảm dung lượng ảnh cho trang Web như:

  • grunt-imageoptim tương tự như grunt-imagemin nhưng chỉ hoạt động trên OS X.
  • grunt-responsive-images tạo file ảnh responsive ở các kích thước khác nhau phù hợp với các loại màn hình khác nhau.
  • grunt-clowncar tác dụng tương tự grunt-responsive-images.
  • grunt-svgmin nén các file svg cũng sử dụng svgo.
  • grunt-tinypng nén ảnh sử dụng dịch vụ tinypng.
  • grunt-spritesmith tạo một sprite sheet cho các ảnh.
  • grunt-webp convert ảnh sang định dang WebP. WebP là định dạng ảnh mới cho phép nén các bức ảnh cả dạng lossless và lossy. Định dạng lossless của WebP có thể nén tới 26% so với file PNG và định dạng lossy thậm chí còn nén được nhiều hơn thế.

grunt-contrib-uglify

Đây là task dùng để minify các file javascript. Task này không chỉ bỏ tất cả các khoảng trắng không cần thiết (bao gồm dấu cách, dấu xuống dòng, v.v...) mà nó còn đổi tên các biến và các hàm để sử dụng những tên ngắn nhất có thể. Điều này có thêm 1 lợi ích đó là các file javascript sau khi uglify sẽ rất khó đọc, cho dù có dùng các tools beautify cũng không thể khôi phục lại trạng thái ban đầu vì khi đó, các hàm và biến chỉ còn là những cái tên vô nghĩa như a, b, c, v.v...

Một vài tùy chọn cho task này đó là sourceMap và banner. Tùy chọn sourceMap sẽ tạo một file map trong cùng thư mục với file đích được tạo ra. Default của option này là false, để kích hoạt nó, bạn set nó là true là được. Tùy chọn banner sẽ chèn thêm một đoạn comment vào đầu file đích. Bạn có thể viết vào đó giải thích ngắn gọn, tên tác giả, version, license, v.v...

Dưới đây là một config đơn giản cho task này.

uglify: {
  dist: {
    options: {
      sourceMap: true,
      banner: "/*! Copyright: Anh Tranngoc */"
    },
    files: {
      'dest/output.min.js': ['src/input.js'],
    }
  }
}

Để mình hoạ cho cách làm của task này, tôi có 1 ví dụ với đoạn javascript sau:

var MyApplication = function() {
  var data = 'hello';

  this.sum = function(first, second) {
    return first + second;
  }

  this.showData = function() {
    return data;
  }
};

Sau khi dùng uglify, kết quả thu được sẽ là

var MyApplication=function(){var a="hello";this.sum=function(a,b){return a+b},this.showData=function(){return a}};

Ngoài ra có một số task khác giúp tối ưu hóa Javascript:

grunt-contrib-cssmin

Tên của task này đã nói lên tất cả, task này sẽ nén các file css. Công việc của nó là xóa bỏ tất cả các dấu cách thừa trong file css. Cũng tương tự như uglify nó cũng có tùy chọn banner.

Một config đơn giản cho task này như sau:

cssmin: {
  dist: {
    options: {
      banner: "/*! Copyright: Anh Tranngoc */"
    },
    files: {
      'dist/css/style.min.css': ['src/css/**/*.css']
    }
  }
}

Với config trên, cssmin sẽ minify tất cả các file css trong thư mục src/css và lưu kết quả vào 1 file duy nhất style.min.css nằm trong thư mục 'dist/css'. Config trên cũng thêm banner vào đầu file min này.

Ngoài ra cũng có một số task khác cũng tăng hiệu suất trang Web của bạn như:

  • grunt-inline-css chuyển các file css rời thành inline css. Việc sử dụng inline css sẽ giúp trình duyệt render nội dung nhanh hơn do không phải load css thêm nữa.
  • grunt-combine-media-queries giúp bạn tổng hợp các media query giống nhau vào trong cùng một câu media query.
  • grunt-revizor nén code css bằng cách thu gọn tên của css selector. Ví dụ .b-tabmenu--item__active-- -> .zS, #success_info-- -> .e6

Ngoài ra có một tools không phải của grunt có thể thu gọn code css. Ví dụ:

a { font-family: Arial; font-style: italic; font-size: 14px; line-height: 18px; font-weight: bold; background-image: url('example.png'); background-color: red; background-size: cover; background-repeat: no-repeat; }

>
> =>
>
> ```css
  a {
    font: italic bold 14px/18px Arial;
    background: red url('example.png') no-repeat / cover;
  }

grunt-uncss

Một task khác cũng thao tác với các file css đó là grunt-uncss. Task này sẽ loại bỏ tất cả các code css không được dùng từ trong các file của project của bạn. Do đó, nó sẽ giảm kích thước các file css và giảm thời gian tải trang. Điều này thực sự hữu ích khi bạn sẽ dụng các template dựng sẵn của người khác hoặc dùng những framework như Bootstrap hay Foundation. Không phải tất cả code css của họ bạn đều dùng, và giữ chúng lại chỉ làm chậm quá trình tải trang.

Có một số giới hạn mà bạn cần tìm hiểu trước ở tài liệu của uncss

Một demo ở trên trang github của uncss cho thấy rằng, nó có thể giảm dung lượng css của Bootstrap từ 120Kb xuống còn 11Kb bằng cách loại bỏ hết các thành phần không được sử dụng.

uncss

Có một số tùy chọn khi config task này, ví dụ như ignore cho phép chúng ta giữ lại những thành phần cho dù hiện tại nó chưa được sử dụng. Hoặc tùy chọn ignoreSheets cho phép chúng ta giữ lại toàn bộ các file chưa được dùng.

Uncss có thể kiểm tra cả những thành phần được render bằng javascript, bằng cách chạy javascript của các trang thông qua PhantomJS. Tuy nhiên, nếu những render này lấy dữ liệu từ AJAX thì nó chưa làm được. Ngoài ra, có một hạn chế là nó không tương thích với các template engine của các framework, nó chỉ làm việc với các file html mà thôi.

Một config ví dụ như sau:

uncss: {
  dist: {
    options: {
      ignore: [/js-.+/, '.special-class'],
      ignoreSheets: [/fonts.googleapis/],
    },
    files: {
      'dist/css/unused-removed.css': ['src/index.html', 'src/contact.html', 'src/service.html']
    }
  }
}

Ngoài grunt-uncss được giới thiệu trên đây, có một task khác là grunt-ucss cũng có tác dụng tìm những thành phần css không được sử dụng ở trang. Tuy nhiên, grunt-ucss có hạn chế là nó không loại bỏ những thành phần này cho chúng ta.

grunt-contrib-htmlmin

Đây là task cuối cùng mà tôi muốn đề cập đến, đó là htmlmin, nó sẽ minify html code. Nó không thực sự giúp ích được nhiều, bởi vì trong quá trình phát triển, chúng ta không thể minify các file HTML được. Và cho dù minify thì nó cũng chỉ có thể giảm một vài Kb mà thôi. Tuy nhiên, một vài Kb với mỗi người nhưng nhiều người dùng thì nó cũng có ý nghĩa hết sức quan trọng.

Dưới đây là một ví dụ về config của task này:

htmlmin: {
  dist: {
    options: {
      removeComments: true,
      collapseWhitespace: true
    },
    files: [{
      expand: true,
      cwd: 'src',
      src: '**/*.html',
      dest: 'dist/'
    }]
  }
}

Với config trên, tất cả các file html trong thư mục src sẽ được minify. Với mỗi file này, htmlmin sẽ xóa tất cả các comment và thu lượng khoảng trắng về mức tối đa, và kết quả được lưu ở dist.

Ngoài những task đã kể trên, có một số task cũng giúp tăng hiệu suất trang Web của bạn như:

  • grunt-contrib-concat dùng để nối nhiều file thành 1 file.
  • grunt-contrib-compress dùng để nén file và thư mục.
  • grunt-zopfli cũng dùng để nén file, thư mục nhưng sử dụng thuật toán Zopfli. Thuật toán Zopfli có thể nén tốt hơn 3-8% so với zlib.
  • grunt-reduce tự động hóa việc tối ưu toàn bộ các file tĩnh (css, js, ảnh, v.v...)

Kết luận

Trong bài viết này, tôi chỉ tập trung vào các task giúp giảm dung lượng những dữ liệu tĩnh để cải thiện tốc độ tải trang Web của bạn. Chúng rất đơn giản, dễ dùng và kết quả thu được biểu hiện rõ ràng ngay trước mắt chúng ta. Hy vọng bài viết có ích cho các bạn đang muốn phát triển Web cho riêng mình 😃

Đây là config đầy đủ của Gruntjs cho cả 5 task trên.

Gruntfile.js

module.exports = function(grunt) {

  grunt.initConfig({
    pkg: grunt.file.readJSON("package.json"),
    imagemin: {
      dist: {
        options: {
          optimizationLevel: 5
        },
        files: [{
           expand: true,
           cwd: "static_root",
           src: ["**/*.{png,jpg,gif,ico}"],
           dest: "static_root"
        }]
      }
    },
    uglify: {
      dist: {
        options: {
           sourceMap: false,
           banner: "/*! Copyright: Anh Tranngoc */"
        },
        files: [{
           expand: true,
           cwd: "static_root",
           src: ["**/*.js"],
           dest: "static_root"
        }]
      }
    },
    cssmin: {
      dist: {
        options: {
           banner: "/*! Copyright: Anh Tranngoc */"
        },
        files: [{
           expand: true,
           cwd: "static_root",
           src: ["**/*.css"],
           dest: "static_root"
        }]
      }
    },
    uncss: {
      dist: {
        files: {
           "static_root/css/unused-removed.css": ["templates/*.html"]
        }
      }
    },
    htmlmin: {
      dist: {
        options: {
           removeComments: true,
           collapseWhitespace: true
        },
        files: [{
           expand: true,
           cwd: "templates",
           src: "**/*.html",
           dest: "static_root/"
        }]
      }
    }
  });

  grunt.loadNpmTasks("grunt-contrib-imagemin");
  grunt.loadNpmTasks("grunt-contrib-uglify");
  grunt.loadNpmTasks("grunt-contrib-cssmin");
  grunt.loadNpmTasks("grunt-uncss");
  grunt.loadNpmTasks("grunt-contrib-htmlmin");
  grunt.file.setBase('../')
  grunt.registerTask("default", ["imagemin", "uglify", "cssmin", "uncss", "htmlmin"]);
};

package.json

{
  "name": "project",
  "version": "1.0.0",
  "description": "This is a project",
  "main": "Gruntfile.js",
  "dependencies": {
    "grunt": "^0.4.5"
  },
  "devDependencies": {
    "grunt-contrib-uglify": "^0.9.2",
    "grunt-contrib-cssmin": "^0.13.0",
    "grunt-contrib-imagemin": "^0.9.4",
    "grunt-uncss": "^0.4.1",
    "grunt-contrib-htmlmin": "^0.4.0"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "***.git"
  },
  "author": "Anh Tranngoc",
  "license": "MIT"
}

All Rights Reserved