• parcel
  • vite
  • js
  • esbuild
  • bundler
  • monorepo

จาก Parcel สู่ Vite: เรื่องราวสั้น ๆ ของการย้ายโค้ด 100K LOC

เราได้ย้ายโปรเจค frontend ทั้งสามของเราจาก Parcel ไปเป็น Vite, และกระบวนการก็... ราบรื่น

Gao
Gao
Founder

เรื่องก่อนหน้า

เรามีสามโปรเจค frontend ที่ Logto: ประสบการณ์การลงชื่อเข้าใช้, Console, และ live preview. โปรเจคเหล่านี้ทั้งหมดใช้ TypeScript, React, และ SASS modules; รวมแล้วมีประมาณ 100K บรรทัดของโค้ด

เราเคยชอบ Parcel สำหรับความง่ายและการตั้งค่าแบบ zero-config. ฉันยังจำได้ถึงวันที่ตกใจกับความง่ายในการตั้งค่าโปรเจคใหม่ด้วย Parcel. คุณเพียงแค่รัน parcel index.html แล้วปัง เหมือนมีเวทมนตร์.

ความง่ายของ Parcel เป็นเหตุผลหลักที่เรายึดติดกับมันมานาน แม้ว่าบางครั้งมันจะอารมณ์ไม่ดี เช่น:

  • Parcel บางครั้งไม่สามารถจับกลุ่มโปรเจคได้เพราะมันไม่พบบาง chunk files ที่มีอยู่จริง
  • มันต้องการการตั้งค่าที่ซับซ้อนเพื่อให้ทำงานกับ monorepo ของเราได้
  • มันไม่รองรับ MDX 3 ตามค่าเริ่มต้น ดังนั้นเราต้องสร้าง custom transformer ขึ้น
  • มันไม่รองรับ manual chunks (ณ ขณะที่เขียน ฟีเจอร์ manual chunks ยังอยู่ ในขั้นทดสอบเชิงทดลอง) ซึ่งโอเคในสถานการณ์ส่วนใหญ่ แต่บางครั้งคุณต้องการมัน

ทำไมเราถึงตัดสินใจย้ายไปที่อื่น?

  1. เราติดอยู่กับ Parcel 2.9.3 ซึ่งปล่อยเมื่อเดือนมิถุนายน 2023 ทุกครั้งที่มีการปล่อยเวอร์ชันใหม่หลังจากนั้น เราพยายามอัพเกรดแต่มันมักจะล้มเหลวด้วยข้อผิดพลาดในการสร้าง
  2. เวอร์ชันล่าสุดของ Parcel คือ 2.12.0, ปล่อยในเดือนกุมภาพันธ์ 2024. แม้ว่าจะมีการ commit แทบทุกวัน แต่ไม่มีการปล่อยเวอร์ชันใหม่ตั้งแต่นั้น

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

คำขออัพเกรด Parcel

เชื่อฉันเถอะ, เราพยายามแล้ว

ทำไมต้อง Vite?

เรารู้จัก Vite จาก Vitest. เมื่อหลายเดือนก่อน, เราเบื่อกับการรองรับ ESM ของ Jest (ใน testing) และต้องการลองสิ่งใหม่. Vitest ชนะใจของเราด้วยการรองรับ ESM แบบเนทีฟและความเข้ากันได้กับ Jest. มันมีประสบการณ์สำหรับนักพัฒนาที่น่าทึ่ง และขับเคลื่อนโดย Vite.

สถานะปัจจุบัน

คุณอาจมีการตั้งค่าที่ต่างกันในโปรเจคของคุณ แต่ปกติแล้วคุณจะพบว่า plugin replacements ในขณะที่ระบบนิเวศของ Vite กำลังบานสะพรั่ง นี่คือการตั้งค่าของเราในขณะที่ย้าย:

  • Monorepo: เราใช้ PNPM (v9) workspaces เพื่อจัดการ monorepo ของเรา
  • Module: เราใช้ ESM modules สำหรับทุกโปรเจคของเรา
  • TypeScript: เราใช้ TypeScript (v5.5.3) สำหรับทุกโปรเจคพร้อม path aliases
  • React: เราใช้ React (v18.3.1) สำหรับทุกโปรเจค frontend
  • Styling: เราใช้ SASS modules สำหรับ styling
  • SVG: เราใช้ SVGs เป็น React components
  • MDX: เรามี MDX ที่มีการรองรับ GitHub Flavored Markdown และ Mermaid
  • Lazy loading: เราจำเป็นต้อง lazy load บางหน้าและ components ของเรา
  • Compression: เราผลิต assets ที่ถูกบีบอัด (gzip และ brotli) สำหรับการสร้าง production

การย้าย

เราเริ่มการย้ายโดยการสร้างโปรเจค Vite ใหม่และทดลองเล่นกับมันเพื่อดูว่ามันทำงานอย่างไร. กระบวนการราบรื่นและการย้ายจริงใช้เวลาเพียงไม่กี่วัน.

รองรับอัตโนมัติ

Vite มีการรองรับอัตโนมัติสำหรับ monorepo, ESM, TypeScript, React, และ SASS. เราเพียงแค่ต้องติดตั้ง plugins และการตั้งค่าที่จำเป็น.

การใช้นามแฝงสำหรับ path

Vite มีการรองรับในตัวสำหรับการใช้นามแฝง เช่นใน tsconfig.json ของเรา:

เราจำเป็นต้องเพิ่มการตั้งค่าเดียวกันใน vite.config.ts ของเรา:

การแทนที่ path ควรเป็น path แบบสมบูรณ์ ในขณะที่มันสัมพันธ์กับรากของโปรเจค. หรือคุณสามารถใช้ plugin vite-tsconfig-paths เพื่ออ่านการใช้นามแฝงจาก tsconfig.json.

React Fast Refresh และ HMR

แม้ว่า Vite จะมีการรองรับในตัวสำหรับ HMR แต่จำเป็นต้องติดตั้ง plugin เพื่อเปิดใช้งาน React Fast Refresh. เราใช้ plugin @vitejs/plugin-react ที่มีการรองรับฟีเจอร์ของ React เช่น Fast Refresh:

SVG เป็น React component

เราใช้ plugin vite-plugin-svgr เพื่อแปลง SVGs เป็น React components. มันง่ายเหมือนการเพิ่ม plugin ลงใน config ของ Vite:

อย่างไรก็ตาม เรายังไม่ได้ระบุเงื่อนไขในการแปลง SVGs เป็น React components ดังนั้นการนำเข้าสินค้าทั้งหมดจะถูกแปลง. plugin มีการตั้งค่าที่ดีกว่า: แปลงเฉพาะ SVGs ที่นำเข้าด้วยนามสกุล .svg?react. เราได้อัพเดทการนำเข้าสินค้าตามนั้น.

SASS modules

แม้ว่า Vite จะมีการรองรับในตัวสำหรับ SASS modules แต่มีสิ่งหนึ่งที่เราต้องใส่ใจ: วิธีการจัดรูปแบบชื่อ class. มันอาจเป็นปัญหาสำหรับผู้ใช้และการทดสอบ integration ของเราหากชื่อ class ไม่จัดเรียงแบบเดียวกัน. การตั้งค่าเพียงบรรทัดเดียวใน vite.config.ts สามารถแก้ปัญหาได้:

อย่างไรก็ตาม Parcel และ Vite มีรูปแบบที่ต่างกันในการนำเข้าไฟล์ SASS:

การใช้ไวยากรณ์ * as นั้นทำงานใน Vite แต่จะทำให้เสียชื่อ class แบบ modularized เมื่อคุณใช้คีย์ในการเข้าถึงวัตถุ styles. เช่น:

การรองรับ MDX

เนื่องจาก Vite ใช้ Rollup เป็นสิ่งสำคัญ เราสามารถใช้ plugin อย่างเป็นทางการ @mdx-js/rollup เพื่อรองรับ MDX รวมถึง plugins ของมันด้วย

plugin remarkGfm ใช้ในการรองรับ GitHub Flavored Markdown, และ plugin rehypeMdxCodeProps ใช้ในการผ่าน props ไปยัง code blocks ในไฟล์ MDX เหมือนกับ Docusaurus ทำ.

การรองรับ Mermaid ใน MDX

เราต้องการใช้ Mermaid diagrams ในไฟล์ MDX ของเราเช่นเดียวกับภาษาการเขียนโปรแกรมอื่น ๆ การใช้งานควรเป็นเรื่องง่ายเหมือน code blocks อื่นๆ:

ควรถูกแสดงเป็น:

เนื่องจากแอปของเรารองรับ theme แสงและมืด เราเขียนโค้ดเล็กน้อยให้ Mermaid diagrams ทำงานกับ theme มืด. เราสร้าง React component:

useTheme เป็น custom hook เพื่อดึง theme ปัจจุบันจาก context. ไลบราลี่ mermaid ถูกนำเข้าแบบอะซิงค์เพื่อลดโหลดหน้าเริ่มต้น.

สำหรับ code block ในไฟล์ MDX เรามี component แบบรวมที่สุดในการทำงาน:

สุดท้ายเรากำหนด MDX provider โดย:

การโหลดแบบ lazy

นี่ไม่ใช่เรื่องเฉพาะของ Vite มันยังคงเป็นสิ่งที่คุ้มค่าที่จะกล่าวถึงเพราะเราอัพเดทหน้าเพจของเราให้ใช้การโหลดแบบ lazy ในระหว่างการย้ายและไม่มีอะไรพังหลังจากนั้น

React มีฟังก์ชัน React.lazy ในตัวเพื่อโหลด components แบบ lazy. อย่างไรก็ตาม มันอาจก่อให้เกิดปัญหาบางอย่างเมื่อคุณกำลังทำงานอย่างรวดเร็ว. เราสร้างไลบราลี่เล็ก ๆ ชื่อ react-safe-lazy เพื่อแก้ปัญหา. มันเป็นตัวแทนโดยตรงสำหรับ React.lazy และคำอธิบายอย่างละเอียดสามารถพบได้ใน โพสต์บล็อกนี้.

การบีบอัด

มี plugin ที่เรียบง่ายชื่อ vite-plugin-compression เพื่อผลิต assets ที่ถูกบีบอัด. มันรองรับทั้งการบีบอัดแบบ gzip และ brotli. การตั้งค่าง่ายมาก:

Manual chunks

ฟีเจอร์ที่ดีของ Vite (หรือ Rollup ที่อยู่ภายใต้) คือ manual chunks. แม้ว่า React.lazy ใช้สำหรับการโหลด components แบบ lazy, เราสามารถควบคุม chunks ได้มากขึ้นโดยการระบุ manual chunks เพื่อตัดสินใจว่า components หรือ modules ไหนควรถูกจับกลุ่มเข้าด้วยกัน.

ตัวอย่างเช่น เราสามารถใช้ vite-bundle-visualizer เพื่อวิเคราะห์ขนาดของ bundle และ dependencies. จากนั้นเราสามารถเขียนฟังก์ชันที่เหมาะสมในการแยก chunks:

เซิร์ฟเวอร์สำหรับการพัฒนา

แตกต่างจากการสร้าง production, Vite จะไม่จับกลุ่มซอร์สโค้ดของคุณในโหมด dev (รวมถึง dependencies ที่เชื่อมโยงได้ใน monorepo เดียวกัน) และปฏิบัติต่อทุก module ตามไฟล์. สำหรับเรา, เบราว์เซอร์จะโหลด modules เป็นร้อย ๆ ครั้งแรก, ซึ่งดูบ้าแต่จริง ๆ แล้วมันไม่เป็นปัญหาในกรณีส่วนใหญ่. คุณสามารถดูการสนทนาได้ ที่นี่.

หากมันเป็นปัญหาสำหรับคุณ, ตัวเลือกอื่น ๆ แต่ไม่สมบูรณ์คือการระบุ dependencies ที่เชื่อมโยงในตัวเลือก optimizeDeps ของ vite.config.ts:

นี้จะ "pre-bundle" dependencies ที่เชื่อมโยงและทำให้เซิร์ฟเวอร์ dev เร็วขึ้น. จุดบกพร่องคือ HMR อาจไม่ทำงานตามที่ต้องการสำหรับ dependencies ที่เชื่อมโยง.

นอกจากนี้ เราใช้ proxy ที่ให้บริการไฟล์ static ใน production และส่งคำขอไปยังเซิร์ฟเวอร์ Vitest ในการพัฒนา. เรามีการตั้งค่าพอร์ตบางอย่างเพื่อหลีกเลี่ยงความขัดแย้งและมันก็ตั้งค่าได้ง่ายใน vite.config.ts:

ตัวแปรสภาพแวดล้อม

แตกต่างจาก Parcel, Vite ใช้แนวทางที่ทันสมัยในการจัดการตัวแปรสภาพแวดล้อมโดยใช้ import.meta.env. มันจะโหลดไฟล์ .env อัตโนมัติและแทนที่ตัวแปรในโค้ด. อย่างไรก็ตาม, มันต้องการให้ตัวแปรสภาพแวดล้อมทั้งหมดมีคำนำหน้า VITE_ (กำหนดได้).

ในขณะที่เราใช้ Parcel, มันแค่แทนที่ตัวแปร process.env โดยไม่ตรวจสอบคำนำหน้า. ดังนั้นเรามีวิธีแก้ไขโดยใช้ฟิลด์ define เพื่อทำให้การย้ายง่ายขึ้น:

นี้ทำให้เราสามารถเพิ่มคำนำหน้าไปที่ตัวแปรสภาพแวดล้อมอย่างค่อยเป็นค่อยไปและลบฟิลด์ define ออก.

สรุป

แค่นี้ล่ะ! เราได้ย้ายโปรเจค frontend ทั้งสามจาก Parcel มาเป็น Vite สำเร็จแล้วและหวังว่าเรื่องสั้นนี้จะช่วยคุณในการย้ายของคุณ. นี่คือสิ่งที่การตั้งค่าในตอนท้ายดูเหมือน: