Skip to content

บทที่ 13 — Modern JavaScript และเครื่องมือมืออาชีพ (Tooling)

← บทที่ 12 | สารบัญ


📖 กล่องศัพท์ — อ่านก่อนเริ่ม (บทนี้คำย่อเยอะที่สุดในเล่ม)

บทนี้คือ "กำแพงอังกฤษ" ที่หนาที่สุดของหนังสือ เพราะวงการเครื่องมือ (tooling) ใช้ทับศัพท์อังกฤษล้วน ๆ แบ่งเป็นสองส่วน:

ส่วนที่ 1 — 5 คำสำคัญที่ต้องรู้ก่อนเริ่มอ่าน:

  • Package / Dependency = ไลบรารี (โค้ดสำเร็จรูปที่คนอื่นเขียนไว้ให้เราเอามาใช้); dependency = "ของที่โปรเจกต์เราต้องพึ่งพา"
  • Package Manager = โปรแกรมจัดการ "อะไหล่" (ดาวน์โหลด/อัปเดตไลบรารี) เช่น npm, pnpm
  • Bundler = ตัวรวมไฟล์โค้ดหลายร้อยไฟล์ให้เหลือไม่กี่ไฟล์ที่เบราว์เซอร์รันได้เร็ว เช่น Vite
  • Linter = ตัวตรวจจับ "กลิ่นโค้ดไม่ดี" ก่อนกลายเป็นบั๊ก เช่น ESLint
  • Formatter = ตัวจัดหน้าตาโค้ดให้สวยเหมือนกันทั้งทีม เช่น Prettier

ส่วนที่ 2 — คำอ้างอิง (เปิดดูได้เมื่อเจอในเนื้อหา ไม่ต้องจำตอนนี้):

  • Tooling = เครื่องมือมืออาชีพรอบ ๆ การเขียนโค้ด (ตัวจัดการไลบรารี, ตัวรวมไฟล์ ฯลฯ)
  • Registry = คลังกลางบนเน็ตที่เก็บ package ของทั้งโลก
  • Transpile = แปลงโค้ดสมัยใหม่/TypeScript/JSX → JavaScript เก่าที่เบราว์เซอร์ทุกตัวเข้าใจ
  • Minify = บีบอัดโค้ด (ลบช่องว่าง คอมเมนต์ ย่อชื่อตัวแปร) ให้ไฟล์เล็กลง
  • Tree-shaking = "เขย่าต้นไม้" — ตัดโค้ดที่ไม่มีใครเรียกใช้ออกตอน bundle
  • CI = Continuous Integration (การรวมงานต่อเนื่อง) = ระบบตรวจสอบโค้ดอัตโนมัติบนเซิร์ฟเวอร์กลางทุกครั้งที่ push (เรื่อง "ทีม" — ผู้เริ่มต้นยังไม่ต้องเข้าใจตอนนี้)
  • CD = Continuous Deployment (การส่งขึ้นใช้งานต่อเนื่อง) = ถ้าตรวจผ่าน ก็ deploy ขึ้น production อัตโนมัติ (เรื่อง "ทีม" เช่นกัน)
  • Monorepo = หลายโปรเจกต์เก็บไว้ใน Git repo (คลังเก็บโค้ด) เดียว
  • ESM / ESModule = ระบบแบ่งโค้ดเป็นไฟล์แบบสมัยใหม่ (import/export — บทที่ 10)
  • symlink = ทางลัดในระบบไฟล์ที่ชี้ไปยังไฟล์จริงที่เก็บอยู่ที่อื่น (ย่อจาก Symbolic Link)
  • opinionated = "มีจุดยืน/ความเห็นชัดเจน" — เครื่องมือที่บังคับสไตล์มาให้เลย ไม่ต้อง config เยอะ
  • ecosystem = "ระบบนิเวศ" — ชุดเครื่องมือ/ไลบรารี/ชุมชนที่ห้อมล้อมเครื่องมือหลัก
  • phantom dependency = "dependency ผี" — โค้ดเผลอใช้ package ที่ไม่ได้ประกาศใน package.json

บทนำ — ทำไมต้องมีบทนี้

ถ้าคุณอ่านมาถึงบทนี้ แปลว่าคุณเขียน JavaScript ได้แล้ว — รู้จัก ตัวแปร, ฟังก์ชัน, object, async (การทำงานแบบไม่รอ), DOM (การควบคุมหน้าเว็บ) ครบหมด แต่มีความจริงข้อหนึ่งที่หนังสือสอนเขียนโปรแกรมมักไม่บอก:

การ "เขียนโค้ดเป็น" กับการ "ทำงานเป็นโปรแกรมเมอร์มืออาชีพ" เป็นคนละเรื่องกัน

พูดง่าย ๆ คือ: เขียนโค้ดให้รันได้ กับเขียนโค้ดด้วยเครื่องมือที่ช่วยให้เร็วขึ้น ปลอดภัยขึ้น และทำงานร่วมกับคนอื่นในทีมได้ เป็นทักษะคนละชุดกัน

ลองนึกภาพช่างไม้สองคน คนแรกมีแค่ค้อนกับเลื่อย เขาต่อโต๊ะได้ แต่ใช้เวลานานและงานออกมาไม่เนียน ส่วนคนที่สองมีโรงงานเต็มไปด้วยเครื่องมือ — เลื่อยวงเดือน, เครื่องเจาะ, เครื่องขัด, ตลับเมตรดิจิทัล เขาต่อโต๊ะตัวเดียวกันได้ในเวลาครึ่งหนึ่ง และงานเนียนกว่ามาก

โปรแกรมเมอร์ก็เหมือนกัน บทนี้คือ "โรงงานเครื่องมือ" ของคุณ เราจะเรียนรู้ว่าโปรเจกต์ JavaScript จริง ๆ ในปี 2026 เขาประกอบกันด้วยอะไรบ้าง:

  • Package Manager — ตัวจัดการ "อะไหล่" (ไลบรารีที่คนอื่นเขียนไว้)
  • Bundler — ตัวรวมไฟล์โค้ดร้อย ๆ ไฟล์ให้กลายเป็นไฟล์ที่เบราว์เซอร์รันได้เร็ว
  • Linter — ตัวตรวจจับ "กลิ่นไม่ดี" ในโค้ดก่อนที่มันจะกลายเป็นบั๊ก
  • Formatter — ตัวจัดระเบียบหน้าตาโค้ดให้สวยเหมือนกันทั้งทีม
  • Test Runner — ตัวรันการทดสอบอัตโนมัติ
  • Git Hooks / CI — ตัวคุมคุณภาพอัตโนมัติทุกครั้งที่ commit หรือ push

💡 อุปมา: ถ้าโค้ดที่คุณเขียนคือ "อาหาร" tooling ก็คือ "ครัวมืออาชีพ" — มีตู้เย็น มีเตาแยกประเภท มีเครื่องล้างจาน อาหารจานเดียวกันแต่ทำในครัวบ้านกับครัวร้านมิชลิน ใช้เวลาและได้คุณภาพต่างกันลิบลับ

ใช้เวลาอ่านบทนี้ประมาณ 3-4 ชั่วโมง และเมื่อจบบท คุณจะ:

  • ตั้งโปรเจกต์ JavaScript/TypeScript ขึ้นมาใหม่ได้ตั้งแต่ศูนย์
  • เข้าใจว่าแต่ละเครื่องมือแก้ปัญหาอะไร (ไม่ใช่แค่ copy คำสั่งมาวาง)
  • อ่าน package.json ของโปรเจกต์คนอื่นได้รู้เรื่อง
  • รู้จัก feature ใหม่ ๆ ของภาษา JavaScript ที่ออกมาในช่วงปี 2020-2025

Part 1: Package Manager — ผู้จัดการอะไหล่

1.1 ปัญหาที่ Package Manager มาแก้

สมมติคุณกำลังเขียนเว็บ และต้องการ "ตัวจัดการวันที่" ที่ฉลาด ๆ คุณมีสองทางเลือก:

  1. เขียนเองทั้งหมด — ใช้เวลาหลายวัน เจอบั๊กเรื่อง timezone, leap year เอง
  2. ใช้ของที่คนอื่นเขียนไว้แล้ว — เช่นไลบรารีชื่อ date-fns ที่มีคนใช้เป็นล้าน ทดสอบมาแล้วอย่างหนัก

แน่นอนว่าทางเลือกที่ 2 ฉลาดกว่า แต่... คุณจะเอาโค้ดของ date-fns มาใส่ในโปรเจกต์ยังไง? จะ copy ไฟล์มาเองเหรอ? แล้วถ้า date-fns ไปเรียกใช้ไลบรารีตัวอื่นอีก 5 ตัวล่ะ? แล้วถ้าเดือนหน้ามันออกเวอร์ชันใหม่แก้บั๊ก คุณจะรู้ได้ยังไง?

นี่คืองานของ Package Manager — มันคือโปรแกรมที่ทำหน้าที่:

  • ดาวน์โหลด ไลบรารี (เรียกว่า "package" หรือ "dependency") จากคลังกลางบนอินเทอร์เน็ต
  • จัดการ dependency ซ้อน dependency — ถ้า package A ต้องการ package B มันจะดึง B มาให้อัตโนมัติ
  • จดบันทึก ว่าโปรเจกต์นี้ใช้ package อะไรเวอร์ชันไหนบ้าง
  • อัปเดต package เมื่อมีเวอร์ชันใหม่

💡 อุปมา: Package Manager คือ "พนักงานสั่งของเข้าร้าน" คุณแค่บอกว่า "อยากได้ date-fns" เขาจะวิ่งไปคลังสินค้ากลาง (ชื่อ npm registry) หยิบของมาให้ พร้อมหยิบของประกอบอื่น ๆ ที่จำเป็นมาด้วย และจดลงบัญชีว่าร้านเรามีอะไรบ้าง

คลังสินค้ากลางที่ว่านี้ชื่อ npm registry (npmjs.com) — เป็นที่เก็บ package ของโลก JavaScript มากกว่า 2 ล้านตัว ใครก็เผยแพร่ package ขึ้นไปได้ฟรี

1.2 npm — ตัวที่ติดมากับ Node.js

npm (ย่อมาจาก Node Package Manager) คือ Package Manager ที่ติดตั้งมาพร้อม Node.js อยู่แล้ว เมื่อคุณลง Node.js คุณจะได้ npm มาฟรี ลองเปิด terminal (= โปรแกรมสำหรับพิมพ์คำสั่ง เช่น Command Prompt หรือ PowerShell บน Windows, Terminal บน Mac — เปิดได้จาก Start menu หรือ Applications) พิมพ์ดู:

bash
npm --version

ถ้าขึ้นเลขเวอร์ชัน แปลว่าพร้อมใช้งาน มาดูคำสั่งที่ใช้บ่อยที่สุด พร้อมคำอธิบายว่าแต่ละคำสั่งทำอะไรจริง ๆ:

bash
# ติดตั้ง package เข้าโปรเจกต์ (ใช้ในโค้ดจริง)
npm install date-fns
# ผลลัพธ์: ดาวน์โหลด date-fns ลงโฟลเดอร์ node_modules/
#         และเพิ่มชื่อมันลงใน "dependencies" ใน package.json

# ติดตั้ง package เป็นเครื่องมือพัฒนา (ไม่ได้ใช้ในโค้ดจริง)
npm install -D eslint
# -D ย่อมาจาก --save-dev → เพิ่มลงใน "devDependencies" แทน

# ถอนการติดตั้ง
npm uninstall date-fns

# อัปเดต package ทุกตัวให้เป็นเวอร์ชันใหม่ (ภายในขอบเขตที่ปลอดภัย)
npm update

# ดูว่ามี package ตัวไหน "ล้าสมัย" บ้าง
npm outdated

# ตรวจสอบช่องโหว่ความปลอดภัยของ package ทั้งหมด
npm audit

# ติดตั้งแบบ "สะอาด" จากไฟล์ lock เป๊ะ ๆ (ใช้บนเซิร์ฟเวอร์ CI)
npm ci

⚠️ จุดที่มือใหม่งงบ่อย — npm install ต่างกับ npm ci ยังไง?

  • npm install = "ติดตั้ง โดยยอมปรับเวอร์ชันได้นิดหน่อยถ้ามีตัวใหม่" — เหมาะกับตอนพัฒนา
  • npm ci = "ติดตั้งให้เป๊ะตรงตามไฟล์ lock ไม่ปรับอะไรเลย ลบ node_modules เดิมทิ้งก่อนด้วย" — เหมาะกับเซิร์ฟเวอร์ที่ต้องการความแน่นอน 100%

(เดี๋ยวเราจะพูดเรื่องไฟล์ lock ใน Part 2)

1.3 ทำไมถึงมี Package Manager หลายตัว?

ถ้า npm ติดมากับ Node อยู่แล้ว ทำไมยังมี pnpm, yarn, bun อีก? คำตอบคือ npm รุ่นแรก ๆ มีปัญหาเรื่อง ความเร็ว และ เปลืองพื้นที่ดิสก์ คนเลยสร้างตัวใหม่มาแข่ง มาดูทีละตัว

pnpm — ตัวที่แนะนำในปี 2026

bash
# ติดตั้ง pnpm (ทำครั้งเดียว)
npm install -g pnpm

# จากนั้นใช้คำสั่งคล้าย npm
pnpm install            # ติดตั้ง package ทั้งหมดในโปรเจกต์
pnpm add date-fns       # เพิ่ม package (เทียบเท่า npm install)
pnpm remove date-fns    # ลบ package

จุดเด่นของ pnpm ที่ทำให้มันชนะ npm:

  • ประหยัดดิสก์มหาศาล — สมมติคุณมี 10 โปรเจกต์ที่ใช้ react เหมือนกัน npm จะ copy react ลงไป 10 ชุด (เปลือง!) แต่ pnpm เก็บ react ไว้ที่เดียวบนเครื่อง แล้วใช้ symlink (หรือ Symbolic Link = ทางลัดในระบบไฟล์ที่ชี้ไปยังไฟล์จริงที่เก็บอยู่ที่อื่น) ชี้ไปหา ทุกโปรเจกต์ใช้ชุดเดียวกัน
  • เข้มงวด ป้องกันบั๊กลึกลับ — npm มีจุดอ่อนเรื่อง phantom dependency ("dependency ผี") คือบั๊กที่เกิดเมื่อโค้ดเรียกใช้ package X ทั้งที่ไม่ได้ประกาศไว้ใน package.json — มันบังเอิญใช้ได้เพราะ X ถูกติดตั้งมาเป็น dependency ของ package อื่นใน node_modules พอวันหนึ่ง package อื่นเลิกใช้ X ผีก็หายไป โค้ดเราพังทันทีโดยไม่รู้สาเหตุ pnpm บล็อกเรื่องนี้ตั้งแต่แรก
  • เร็ว — ติดตั้งแบบขนาน (parallel — ทำหลายอย่างพร้อมกัน ไม่ใช่ทีละอัน)

💡 อุปมา: npm เหมือนคนที่ซื้อเกลือถุงใหม่ทุกครั้งที่ทำอาหารแต่ละเมนู ส่วน pnpm มีเกลือถุงกลางในครัว ทุกเมนูตักจากถุงเดียวกัน — ประหยัดและจัดการง่ายกว่า

yarn

bash
yarn add date-fns
yarn add -D eslint
yarn remove date-fns

yarn คือ Package Manager ที่ Facebook ร่วมกับ Google, Tilde, Exponent สร้างมาแก้ปัญหา npm รุ่นเก่า (ราว ๆ ปี 2016) มี 2 เวอร์ชันใหญ่: Classic (v1) ที่ยังเห็นใช้กันเยอะ และ Berry (v2+) ที่มีระบบใหม่ชื่อ Plug'n'Play (วิธีจัดการ package แบบไม่ต้องมีโฟลเดอร์ node_modules ก้อนใหญ่ — ขั้นสูง ข้ามได้) ปัจจุบัน pnpm ได้รับความนิยมแซงไปแล้ว แต่คุณยังเจอ yarn ในโปรเจกต์เก่า ๆ อยู่

bun

bash
bun add date-fns
bun install

bun ไม่ใช่แค่ package manager แต่เป็น runtime (ตัวรัน JavaScript) ทั้งตัว — เป็นคู่แข่งของ Node.js เลย จุดขายคือ "เร็วมาก" เพราะเขียนด้วย ภาษา Zig (ภาษาโปรแกรมมิ่งระดับล่างที่เน้นความเร็ว คล้าย C/C++ — ไม่ต้องรู้จักตอนนี้ก็ได้ แค่รู้ว่าเป็นเหตุผลที่ Bun เร็ว) ซึ่งรันได้เร็วกว่า JavaScript มาก จึงทำให้ Bun เร็วกว่า Node.js แต่ ณ ปี 2026 Bun ยังถือว่าใหม่กว่า และ ecosystem (ระบบนิเวศ — ชุดเครื่องมือ/ไลบรารี/ชุมชนที่ห้อมล้อม) ยังตามหลัง Node อยู่บ้าง

🚀 ขั้นสูง — ข้ามได้: Bun runtime แทน Node ได้ในงานหลายอย่าง แต่บาง library ที่ใช้ Node API เฉพาะ (node:fs, native modules) อาจยังไม่รองรับ 100% — โปรเจกต์ production แนะนำให้ตรวจก่อนว่า dependency ทั้งหมดใช้ได้

🛠️ Checkpoint Part 1: ถ้าเพื่อนส่งโปรเจกต์มาให้คุณ แล้วบอกว่า "ลง dependency ให้หน่อย" คุณต้องพิมพ์คำสั่งอะไร? — คำตอบ: npm install (หรือ pnpm install) เฉย ๆ โดยไม่ต้องระบุชื่อ package เพราะมันจะอ่านรายชื่อทั้งหมดจาก package.json มาให้เอง


Part 2: package.json — บัตรประจำตัวของโปรเจกต์

2.1 ไฟล์นี้คืออะไร

ทุกโปรเจกต์ JavaScript จะมีไฟล์ชื่อ package.json อยู่ที่ราก (root) ของโปรเจกต์ ไฟล์นี้คือ "บัตรประจำตัว + ใบสั่งงาน" ของโปรเจกต์ — มันบอกว่าโปรเจกต์นี้ชื่ออะไร เวอร์ชันอะไร ใช้ package อะไรบ้าง และมีคำสั่งอะไรให้รันได้บ้าง

สร้างไฟล์นี้ได้ด้วยคำสั่งนี้ใน terminal (ไม่ใช่ในไฟล์ .js):

bash
npm init -y

-y แปลว่า "yes ทุกคำถาม" → สร้างไฟล์ default ให้เลย โดยไม่ต้องตอบคำถามทีละข้อ

มาดูตัวอย่าง package.json แบบเต็ม พร้อมคำอธิบายทุกบรรทัด:

json
{
  "name": "my-app",
  "version": "1.0.0",
  "description": "เว็บแอปตัวอย่าง",
  "type": "module",
  "main": "dist/index.js",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "test": "vitest",
    "lint": "eslint .",
    "format": "prettier --write ."
  },
  "dependencies": {
    "react": "^19.0.0"
  },
  "devDependencies": {
    "vite": "^6.0.0",
    "typescript": "^5.8.0"
  },
  "engines": {
    "node": ">=20"
  },
  "packageManager": "pnpm@9.15.0"
}

อธิบายทีละช่อง:

  • name — ชื่อโปรเจกต์ ถ้าจะเผยแพร่ขึ้น npm ต้องเป็นชื่อที่ไม่ซ้ำกับใคร
  • version — เวอร์ชันปัจจุบัน ใช้รูปแบบ Semver (เดี๋ยวอธิบายข้อ 2.3)
  • description — คำอธิบายสั้น ๆ
  • type — บอกว่าโปรเจกต์ใช้ระบบโมดูลแบบไหน "module" = ใช้ import/export แบบสมัยใหม่ (ESM) ถ้าไม่ใส่จะเป็นแบบเก่า (CommonJS, ใช้ require)
  • main — ไฟล์หลักที่จะถูกโหลดเมื่อมีคนimportโปรเจกต์นี้ (สำคัญตอนทำ library)
  • scripts — ⭐ ช่องสำคัญที่สุด ดูข้อ 2.2
  • dependencies — package ที่โค้ดจริง "ต้องใช้ตอนรัน" เช่น react
  • devDependencies — package ที่ใช้ "ตอนพัฒนา" เท่านั้น เช่น eslint, ตัวรันเทสต์ — เวลา deploy ขึ้น production ไม่ต้องลงพวกนี้ก็ได้
  • engines — กำหนดว่าต้องใช้ Node.js เวอร์ชันขั้นต่ำเท่าไร
  • packageManager — ระบุว่าทีมตกลงใช้ package manager ตัวไหนเวอร์ชันอะไร (หมายเหตุ: ช่องนี้จะ "บังคับใช้จริง" ก็ต่อเมื่อเปิด corepack — เครื่องมือที่ติดมากับ Node — ด้วยคำสั่ง corepack enable ถ้าไม่เปิด จะเป็นแค่ "บันทึก" ให้คนอ่านเฉย ๆ)

2.2 scripts — ปุ่มลัดของโปรเจกต์

ช่อง scripts คือที่เก็บ "คำสั่งลัด" ลองดูบรรทัดนี้:

json
"dev": "vite"

มันแปลว่า "เวลาฉันพิมพ์ npm run dev ให้ไปรันคำสั่ง vite แทน" ประโยชน์คือ:

  1. คำสั่งจริงอาจยาวและซับซ้อน เช่น "build": "tsc && vite build --mode production" — แทนที่จะจำทั้งหมด เราจำแค่ npm run build
  2. ทุกคนในทีมใช้คำสั่งเดียวกัน ไม่ต้องเดา

วิธีเรียกใช้:

bash
npm run dev       # รัน script ชื่อ dev
npm run build     # รัน script ชื่อ build
npm test          # script ชื่อ test, start, stop เรียกได้โดยไม่ต้องมี "run"

⚠️ จุดที่งงบ่อย: script ชื่อ test, start, stop, restart เป็นชื่อพิเศษ เรียกได้เลยโดยไม่ต้องมีคำว่า run — แต่ script ชื่ออื่น (เช่น dev, lint) ต้องมี run เสมอ

2.3 Semver — ระบบเลขเวอร์ชัน

อ่าน ^19.0.0 ในไฟล์ dependencies แล้วงงไหม? ระบบตัวเลขนี้ชื่อ Semantic Versioning (Semver) — เป็นมาตรฐานสากลที่บอกว่าเวอร์ชันไหนปลอดภัยที่จะอัปเดต ทำความเข้าใจง่าย ๆ ดังนี้:

เลขเวอร์ชันมี 3 ส่วนคั่นด้วยจุด: MAJOR.MINOR.PATCH เช่น 19.2.5

  • MAJOR (19) — เพิ่มเมื่อมีการเปลี่ยนแปลงที่ "ทำให้โค้ดเก่าพัง" — เรียกว่า breaking change (การเปลี่ยนแปลงที่ทำให้โค้ดที่เคยใช้เวอร์ชันก่อนหน้าใช้ต่อไม่ได้)
  • MINOR (2) — เพิ่มเมื่อมีฟีเจอร์ใหม่ แต่โค้ดเก่ายังใช้ได้
  • PATCH (5) — เพิ่มเมื่อแก้บั๊กเล็ก ๆ

แล้วสัญลักษณ์ข้างหน้าเลขล่ะ?

text
^1.2.3   = ">=1.2.3 และ <2.0.0"   → อัปเดต MINOR/PATCH ได้ แต่ห้ามข้าม MAJOR (พบบ่อยสุด)
~1.2.3   = ">=1.2.3 และ <1.3.0"   → อัปเดตได้แค่ PATCH
1.2.3    = "เป๊ะ 1.2.3 เท่านั้น"   → ไม่ยอมให้ขยับเลย
*        = "เวอร์ชันอะไรก็ได้"     → อันตราย อย่าใช้

💡 อุปมา: ^ (caret) เหมือนบอกว่า "ปรับปรุงรถได้ เปลี่ยนยาง เปลี่ยนน้ำมันได้ แต่ห้ามเปลี่ยนเป็นรถคันใหม่ทั้งคัน" — ส่วน MAJOR คือการเปลี่ยนคันใหม่

2.4 package-lock.json — ทำไมห้ามลบ

นอกจาก package.json คุณจะเห็นไฟล์ชื่อ package-lock.json (หรือ pnpm-lock.yaml) เกิดขึ้นมาด้วย หลายคนเห็นแล้วอยากลบเพราะมันยาวและดูรก อย่าลบเด็ดขาด!

ปัญหาคือ package.json เขียนว่า ^19.0.0 ซึ่งแปลว่า "19 อะไรก็ได้" ถ้าคุณติดตั้งวันนี้ได้ 19.0.0 แต่เพื่อนติดตั้งพรุ่งนี้อาจได้ 19.1.3 — เวอร์ชันต่างกัน อาจเจอบั๊กคนละแบบ!

ไฟล์ lock แก้ปัญหานี้ — มันจดไว้เป๊ะ ๆ ว่า "ตอนติดตั้งครั้งล่าสุด ได้เวอร์ชันอะไรมาบ้าง ลึกลงไปทุกชั้น" เมื่อเพื่อนรัน npm install มันจะอ่านไฟล์ lock แล้วได้เวอร์ชันเดียวกันเป๊ะกับคุณ

⚠️ กฎเหล็ก: ต้อง commit ไฟล์ lock ขึ้น Git เสมอ — มันคือสิ่งที่รับประกันว่าทุกคนในทีม (และเซิร์ฟเวอร์) ใช้ของชุดเดียวกัน

2.5 node_modules — โฟลเดอร์ที่ห้าม commit

ตรงข้ามกับไฟล์ lock — โฟลเดอร์ node_modules/ คือที่เก็บโค้ดจริงของ package ทุกตัว มันมักจะใหญ่มาก (เป็นร้อย MB) เพราะฉะนั้น:

  • ห้าม commit node_modules/ ขึ้น Git — ใส่ไว้ในไฟล์ .gitignore
  • ✅ ใครได้โปรเจกต์ไป แค่รัน npm install มันจะสร้าง node_modules/ ขึ้นมาใหม่จากไฟล์ lock เอง

💡 สรุปความสัมพันธ์: package.json = "ใบสั่งของ" (commit) → package-lock.json = "ใบเสร็จละเอียด" (commit) → node_modules/ = "ของจริงในคลัง" (ไม่ commit, สร้างใหม่ได้เสมอ)

🛠️ Checkpoint Part 2: ไฟล์ไหนต้อง commit ขึ้น Git: package.json, package-lock.json, node_modules/? — คำตอบ: สองตัวแรก commit, node_modules/ ไม่ commit


Part 3: Bundler — เครื่องรวมและเพิ่มประสิทธิภาพโค้ด

3.1 ปัญหาที่ Bundler มาแก้

ในบทที่ 10 เราเรียนเรื่อง ES Modules — แตกโค้ดออกเป็นหลายไฟล์แล้ว import หากัน ดีต่อการอ่านมาก แต่พอเอาขึ้นเว็บจริงจะเจอปัญหา:

  1. ช้า — ถ้าโปรเจกต์มี 500 ไฟล์ เบราว์เซอร์ต้องยิง request ขอไฟล์ 500 ครั้ง เว็บโหลดอืดมาก
  2. เบราว์เซอร์เก่าไม่เข้าใจ — โค้ดสมัยใหม่ (หรือ TypeScript, JSX) เบราว์เซอร์รันตรง ๆ ไม่ได้
  3. ไฟล์ใหญ่เกินจำเป็น — โค้ดมีคอมเมนต์ มีช่องว่าง มีชื่อตัวแปรยาว ๆ ที่ผู้ใช้ไม่จำเป็นต้องดาวน์โหลด

Bundler คือเครื่องมือที่แก้ทั้ง 3 ปัญหานี้ หน้าที่ของมันคือ:

  • Bundle (รวม) — เอาไฟล์ร้อย ๆ ไฟล์มามัดรวมเหลือไม่กี่ไฟล์
  • Transpile (แปลง) — แปลงโค้ดสมัยใหม่/TypeScript/JSX ให้เป็น JavaScript ธรรมดาที่เบราว์เซอร์ทุกตัวเข้าใจ
  • Minify (บีบอัด) — ลบคอมเมนต์ ลบช่องว่าง ย่อชื่อตัวแปร → ไฟล์เล็กลงมาก
  • Tree-shaking (เขย่าต้นไม้) — ตัดโค้ดที่ไม่มีใครเรียกใช้ทิ้ง

💡 อุปมา: Bundler เหมือน "บริษัทขนของย้ายบ้าน" — แทนที่คุณจะขนของทีละชิ้น 500 เที่ยว เขาแพ็คทุกอย่างลงกล่องไม่กี่กล่อง บีบอัดให้แน่น และทิ้งของที่ไม่ใช้ออก แล้วขนรอบเดียวจบ

3.2 Vite — Bundler ที่แนะนำ

ในปี 2026 Bundler ที่เป็นมาตรฐานสำหรับสร้างเว็บแอปคือ Vite (อ่านว่า "วีท" แปลว่า "เร็ว" ในภาษาฝรั่งเศส) เริ่มต้นง่ายมาก:

bash
# สร้างโปรเจกต์ใหม่
npm create vite@latest my-app

# Vite จะถามว่าจะใช้ framework อะไร (React, Vue, vanilla...)
# เลือกแล้วทำตามนี้:
cd my-app
npm install
npm run dev

หลังรัน npm run dev Vite จะเปิดเซิร์ฟเวอร์พัฒนา (มักที่ http://localhost:5173) ให้คุณเปิดเว็บดูผลได้ทันที

💡 localhost หมายถึง "เครื่องคอมพิวเตอร์ของคุณเอง" — เปิด URL นี้ใน browser ได้เลย ไม่ต้องใช้อินเทอร์เน็ต

ไฟล์ตั้งค่าชื่อ vite.config.js:

javascript
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
    // plugins: เพิ่มความสามารถ เช่น รองรับ React
    plugins: [react()],

    // resolve.alias: สร้าง "ทางลัด" ของ path
    // ทำให้เขียน import "@/utils" แทน "../../../utils" ได้
    resolve: {
        alias: { "@": "/src" }
    },

    // server: ตั้งค่าเซิร์ฟเวอร์ตอน dev
    // port: 3000 ที่ตั้งไว้นี้จะแทนที่ค่า default (5173) — ถ้าไม่ตั้ง Vite จะใช้พอร์ต 5173
    server: { port: 3000 },

    // build: ตั้งค่าตอน build ขึ้น production
    build: { target: "es2022" }
});

3.3 ความลับของ Vite — ทำไมมันเร็ว

Vite ฉลาดตรงที่ แบ่งการทำงานเป็น 2 โหมด:

โหมด Dev (ตอนพัฒนา): Vite ไม่ bundle เลย! มันใช้ความสามารถ ES Modules ที่เบราว์เซอร์สมัยใหม่มีอยู่แล้ว ส่งไฟล์ตรง ๆ ทีละไฟล์ตามที่เบราว์เซอร์ขอ — ผลคือเซิร์ฟเวอร์ dev เปิดได้เร็วมาก ไม่ว่าโปรเจกต์จะใหญ่แค่ไหน และเมื่อคุณแก้โค้ด มันจะอัปเดตเฉพาะไฟล์ที่แก้ — เรียกว่า HMR (Hot Module Replacement — อัปเดตเฉพาะส่วนที่แก้โดยไม่รีโหลดทั้งหน้า) หน้าเว็บเปลี่ยนทันทีโดยไม่ต้องรีเฟรช state ของ component (เช่นค่าใน input หรือ scroll position) ยังอยู่เหมือนเดิม

โหมด Build (ตอนขึ้นจริง): Vite ใช้เครื่องมือชื่อ Rollup มา bundle + minify เต็มรูปแบบ ได้ไฟล์เล็กและเร็วสำหรับผู้ใช้จริง

💡 อุปมา: ตอนซ้อมละคร (dev) นักแสดงไม่ต้องแต่งหน้าแต่งตัวเต็มยศ — เปลี่ยนฉากเร็ว ลองผิดลองถูกได้ไว แต่ตอนแสดงจริง (build) ทุกอย่างถูกจัดเต็มเพื่อผู้ชม

3.4 เปรียบเทียบ Bundler

เครื่องมือเขียนด้วยเร็วตอน Devเร็วตอน Buildตั้งค่าเหมาะกับ
ViteJS + esbuild + Rollupเร็วมากดีง่ายเว็บแอปทั่วไป ⭐
RspackRustเร็วมากเร็วมากคล้าย Webpackโปรเจกต์ขนาดใหญ่/migrate จาก Webpack
esbuildGoเร็วมากเร็วมากระดับล่างใช้เป็นเครื่องมือเบื้องหลัง (Vite ก็ใช้)
RolldownRustเร็วมากเร็วมากคล้าย Rollupbundler รุ่นใหม่บน Oxc (ใช้ภายในโดย Vite 6+)
WebpackJSช้ากว่าพอใช้ซับซ้อนยังใช้แพร่หลายมาก โดยเฉพาะโปรเจกต์ที่มีอยู่
RollupJS-ดีกลาง ๆสร้าง library
TurbopackRustเร็วมากเร็วมากผูกกับ Next.jsใช้กับ Next.js — สถานะ dev server และ production build เปลี่ยนเร็วในแต่ละเวอร์ชัน Next.js ควรตรวจสอบสถานะล่าสุดที่ Next.js changelog ก่อนใช้จริง
Bun (bundler)Zigเร็วมากเร็วมากง่ายโปรเจกต์ที่ใช้ Bun runtime อยู่แล้ว

💡 หมายเหตุ: Webpack ไม่ใช่ "legacy" ในความหมายว่าเลิกใช้ — ยังเป็น bundler ที่ โปรเจกต์เว็บส่วนใหญ่ในโลกยังใช้อยู่ และยังพัฒนาต่อเนื่อง แค่เครื่องมือใหม่ ๆ (Vite/Rspack/esbuild) ที่เขียนด้วยภาษาเร็ว ๆ มาแซงเรื่องความเร็วเฉย ๆ — ถ้าคุณเข้าทีมแล้วเขาใช้ Webpack ก็ไม่ได้แปลว่าใช้ของเก่าตกยุค

คำแนะนำปี 2026:

  • เริ่มโปรเจกต์เว็บแอปใหม่ → Vite (เป็นมาตรฐานตอนนี้)
  • โปรเจกต์ขนาดใหญ่ที่ต้องการความเร็ว build สูง / migrate จาก Webpack แบบ drop-in → Rspack
  • ทำ library → Rollup หรือ tsup
  • ใช้ Next.js → ตาม default ของ Next (Turbopack สำหรับ dev, Webpack สำหรับ build — กำลังเปลี่ยนเป็น Turbopack ทั้งหมด)

🛠️ Checkpoint Part 3: ทำไม Vite ถึงเปิดเซิร์ฟเวอร์ dev ได้เร็วแม้โปรเจกต์ใหญ่? — เพราะตอน dev มันไม่ bundle ทั้งหมด แต่ส่งไฟล์ทีละไฟล์ตามที่เบราว์เซอร์ขอผ่าน ES Modules


Part 4: Linter — ผู้ตรวจจับโค้ดมีกลิ่น

4.1 Linter คืออะไร แก้ปัญหาอะไร

คำว่า "lint" เดิมแปลว่า "ขุยผ้า" — เศษเล็ก ๆ บนเสื้อผ้าที่ไม่ได้ทำให้เสื้อพัง แต่ดูไม่เรียบร้อย Linter ในการเขียนโปรแกรมคือเครื่องมือที่อ่านโค้ดของคุณ แล้วชี้จุดที่ "น่าจะมีปัญหา" — ทั้งที่โค้ดยังรันได้อยู่

ตัวอย่างที่ Linter จับได้:

javascript
let total = 0;
let totl = computePrice();   // ⚠️ ประกาศตัวแปร totl แต่ไม่เคยใช้ — พิมพ์ผิดรึเปล่า?

let user;
if (user = null) {           // ⚠️ ใช้ = (กำหนดค่า) แทน === (เปรียบเทียบ) — บั๊กแน่นอน!
    console.log("no user");
}

โค้ดด้านบนรันได้ ไม่ error แต่มันมี บั๊กซ่อนอยู่ Linter จะเตือนคุณก่อนที่บั๊กพวกนี้จะหลุดไปถึงผู้ใช้

💡 อุปมา: Linter เหมือน "บรรณาธิการ" ที่อ่านต้นฉบับก่อนตีพิมพ์ — เขาไม่ได้แต่งเรื่องให้ แต่ชี้ว่า "ตรงนี้สะกดผิด" "ประโยคนี้กำกวม" "ย่อหน้านี้ซ้ำกับข้างบน"

4.2 ESLint — มาตรฐานของวงการ

bash
npm install -D eslint
npx eslint --init
# คำสั่งนี้จะถามคำถามแล้วสร้างไฟล์ตั้งค่าให้

💡 npx คืออะไร?npx = คำสั่งที่มากับ npm สำหรับรัน package โดยไม่ต้องติดตั้งถาวร ใช้เมื่ออยากรันเครื่องมือครั้งเดียวหรือรันเครื่องมือที่ลงไว้ใน devDependencies ตัวอย่างเช่น npx eslint จะหา eslint ใน node_modules ของโปรเจกต์มารันให้อัตโนมัติ

ไฟล์ตั้งค่าสมัยใหม่ (ปี 2026) ชื่อ eslint.config.js ใช้รูปแบบ "flat config" (การตั้งค่าแบบแบน — เขียนกฎทั้งหมดเป็น array เดียว ไม่ซ้อนชั้น อ่านง่ายกว่าแบบเก่า):

javascript
import js from "@eslint/js";

export default [
    // เอากฎมาตรฐานที่ ESLint แนะนำมาใช้
    js.configs.recommended,

    // แล้วปรับแต่งกฎเพิ่มเอง
    {
        rules: {
            // "warn" = เตือน (เหลือง), "error" = ผิดร้ายแรง (แดง), "off" = ปิด
            "no-unused-vars": "warn",   // ห้ามมีตัวแปรที่ประกาศแล้วไม่ใช้
            "no-console": "warn",        // เตือนเมื่อมี console.log ค้างอยู่
            "eqeqeq": "error"            // บังคับใช้ === แทน ==
        }
    }
];

วิธีใช้:

bash
npx eslint .          # ตรวจทุกไฟล์ แล้วรายงานปัญหา
npx eslint . --fix    # ตรวจ + แก้ปัญหาที่แก้อัตโนมัติได้ให้เลย

4.3 Biome — ทางเลือกใหม่ที่เร็วกว่า

bash
npm install -D @biomejs/biome
npx biome init
npx biome check .

Biome เป็นเครื่องมือรุ่นใหม่ (มาแรงตั้งแต่ปี 2024) จุดเด่น:

  • All-in-one — เป็นทั้ง Linter และ Formatter ในตัวเดียว (ESLint เป็นแค่ Linter)
  • เร็วกว่า ESLint มาก — เพราะเขียนด้วยภาษา Rust (ภาษาโปรแกรมมิ่งที่เน้นความเร็ว คล้าย Zig ที่กล่าวถึงไปก่อนหน้านี้ ไม่ต้องรู้จักตอนนี้ก็ได้) ทีม Biome เผยแพร่เบนช์มาร์กของตัวเองว่าเร็วกว่า ESLint หลายเท่า แต่นี่เป็นตัวเลขจากผู้พัฒนาเอง ยังไม่มีแหล่งอิสระยืนยันชัดเจน ผลจริงขึ้นกับโปรเจกต์และ plugin ที่ใช้ — ควรตรวจสอบเบนช์มาร์กล่าสุดก่อนอ้างอิงตัวเลขจริงจัง
  • ตั้งค่าน้อย — มันมีจุดยืนของตัวเองชัดเจน (opinionated — มี "จุดยืน/ความเห็นชัดเจน" ว่าควรทำแบบไหน เลยไม่ต้องให้เราตั้งค่าเยอะ)

ถ้าเริ่มโปรเจกต์ใหม่ในปี 2026 Biome เป็นตัวเลือกที่น่าสนใจมาก แต่ ESLint ยังมี ecosystem (ระบบนิเวศ — ชุดเครื่องมือ/ไลบรารี/ชุมชนที่ห้อมล้อม) ของ plugin ที่ใหญ่กว่า

💡 Prettier vs Biome (formatter เทียบกัน): ถ้าโปรเจกต์เก่าใช้ Prettier อยู่แล้ว → อยู่กับ Prettier ต่อก็ดี เสถียรและมีคนใช้เยอะที่สุด ถ้าเริ่มใหม่และอยากได้ทั้ง lint + format ในตัวเดียวเร็ว ๆ → ลอง Biome ฟีเจอร์การ format ใกล้เคียง Prettier มากแล้ว (ยังต่างรายละเอียดเล็กน้อย เช่นการตัดบรรทัด)

🛠️ Checkpoint Part 4: โค้ด if (user = null) รันได้ไม่ error แล้วทำไม Linter ถึงเตือน? — เพราะ = คือการกำหนดค่า ไม่ใช่เปรียบเทียบ โปรแกรมเมอร์น่าจะตั้งใจเขียน === มากกว่า มันคือบั๊กที่ซ่อนอยู่


Part 5: Formatter — ผู้จัดระเบียบหน้าตาโค้ด

5.1 ปัญหาที่ Formatter มาแก้

Linter จับ "ตรรกะที่น่าจะผิด" ส่วน Formatter จัดการเรื่อง "หน้าตา" ล้วน ๆ — เว้นวรรคกี่ช่อง, ใช้ single quote หรือ double quote, ขึ้นบรรทัดใหม่ตรงไหน

ปัญหาคือ ถ้าทีมมี 5 คน แต่ละคนชอบสไตล์ต่างกัน:

javascript
// คนที่ 1 ชอบเขียนแบบนี้
const user = {name:'Ann',age:25}

// คนที่ 2 ชอบแบบนี้
const user = {
    name: "Ann",
    age: 25
};

โค้ดทำงานเหมือนกันเป๊ะ แต่หน้าตาต่างกัน เวลาดู Git diff จะเต็มไปด้วยการเปลี่ยนแปลงที่ไม่ใช่เนื้อหาจริง น่ารำคาญและทะเลาะกันได้

Formatter แก้ปัญหานี้โดย "จัดรูปแบบโค้ดให้เหมือนกันหมดอัตโนมัติ" — ไม่ว่าใครเขียน พอเซฟปุ๊บ โค้ดถูกจัดให้เป็นสไตล์เดียวกันทันที จบการถกเถียง

💡 อุปมา: Formatter เหมือน "กฎการแต่งกายของบริษัท" — ไม่ว่าพนักงานจะชอบเสื้อสีอะไร พอเข้าออฟฟิศทุกคนใส่ยูนิฟอร์มเดียวกัน ดูเป็นระเบียบและไม่ต้องเสียเวลาเลือก

5.2 Prettier

Prettier คือ Formatter ที่นิยมที่สุด:

bash
npm install -D prettier

ไฟล์ตั้งค่า .prettierrc.json:

json
{
    "semi": true,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "es5",
    "printWidth": 100
}

อธิบายแต่ละตัวเลือก:

  • semi — ใส่ semicolon ท้ายบรรทัดไหม (true = ใส่)
  • singleQuote — ใช้ ' แทน " ไหม
  • tabWidth — ย่อหน้ากี่ช่อง
  • trailingComma — ใส่ comma ท้ายรายการสุดท้ายไหม
  • printWidth — บรรทัดยาวเกินกี่ตัวอักษรให้ตัดขึ้นบรรทัดใหม่

วิธีใช้:

bash
npx prettier --write .    # จัดรูปแบบไฟล์ทั้งหมด (เขียนทับ)
npx prettier --check .    # แค่ตรวจว่าจัดรูปแบบถูกไหม ไม่แก้

แต่ในทางปฏิบัติ คุณจะตั้งให้ VS Code จัดรูปแบบอัตโนมัติทุกครั้งที่เซฟ — ติดตั้ง extension ชื่อ "Prettier" แล้วเปิด setting "Format On Save"

5.3 ให้ ESLint กับ Prettier อยู่ร่วมกัน

ปัญหา: ทั้ง ESLint และ Prettier ต่างก็มีกฎเรื่องหน้าตาโค้ด ถ้าใช้คู่กันมันอาจ "เถียงกัน" — ESLint อยากให้แบบหนึ่ง Prettier อยากให้อีกแบบ

วิธีแก้: ติดตั้ง eslint-config-prettier

bash
npm install -D eslint-config-prettier

มันจะ "ปิดกฎเรื่องหน้าตาทั้งหมดของ ESLint" — แล้วปล่อยให้ Prettier ดูแลเรื่องหน้าตาคนเดียว ส่วน ESLint ก็โฟกัสที่ตรรกะ แบ่งงานกันชัดเจน

💡 สรุปการแบ่งงาน: ESLint = "โค้ดนี้น่าจะมีบั๊กไหม?" (ตรรกะ) | Prettier = "โค้ดนี้หน้าตาเป็นระเบียบไหม?" (รูปแบบ)


Part 6: Test Runner — ผู้ทดสอบโค้ดอัตโนมัติ

6.1 ทำไมต้องเขียนเทสต์

สมมติคุณเขียนฟังก์ชันคำนวณราคา วันนี้มันทำงานถูก แต่อีกเดือนคุณกลับมาแก้โค้ดส่วนอื่น แล้วเผลอทำฟังก์ชันนี้พัง — คุณจะรู้ได้ยังไง? ถ้าต้องกดทดสอบเองทุกฟังก์ชันทุกครั้งที่แก้โค้ด คงไม่ไหว

Test (การทดสอบอัตโนมัติ) คือโค้ดที่คุณเขียนขึ้นมาเพื่อ "ตรวจสอบโค้ดอีกชุดหนึ่ง" หลักการคือ: เขียนครั้งเดียว รันได้ตลอดไป ทุกครั้งที่แก้โค้ด รันเทสต์ดู ถ้าผ่านหมด = มั่นใจว่าไม่ได้ทำอะไรพัง

💡 อุปมา: เทสต์เหมือน "สัญญาณกันขโมยในบ้าน" — ติดตั้งครั้งเดียว จากนั้นมันเฝ้าให้ตลอด ถ้ามีอะไรผิดปกติ (โค้ดพัง) มันร้องเตือนทันที คุณไม่ต้องเดินตรวจบ้านเองทุกคืน

6.2 Vitest

Vitest คือ Test Runner ที่จับคู่กับ Vite ได้ดีที่สุด เร็ว และใช้คำสั่งคล้าย Jest (ตัวที่นิยมมาก่อน)

bash
npm install -D vitest

สมมติเรามีไฟล์ math.js:

javascript
export function add(a, b) {
    return a + b;
}

เราเขียนไฟล์เทสต์ชื่อ math.test.js (ลงท้าย .test.js เพื่อให้ Vitest รู้ว่าเป็นไฟล์เทสต์):

javascript
import { describe, it, expect } from "vitest";
import { add } from "./math.js";

// describe = จัดกลุ่มเทสต์ที่เกี่ยวข้องกัน
describe("ฟังก์ชัน add", () => {

    // it = เทสต์หนึ่งกรณี อ่านเป็นประโยค "it adds two numbers" (= "มันบวกเลขสองตัว")
    it("บวกเลขสองตัวได้ถูกต้อง", () => {
        // expect(...).toBe(...) = "ฉันคาดหวังว่าผลลัพธ์จะเท่ากับ..."
        expect(add(2, 3)).toBe(5);
    });

    it("บวกเลขติดลบได้", () => {
        expect(add(-1, -1)).toBe(-2);
    });
});

โครงสร้างของเทสต์อ่านได้เป็นภาษาคน: "describe ฟังก์ชัน add — it บวกเลขสองตัวได้ — expect ผลของ add(2,3) toBe 5"

รันด้วย:

bash
npx vitest        # รันเทสต์ และเฝ้าดูการแก้ไขไฟล์ รันใหม่อัตโนมัติ
npx vitest run    # รันครั้งเดียวจบ (ใช้บน CI)

ถ้าผ่านจะขึ้นเขียว ✓ ถ้าไม่ผ่านจะขึ้นแดง ✗ พร้อมบอกว่าคาดหวังอะไร แต่ได้อะไรมา

6.3 ประเภทของเทสต์

ในงานจริงมีเทสต์หลายระดับ:

  • Unit test — ทดสอบชิ้นเล็กที่สุด เช่น ฟังก์ชันเดียว (อย่างตัวอย่าง add ข้างบน) เร็วและเขียนง่ายที่สุด
  • Integration test — ทดสอบว่าหลายส่วนทำงานร่วมกันถูกไหม
  • E2E test (End-to-End) — ทดสอบเหมือนผู้ใช้จริง เปิดเบราว์เซอร์ คลิกปุ่ม กรอกฟอร์ม เครื่องมือยอดนิยมคือ Playwright (E2E testing ด้วย Playwright เป็นเรื่องระดับกลาง-สูง — ตอนนี้แค่รู้ว่ามีเครื่องมือนี้อยู่)

🛠️ Checkpoint Part 6: ประโยชน์หลักของการเขียนเทสต์อัตโนมัติคืออะไร? — เมื่อแก้โค้ดในอนาคต รันเทสต์แล้วรู้ทันทีว่าทำของเดิมพังหรือเปล่า ไม่ต้องทดสอบมือเองทุกครั้ง


Part 7: Git Hooks — ยามเฝ้าประตูก่อน commit

7.1 ปัญหา

เครื่องมือทั้งหมดข้างบน (lint, format, test) จะมีประโยชน์ก็ต่อเมื่อ "มีคนรันมัน" แต่ในความเป็นจริง คนมักลืม หรือขี้เกียจ แล้วโค้ดที่มีปัญหาก็หลุดเข้า Git

ทางแก้คือ ทำให้มันรันอัตโนมัติ — ทุกครั้งที่มีคนพยายาม commit ให้ระบบรัน lint + format + test ก่อน ถ้าไม่ผ่าน = ห้าม commit นี่คือสิ่งที่ Git Hooks ทำได้

7.2 husky + lint-staged

bash
npm install -D husky lint-staged
npx husky init
  • husky — ตัวที่ทำให้ตั้ง Git Hooks ได้ง่าย
  • lint-staged — ตัวที่ฉลาดพอจะรันเครื่องมือ เฉพาะไฟล์ที่กำลังจะ commit (staged files) เท่านั้น ไม่ใช่ทั้งโปรเจกต์ — เร็วกว่ามาก

หลังรัน npx husky init จะมีไฟล์ .husky/pre-commit เกิดขึ้น แก้ให้เป็น:

bash
npx lint-staged

แล้วเพิ่มใน package.json:

json
{
  "lint-staged": {
    "*.{js,ts}": ["eslint --fix", "prettier --write"],
    "*.{json,md}": ["prettier --write"]
  }
}

อ่านว่า: "ไฟล์ .js และ .ts ที่กำลังจะ commit → รัน eslint แก้ + prettier จัดรูปแบบ | ไฟล์ .json และ .md → รัน prettier"

ผลคือ ทุกครั้งที่คุณพิมพ์ git commit husky จะแทรกตัวเข้ามารัน lint-staged ก่อน ถ้า ESLint เจอ error ที่แก้อัตโนมัติไม่ได้ → commit ถูกยกเลิก คุณต้องแก้ก่อน

💡 อุปมา: Git Hook คือ "เครื่องสแกนกระเป๋าที่สนามบิน" — ก่อนผ่านประตู (commit) ทุกคนต้องผ่านการสแกน ของต้องห้าม (โค้ดมีปัญหา) จะถูกสกัดตั้งแต่ต้นทาง


Part 8: CI/CD — โรงงานอัตโนมัติบนคลาวด์

🚀 โซนขั้นสูง — ข้ามได้

Part 8 (CI/CD) และ Part 9 (Monorepo) เป็นเรื่องของ "ทีม/โปรเจกต์ใหญ่" — มือใหม่ที่เพิ่งหัดเขียนคนเดียวยัง ไม่จำเป็นต้องใช้ อ่านผ่าน ๆ พอให้รู้ว่ามีอะไรบ้างก็พอ เดี๋ยวพอทำงานเป็นทีมจริงค่อยกลับมาอ่านละเอียด ไม่ต้องเครียดถ้ายังไม่เข้าใจตอนนี้

8.1 CI คืออะไร

Git Hooks ทำงานบนเครื่องคุณ แต่มันถูก "ข้าม" ได้ (เช่นพิมพ์ git commit --no-verify) และมันเช็คแค่เครื่องคุณคนเดียว

CI (Continuous Integration) คือการรันการตรวจสอบเดียวกันนี้ บนเซิร์ฟเวอร์กลาง ทุกครั้งที่มีคน push โค้ดขึ้น หรือเปิด Pull Request — ไม่มีใครข้ามได้ และทุกคนเห็นผลร่วมกัน

CD (Continuous Deployment) คือขั้นต่อไป — ถ้า CI ผ่านหมด ให้ deploy ขึ้น production อัตโนมัติเลย

8.2 GitHub Actions

ถ้าโค้ดคุณอยู่บน GitHub คุณใช้ GitHub Actions ทำ CI ได้ฟรี แค่สร้างไฟล์ .github/workflows/ci.yml:

yaml
name: CI
# on: รันเมื่อไร — ที่นี่คือเมื่อ push หรือเปิด pull request
on: [push, pull_request]

jobs:
  test:
    # runs-on: ใช้เครื่องเสมือนระบบอะไร
    runs-on: ubuntu-latest
    steps:
      # ดึงโค้ดจาก repo มาลงเครื่องเสมือน
      - uses: actions/checkout@v4

      # ติดตั้ง Node.js
      - uses: actions/setup-node@v4
        with:
          node-version: 22

      # ติดตั้ง pnpm (version ควรตรงกับ "packageManager" ใน package.json)
      - uses: pnpm/action-setup@v3
        with:
          version: 9

      # ติดตั้ง dependency แบบเป๊ะตาม lock file
      # --frozen-lockfile = "ห้ามแก้ไฟล์ lock" — ติดตั้งให้ตรงเป๊ะตามที่จดไว้ ไม่ขยับเวอร์ชันเลย (เหมาะกับ CI ที่ต้องการความแน่นอน)
      - run: pnpm install --frozen-lockfile

      # รันการตรวจสอบทีละขั้น ถ้าขั้นไหนพัง CI ทั้งหมดถือว่าพัง
      - run: pnpm lint
      - run: pnpm test
      - run: pnpm build

ทุกครั้งที่มีคน push GitHub จะสร้างเครื่องเสมือนสะอาด ๆ ขึ้นมา รันทุกขั้นตอนนี้ แล้วโชว์เครื่องหมาย ✓ เขียว หรือ ✗ แดง บนหน้า Pull Request

💡 อุปมา: CI เหมือน "ด่านตรวจคุณภาพในโรงงาน" — สินค้าทุกชิ้น (ทุก commit) ต้องผ่านด่านนี้ก่อนถึงมือลูกค้า ไม่มีข้อยกเว้น


Part 9: Monorepo — หลายโปรเจกต์ในที่เดียว

9.1 Monorepo คืออะไร

เมื่อโปรเจกต์โตขึ้น คุณอาจมีหลายส่วน: เว็บลูกค้า, เว็บแอดมิน, ไลบรารี UI ที่ใช้ร่วมกัน, ไลบรารี utility

ทางเลือกแรกคือแยกแต่ละส่วนเป็น Git repo ของตัวเอง (เรียก "polyrepo") แต่จะเจอปัญหา — แก้ไลบรารี UI ทีต้องเผยแพร่เวอร์ชันใหม่ แล้วไปอัปเดตทีละ repo ที่ใช้มัน วุ่นวายมาก

Monorepo คือการเก็บทุกส่วนไว้ใน Git repo เดียว แต่จัดเป็นโฟลเดอร์ย่อย ๆ:

text
my-monorepo/
├── package.json          (ตัวคุม workspace)
├── packages/
│   ├── ui/               (@my/ui — ไลบรารี UI)
│   └── utils/            (@my/utils — ไลบรารี utility)
└── apps/
    ├── web/              (เว็บลูกค้า — ใช้ @my/ui, @my/utils)
    └── admin/            (เว็บแอดมิน — ใช้ @my/ui)

ข้อดี: แก้ไลบรารี UI ปุ๊บ ทุกแอปเห็นผลทันที ไม่ต้องเผยแพร่เวอร์ชัน, เห็นโค้ดทั้งระบบในที่เดียว, แก้ข้ามส่วนใน commit เดียวได้

9.2 Workspace — ของฟรีที่มากับ Package Manager

npm, pnpm, yarn ทุกตัวมีฟีเจอร์ workspace อยู่แล้ว แค่ตั้งค่าใน package.json ราก:

json
{
  "workspaces": ["packages/*", "apps/*"]
}

(สำหรับ pnpm ใช้ไฟล์แยกชื่อ pnpm-workspace.yaml)

แค่นี้ package manager ก็เข้าใจว่าโฟลเดอร์เหล่านี้คือ package ที่ import หากันได้ — apps/web เขียน import { Button } from "@my/ui" ได้เลย

9.3 เครื่องมือ Monorepo ขั้นสูง

เมื่อ monorepo ใหญ่ขึ้น แค่ workspace ไม่พอ คุณต้องการตัวที่ทำงานฉลาด เช่น "build เฉพาะส่วนที่เปลี่ยน" หรือ "build แบบขนานและจำผลไว้ (cache)":

  • Turborepo — เซ็ตอัปง่าย เน้นการ cache และ build แบบขนาน เหมาะกับทีมส่วนใหญ่
  • Nx — ทรงพลังกว่า มีระบบ plugin และ dependency graph เหมาะกับองค์กรใหญ่

ตัวอย่าง turbo.json:

json
{
  "tasks": {
    "build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
    "test": { "dependsOn": ["build"] }
  }
}

"dependsOn": ["^build"] แปลว่า "ก่อน build ส่วนนี้ ให้ build ส่วนที่มันพึ่งพา (dependency) ให้เสร็จก่อน" — Turborepo จะจัดลำดับให้อัตโนมัติ


Part 10: Modern JavaScript — ฟีเจอร์ใหม่ของภาษา

JavaScript มีคณะกรรมการ (ชื่อ TC39) ที่ออกฟีเจอร์ใหม่ทุกปี เวอร์ชันตั้งชื่อตามปี เช่น ES2020, ES2021 ส่วนนี้รวมฟีเจอร์เด่นที่คุณควรรู้จัก พร้อมตัวอย่างใช้งานจริง

10.1 ES2020

ES2020 นำฟีเจอร์ที่ใช้บ่อยที่สุดในยุคใหม่มาให้ — ?. (optional chaining, เข้าถึง property ที่อาจไม่มีโดยไม่ error), ?? (nullish coalescing, ค่าสำรองเฉพาะ null/undefined), BigInt และ dynamic import:

javascript
// Optional chaining ?. — เข้าถึง property ที่อาจไม่มี โดยไม่ error
const city = user?.address?.city;
// ถ้า user หรือ address เป็น null/undefined → ได้ undefined แทนที่จะ error

// Nullish coalescing ?? — ใช้ค่าสำรองเฉพาะเมื่อเป็น null/undefined
const port = config.port ?? 3000;
// ต่างจาก || ตรงที่ ?? ไม่นับ 0 หรือ "" เป็น "ค่าว่าง"

// BigInt — เลขจำนวนเต็มที่ใหญ่เกินขีดจำกัดปกติ
const huge = 9007199254740991n;   // มี n ต่อท้าย

// Dynamic import() — โหลดโมดูลตอนรันไทม์ (โหลดเมื่อจำเป็น)
const module = await import("./heavy-feature.js");

10.2 ES2021

ES2021 เพิ่มความสะดวกหลายอย่าง — replaceAll (แทนที่ทุกตำแหน่ง), logical assignment (??=, ||= กำหนดค่าแบบมีเงื่อนไข), และ Promise.any (ได้ตัวแรกที่สำเร็จ):

javascript
// String.replaceAll — แทนที่ทุกตำแหน่ง (เดิม replace แทนแค่ตำแหน่งแรก)
"a-b-c".replaceAll("-", "+");   // "a+b+c"

// Logical assignment — กำหนดค่าแบบมีเงื่อนไข
count ??= 0;     // ถ้า count เป็น null/undefined ให้ตั้งเป็น 0
list ||= [];     // ถ้า list เป็นค่า falsy ให้ตั้งเป็น []

// Promise.any — ได้ Promise แรกที่สำเร็จ
const fastest = await Promise.any([fetchA(), fetchB(), fetchC()]);

10.3 ES2022

ES2022 มีของสำคัญหลายตัว — top-level await (ใช้ await นอกฟังก์ชันในไฟล์ module ได้), private field (#), Array.at() (เข้าถึงด้วย index ติดลบ), และ Error.cause (แนบสาเหตุต้นตอ):

javascript
// Top-level await — ใช้ await ได้นอกฟังก์ชัน (ในไฟล์โมดูล)
const data = await fetch("/api").then(r => r.json());

// Private fields ในคลาส — ขึ้นต้นด้วย #
class BankAccount {
    #balance = 0;              // ข้างนอกคลาสเข้าถึงไม่ได้เลย
    deposit(amount) { this.#balance += amount; }
}

// Array.at — เข้าถึงสมาชิกด้วยดัชนีติดลบได้
[10, 20, 30].at(-1);          // 30 (ตัวสุดท้าย)

// Object.hasOwn — เช็ค property อย่างปลอดภัย
Object.hasOwn(user, "name");  // true/false

// Error.cause — แนบสาเหตุต้นตอของ error
throw new Error("โหลดข้อมูลล้มเหลว", { cause: originalError });

10.4 ES2023

ES2023 เน้น array method แบบ immutable (ไม่แก้ของเดิม) — toSorted, toReversed คืน array ใหม่ ปลอดภัยกว่า sort/reverse เดิมที่แก้ต้นฉบับ และ findLast ค้นจากท้าย:

javascript
// Array immutable methods — เรียง/กลับ array โดยไม่แก้ของเดิม
const sorted = numbers.toSorted();      // ได้ array ใหม่ที่เรียงแล้ว
const reversed = numbers.toReversed();  // ได้ array ใหม่ที่กลับด้าน
// numbers ตัวเดิมไม่เปลี่ยน — ต่างจาก .sort() / .reverse() เดิมที่แก้ของเดิม

// findLast — ค้นหาจากท้าย array
[1, 2, 3, 4].findLast(n => n % 2 === 0);   // 4

10.5 ES2024

javascript
// Object.groupBy — จัดกลุ่ม array ตามเงื่อนไข
const people = [
    { name: "Ann", age: 25 },
    { name: "Bob", age: 17 },
    { name: "Cat", age: 30 }
];
const grouped = Object.groupBy(people, p => p.age >= 18 ? "adult" : "minor");
// { adult: [Ann, Cat], minor: [Bob] }

// Promise.withResolvers — สร้าง Promise พร้อมดึง resolve/reject ออกมา
const { promise, resolve, reject } = Promise.withResolvers();

10.6 ES2025

ES2025 รวมฟีเจอร์เด่นที่ทยอยมาจาก TC39 Stage 4 — แต่ฟีเจอร์ใหม่ ๆ มักมีการปรับเปลี่ยนสถานะ/ปีที่บรรจุจริงอยู่เรื่อย ๆ (บางตัวอาจเลื่อนไปอยู่ปีถัดไป) ควรตรวจสอบสถานะ TC39 ล่าสุดที่ tc39.es/process-document ก่อนอ้างอิงว่าฟีเจอร์ใดอยู่ในปีไหนแน่นอน:

javascript
// Set methods (เป็นมาตรฐานจริงใน ES2025) — ดำเนินการกับเซตแบบคณิตศาสตร์
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
a.intersection(b);         // Set {2, 3} — สมาชิกที่มีร่วมกัน
a.union(b);                // Set {1, 2, 3, 4} — รวมทั้งหมด
a.difference(b);           // Set {1} — มีใน a แต่ไม่มีใน b
a.symmetricDifference(b);  // Set {1, 4} — มีในฝั่งใดฝั่งหนึ่งแต่ไม่มีทั้งสอง (คล้าย XOR)
a.isSubsetOf(b);           // false
a.isDisjointFrom(b);       // false

// Iterator helpers — ใช้ map/filter/take กับ iterator (lazy ประหยัด memory)
function* naturals() { let n = 1; while (true) yield n++; }
const firstTenSquares = naturals()
    .map(n => n * n)
    .take(10)              // หยุดที่ 10 ตัว ไม่รันต่อ
    .toArray();
// [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
// ต่างจาก array methods ตรง iterator helpers ทำแบบ "lazy" — ใช้กับ infinite sequence ได้

// ⚠️ หมายเหตุ: Promise.try ยังเป็น TC39 Stage 4 ที่ทยอยรองรับในแต่ละ runtime — ตรวจสอบ
// Node.js/browser support ที่ caniuse.com หรือ node.green ก่อนใช้งานจริงโดยไม่มี polyfill
// Promise.try — ห่อ function (sync หรือ async) ให้คืน Promise และดัก error ในขั้นตอนแรกได้
// ประโยชน์หลัก: ป้องกัน error แบบ sync หลุดออกนอก promise chain
//
// ❌ แบบเก่า: throw แบบ sync "หนีออก" จาก .catch() ได้!
// function bad() {
//     if (someCondition) throw new Error("sync!");  // ไม่ถูก .catch() จับ
//     return fetch("/api");
// }
//
// ✅ แบบใหม่: Promise.try จับทั้ง sync throw และ async rejection
const result = await Promise.try(() => mayThrowSyncOrAsync());
// .catch() จะจับได้ทั้ง error แบบ sync และแบบ async

// RegExp.escape — escape ตัวอักษรพิเศษใน regex ปลอดภัยกว่าเขียนเอง
// ⚠️ หมายเหตุ: RegExp.escape เป็น TC39 Stage 4 แต่ไม่ได้อยู่ใน ES2025 — มุ่งเป้า ES2026
// ตรวจสอบ browser support ที่ MDN ก่อนใช้งานโดยไม่มี polyfill
const safe = new RegExp(RegExp.escape(userInput));

10.7 using / await using — ปลด resource อัตโนมัติ (ES2025)

using และ await using เป็นส่วนหนึ่งของ ES2025 (Explicit Resource Management proposal — ข้อเสนอมาตรฐานเรื่อง "จัดการปิด resource อัตโนมัติ") ทำงานคล้าย try-finally แต่สวยกว่า ใช้กับ resource ที่ต้องปิด (ไฟล์, connection, lock) เวอร์ชัน runtime ที่รองรับ (Node.js, เบราว์เซอร์, TypeScript) เปลี่ยนแปลงเร็ว — ตรวจสอบสถานะ/เวอร์ชันล่าสุดที่ caniuse.com หรือ node.green ก่อนใช้งานจริง แทนที่จะอิงตัวเลขเวอร์ชันใด ๆ ที่เขียนไว้ ณ ตอนนี้:

⚠️ ข้อควรระวังเรื่อง async context: await using ต้องอยู่ใน async function หรือ ES module ที่รองรับ top-level await — เหมือนกับการใช้ await ทั่วไป

javascript
// resource ที่จะใช้กับ using ต้องมี method [Symbol.dispose]() หรือ [Symbol.asyncDispose]()
// หมายเหตุ: realOpen และ realClose เป็นชื่อสมมติ — ในงานจริงจะเป็น Node.js fs.openSync() และ
// fs.closeSync() หรือ API จริงอื่น ๆ เช่น database connection
function openFile(path) {
    const handle = realOpen(path);
    return {
        read() { /* ... */ },
        [Symbol.dispose]() { realClose(handle); }   // ถูกเรียกอัตโนมัติตอนออก scope
    };
}

{
    using file = openFile("data.txt");
    file.read();
    // ออกจาก block — file ถูกปิดให้อัตโนมัติ ไม่ต้องเขียน try-finally
}

// async version — ต้องอยู่ใน async function หรือ ES module top level
// (เหมือนการใช้ await ทั่วไป)
{
    await using conn = await openDbConnection();
    await conn.query("...");
    // ออก scope — conn ถูก dispose อัตโนมัติแบบ async
}

💡 ใช้แทน pattern try { ... } finally { resource.close(); } ที่ลืมง่าย — เอนจิ้นเรียก dispose ให้เองเมื่อตัวแปรหลุด scope

⚠️ ข้อควรระวัง: ฟีเจอร์ใหม่ ๆ โดยเฉพาะ ES2024/2025 อาจยังไม่รองรับในเบราว์เซอร์/Node เก่า ก่อนใช้งานจริงควรตรวจสอบสถานะ/เวอร์ชันล่าสุดที่เว็บ caniuse.com หรือพึ่ง Bundler ที่ transpile ให้


Part 11: Type Checking ใน JavaScript ด้วย JSDoc

คุณอาจคิดว่าต้องเปลี่ยนไปใช้ TypeScript ถึงจะได้ระบบ type แต่จริง ๆ JavaScript ธรรมดาก็ได้ type checking ระดับหนึ่งผ่าน JSDoc — คอมเมนต์รูปแบบพิเศษ

javascript
/**
 * คำนวณราคารวมหลังหักส่วนลด
 * @param {number} price - ราคาตั้งต้น
 * @param {number} discount - ส่วนลดเป็นเปอร์เซ็นต์ (0-100)
 * @returns {number} ราคาสุทธิ
 */
function finalPrice(price, discount) {
    return price * (1 - discount / 100);
}

ถ้าเปิดไฟล์นี้ใน VS Code มันจะเข้าใจ type จากคอมเมนต์ — เตือนทันทีถ้าคุณส่ง string เข้าไปแทน number และจะขึ้นช่วยเหลือ (autocomplete) ให้ด้วย

ถ้าอยากให้เข้มขึ้น สร้างไฟล์ jsconfig.json:

json
{
    "compilerOptions": {
        "checkJs": true,
        "allowJs": true,
        "noEmit": true
    }
}

"checkJs": true สั่งให้ตรวจ type ของไฟล์ .js จริงจังเหมือนไฟล์ TypeScript — แต่ยังเขียน .js ธรรมดาอยู่ เหมาะกับโปรเจกต์เล็กที่ยังไม่อยากเปลี่ยนไป TypeScript เต็มตัว ส่วน "noEmit": true แปลว่า "แค่ตรวจ type อย่างเดียว ไม่ต้องสร้างไฟล์ output ใหม่" (เพราะเราต้องการแค่คำเตือน ไม่ได้ต้องการให้มัน compile ไฟล์ .js ออกมาอีกชุด)

💡 บทถัดไป (หมวด TypeScript) คุณจะได้ระบบ type ที่ทรงพลังกว่านี้มาก — JSDoc เป็นแค่จุดเริ่มต้น


Part 12: Lab — ลงมือสร้างโปรเจกต์มืออาชีพ

ถึงเวลาประกอบทุกอย่างเข้าด้วยกัน ทำตามทีละขั้น:

ขั้นที่ 1 — สร้างโปรเจกต์ด้วย Vite

bash
npm create vite@latest my-pro-app
# เลือก: Vanilla → JavaScript
cd my-pro-app
npm install

ขั้นที่ 2 — เพิ่มเครื่องมือคุณภาพ

bash
npm install -D eslint @eslint/js prettier eslint-config-prettier
npm install -D vitest

ขั้นที่ 3 — สร้างไฟล์ตั้งค่า สร้าง eslint.config.js และ .prettierrc.json ตามตัวอย่างใน Part 4 และ Part 5

ขั้นที่ 4 — เพิ่ม scripts ใน package.json

json
"scripts": {
    "dev": "vite",
    "build": "vite build",
    "test": "vitest run",
    "lint": "eslint .",
    "format": "prettier --write ."
}

ขั้นที่ 5 — เขียนโค้ดและเทสต์ สร้าง src/math.js กับ src/math.test.js ตามตัวอย่างใน Part 6 แล้วรัน:

bash
npm test      # ควรขึ้น ✓ เขียว
npm run lint  # ตรวจคุณภาพโค้ด

ขั้นที่ 6 (ท้าทาย) — เพิ่ม Git Hook ตั้ง husky + lint-staged ตาม Part 7 แล้วลอง commit ดู สังเกตว่ามันรัน lint ให้อัตโนมัติ

ถ้าทำครบ 6 ขั้น คุณได้ตั้งโปรเจกต์ในมาตรฐานเดียวกับที่บริษัทมืออาชีพใช้แล้ว 🎉


Part 13: Stack มาตรฐานปี 2026 (สรุปอ้างอิง)

นี่คือชุดเครื่องมือที่ทีมส่วนใหญ่ใช้กันในปี 2026:

text
Package Manager:  pnpm
Bundler:          Vite (เว็บแอป) / Rollup, tsup (library)
ภาษา:             TypeScript (โหมด strict)
Linter:           ESLint หรือ Biome
Formatter:        Prettier หรือ Biome
Test:             Vitest + Testing Library (unit) / Playwright (e2e)
Git Hook:         husky + lint-staged
CI/CD:            GitHub Actions
Monorepo:         pnpm workspaces + Turborepo

Part 14: คำสั่งที่ใช้บ่อย (Cheat Sheet)

bash
# สร้างโปรเจกต์ใหม่
pnpm create vite@latest my-app

# เพิ่ม dependency
pnpm add zod              # dependency ปกติ
pnpm add -D vitest        # devDependency

# รัน script
pnpm dev                  # เซิร์ฟเวอร์พัฒนา
pnpm build                # build ขึ้น production
pnpm test                 # รันเทสต์
pnpm lint                 # ตรวจคุณภาพโค้ด

# ดูแล dependency
pnpm outdated             # ดูตัวที่ล้าสมัย
pnpm update               # อัปเดต
pnpm audit                # ตรวจช่องโหว่ความปลอดภัย

Part 15: Checkpoint — ทบทวนความเข้าใจ

ลองตอบในใจก่อนดูเฉลย:

  1. Package Manager แก้ปัญหาอะไรให้เรา?
  2. ^1.2.3 ในเลขเวอร์ชันแปลว่าอะไร?
  3. dependencies ต่างจาก devDependencies ยังไง?
  4. ไฟล์ package-lock.json มีไว้ทำไม ทำไมห้ามลบ?
  5. โฟลเดอร์ node_modules/ ควร commit ขึ้น Git ไหม?
  6. ทำไม Vite ถึงเปิดเซิร์ฟเวอร์ dev ได้เร็ว?
  7. Linter กับ Formatter ต่างกันยังไง?
  8. husky + lint-staged ทำงานตอนไหน?
  9. CI (Continuous Integration) คืออะไร?
  10. Optional chaining ?. ช่วยอะไร?

เฉลยย่อ: (1) ดาวน์โหลด/จัดการ/อัปเดตไลบรารีของคนอื่นให้อัตโนมัติ (2) อัปเดต MINOR/PATCH ได้ แต่ห้ามข้าม MAJOR (3) dependencies ใช้ตอนรันจริง, devDependencies ใช้แค่ตอนพัฒนา (4) ล็อกเวอร์ชันให้ทุกคนได้ของเหมือนกันเป๊ะ (5) ไม่ commit สร้างใหม่ได้จาก install (6) ตอน dev ไม่ bundle ส่งไฟล์ทีละไฟล์ผ่าน ESM (7) Linter จับตรรกะที่น่าจะผิด, Formatter จัดหน้าตา (8) ทุกครั้งที่ git commit (9) รันการตรวจสอบบนเซิร์ฟเวอร์กลางทุกครั้งที่ push (10) เข้าถึง property ซ้อนที่อาจไม่มี โดยไม่ทำให้โปรแกรม error


Part 16: สรุปบทนี้

บทนี้พาคุณก้าวจาก "คนเขียนโค้ดเป็น" สู่ "โปรแกรมเมอร์ที่ทำงานแบบมืออาชีพ" — ความต่างไม่ได้อยู่ที่เขียนโค้ดเก่งกว่า แต่อยู่ที่ มีระบบและเครื่องมือมาช่วยรักษาคุณภาพ

ประเด็นสำคัญที่ต้องจำ:

  • Package Manager (pnpm) — จัดการไลบรารีของคนอื่น เข้าใจ package.json, semver, ไฟล์ lock
  • Bundler (Vite) — รวม + แปลง + บีบอัดโค้ดให้พร้อมขึ้นเว็บจริง
  • Linter (ESLint) — จับตรรกะที่น่าจะมีบั๊ก
  • Formatter (Prettier) — จัดหน้าตาโค้ดให้เป็นมาตรฐานเดียวกัน
  • Test Runner (Vitest) — ทดสอบโค้ดอัตโนมัติ มั่นใจว่าแก้แล้วไม่พัง
  • Git Hooks + CI — บังคับให้การตรวจสอบเกิดขึ้นอัตโนมัติ ไม่มีใครข้ามได้
  • Monorepo — จัดการหลายโปรเจกต์ในที่เดียว
  • Modern JS — รู้จักฟีเจอร์ใหม่ ES2020-2025 (Set methods, Iterator helpers, using/await using)

หัวใจของบทนี้: เครื่องมือเหล่านี้ไม่ได้มีไว้ทำให้ดูเท่ — มันมีไว้เพื่อให้คุณ (และทีม) เขียนโค้ดได้เร็วขึ้น มั่นใจขึ้น และทำพังน้อยลง ทุกเครื่องมือเกิดจากปัญหาจริงที่โปรแกรมเมอร์เจอ เมื่อคุณเข้าใจ "ปัญหา" ก่อน คุณจะเลือกใช้ "เครื่องมือ" ได้ถูกต้อง


🎓 จบหมวด JavaScript!

ยินดีด้วย! คุณเรียนจบหมวด JavaScript ทั้ง 14 บท (บทที่ 0-13) แล้ว — ตั้งแต่ syntax พื้นฐานในบทที่ 0 จนถึง modern tooling ในบทนี้ คุณมีพื้นฐานที่แข็งแรงพอจะต่อยอดไปทางไหนก็ได้

ขั้นต่อไปที่แนะนำ:

  • TypeScript — เพิ่มระบบ type ให้ JavaScript ลดบั๊กตั้งแต่ก่อนรันโค้ด (แนะนำให้เรียนต่อทันที)
  • React — ไลบรารีสร้าง UI ที่นิยมที่สุด
  • Angular — เฟรมเวิร์กครบวงจรสำหรับงานองค์กร
  • Node.js — เอา JavaScript ไปเขียนฝั่ง backend

← บทที่ 12 | สารบัญ