การ import ไอคอนแบบ dynamic เพียงครั้งเดียวทำเอา dev server พังบนเครื่อง Windows Subsystem for Linux 2 (WSL2) ที่มี RAM 16 GB โดยกระบวนการ vmmemWSL กินหน่วยความจำทั้งหมดที่มีจนทำให้หน้าต่าง Linux ทั้งหมดค้าง เหตุการณ์นี้เกิดขึ้นขณะรันโปรเจกต์ Next.js 16 ที่ใช้ Turbopack และเกิดขึ้นทั้งที่มีการตั้งค่าไฟล์ .wslconfig เพื่อจำกัดการใช้งาน RAM ของ VM ไว้แล้ว

ทำไมการ import เล็กๆ ถึงกลายเป็นปัญหาใหญ่ได้

นักพัฒนาพยายามดึงไอคอนมาใช้ในขณะรันไทม์ (runtime) ด้วย dynamic entry point ซึ่งการ import ดังกล่าวได้ดึงแพ็กเกจไอคอนที่มีโมดูลอยู่ประมาณ 9,000 โมดูลเข้ามา Turbopack ซึ่งเป็น bundler ที่เขียนด้วย Rust ที่ขับเคลื่อน dev server ของ Next.js 16 จะสร้าง module map แบบเต็มรูปแบบสำหรับทุกแพ็กเกจที่มันเข้าไปแตะ ในโหมด dev ตัว map นี้จะถูกเก็บไว้ในหน่วยความจำและอัปเดตทุกครั้งที่มีการเปลี่ยนไฟล์ การโหลดแพ็กเกจไอคอนทั้งหมดทำให้ Turbopack ต้องจัดสรร RAM จำนวนมหาศาล จนไปชนเพดานที่ตั้งไว้ในไฟล์ .wslconfig อย่างรวดเร็ว เมื่อถึงขีดจำกัด WSL VM ก็หยุดตอบสนอง การกด Ctrl + C ไม่ได้ผล และทางออกเดียวคือต้องบังคับปิดเครื่อง Windows (host) เท่านั้น

การ build สำหรับ production ของโค้ดชุดเดียวกันนี้สามารถทำงานได้สำเร็จ เพราะ bundler จะทำการ compile กราฟเพียงครั้งเดียว ส่งออก assets แล้วก็จบการทำงานไป แต่สำหรับ dev server นั้น จะต้องเก็บกราฟไว้ในหน่วยความจำเพื่อรองรับการทำ hot-reloading ดังนั้น การ build ผ่าน (green build) จึงไม่ได้การันตีว่าสภาพแวดล้อมในการพัฒนา (dev environment) จะสามารถรองรับรูปแบบการ import แบบเดียวกันนี้ได้

ผลกระทบในวงกว้าง

นักพัฒนาที่ใช้เครื่องมือสาย Linux ภายใน Windows ต้องพึ่งพา WSL2 เพื่อแยกการใช้งานทรัพยากรออกจากกัน เมื่อการ import เพียงครั้งเดียวทำให้หน่วยความจำของ VM หมดลง เครื่อง host ทั้งหมดอาจทำงานช้าลงหรือหยุดตอบสนอง ซึ่งส่งผลกระทบต่อ container หรือแอปพลิเคชันอื่นๆ ในเครื่องเดียวกันด้วย เหตุการณ์นี้ยังชี้ให้เห็นถึงความไม่สอดคล้องกันระหว่าง flag ปรับแต่งหน่วยความจำของ Node.js ทั่วไป กับสถาปัตยกรรมของ Turbopack เนื่องจาก Turbopack ทำงานบน Rust ไม่ใช่ V8 ดังนั้นการเพิ่ม flag --max-old-space-size ของ Node จึงไม่ช่วยลดการใช้ RAM ของมันเลย

สิ่งที่ผิดพลาดจริงๆ คืออะไร

  • Dynamic entry point: คำสั่ง import บอกให้ bundler ปฏิบัติต่อแพ็กเกจไอคอนทั้งหมดเสมือนเป็นโมดูลเดียวที่โหลดแบบ lazy-load แต่ Turbopack กลับตอบสนองด้วยการทำ eager parsing ทุกไฟล์เพื่อสร้าง module map
  • Dev-server graph: ต่างจากการ compile สำหรับ production ที่ทำเพียงครั้งเดียว dev server จะเก็บ dependency graph ทั้งหมดไว้ใน RAM เพื่อให้สามารถตอบสนองต่อการเปลี่ยนแปลงไฟล์ได้ทันที
  • Memory cap: ไฟล์ .wslconfig จำกัด RAM ของ VM ไว้เพียงบางส่วนของ RAM เครื่อง host เมื่อความต้องการของ Turbopack เกินขีดจำกัดนั้น VM จึงค้าง

วิธีแก้ไขที่ช่วยลดการใช้หน่วยความจำลงครึ่งหนึ่ง

นักพัฒนาได้ปรับเปลี่ยน 3 จุดที่ใช้งานได้จริง ซึ่งช่วยลดการใช้ RAM จาก 3.6 GB เหลือเพียง 1.87 GB และทำให้ระบบกลับมาเสถียรอีกครั้ง:

  1. เลิกใช้ dynamic entry points สำหรับ asset ขนาดเล็ก – ให้ import เฉพาะไอคอนที่จำเป็นต้องใช้เท่านั้น เช่น import { SearchIcon } from 'icon-pack/search' หากต้องการใช้เพียงไม่กี่อัน การใช้ inline SVGs จะยิ่งเบากว่าด้วยซ้ำ
  2. เปิดใช้งาน optimizePackageImports ใน next.config.ts – ตัวเลือกนี้จะบอกให้ Turbopack แก้ไขการ import ไปยังพาธไฟล์ที่เจาะจง แทนที่จะเป็น root ของแพ็กเกจ เพื่อป้องกันไม่ให้มันโหลดโครงสร้างต้นไม้ (tree) ของแพ็กเกจทั้งหมด
  3. อย่าพึ่งพา heap flags ของ Node – เนื่องจากหน่วยความจำของ Turbopack ถูกควบคุมโดย Rust runtime ดังนั้น --max-old-space-size จึงไม่มีผลต่อปัญหานี้

การคงขีดจำกัดใน .wslconfig ไว้ยังคงเป็นสิ่งที่แนะนำ เพราะการตั้งเพดานไว้แบบ hard cap อาจทำให้ VM หยุดชะงักแทนที่จะทำให้เครื่อง Windows host ค้าง ซึ่งจะช่วยให้คุณมีโอกาสเข้าไปจัดการก่อนที่ทุกอย่างจะหยุดทำงาน

สิ่งที่ควรเฝ้าระวังใน workflow ของคุณ

  • Memory dashboards: เครื่องมืออย่าง htop ภายใน WSL หรือ Windows Task Manager สามารถช่วยให้เห็นว่า vmmemWSL พุ่งสูงขึ้นเมื่อใด ควรตั้งการแจ้งเตือนหากการใช้งานเข้าใกล้ขีดจำกัดที่ตั้งไว้
  • Package size audits: ก่อนจะเพิ่ม library ใดๆ ให้ตรวจสอบว่ามันมาพร้อมกับโมดูลจำนวนเท่าใด แพ็กเกจไอคอนขนาดใหญ่, ชุดเครื่องมือ utility หรือ component libraries สามารถทำให้ dev graph บวมขึ้นได้อย่างเงียบๆ
  • Selective imports: เลือกใช้ named imports หรือพาธไฟล์โดยตรง แทนการใช้ wildcard หรือ dynamic imports โดยเฉพาะในสภาพแวดล้อม dev ที่ bundler ต้องเก็บทุกอย่างไว้ในหน่วยความจำ
  • Production vs. dev parity: ให้ถือว่าการ build สำหรับ production ที่สำเร็จเป็นขั้นตอนการตรวจสอบแยกต่างหาก ควรลองรัน dev server พร้อมกับตัวตรวจสอบหน่วยความจำเพื่อดักจับปัญหาที่อาจปรากฏขึ้นเฉพาะระหว่างการทำ hot-reloading เท่านั้น

บทสรุป

แพ็กเกจไอคอนที่ถูก import แบบ dynamic เพียงอันเดียวสามารถกิน RAM มากพอที่จะทำให้ dev server ของ Next.js บน WSL2 อัมพาตได้ แม้ว่าจะมีการจำกัดทรัพยากรของเครื่อง host ไว้แล้วก็ตาม การหลีกเลี่ยงการ import แบบกว้างๆ, การเปิดใช้งานการปรับแต่งการ import ในระดับแพ็กเกจ และการตรวจสอบการใช้หน่วยความจำ จะช่วยให้นักพัฒนาสามารถรักษาความลื่นไหลของสภาพแวดล้อม Linux-inside-Windows และหลีกเลี่ยงการต้องบังคับปิดเครื่องได้