Thực hiện minify javascript, css files sử dụng Gradle

Gần đây mình đang tham gia vào một dự án Java (Spring MVC, Hibernate, Gradle, BootStrap, FreeMarker. Hôm nay sẽ tổng kết quá trình thực hiện minify Javascript và CSS trong Java WAR archive.
Còn minify là gì? Minify là quá trình rút gọn code, xóa tự động các ký tự không cần thiết và làm nhỏ kích thước của file có thể nhỏ bằng nhiều cách khác. (File có thể thu nhỏ còn lại khoảng 50-60% so với kích thước file ban đầu).

Plugins

Sau khi tìm hiểu thì mình quyết định sử dụng 2 plugin sau.
eriwen/gradle-js-plugin
eriwen/gradle-css-plugin
Theo thiết kế ban đầu các file Javascript, CSS ngoài các file common.js, common.css dùng chung cho tất cả các trang thì các trang lại có sử dụng riêng lẻ file .js, .css riêng lẻ. Vì thế nên phần minify cũng thực hiện theo từng file riêng lẻ.

Định nghĩa dependencies trong build.gradle

[code lang=”groovy”]
buildscript {
repositories {
jcenter()
}
dependencies {
classpath ‘net.saliman:gradle-cobertura-plugin:2.2.7’
classpath "com.eriwen:gradle-js-plugin:1.12.1"
classpath ‘com.eriwen:gradle-css-plugin:1.11.1’
}
}
apply plugin: ‘js’
apply plugin: ‘css’
[/code]
Định nghĩa source file trong build.gradle
[code lang=”groovy”]
//Local variables
def webAppDir = "src/main/webapp"
def versionedOutputDir = "${buildDir}/versioned-files"
def minifyOutputDir = "${buildDir}/minified"
javascript.source {
custom {
js {
srcDir webAppDir + ‘/js’
include "*"
exclude "lib/*"
}
}
}
// Declare your sources
css.source {
custom {
css {
srcDir webAppDir + ‘/css’
include "*.css"
exclude "favicon.ico"
}
}
}
[/code]

Định nghĩa task

Có các phần xử lý chính:
– Minify Javascript
– Minify CSS
– Copy vào file WAR

Minify Javascript

Đọc tất cả các file .js trong thư mục và định nghĩa các task dominifyJs0, dominifyJs1,… theo index.
[code lang=”groovy”]
javascript.source.custom.js.files.eachWithIndex { jsFile, idx ->
tasks.create(name: "dominifyJs${idx}", type: com.eriwen.gradle.js.tasks.MinifyJsTask) {
if (jsFile.getParentFile().getName() != "js") {
source = jsFile
dest = minifyOutputDir + "/js/" + jsFile.getParentFile().getName() + "/${jsFile.name}"
closure {
compilationLevel = ‘SIMPLE_OPTIMIZATIONS’
warningLevel = ‘QUIET’
}
} else {
source = jsFile
dest = minifyOutputDir + "/js/${jsFile.name}"
closure {
compilationLevel = ‘SIMPLE_OPTIMIZATIONS’
warningLevel = ‘QUIET’
}
}
}
}
task individualJsMinify(dependsOn: tasks.matching { Task task -> task.name.startsWith("dominifyJs") })
[/code]
Kết quả
[code lang=”bash”]
$ ./gradlew individualJsMinify
:dominifyJs0
:dominifyJs1
:dominifyJs10
:dominifyJs11
:dominifyJs12
:dominifyJs2
:dominifyJs3
:dominifyJs4
:dominifyJs5
:dominifyJs6
:dominifyJs7
:dominifyJs8
:dominifyJs9
:individualJsMinify
[/code]
Các file Javascript được minify sẽ được tạo trong thư mục build/minified/js
Minify CSS
Đọc tất cả các file .css trong thư mục và định nghĩa các task dominifyCss0, dominifyCss1,… theo index.
[code lang=”groovy”]
css.source.custom.css.files.eachWithIndex { cssFile, idx ->
tasks.create(name: "dominifyCss${idx}", type: com.eriwen.gradle.css.tasks.MinifyCssTask) {
if (cssFile.getParentFile().getName() != "css") {
source = cssFile
dest = minifyOutputDir + "/css/" + cssFile.getParentFile().getName() + "/${cssFile.name}"
closure {
compilationLevel = ‘SIMPLE_OPTIMIZATIONS’
}
} else {
source = cssFile
dest = minifyOutputDir + "/css/${cssFile.name}"
closure {
compilationLevel = ‘SIMPLE_OPTIMIZATIONS’
}
}
}
}
task individualCssMinify(dependsOn: tasks.matching { Task task -> task.name.startsWith("dominifyCss") })
[/code]
Kết quả
[code lang=”bash”]
$ ./gradlew individualCssMinify
:dominifyCss0
:dominifyCss1
:individualCssMinify
[/code]
Các file CSS được minify sẽ được tạo trong thư mục build/minified/css
Copy minified files vào WAR
[code lang=”groovy”]
war {
dependsOn individualJsMinify
dependsOn individualCssMinify
exclude "js/*.js"
from(minifyOutputDir + "/js", {
into ‘js/’ + version
})
exclude "css/*.css"
from( minifyOutputDir + "/css", {
into ‘css/’ + version
})
war.archiveName "ROOT.war"
}
[/code]
Kết quả
[code lang=”bash”]
$ ./gradlew war
:dominifyCss0
:dominifyCss1
:individualCssMinify
:dominifyJs0
:dominifyJs1
:dominifyJs10
:dominifyJs11
:dominifyJs12
:dominifyJs2
:dominifyJs3
:dominifyJs4
:dominifyJs5
:dominifyJs6
:dominifyJs7
:dominifyJs8
:dominifyJs9
:individualJsMinify
:war
[/code]
Các file Javascript, CSS đã được minify trong thư mục build/minified sẽ được copy khi tạo file Java WAR.

Conclusion

Như trên mình đã trình bày các bước thực hiện minify các file Javascript, CSS sử dụng Gradle.
Rất mong sự phản hồi, đóng góp từ các bạn.

Tham khảo

  • http://www.bhaweshkumar.com/blog/2014/08/29/minify-javascript-and-css-using-gradle/
  • https://discuss.gradle.org/t/add-generated-files-with-gradle-before-war-creation/7134

call() và apply()

2 phương thức hữu dụng của các đối tượng function là call() và apply().

Chúng cho phép các đối tượng mượn các phương thức từ các đối tượng khác và gọi chúng ngay bên trong bản thân chúng.đây là 1 cách rất mạnh mẽ và dễ dàng để giảm thiểu mã code Ta xét ta có 1 đối tượng some_obj object, chứa 1 phương thức say()

var some_obj = {
    name: ‘Ninja’,
    say: function(who) {
          return ‘Haya ‘ + who + ‘, I am a ‘ + this.name;
    }
}
Ta có thể gọi phương thức say() như sau :
>>> some_obj.say(‘Dude’);
“Haya Dude, I am a Ninja”

Giờ ta tạo ra 1 đối tượng đơn giản my_obj, mà chỉ có 1 thuộc tính name :
>>> my_obj = {name: ‘Scripting guru’};

Bên trong đối tượng my_obj không có phương thức say() tuy nhiên ta lại muốn mược phương thức say() này từ đối tượng some_obj vào trong đối tượng my_obj thì ta làm như sau :
>>> some_obj.say.call(my_obj, ‘Dude’);

Nó đã hoạt động.nhưng chuyện gì sẽ xảy ra ? ta gọi phương thức call() của phương thức say() , 2 tham số được chuyển tiếp là đối tượng my_obj và chuỗi ‘Dude’.kết quả khi phương thức say() được gọi, thì có tham chiếu tới đối tượng this, khi này đối tượng this chỉ
tới đối tượng my_obj.do vậy this.name không trả về Ninja nhưng lại trả về

Scripting guru
Nếu ta có nhiều hơn các tham số được chuyển tiếp vào khi gọi phương thức call(), thì cứ việc chuyển tiếp chúng :
some_obj.someMethod.call(my_obj, ‘a’, ‘b’, ‘c’);

nếu ta không chuyển tiếp 1 đối tượng như 1 tham số đầu tiên bên trong call() hay là null, thì đối tượng toàn cục sẽ được giả định phương thức appy() là việc giống với cách của call() nhưng với 1 chút khác biệt là tất cả các tham số mà ta muốn chuyển tiếp vào phương thức của 1 đối tượng khác thì chúng phải được chuyển tiếp như 1 mảng như ví dụ sau :
some_obj.someMethod.apply(my_obj, [‘a’, ‘b’, ‘c’]);

some_obj.someMethod.call(my_obj, ‘a’, ‘b’, ‘c’);

hay như làm trong ví dụ trên :
>>> some_obj.say.apply(my_obj, [‘Dude’]);

“Haya Dude, I am a Scripting guru”

TÌm hiểu cấu trúc dữ liệu request và response cho GCM.

Lần trước mình đã tìm hiểu cơ bản về Google  Cloud Message for Android (GCM).
http://tuantranf.wordpress.com/2013/04/16/android-notification-by-nodejs-1-google-cloud-messaging-for-android/
Tool hỗ trợ: Một add-on của chrome dùng để thực hiện request.
DEV HTTP CLIENT
Cottonballs:
Server giả lập trên local host để thay thế cho GCM. Thay vì gửi message đến GCM có thể gửi đến cottonballs để test.Ngoài ra có các chức năng tạo delay, failure để test.
Hôm nay mình sẽ tìm hiểu kĩ hơn về cấu trúc dữ liệu khi tạo request và giải mãi response từ server của GCM trước khi bắt tay vào thiết kế ứng dụng.
Nguồn http://developer.android.com/google/gcm/gcm.html

Cấu trúc dữ liệu request

Header của HTML

  • Authorization: key=YOUR_API_KEY
  • Content-Type:
    •  application/json (Nếu request là JSON)
    •  application/x-www-form-urlencoded;charset=UTF-8 (nếu request là plain text)

Cụ thể nội dung của Header sẽ như sau.(Vì mình chỉ dùng Json nên sẽ lược bỏ phần chú thích về plain text.

Content-Type:application/json
Authorization:key=AIzaSyB-1uEai2WiUapxCs2Q0GZYzPu7Udno5aA

Về dữ liệu JSON gửi đi:

{
// ID đăng ký của các device cần gửi (được quản lý trong DB)
"registration_ids":["4", "8", "15", "16", "23", "42"],
// Một số thiết định khác
// Tên group dùng để nhóm các notification cùng nhóm lại với nhau.
"collapse_key": "update",
// Thời gian sống sót của message trên server GCM khi failure do device offline.Mặc định là 4 tuần.
"time_to_live": 180,
// Đợi đến khi device idle thì mới gửi
"delay_while_idle": true,
// Tên package khi chỉ định chỉ gửi cho các device có pakage này
"restricted_package_name": "package_name",
// chế độ dryrun để test.
"dry_run": false,
// Dữ liệu cần gửi
"data": {
"score": "4x8",
"time": "15:16.2342"
}
}

Chú ý: Tổng kích thước của một lần gửi (RegistrationIDs + Options + Data) giới hạn là 4kb, khi số lượng Registration IDs càng nhiều thì dung lượng data giới hạn lại càng bé đi.

Giải mã GCM Response

Status code trả về khi gửi message
200 Message được xử lý thành công (POST OK).Thực tế có push đến từng device hay không thì ??? 🙁
Khi gửi thành công thì sẽ trả về thông tin của message.
400 Xử lý JSON đã gửi trướng hợp parse request thất bại, hoặc thiếu các mục yêu cầu trong JSON, phải sửa lại JSOn trước khi thử lại
401 Có lỗi thì xử ly đăng nhập, xác nhận của ID người gửi (YOUR_API_KEY)
5xx Các lỗi trong khoảng 500-599  lỗi của server GCM khi thực hiện request và sẽ thử lại nếu Header thiết định parameter Retry-After
Nội dung GCM response// Trường hợp gửi message đến 6 ID (IDs 4, 8, 15, 16, 23, và 42)
// 3 messages thành công , 3 thất bại ta sẽ có response JSON như sau
{"multicast_id": 216, // ID của message
"success": 3, // Số lượng xử lý thành công
"failure": 3, // Số lượng xử lý thất bại
"canonical_ids": 1, // ID of the last registration requested by your application là ID đăng ký cuối cùng trường hợp 1 device đăng ký nhiều ID
"results": [ // Kết quả gửi message
{ "message_id": "1:0408" }, // Message id 1 thành công , không cần gì (ID4)
{ "error": "Unavailable" },  // Cần gửi lại (ID8)
{ "error": "InvalidRegistration" }, // Lỗi dữ liệu đăng ký (ID15)
{ "message_id": "1:1516" }, // Gửi thành công (ID16)
{ "message_id": "1:2342", "registration_id": "32" }, // Gửi thành công như có yêu cầu update registration ID từ 23->23 (ID23)
{ "error": "NotRegistered"} // Lỗi ID không đăng ký / bị xoá do remove application trên device (ID42)
]
}

Conclusion

Hôm nay mình đã tìm hiểu về cấu trúc (format) dữ liệu khi request thông tin đến GCM server và giải mã response của GCM để xử lý retry hay hiển thị lỗi.