Javascript Modules คืออะไร มีอะไรบ้าง? — CJS, AMD, UMD, ESM
Javascript Module
- module ในที่นี้เปรียบเทียบเหมือน building block of code เป็นบล็อคไม้ที่นำมาต่อรวมกันได้ แล้วแต่วัตถุประสงค์ที่เราต้องการ
- ปกติจะจัดการ module โดย 1 module เป็น 1 file
- มักประกอบด้วย function, object หรือ class ที่เกี่ยวข้องกัน มีวัตถุประสงค์เฉพาะของแต่ละ module
- ใช้จัดระเบียบโค้ด เพื่อเลี่ยงไม่ให้มีชื่อซ้ำและเก็บไว้ในพื้นที่แยกต่างหาก
- ใช้ import และ export ทำให้แชร์โค้ดร่วมกันได้ภายใน application ได้
- สามารถแชร์โค้ดหรือ reuse code ได้ เช่น npm ที่ไว้ให้เราสามารถ access ทั้ง 700,000 packages ได้
“Modules give the code structure and boundaries”
เริ่มจากตอนแรกที่ Javascript ถูกพัฒนาขึ้นมานั้นยังไม่มี module syntax (ไม่มี import/export) เป็นเพียงแค่ inline script หรือใช้แค่ script tag เท่านั้น ปัญหาของการไม่ได้ใช้ module เช่น
- Lack of code usability: reuse โค้ดไม่ได้เพราะไม่มี import/export ต้อง copy ไปวางที่ต้องการเอง
- Dependency management issue: เช่น การวางตำแหน่งของ script tag, dependency ไม่ครบ หรือถ้าต้องการลบอันไหนก็ต้องตามไปลบทุกที่ที่เอาไปวางไว้
- Maintainability challenges: จัดการโค้ดได้ยาก เช่น ถ้าต้องการแก้ไข function ที่ถูก copy ไปวางไว้หลายหน้า ก็อาจจะพลาดแก้ไม่ครบ เป็นต้น
นักพัฒนาจึงเริ่มสร้างสิ่งที่เรียกว่า Modules ขึ้นมาเพื่อพยายามแก้ไขปัญหาที่เกิดขึ้น โดยต่างคนต่างพัฒนา module ของตนเอง เช่น Yahoo
Yahoo มี JS module ของตัวเอง สร้างขึ้นเพื่อช่วยให้นักพัฒนาเว็บไซต์สามารถทำงานกับข้อมูลจาก Yahoo APIs ได้อย่างง่ายดายและมีประสิทธิภาพมากขึ้น โดยใช้ jQuery ajax method ในการเรียกข้อมูลจาก API แต่ปัจจุบันได้ยกเลิกการให้บริการแล้ว
ต่อมา JavaScript community จึงได้ร่วมกันพัฒนา Javascript modules ประเภทอื่นอีกตามมาเพื่อเป็น standard ในการใช้งาน โดยสามารถแบ่งประเภทหลักๆ ได้แก่ CJS, AMD, UMD, และ ESM
CJS
- ย่อมาจาก CommonJS
- ใช้ใน server-side เป็นหลัก เช่น Node.js เนื่องจากตอนพัฒนานั้น NodeJS กำลังบูมๆ (เพราะฉะนั้น NodeJS จึงใช้ CJS module format เรื่อยมา)
- เป็นการ import module แบบ Synchornous
- ไม่สามารถทำงานบน browser ได้
- สามารถแชร์โค้ดระหว่างกันได้
- ถ้าจะ import ใช้ require() method เป็นการ copy imported object ของ library นั้นๆ มาใช้งาน
- ถ้าจะ export ใช้ module object (assign to module.exports)
//main.js
const AwesomePerson = require('./awesome'); // import from other module
//awesome.js
function AwesomePerson(name) {
this.name = name;
}
AwesomePerson.prototype.speakTruth = function() {
console.log(`${this.name} is awesome!`);
}
module.exports = AwesomePerson; // export to other module
AMD
- ย่อมาจาก Asynchronous Module Definition
- ตรงตัวตามชื่อ import module แบบ Asynchronous
- เน้นใช้กับ client-side (browser) ใช้เมื่อต้องการโหลด module แบบ dynamic
การโหลด module แบบ dynamic คือโหลดและใช้โมดูลในขณะที่ program กำลังรันขึ้นมา และ module จะถูกโหลดเมื่อต้องการใช้งานเท่านั้น ทำให้สามารถลดเวลาในการโหลดหน้า browser ได้
define(['jquery'], function ($) { // array of dependency
// methods
function myFunc() {};
// exposed public methods
return myFunc;
});
หรือเขียนแบบ CommonJS wrapping ก็ได้ (ใช้ require ในการ import module)
define(function (require) {
var dep1 = require('dep1'),
dep2 = require('dep2');
return function () {};
});
UMD
- ย่อมาจาก Universal Module Definition
- ถูกสร้างขึ้นมาเพื่อให้สามารถทำงานได้ทั้ง server-side และ client-side
- เป็น pattern ที่ทำให้สามารถใช้งาน module หลายๆ แบบด้วยกันได้
UMD ช่วยให้ module สามารถใช้งานได้ใน environment ต่างๆ ไม่ว่าจะเป็น AMD, CJS หรือ Browser globals โดยที่ไม่ต้องเปลี่ยนโค้ดของ module เองตามแต่ละ enviroment ที่จะไปรัน
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
//AMD
define(['jquery'], factory)
} else if (typeof exports === 'object') {
// Node, CommonJS-like
module.exports = factory(require('jquery'));
} else {
// Browser globals (root is window)
root.returnExports = factory(root.jquery);
}
}(this, function ($) {
// methods
function myFunction() {};
// exposed public method
return myFunct;
}));
ในตัวอย่างนี้ มีการสร้างฟังก์ชั่นแบบ self-executing ที่รับพารามิเตอร์สองตัว คือ root และ factory
จากโค้ดจะเห็นได้ว่า UMD pattern มีการใช้ condition เช็ค execution environment ก่อนและจะเลือกใช้วิธีการ import module ให้เหมาะสมมากที่สุด
เช่น
if (typeof define === 'function' && define.amd) {
//AMD
define(['jquery'], factory)
}
if นี้เป็นการเช็คว่าโค้ดชุดนี้ได้ execute ผ่าน AMD module loader หรือไม่ ถ้าใช่ module จะถูก import โดยการเรียก define function (โดยระบุ jquery เป็น dependencies)
else if (typeof exports === 'object') {
// Node, CJS
module.exports = factory(require('jquery'));
}
else if นี้เช็คว่าโค้ดชุดนี้ถูกโหลดผ่าน Node.js หรือระบบ CJS หรือไม่
else {
// Browser globals (root is window)
root.returnExports = factory(root.jquery);
}
และ else จะทำการ import module โดยใช้ factory() และการส่ง jQuery เข้าไปใน parameter
ESM
- ESM มาจาก ECMAScript Module
- ใช้คำสั่ง import / export
- เป็น standard ที่ออกแบบมาเพื่อให้แน่ใจว่า javascript code ชุดเดียวกันสามารถทำงานบน browser ที่แตกต่างกันได้ (เช่น โค้ดเว็บเดียวกัน สามารถเปิดได้ทั้งบน Firefox, Chrome และ Safari)
- ปัจจุบันถูก support โดย browsers ทั้งหมดแล้ว
- ESM สามารถใช้ได้ทั้ง server-side และ client-side
- ช่วยให้ bundler เช่น Webpack สามารถลบโค้ดที่ไม่จำเป็นออกได้ ทำให้เว็บไซต์สามารถส่งโค้ดน้อยลงเพื่อให้โหลดเร็วขึ้นได้
Module bundler: การทำให้ javascript รวมกันเป็นก้อนเดียวเพื่อให้ html อ่านได้
Export
// msg.js
export default const msg = 'Yay ES6!';
// lib.js
export const sum = (a, b) => a + b;
export const product = (a, b) => a * b;
export const quotient = (a, b) => a / b;
Import
import msg from './msg.js';
import * as lib from './lib.js';
import { sum, product } from './lib.js';
ESM สามารถรันใน script tag ได้ด้วย
// Add a module script
<script type="module" src="main.js"></script> // specified for module type
// Fallback for older browser
<script nomodule src="bundle.js"></script>
สรุป
- ESM — ปัจจุบันใช้ดีที่สุด เพราะ syntax เขียนง่าย, รองรับการโหลดแบบ asynchronous, ใช้งานได้ทั้งบน client, server เป็น recommended format สำหรับ front-end applications ในปัจจุบัน
- UMD — ทำงานได้ทั้งบนฝั่ง client และ server, ปกติใช้เป็น fallback ในกรณีที่ ESM ทำงานไม่ได้
- CJS — โหลด module แบบ synchronous เหมาะกับ server-side
- AMD — โหลด module แบบ asynchronous เหมาะกับ client-side
reference: