GruntJS ทำเรื่องยุ่งยากให้ง่ายกันเหอะ(ว่ะ)
สำหรับนักพัฒนาเว็บไซต์โดยเฉพาะผู้ที่ต้องทำงานเกี่ยวกับสายงานเบื้องหน้า หรือ ที่เราเรียกกันติดปากว่า Front-end developer ก็คงจะมีเครื่องไม้เครื่องมือเยอะแยะมากมายจนนับไม่ถ้วน ซึ่งแต่ละภาษาที่ใช้เขียนเองก็จะมีเครื่องมือเฉพาะเจาะจงแยกกันไป ผมขอพูดถึง GruntJS ในแบบของคนที่ต้องทำงานเกี่ยวกับ HTML, CSS และ Javascript พื้นฐานเป็นหลัก (ซึ่งอาจจะครอบคลุมไปยัง plugin ต่างๆ ของแต่ละภาษาเช่น jade สำหรับ html, sass หรือ less สำหรับ css หรือ coffeeScript ของ Javascript) GruntJS จะมีส่วนช่วยให้งานของคุณง่าย และ เป็นระเบียบมากขึ้น ผมรับรองว่าถ้าคุณนำมันไปใช้กับโปรเจคอื่นๆที่จะเกิดขึ้นข้างหน้า คุณจะต้องบอกว่ามันค่อนข้างเจ๋ง และ ลดเวลาจิปาถะเล็กน้อยลงไปได้มากเลยทีเดียว
ทำไมต้องใช้ GruntJS
ผมรู้จักกับ GruntJS แรกๆ เพราะว่ามันติดมากับ Yeoman ซึ่งตอนนั้นกำลังหาอะไรเล่นใหม่อยู่ แรกๆ ก็ไม่ได้สนใจว่าเจ้า GruntJS จะทำอะไรได้บ้าง ที่เน้นตอนนั้นก็เห็นจะเป็น bower ที่ค่อนข้างเรียก jQuery plugin อื่นๆ เข้ามาใช้งานได้สะดวกไม่ต้องไปไล่เปิดเว็บไซต์หาโหลดตามลิงค์ของผู้พัฒนา plugin นั้นๆ แต่พอลองใช้ได้จริงๆจังๆ ก็พบว่า สิ่งที่มีประโยชน์ที่สุดนั้นน่าจะเป็น GruntJS จนเดี๋ยวนี้เองก็เรียกได้ว่าแทบทุกโปรเจคใหม่ๆ ก็จะใช้งาน GruntJS เข้ามาด้วยเสมอ
ผมขอยกตัวอย่างง่ายๆ ที่น่าจะถูกใจคนไทยหลายคนอย่างการ minify ทั้ง CSS เอย Javascript เอย หรือแม้ HTML ผมขอยกตัวอย่างให้เห็นภาพชัดๆ ขึ้นหน่อยอย่างขั้นตอน development กับ production บางครั้งเวลาเราทำงานกับไฟล์ที่อยู่ในเครื่องก็อาจจะไม่ต้องการ minify ไฟล์ในโปรเจค เพราะจำเป็นต้องแก้ไข หรือ เพิ่มส่วนต่างๆให้สะดวก และ รวดเร็ว แต่เมื่อเวลาที่เรากำลังจะอัพโหลดไฟล์ขึ้นไปบนเซิฟเวอร์แล้วก็อยากจะให้มันเป็น minify version พูดง่ายๆ ก็คือป้องกันการคัดลอก code และ เพื่อบีบไฟล์ให้ลดเวลาในการโหลดหน้าเว็บเพจลง
ซึ่งถ้าไม่ใช้ GruntJS คุณก็อาจจะต้องไปหาเว็บไซต์ที่รับ minify code แบบออนไลน์ ซึ่งก็ค่อนข้างเสียเวลาพอสมควร นอกจากการ minify code เพื่อบีบอัดขนาดไฟล์ให้เล็กลงแล้ว GruntJS ยังสามารถทำสิ่งอื่นที่เป็นประโยชน์ได้อีกมากเช่น compile LESS หรือ SASS โดยไม่จำเป็นต้องใช้ command line หรือ ลิงค์ไฟล์ compile อื่นๆ เพิ่มเข้ามาในโปรเจค การตรวจสอบ syntax, การเชื่อมไฟล์หลายไฟล์ให้เป็นไฟล์เดียว, การทำ livereload เวลาเราแก้ไขส่วนต่างๆ เป็นต้น
อยากจะเริ่มใช้ GruntJS แล้ว
คุณจำเป็นต้องมี nodeJS และ ติดตั้ง Node Package Manager (npm) อยู่ในเครื่องเสียก่อน สามารถเข้าไปอ่านวิธีการติดตั้ง NodeJS และ npm ได้ที่เว็บไซต์ http://nodejs.org/ (ติดตั้งง่าย โหลดมาคลิกๆ ก็ใช้ได้แล้ว)
1. เข้าไปที่เว็บไซต์ http://gruntjs.com/getting-started ทำการติดตั้งผ่าน command line โดยการพิมพ์
npm install -g grunt-cli
2. ที่โปรเจคของเรา ให้สร้างไฟล์ที่ชื่อว่า package.json แล้วลองใส่ข้อมูลเป็น json format ตามนี้ลงไปก่อน หรือจะเป็นอย่างอื่นตามที่คุณต้องการเลยก็ได้
{ "name": "jir4yu-demo-project", "version": "0.1.0", "private": true, "author": "Jirayu L", "devDependencies": { "grunt": "~0.4.5", "grunt-contrib-jshint": "~0.10.0", "grunt-contrib-uglify": "~0.5.0" } }
โดยไฟล์ package.json นี้เองจะเป็นตัวสื่อสารกับ npm ว่า โปรเจคของเราชื่ออะไร เวอร์ชั่นอะไร ใครเป็นเจ้าของ จะใช้งานได้จำเป็นต้องมี plugin ตัวใดติดตั้งอยู่ก่อนหรือไม่ ฯลฯ ซึ่งไฟล์ package.json นี้เองเราไม่จำเป็นต้องใส่ใจอะไรมากเท่าไหร่ เขียนให้ถูก syntax แล้วใส่ devDependencies ตามที่เราต้องการก็พอ (ดูได้จาก list ในเว็บไซต์ https://www.npmjs.org/ – ใช้งานทำนองเดียวกับ bower นั่นแหละ)
เมื่อบันทึกไฟล์แล้วให้อัพเดท npm ด้วยคำสั่ง
sudo npm update
แล้วนั่งจิบกาแฟรอสักครู่..
3. สร้างไฟล์หลักของ GruntJS ตั้งชื่อว่า Gruntfile.js ซึ่งไฟล์นี้จะเก็บรายระเอียดการทำงานของ Grunt เอาไว้
module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), }); grunt.registerTask('default', []); };
ลองเขียนเข้าไปแค่นี้ก่อนก็ได้ครับ หลังจากเซฟไฟล์เรียบร้อยก็ลองพิมพ์ grunt ผ่าน command line (cmd หรือ termanal) ไปอย่างเดียวแล้วเคาะ enter จะได้ดังภาพด้านล่าง
ทุกครั้งที่เขียน Gruntfile คุณสามารถคัดลอก code ดังข้างบนไปใช้ได้ตลอด เพราะแทบทุกกรณีที่เราเขียน Gruntfile นั้นจำเป็นจำต้องขึ้นด้วย
module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), }); };
โดยกำหนด initConfig ไปยังตำแหน่งไฟล์ package.json ที่เพิ่งเขียนขึ้นก่อนหน้า เรียกได้ว่าเป็น syntax พื้นฐานที่ใช้กันบ่อย และ สุดท้ายเราก็สร้าง Task ขึ้นมาด้วย syntax registerTask ที่ชื่อว่า default (ถ้าตั้งเป็น default เวลาใช้ command line ก็จะไม่ต้องพิมพ์อะไรต่อท้าย grunt เหมือนที่เราทำเมื่อสักครู่)
grunt.registerTask('default', []);
ทีนี้เราก็เรียนรู้วิธีใช้ Grunt พื้นฐานกันแล้ว
Minify ไฟล์ Javascript ด้วย GruntJS
GruntJS มี plugin ที่ชื่อว่า uglify ในการ minify Javascript ให้ผู้อ่านเปิดไฟล์ package.json ขึ้นมาดูอีกครั้งจะเห็นว่าเราได้ทำการเพิ่มบรรทัด
"grunt-contrib-uglify": "~0.5.0"
เข้าไปแล้วก็ได้อัพเดท npm ไปเรียบร้อยแล้วในขั้นตอนก่อนหน้า วิธีเรียกใช้ก็สามารถทำได้ง่ายๆ โดยเข้าไปเพิ่มคำสั่งใน Gruntfile.js เป็นดังนี้
module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), uglify: { my_target: { files: { 'js/main.min.js': ['js/main.js'] } } }, }); grunt.registerTask('default', []); };
ให้ลองดูคำสั่ง uglify พื้นฐานที่เราเพิ่มเข้ามา ซึ่ง code ข้างต้นเป็นตัวอย่างคำสั่งบอกว่าให้ GruntJS ทำการ minify ไฟล์ที่ชื่อว่า main.js ซึ่งอยู่ใน folder ที่ชื่อว่า js ออกมาเป็นไฟล์ใหม่ที่ชื่อว่า main.min.js ซึ่งให้สร้างใหม่อยู่ใน folder เดียวกัน
module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), uglify: { my_target: { files: { 'js/main.min.js': ['js/main.js'] } } }, }); grunt.registerTask('default', ['uglify']); // do uglify grunt.loadNpmTasks('grunt-contrib-uglify'); // load uglify };
ใส่ชุดคำสั่ง uglify เข้าไปในบรรทัด registerTask และ ทำการโหลด plugin เข้ามาที่บรรทัดถัดมา ก็สามารถใช้งาน uglify ได้แล้ว, กลับไปที่ terminal แล้วลองพิมพ์ grunt ดูอีกครั้ง
GruntJS จะแจ้งว่าไม่สามารถสร้าง main.min.js ได้ เป็นเพราะว่า เรายังไม่ได้สร้างไฟล์ main.js หรือ ไม่มีไฟล์ main.js นั่นเองครับ คราวนี้เมื่อเราลองเขียนไฟล์ main.js เรียบร้อยแล้วก็ลองพิมพ์ grunt เข้าไปใหม่จะได้ผลลัพธ์ดังรูปด้านล่างแล้ว
กลับไปดูที่ folder js/ ของเราจะพบว่ามีไฟล์ main.min.js พร้อมทำการ minify ให้เรียบร้อย เป็นอันสำเร็จกับการทำ minify Javascript ด้วย GruntJS
* อ่าน options เกี่ยวกับ uglify เพิ่มเติมได้ที่: https://www.npmjs.org/package/grunt-contrib-uglify
GruntJS Concat
หากเรามีไฟล์ Javascript หรือ CSS อยู่หลายไฟล์ เวลาจะลิงค์เข้ามาในไฟล์หลักก็คงจะดูไม่ clean แล้วก็ดูยุ่งยากด้วย หากเราสามารถรวมไฟล์ต่างๆ ที่เยอะแยะเหล่านั้นให้เข้ามาเป็นไฟล์เดียวกันได้ เวลาเรียกใช้ก็จะได้ไม่ต้องเรียกหลายไฟล์ให้เว็บไซต์เสียเวลาโหลดมากขึ้นด้วย
GruntJS มี plugin ที่ชื่อว่า concat ที่มีความสามารถรวมข้อมูลจากไฟล์ต่างๆ ที่เรากำหนดให้มาเรียงต่อกันเป็นไฟล์เดียวได้ วิธีการใช้งานก็แค่โหลด concat เข้ามาใน package.json และ เรียกใช้ผ่าน Gruntfile.js ดังเช่น uglify ข้างต้น
package.json ให้เพิ่มหนึ่งบรรทัด คือ grunt-contrib-concat
“grunt-contrib-concat”: “~0.5.0”
ส่วน Gruntfile.js ก็จะประกอบไปด้วย syntax ของ concat ดังนี้ (เพิ่มเติมจาก code เก่า)
module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), uglify: { my_target: { files: { 'js/main.min.js': ['js/main.js'] } } }, concat: { dist: { src: ['js/vendor/*.js'], dest: 'js/main.js' } } }); grunt.registerTask('default', ['concat, uglify']); // do concat and uglify grunt.loadNpmTasks('grunt-contrib-uglify'); // load uglify grunt.loadNpmTasks('grunt-contrib-concat'); // load concat };
ใน block ของ concat เราจะให้ไฟล์ที่มีนามสกุล .js ทั้งหมด (ใช้เครื่องหมาย*) ที่อยู่ใต้ folder js/vendor/ นั้นเรียงข้อมูลในไฟล์ต่อกัน โดยสร้างเป็นไฟล์ output ใหม่ที่ชื่อว่า main.js จากนั้นก็เพิ่ม Task ชุดคำสั่ง concat เข้าไปก่อน uglify เพื่อให้รวมไฟล์ทั้งหมดก่อน แล้วค่อย minify ในทีเดียว
ทำการโหลด grunt-contrib-concat ที่ด้านล่างของไฟล์ แล้วกลับไปที่ terminal พิมพ์คำสั่ง grunt เท่านี้ก็จะได้ไฟล์เดียวที่รวม code ของแต่ละไฟล์ที่เราต้องการเอาไว้ พร้อมกับการ minify ให้เรียบร้อย
* อ่าน options เกี่ยวกับ concat เพิ่มเติมได้ที่: https://github.com/gruntjs/grunt-contrib-concat
LESS & SASS Precompiler with GruntJS
พออ่านบทความมาได้สักพัก ผู้อ่านก็พอจะจับใจความได้แล้วว่า ถ้าจะเพิ่ม plugin ใดๆ เข้ามาใหม่ ก็จะต้องไปเพิ่มรายการ devDependencies ใน package.json ก่อนเสมอ แล้วก็ค่อยมาเพิ่มชุดคำสั่งในไฟล์ Gruntfile.js ซึ่ง LESS กับ SASS ก็ใช้วิธีการแบบเดียวกัน
ทุกครั้งเวลาเราใช้งาน CSS precompiler ก็อาจจะต้องเรียกไฟล์เข้ามาใช้บ้าง หรือ ก็ค่อย command line (ใน LESS) เปลี่ยนไฟล์ให้เป็น CSS เอา หรืออาจจะใช้คำสั่ง watch (ใน SASS) เวลามีการแก้ไขไฟล์ ก็ค่อย compile ออกมาเป็น CSS บ้าง ซึ่งบางครั้งเราจะเสียเวลาตรงส่วนนี้ไปพอสมควร ถ้าใช้ GruntJS ก็อาจจะช่วยให้เราทำงานได้เร็วขึ้น
ทำการเพิ่ม plugin ใน package.json
{ "name": "jir4yu-demo-project", "version": "0.1.0", "private": true, "author": "Jirayu L", "devDependencies": { "grunt": "~0.4.5", "grunt-contrib-jshint": "~0.10.0", "grunt-contrib-uglify": "~0.5.0", "grunt-contrib-concat": "~0.5.0", "grunt-contrib-sass": "~0.7.3", "grunt-contrib-less": "~0.11.4" } }
เพิ่มชุดคำสั่งใน Gruntfile.js (ยกตัวอย่าง ทั้ง LESS และ SASS) เพิ่มเติมจาก code เก่า
module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), uglify: { my_target: { files: { 'js/main.min.js': ['js/main.js'] } } }, concat: { dist: { src: ['js/vendor/*.js'], dest: 'js/main.js' } }, sass: { dist: { files: [{ expand: true, cwd: 'scss', src: ['*.scss'], ext: '.css' }] } }, less: { options: { paths: 'less/', }, build: { src: 'less/style.less', dest: 'less/style.css' } }, }); grunt.registerTask('default', ['less, sass, concat, uglify']); grunt.loadNpmTasks('grunt-contrib-uglify'); // load uglify grunt.loadNpmTasks('grunt-contrib-concat'); // load concat grunt.loadNpmTasks('grunt-contrib-less'); // load LESS grunt.loadNpmTasks('grunt-contrib-sass'); // load SASS };
เราสามารถกำหนด folder ที่ต้องการให้ GruntJS เข้าไปค้นหาไฟล์ได้ เช่น SASS ที่ใช้คำสั่ง cwd เลือกไปที่ folder scss หรือ LESS ที่ใช้ options object แล้วเลือก paths ไปที่ folder less ทั้งนี้ขึ้นอยู่กับโครงสร้างโปรเจคของผู้ใช้งานเองด้วย หากมีข้อสงสัย, ต้องการเรียนรู้เกี่ยวกับตัวเลือกอะไรเพิ่มเติมก็ให้เข้าไปที่หน้า github ของ plugin แต่ละตัว จะมีวิธีการใช้งานเบื้องต้นให้อยู่ครับ
LESS: https://github.com/gruntjs/grunt-contrib-less
SASS: https://github.com/gruntjs/grunt-contrib-sass
Grunt Watch ทำงานทุกการเปลี่ยนแปลง
watch ถือเป็นอีก plugin หนึ่งที่จะช่วยให้เราสามารถทำงานได้เร็วขึ้น ยิ่งโดยเฉพาะกับพวก Precompiler อย่าง LESS หรือ SASS แล้ว เมื่อมีการแก้ไขไฟล์ .less หรือ .sass ไฟล์ใดไฟล์หนึ่งใน folder ที่เรากำหนด grunt จะช่วย compile ไฟล์ต้นทาง หรือไฟล์แรกให้ใหม่
เหมาะกับคนที่มีไฟล์เยอะๆ แล้วเรียก import เข้ามาหลายๆไฟล์ที่ไฟล์หลักตัวใดตัวหนึ่ง อย่างเช่น Twitter Bootstrap
เพิ่ม grunt watch เข้ามาใน package.json
{ "name": "jir4yu-demo-project", "version": "0.1.0", "private": true, "author": "Jirayu L", "devDependencies": { "grunt": "~0.4.5", "grunt-contrib-jshint": "~0.10.0", "grunt-contrib-uglify": "~0.5.0", "grunt-contrib-concat": "~0.5.0", "grunt-contrib-sass": "~0.7.3", "grunt-contrib-less": "~0.11.4", "grunt-contrib-watch": "~0.6.1" } }
จากนั้นเพิ่มชุดคำสั่ง watch ลงไปใน Gruntfile.js
module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), uglify: { my_target: { files: { 'js/main.min.js': ['js/main.js'] } } }, concat: { dist: { src: ['js/vendor/*.js'], dest: 'js/main.js' } }, sass: { dist: { files: [{ expand: true, cwd: 'scss', src: ['*.scss'], ext: '.css' }] } }, less: { options: { paths: 'less/', }, build: { src: 'less/style.less', dest: 'less/style.css' } }, watch: { files: 'scss/*.scss', tasks: ['sass'] }, }); grunt.registerTask('default', ['less, sass, concat, uglify']); grunt.loadNpmTasks('grunt-contrib-uglify'); // load uglify grunt.loadNpmTasks('grunt-contrib-concat'); // load concat grunt.loadNpmTasks('grunt-contrib-less'); // load LESS grunt.loadNpmTasks('grunt-contrib-sass'); // load SASS grunt.loadNpmTasks('grunt-contrib-watch'); // load watch };
จากตัวอย่าง ผู้เขียนให้ชุดคำสั่ง watch คอยตรวจสอบไฟล์นามสกุล .scss ใน folder scss ทั้งหมด และ เมื่อใดก็ตามที่มีการเปลี่ยนแปลงไม่ว่าไฟล์ใดไฟล์หนึ่งใน folder นี้ จะเรียกให้ชุดคำสั่ง sass ทำงานตามมาด้วย สรุปง่ายๆก็คือ ถ้าผู้เขียนแก้ไขไฟล์ .scss ไฟล์ใดไฟล์หนึ่ง GruntJS จะทำการ compile จาก SASS เป็น CSS ให้เองทันทีโดยไม่จำเป็นต้องไปนั่งเสียเวลาพิมพ์ command line บ่อยๆ
watch จะต่างจากชุดคำสั่งอื่นๆ อยู่นิดหน่อยตรงที่ต้องพิมพ์
grunt watch
ลงใน terminal เพื่อให้ grunt run background และ ทำการตรวจสอบไฟล์ครับ
** หมายเหตุ: ทุกครั้งที่มีการเปลี่ยนแปลง package.json อย่าลืมอัพเดทโดยใช้คำสั่ง npm update **
ขอบคุณมากค่ะ หาบทความการติดตั้ง grunt มานานแล้ว อธิบายได้ละเอียดมากค่ะ
ขอบคุณครับอธิบายได้ดีเลย เขียนได้ละเอียดครับ ติดตามผลงานเรื่อยๆ สู้ๆนะครับ
บทความดีมากเลย เขียนและอธิบายได้ดีมากครับ :D น่าจะมี cssmin กับ compress ด้วยนะครับ
ขอบคุณครับ ในอนาคตอาจจะมี edit เพิ่มด้วยครับ
หากบทความผิดพลาดอะไรสามารถแนะนำได้นะครับ
ขอบคุณครับ อธิบายได้เห็นภาพมากครับ
อยากทราบ การทำ livereload อ่าค่ะ ไม่ทราบว่าเวลาทำมันต้องเชื่อมต่อ internet มั้ยค่ะ
ไม่จำเป็นครับ หากส่วนใหญ่ content บนหน้าเว็บของเราเป็นแบบ offline (ไม่ได้เรียก api หรือลิงค์ไฟล์ library ข้างนอก) สามารถทำ livereload ได้เลยครับผ่าน port number
อ่อ คือ ปัญหาตอนนี้คือ มันเปิด port number ไม่ได้อ่ะค่ะ livereload เลยไม่ทำงาน
set ตามนี้แล้วใช่มั้ยครับ https://github.com/gruntjs/grunt-contrib-watch#optionslivereload
ปรกติแล้วถ้าทำบน localhost และตั้งค่าเป็น livereload: true ก็จะใช้ได้เลยครับ