Wichita Serchuit
Under Ledger
Published in
4 min readMar 29, 2023

--

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”

credit: JS Module (IIFE →CJS→AMD →ES6)

เริ่มจากตอนแรกที่ 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 อ่านได้

credit: create-react-app หลบไป! มาสร้าง module bundler ด้วย Parcel กันเถอะ
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:

--

--