โหมดมืด
บทที่ 10 — Modules: แบ่งโค้ดเป็นไฟล์
จนถึงตอนนี้เราเขียนโค้ดในไฟล์เดียว — แต่โปรแกรมจริงมีหลายร้อยไฟล์ บทนี้สอนวิธีแบ่งโค้ดเป็นไฟล์และเชื่อมโยงกัน
อ่านจบบทนี้คุณจะ:
- เข้าใจว่าทำไมต้องแบ่งโค้ดเป็น module
- ใช้
import/exportเป็น - เข้าใจความต่างระหว่าง ESM กับ CommonJS
- ใช้ dynamic import เพื่อโหลด module ตามต้องการ
ใช้เวลา 2 ชั่วโมง
Part 1: ทำไมต้องมี Module
1.1 ปัญหาของโค้ดไฟล์เดียว
ลองนึกโปรแกรมจริง — มันมีโค้ดเป็นพันเป็นหมื่นบรรทัด ถ้าเขียนในไฟล์เดียว:
- หาโค้ดไม่เจอ — เลื่อนหา function ในไฟล์ 5000 บรรทัด
- ชื่อชนกัน — มีตัวแปร
dataหลายที่ ทับกันโดยไม่ตั้งใจ - แก้แล้วพังที่อื่น — ไม่รู้ว่าโค้ดส่วนไหนเกี่ยวกับส่วนไหน
- ทำงานเป็นทีมไม่ได้ — ทุกคนแก้ไฟล์เดียวกัน conflict ตลอด
1.2 ทางแก้ — Module
Module = การแบ่งโค้ดออกเป็นไฟล์ ๆ โดยแต่ละไฟล์ "ประกาศ" ว่าจะแชร์อะไรออกไป และ "ขอใช้" อะไรจากไฟล์อื่น
หลักการ:
- 1 ไฟล์ = 1 module
- แต่ละ module มี "ของส่วนตัว" (ตัวแปร function ในไฟล์) ที่ไฟล์อื่นมองไม่เห็น
- module
exportสิ่งที่อยากแชร์ - module อื่น
importสิ่งที่ต้องการมาใช้
💡 เปรียบเทียบ: module เหมือน "แผนกในบริษัท" — แต่ละแผนกทำงานของตัวเอง มีของส่วนตัว — แต่มี "หน้าต่างบริการ" (export) ให้แผนกอื่นมาขอใช้ — และแผนกอื่นก็มา "ยื่นเรื่อง" (import) ขอบริการ
Part 2: ES Modules (ESM) — มาตรฐานสมัยใหม่
JavaScript สมัยใหม่ใช้ระบบ module ชื่อ ES Modules (ESM) — ใช้คำสั่ง import / export
2.1 Export — แชร์ออกไป
มี 2 แบบ — named export และ default export
Named Export — แชร์หลายอย่าง (มีชื่อ)
javascript
// ไฟล์: math.js
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}ใส่ export หน้าสิ่งที่อยากแชร์ — แชร์ได้หลายอย่าง
หรือ export รวมท้ายไฟล์:
javascript
const PI = 3.14159;
function add(a, b) { return a + b; }
function multiply(a, b) { return a * b; }
export { PI, add, multiply }; // export รวมทีเดียวDefault Export — แชร์ "ของหลัก" 1 อย่าง
javascript
// ไฟล์: User.js
export default class User {
constructor(name) {
this.name = name;
}
}default = "ของหลักของไฟล์นี้" — มีได้ไฟล์ละ 1 อันเท่านั้น
2.2 Import — ขอมาใช้
Import Named Export
javascript
// ไฟล์: main.js
import { add, PI } from "./math.js";
console.log(add(2, 3)); // 5
console.log(PI); // 3.14159ชื่อใน { } ต้องตรงกับชื่อที่ export
เปลี่ยนชื่อตอน import ด้วย as:
javascript
import { add as sum } from "./math.js";
console.log(sum(2, 3)); // 5import ทุกอย่างเป็น object เดียว:
javascript
import * as math from "./math.js";
console.log(math.add(2, 3));
console.log(math.PI);Import Default Export
javascript
import User from "./User.js"; // ไม่มี { } — และตั้งชื่ออะไรก็ได้
const u = new User("Alice");default export — ตอน import ไม่ต้องมี { } และตั้งชื่ออะไรก็ได้ (ไม่ต้องตรงกับชื่อตอน export)
Import ทั้ง default และ named
javascript
import User, { PI, add } from "./module.js";
// User = default, PI/add = named2.3 ⚠️ ความต่าง Named กับ Default
| Named Export | Default Export | |
|---|---|---|
| จำนวนต่อไฟล์ | หลายอันได้ | 1 อันเท่านั้น |
| import | ต้องมี { } | ไม่มี { } |
| ชื่อตอน import | ต้องตรง (หรือใช้ as) | ตั้งชื่ออะไรก็ได้ |
→ คำแนะนำ: ใช้ named export เป็นหลัก (ชื่อตรงกันทุกที่ ค้นหาง่าย refactor ง่าย) — ใช้ default เมื่อไฟล์มี "ของหลัก" ชัดเจน 1 อย่าง
2.4 เปิดใช้ ESM
ESM ใช้ได้เมื่อ:
ใน Node.js — ใส่ "type": "module" ใน package.json (จำจากบทที่ 01):
json
{ "type": "module" }💡 package.json คืออะไร? — ไฟล์ตั้งค่าโปรเจกต์ที่อยู่ที่ root ของโฟลเดอร์ สร้างได้ด้วยคำสั่ง
npm init -yถ้ายังไม่มี (เรียนละเอียดในบทที่ 13)
ในเบราว์เซอร์ — ใส่ type="module" ใน script tag:
html
<script type="module" src="main.js"></script>Part 3: รายละเอียดของ import/export
3.1 import ต้องระบุ path ให้ถูก
javascript
import { x } from "./math.js"; // ไฟล์ในโฟลเดอร์เดียวกัน — ต้องมี ./
import { x } from "../utils.js"; // โฟลเดอร์แม่ — ../
import { x } from "./lib/helper.js"; // โฟลเดอร์ย่อย
import dayjs from "dayjs"; // library จาก node_modules — ใส่แค่ชื่อ (ไม่มี ./)💡 ทำไม
"dayjs"ใส่แค่ชื่อ ไม่มี./? — เมื่อ Node.js เห็น path ที่ไม่ขึ้นต้นด้วย./หรือ../มันจะตีความว่าเป็น "ชื่อ package" แล้วไปหาในโฟลเดอร์node_modules/ของโปรเจกต์โดยอัตโนมัติ (เรียก path แบบนี้ว่า "bare specifier")
⚠️ ใน Node.js ESM ต้องใส่นามสกุลไฟล์ครบ — ./math.js (ไม่ใช่ ./math) — ใช้ทุกตัวอย่างในบทนี้ และทุก import แบบ relative (./ หรือ ../) ต้องลงท้าย .js เสมอ
💡 ถ้ายังไม่ได้เรียน TypeScript — ไม่ต้องสนใจส่วนนี้ ใช้
.jsธรรมดาได้เลย สำหรับผู้ที่เรียน TypeScript: TypeScript ใช้.jsใน import แม้ไฟล์จริงจะเป็น.ts— อ่านเพิ่มในบท TypeScript
3.2 import ถูก "ยกขึ้นบนสุด" (hoisted)
ในทางเทคนิค — ไม่ว่าจะเขียน import ไว้ตรงไหนในไฟล์ มันถูกประมวลผลก่อนโค้ดอื่นเสมอ (เรียกว่า "hoisting" = การยกขึ้น — คือ JS จะประมวลผลบางส่วนของโค้ดล่วงหน้าก่อนรันตามลำดับบรรทัดจริง เคยเจอเรื่องนี้กับ var และ function declaration ในบทที่ 02/05 แล้ว import ก็ทำงานแบบเดียวกัน คือถูกประมวลผลก่อนโค้ดอื่น) ดังนั้นโค้ดด้านล่างนี้ "รันได้":
javascript
console.log(add(2, 3)); // ใช้ได้ — แม้ import อยู่ข้างล่าง (เพราะ import ถูกยกขึ้น)
import { add } from "./math.js";→ แต่ในทางปฏิบัติ — เขียน import ไว้บนสุดของไฟล์เสมอ เพื่อให้อ่านง่ายและ ESLint/Prettier ไม่เตือน (มาตรฐานทีมส่วนใหญ่บังคับ)
3.3 Re-export — ส่งต่อ
ไฟล์หนึ่งสามารถ "ส่งต่อ" export ของไฟล์อื่น:
javascript
// ไฟล์: index.js
export { add, multiply } from "./math.js";
export { User } from "./User.js";
export { formatDate } from "./date.js";แล้วไฟล์อื่น import จากที่เดียว:
javascript
import { add, User, formatDate } from "./index.js";เทคนิคนี้เรียก "barrel file" (ไฟล์รวม export — รวบ export จากหลายไฟล์ไว้ที่จุดเดียว ให้ import จากที่เดียวได้สะดวก)
⚠️ ข้อควรระวัง: barrel file อาจทำให้ bundler (เครื่องมือรวมไฟล์ — บทที่ 13) ตัดโค้ดที่ไม่ใช้ (tree-shaking — อธิบายเต็มในบทที่ 13) ได้ยากขึ้น เพราะการ import 1 อย่างจาก barrel อาจดึง module อื่น ๆ เข้ามาด้วย — bundler สมัยใหม่อย่าง Vite 5+ จัดการเรื่องนี้ได้ดีขึ้น แต่โปรเจกต์ขนาดใหญ่ควรพิจารณา import ตรงจากไฟล์ต้นทางแทน
Part 4: Module เป็น "ของชิ้นเดียว" (singleton) ที่ใช้ร่วมกัน
เรื่องสำคัญที่ต้องเข้าใจ — module ถูกรันแค่ครั้งเดียว ไม่ว่าจะ import กี่ที่ (singleton = "ของชิ้นเดียว" — มีอยู่ชุดเดียวในทั้งโปรแกรม ทุกที่ที่เรียกใช้ได้ของชุดเดียวกัน)
javascript
// ไฟล์: counter.js
let count = 0;
export function increment() { return ++count; }
export function getCount() { return count; }javascript
// ไฟล์ a.js
import { increment } from "./counter.js";
increment();
increment();
// ไฟล์ b.js
import { getCount } from "./counter.js";
console.log(getCount()); // 2 — เห็นค่าที่ a.js เพิ่ม!a.js กับ b.js import counter.js คนละที่ — แต่ใช้ count ตัวเดียวกัน เพราะ module รันครั้งเดียว
→ ใช้ประโยชน์: สร้าง "state ส่วนกลาง" ที่ทุกไฟล์ใช้ร่วมกัน (เช่น config, การตั้งค่า)
Part 5: CommonJS — ระบบ module เก่าของ Node.js (อ่านโค้ดเก่าออก)
📌 สรุปสั้น (ปี 2026): โปรเจกต์ใหม่ทุกตัวควรใช้ ESM — CommonJS เหลือไว้แค่ "อ่านออก" และไว้ทำงานกับโค้ด/package เก่าเท่านั้น Part นี้สอนแค่ระดับ "อ่านโค้ดเก่ารู้เรื่อง"
5.1 syntax ของ CommonJS
ก่อนจะมี ES Modules มาตรฐาน Node.js ใช้ระบบโมดูลของตัวเองชื่อ CommonJS (CJS) — export ด้วย module.exports และนำเข้าด้วย require() ยังเจอในโค้ด Node เก่าและ package จำนวนมาก จึงควรอ่านออก:
javascript
// export (CommonJS)
// ไฟล์: math.cjs (หรือ math.js ในโปรเจกต์ที่ไม่ใส่ "type": "module")
function add(a, b) { return a + b; }
const PI = 3.14159;
module.exports = { add, PI };javascript
// import (CommonJS)
// ไฟล์: main.cjs
const { add, PI } = require("./math.cjs");CommonJS ใช้ module.exports และ require() — ไม่ใช่ export/import
5.2 ESM vs CommonJS
| ESM (มาตรฐาน ปี 2026) | CommonJS (legacy/interop) | |
|---|---|---|
| export | export | module.exports |
| import | import | require() |
| ใช้ในเบราว์เซอร์ | ✅ ได้ | ❌ ไม่ได้ |
| วิเคราะห์โครงสร้าง | ก่อนรัน (static — รองรับ tree-shaking ดูบทที่ 13) | ตอนรัน (dynamic — require() คือ function ธรรมดา) |
| มาตรฐาน ECMAScript | ✅ มาตรฐานทางการ | ❌ เฉพาะ Node.js |
| top-level await | ✅ ใช้ได้ | ❌ ไม่ได้ |
⚠️ หมายเหตุเรื่อง "static vs dynamic": ทั้งคู่ "รัน" ตอนรันไทม์เหมือนกัน ความต่างคือ ESM วิเคราะห์โครงสร้าง import/export ได้ตั้งแต่ก่อนรัน (เพราะ syntax เป็นคีย์เวิร์ดเฉพาะ) — bundler เลย tree-shake (ตัดโค้ดที่ไม่ได้ใช้) ได้ ส่วน CJS
require()เป็น function ธรรมดา จะรู้ได้ตอนรันถึงบรรทัดนั้นเท่านั้น
กฎปี 2026: โปรเจกต์ใหม่ ใช้ ESM ตลอด — รู้จัก CommonJS ไว้เพื่อ (1) อ่านโค้ดเก่าออก (2) ทำงานกับ package เก่าที่ยังเป็น CJS อยู่
5.3 Interop — เรียก ESM จาก CJS และกลับกัน (Node 22+)
ความจริงอย่างหนึ่งคือ ecosystem ของ Node ยังมี CJS package เยอะ — Node เลยมีระบบให้ใช้ข้ามกันได้:
จาก ESM ไป CJS — ใช้ import ได้ตรง ๆ:
javascript
// ไฟล์ ESM
import legacy from "some-cjs-package"; // ได้ default export = module.exportsจาก CJS ไป ESM (เดิม): ต้องใช้ await import() (dynamic import) เท่านั้น — require() ตรง ๆ ไม่ได้
Node.js เวอร์ชันใหม่ ๆ มีฟีเจอร์ require(esm) — ใช้ require() กับไฟล์ ESM ได้โดยตรง (ตราบใดที่ไฟล์นั้นไม่มี top-level await):
javascript
// CJS ใน Node.js เวอร์ชันที่รองรับ require(esm)
const { add } = require("./math.js"); // math.js เป็น ESM ก็เรียกได้⚠️ หมายเหตุเวอร์ชัน: เวอร์ชันที่แน่นอนที่ฟีเจอร์นี้ทำงานได้โดยไม่ต้องใส่ flag อาจเปลี่ยนแปลงได้ — ตรวจสอบสถานะ/เวอร์ชันล่าสุดก่อนใช้ (เช่นดูจาก release notes ของ Node.js) และรันคำสั่ง
node --versionเพื่อดูว่าเครื่องใช้เวอร์ชันไหนอยู่
ฟีเจอร์นี้ช่วยให้การ migrate จาก CJS ไป ESM ค่อย ๆ ทำได้ — ไม่ต้องเปลี่ยนทั้งโปรเจกต์ในรอบเดียว แต่โปรเจกต์ใหม่ก็ยังควรเป็น ESM ตั้งแต่แรก
Part 6: Dynamic Import — โหลดตามต้องการ
ความต่างระหว่าง static กับ dynamic:
- static (ตายตัวล่วงหน้า) =
import ... from "..."ที่เขียนไว้บนสุดของไฟล์ — โหลดทันทีตั้งแต่โปรแกรมเริ่ม - dynamic (โหลดตอนรัน) =
import(...)แบบ function — ตัดสินใจตอนโค้ดทำงานจริงว่าจะโหลดหรือไม่
import ปกติ (static) ต้องเขียนบนสุดของไฟล์ และโหลดทันที — แต่บางครั้งเราอยาก "โหลดเมื่อต้องการ" เท่านั้น
ใช้ dynamic import — import() ใช้แบบ function:
javascript
async function loadFeature() {
const module = await import("./heavyFeature.js");
module.doSomething();
}
// โหลดเฉพาะตอนผู้ใช้คลิกปุ่ม
// (addEventListener = ผูก function ให้ทำงานเมื่อเกิด event — เรียนละเอียดในบทที่ 12)
button.addEventListener("click", loadFeature);import() คืน Promise (จำบทที่ 09) → ใช้ await
ประโยชน์ — ไม่ต้องโหลดโค้ดที่ยังไม่ใช้:
- หน้าเว็บโหลดเร็วขึ้น (โหลดแค่โค้ดที่จำเป็นตอนแรก)
- โค้ดส่วนที่ใหญ่ ๆ โหลดเมื่อผู้ใช้ต้องการจริง
นี่คือเทคนิค "code splitting" (แตกโค้ดเป็นชิ้น ๆ เพื่อโหลดเฉพาะที่ต้องใช้) — bundler สมัยใหม่ (บทที่ 13) ใช้เทคนิคนี้
Part 7: ESM ในเบราว์เซอร์
7.1 ใช้งานพื้นฐาน
เบราว์เซอร์รองรับ ES Modules โดยตรง — แค่ใส่ type="module" ใน <script> แล้วไฟล์นั้นก็ import ไฟล์อื่นได้ (ต้องระบุ ./ และนามสกุล .js เต็ม):
html
<script type="module" src="main.js"></script>ใน main.js import ไฟล์อื่นได้:
javascript
import { greet } from "./greet.js"; // ต้องมี ./ และ .js
greet();7.2 ความพิเศษของ module script
<script type="module"> ต่างจาก <script> ธรรมดา:
- รันแบบ defer อัตโนมัติ — รอ HTML โหลดเสร็จก่อน (จำ
deferจากบทที่ 01) - อยู่ใน strict mode อัตโนมัติ
- ตัวแปรไม่รั่วออก global
7.3 ⚠️ import library ในเบราว์เซอร์
javascript
import _ from "lodash"; // ❌ เบราว์เซอร์ไม่รู้จัก "lodash" (ตามค่าเริ่มต้น)เบราว์เซอร์โดยปกติรู้จักแค่ path ที่ขึ้นต้นด้วย ./, ../, หรือ URL เต็มเท่านั้น — ส่วน "bare specifier" (ชื่อ package เปล่า ๆ อย่าง "lodash") มันไม่รู้ว่าจะไปหาที่ไหน
มี 3 วิธีแก้:
- ใช้ URL เต็ม — โหลดตรงจาก CDN:
import _ from "https://esm.sh/lodash" - ใช้ Import Map — บอกเบราว์เซอร์ว่า "lodash" หมายถึงอะไร (เบราว์เซอร์สมัยใหม่รองรับทุกตัวแล้ว):html
<script type="importmap"> { "imports": { "lodash": "https://esm.sh/lodash" } } </script> <script type="module"> import _ from "lodash"; // ✅ ใช้ได้แล้ว </script> - ใช้ bundler (เครื่องมือรวมไฟล์ JS อย่าง Vite, esbuild — บทที่ 13) — มันแปลง
"lodash"ให้เป็น path ที่เบราว์เซอร์รู้จักตอน build
→ ในงานจริงส่วนใหญ่ — ใช้ bundler เพราะมันจัดการเรื่องนี้และอื่น ๆ ให้ครบ (minify, tree-shaking ฯลฯ — ดูบทที่ 13) ส่วน import map เหมาะกับงานเล็ก/prototype ที่ไม่อยาก setup bundler
Part 8: รูปแบบการจัดโครงสร้างไฟล์
8.1 จัดกลุ่มตามความรับผิดชอบ
เมื่อโปรเจกต์โต ควรแยกไฟล์เป็นโฟลเดอร์ตาม "หน้าที่" (utils, models, services) เพื่อให้หาของเจอและดูแลง่าย โครงสร้างด้านล่างเป็นแบบที่นิยม:
text
src/
├── main.js # จุดเริ่มต้น
├── utils/
│ ├── math.js
│ ├── date.js
│ └── string.js
├── models/
│ ├── User.js
│ └── Product.js
└── services/
└── api.js8.2 ตัวอย่าง — module ที่ออกแบบดี
javascript
// utils/math.js — module ของ pure function
export function add(a, b) { return a + b; }
export function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}javascript
// services/api.js — module ที่จัดการเรื่อง API
const BASE_URL = "https://api.example.com";
export async function getUser(id) {
const response = await fetch(`${BASE_URL}/users/${id}`);
return response.json();
}
export async function getProducts() {
const response = await fetch(`${BASE_URL}/products`);
return response.json();
}javascript
// main.js — รวมทุกอย่าง
// หมายเหตุ: ไฟล์นี้ใช้ top-level await — ต้องเป็น ES module
// (ตั้ง "type": "module" ใน package.json หรือใช้นามสกุล .mjs)
import { getUser } from "./services/api.js";
import { clamp } from "./utils/math.js";
const user = await getUser(1);
console.log(user);หลักการออกแบบ module ที่ดี:
- 1 module = 1 ความรับผิดชอบ (math ก็เรื่องเลข, api ก็เรื่อง API)
- export เฉพาะสิ่งที่จำเป็น — ของที่เป็นรายละเอียดภายในไม่ต้อง export
- ตั้งชื่อไฟล์ให้สื่อความหมาย
Part 9: Lab — ลงมือทำ
Lab 1: สร้าง module แรก
ฝึกแยกโค้ดเป็น module — สร้าง math.js ที่ export ฟังก์ชัน/ค่าออกไป แล้ว import มาใช้ใน main.js:
math.js:
javascript
export function add(a, b) { return a + b; }
export function subtract(a, b) { return a - b; }
export const PI = 3.14159;main.js:
javascript
import { add, subtract, PI } from "./math.js";
console.log(add(5, 3)); // 8
console.log(subtract(10, 4)); // 6
console.log(PI); // 3.14159ขั้นตอนรันตัวอย่างนี้:
- สร้างโฟลเดอร์ใหม่ เช่น
my-modules - รันคำสั่งนี้ใน terminal:
npm init -yเพื่อสร้างpackage.json - เปิด
package.jsonแล้วเพิ่ม"type": "module"(หรือแก้ให้มีบรรทัดนี้) - สร้างไฟล์
math.jsและmain.jsตามตัวอย่างด้านบน - รันด้วยคำสั่ง:
node main.js
Lab 2: Default export
ฝึกใช้ export default (ส่งออกค่าหลักหนึ่งค่าต่อไฟล์) แล้ว import โดยตั้งชื่อเองตอนนำเข้า — ต่างจาก named export ที่ต้องใช้ชื่อตรงกัน:
Calculator.js:
javascript
export default class Calculator {
constructor() {
this.result = 0;
}
add(n) { this.result += n; return this; }
subtract(n) { this.result -= n; return this; }
getResult() { return this.result; }
}main.js:
javascript
import Calculator from "./Calculator.js";
const calc = new Calculator();
const result = calc.add(10).subtract(3).add(5).getResult();
console.log(result); // 12Lab 3: Module เป็น singleton
config.js:
javascript
let settings = { theme: "light", lang: "th" };
export function getSettings() { return settings; }
export function setTheme(theme) { settings.theme = theme; }a.js:
javascript
import { setTheme } from "./config.js";
setTheme("dark");main.js:
javascript
import "./a.js"; // รัน a.js (เปลี่ยน theme)
import { getSettings } from "./config.js";
console.log(getSettings()); // { theme: "dark", lang: "th" }💡 ทำไมลำดับถึงถูก? — ใน ES Modules module ที่ถูก import จะรันตามลำดับ dependency ก่อนที่โค้ดในไฟล์ปัจจุบันจะเริ่มทำงาน ดังนั้น
a.js(ซึ่งเรียกsetTheme("dark")) จะรันเสร็จก่อนconsole.log(getSettings())เสมอ — ลำดับของ import statement ในไฟล์จึงสำคัญสำหรับ side effects แบบนี้
Part 10: Checkpoint
- ทำไมต้องแบ่งโค้ดเป็น module?
- Named export ต่าง default export ยังไง?
- import named export ต้องมี
{ }ไหม? default ล่ะ? - ทำไมแนะนำใช้ named export เป็นหลัก?
- เปิดใช้ ESM ใน Node.js ทำยังไง?
- "Module เป็น singleton" หมายความว่ายังไง?
- ESM ต่าง CommonJS ยังไง?
- Dynamic import ใช้ตอนไหน? มีประโยชน์อะไร?
- ทำไม import library ในเบราว์เซอร์ตรง ๆ ไม่ได้?
- Barrel file คืออะไร?
Part 11: สรุปบทนี้
- Module = แบ่งโค้ดเป็นไฟล์ — แต่ละไฟล์
exportสิ่งที่แชร์ และimportสิ่งที่ต้องการ - ESM = มาตรฐานสมัยใหม่ —
export/import - Named export (หลายอัน ต้องมี
{ }) — ใช้เป็นหลัก - Default export (1 อัน ไม่มี
{ }) — ใช้กับ "ของหลัก" ของไฟล์ - Module เป็น singleton — รันครั้งเดียว ทุกที่ที่ import ใช้ของเดียวกัน
- CommonJS (
require/module.exports) = ระบบเก่าของ Node — รู้จักไว้อ่านโค้ดเก่า - Dynamic import (
import()) — โหลด module ตามต้องการ (code splitting) - 1 module = 1 ความรับผิดชอบ
บทต่อไป — Error Handling: จัดการข้อผิดพลาด