สำรวจสถาปัตยกรรมปลั๊กอินของ Vite และเรียนรู้วิธีสร้างปลั๊กอินแบบกำหนดเองเพื่อปรับปรุงเวิร์กโฟลว์การพัฒนาของคุณ พร้อมตัวอย่างที่ใช้งานได้จริงสำหรับทุกคน
ไขปริศนาสถาปัตยกรรมปลั๊กอินของ Vite: คู่มือการสร้างปลั๊กอินแบบกำหนดเองฉบับสากล
Vite เครื่องมือบิวด์ที่รวดเร็วปานสายฟ้า ได้ปฏิวัติการพัฒนาฟรอนต์เอนด์ ความเร็วและความเรียบง่ายของมันส่วนใหญ่มาจากสถาปัตยกรรมปลั๊กอินที่ทรงพลัง สถาปัตยกรรมนี้ช่วยให้นักพัฒนาสามารถขยายฟังก์ชันการทำงานของ Vite และปรับแต่งให้เข้ากับความต้องการเฉพาะของโปรเจกต์ได้ คู่มือนี้จะพาคุณสำรวจระบบปลั๊กอินของ Vite อย่างละเอียด เพื่อให้คุณสามารถสร้างปลั๊กอินแบบกำหนดเองและเพิ่มประสิทธิภาพเวิร์กโฟลว์การพัฒนาของคุณได้
ทำความเข้าใจหลักการสำคัญของ Vite
ก่อนที่จะลงมือสร้างปลั๊กอิน สิ่งสำคัญคือต้องเข้าใจหลักการพื้นฐานของ Vite:
- On-Demand Compilation: Vite จะคอมไพล์โค้ดก็ต่อเมื่อเบราว์เซอร์ร้องขอเท่านั้น ซึ่งช่วยลดระยะเวลาในการเริ่มต้นได้อย่างมาก
- Native ESM: Vite ใช้ประโยชน์จาก ECMAScript modules (ESM) แบบเนทีฟสำหรับการพัฒนา ทำให้ไม่จำเป็นต้องมีการบันเดิลในระหว่างการพัฒนา
- Rollup-Based Production Build: สำหรับการบิวด์เพื่อใช้งานจริง Vite จะใช้ Rollup ซึ่งเป็นบันเดลเลอร์ที่ได้รับการปรับให้เหมาะสมที่สุด เพื่อสร้างโค้ดที่มีประสิทธิภาพและพร้อมสำหรับโปรดักชัน
บทบาทของปลั๊กอินในระบบนิเวศของ Vite
สถาปัตยกรรมปลั๊กอินของ Vite ถูกออกแบบมาให้มีความยืดหยุ่นสูง ปลั๊กอินสามารถ:
- แปลงโค้ด (เช่น การคอมไพล์ TypeScript, การเพิ่ม preprocessors)
- ให้บริการไฟล์แบบกำหนดเอง (เช่น การจัดการ static assets, การสร้างโมดูลเสมือน)
- แก้ไขกระบวนการบิวด์ (เช่น การปรับขนาดรูปภาพ, การสร้าง service workers)
- ขยายความสามารถของ Vite CLI (เช่น การเพิ่มคำสั่งแบบกำหนดเอง)
ปลั๊กอินคือกุญแจสำคัญในการปรับ Vite ให้เข้ากับความต้องการของโปรเจกต์ต่างๆ ตั้งแต่การปรับเปลี่ยนเล็กน้อยไปจนถึงการผสานรวมที่ซับซ้อน
เจาะลึกสถาปัตยกรรมปลั๊กอินของ Vite
ปลั๊กอินของ Vite โดยพื้นฐานแล้วคือ JavaScript object ที่มีคุณสมบัติเฉพาะซึ่งกำหนดการทำงานของมัน มาดูองค์ประกอบสำคัญกัน:
การกำหนดค่าปลั๊กอิน
ไฟล์ `vite.config.js` (หรือ `vite.config.ts`) คือที่ที่คุณกำหนดค่าโปรเจกต์ Vite ของคุณ รวมถึงการระบุว่าจะใช้ปลั๊กอินใดบ้าง ตัวเลือก `plugins` จะรับค่าเป็นอาร์เรย์ของอ็อบเจกต์ปลั๊กอิน หรือฟังก์ชันที่คืนค่าเป็นอ็อบเจกต์ปลั๊กอิน
// vite.config.js
import myPlugin from './my-plugin';
export default {
plugins: [
myPlugin(), // เรียกใช้ฟังก์ชันปลั๊กอินเพื่อสร้าง instance ของปลั๊กอิน
],
};
คุณสมบัติของอ็อบเจกต์ปลั๊กอิน
อ็อบเจกต์ปลั๊กอินของ Vite สามารถมีคุณสมบัติหลายอย่างที่กำหนดการทำงานของมันในแต่ละช่วงของกระบวนการบิวด์ นี่คือรายละเอียดของคุณสมบัติที่พบบ่อยที่สุด:
- name: ชื่อเฉพาะสำหรับปลั๊กอิน ซึ่งเป็นสิ่งจำเป็นและช่วยในการดีบักและแก้ไขข้อขัดแย้ง ตัวอย่าง: `'my-custom-plugin'`
- enforce: กำหนดลำดับการทำงานของปลั๊กอิน ค่าที่เป็นไปได้คือ `'pre'` (ทำงานก่อนปลั๊กอินหลัก), `'normal'` (ค่าเริ่มต้น), และ `'post'` (ทำงานหลังปลั๊กอินหลัก) ตัวอย่าง: `'pre'`
- config: อนุญาตให้แก้ไขอ็อบเจกต์การกำหนดค่าของ Vite โดยจะได้รับ user config และ environment (mode และ command) ตัวอย่าง: `config: (config, { mode, command }) => { ... }`
- configResolved: ถูกเรียกหลังจากที่ Vite config ได้รับการแก้ไขอย่างสมบูรณ์แล้ว มีประโยชน์สำหรับการเข้าถึงอ็อบเจกต์ config สุดท้าย ตัวอย่าง: `configResolved(config) { ... }`
- configureServer: ให้สิทธิ์เข้าถึง instance ของ development server (คล้าย Connect/Express) มีประโยชน์สำหรับการเพิ่ม middleware แบบกำหนดเองหรือแก้ไขการทำงานของเซิร์ฟเวอร์ ตัวอย่าง: `configureServer(server) { ... }`
- transformIndexHtml: อนุญาตให้แปลงไฟล์ `index.html` มีประโยชน์สำหรับการแทรกสคริปต์, สไตล์, หรือ meta tags ตัวอย่าง: `transformIndexHtml(html) { ... }`
- resolveId: อนุญาตให้ดักจับและแก้ไขการ resolve โมดูล มีประโยชน์สำหรับตรรกะการ resolve โมดูลแบบกำหนดเอง ตัวอย่าง: `resolveId(source, importer) { ... }`
- load: อนุญาตให้โหลดโมดูลแบบกำหนดเองหรือแก้ไขเนื้อหาของโมดูลที่มีอยู่ มีประโยชน์สำหรับโมดูลเสมือนหรือ loader แบบกำหนดเอง ตัวอย่าง: `load(id) { ... }`
- transform: แปลงซอร์สโค้ดของโมดูล คล้ายกับปลั๊กอิน Babel หรือ PostCSS ตัวอย่าง: `transform(code, id) { ... }`
- buildStart: ถูกเรียกเมื่อเริ่มต้นกระบวนการบิวด์ ตัวอย่าง: `buildStart() { ... }`
- buildEnd: ถูกเรียกหลังจากกระบวนการบิวด์เสร็จสิ้น ตัวอย่าง: `buildEnd() { ... }`
- closeBundle: ถูกเรียกหลังจากที่ bundle ถูกเขียนลงดิสก์แล้ว ตัวอย่าง: `closeBundle() { ... }`
- writeBundle: ถูกเรียกก่อนที่จะเขียน bundle ลงดิสก์ เพื่อให้สามารถแก้ไขได้ ตัวอย่าง: `writeBundle(options, bundle) { ... }`
- renderError: อนุญาตให้เรนเดอร์หน้าข้อผิดพลาดแบบกำหนดเองระหว่างการพัฒนา ตัวอย่าง: `renderError(error, req, res) { ... }`
- handleHotUpdate: อนุญาตให้ควบคุม HMR ได้อย่างละเอียด ตัวอย่าง: `handleHotUpdate({ file, server }) { ... }`
Plugin Hooks และลำดับการทำงาน
ปลั๊กอินของ Vite ทำงานผ่านชุดของ hooks ที่ถูกเรียกในขั้นตอนต่างๆ ของกระบวนการบิวด์ การทำความเข้าใจลำดับการทำงานของ hooks เหล่านี้เป็นสิ่งสำคัญในการเขียนปลั๊กอินที่มีประสิทธิภาพ
- config: แก้ไข Vite config
- configResolved: เข้าถึง config ที่แก้ไขแล้ว
- configureServer: แก้ไข dev server (เฉพาะในโหมด development)
- transformIndexHtml: แปลงไฟล์ `index.html`
- buildStart: เริ่มต้นกระบวนการบิวด์
- resolveId: Resolve รหัสโมดูล
- load: โหลดเนื้อหาโมดูล
- transform: แปลงโค้ดโมดูล
- handleHotUpdate: จัดการ Hot Module Replacement (HMR)
- writeBundle: แก้ไข bundle ที่ส่งออกก่อนเขียนลงดิสก์
- closeBundle: ถูกเรียกหลังจากที่ bundle ที่ส่งออกถูกเขียนลงดิสก์แล้ว
- buildEnd: สิ้นสุดกระบวนการบิวด์
สร้างปลั๊กอิน Vite แบบกำหนดเองตัวแรกของคุณ
เรามาสร้างปลั๊กอิน Vite ง่ายๆ ที่จะเพิ่ม banner ที่ส่วนบนสุดของไฟล์ JavaScript แต่ละไฟล์ใน production build กันดีกว่า banner นี้จะประกอบด้วยชื่อและเวอร์ชันของโปรเจกต์
การนำไปใช้งาน (Implementation)
// banner-plugin.js
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
export default function bannerPlugin() {
return {
name: 'banner-plugin',
apply: 'build',
transform(code, id) {
if (!id.endsWith('.js')) {
return code;
}
const packageJsonPath = resolve(process.cwd(), 'package.json');
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
const banner = `/**\n * Project: ${packageJson.name}\n * Version: ${packageJson.version}\n */\n`;
return banner + code;
},
};
}
คำอธิบาย:
- name: กำหนดชื่อปลั๊กอินเป็น 'banner-plugin'
- apply: ระบุว่าปลั๊กอินนี้ควรทำงานเฉพาะในระหว่างกระบวนการบิวด์เท่านั้น การตั้งค่านี้เป็น 'build' ทำให้ทำงานเฉพาะในโปรดักชัน เพื่อหลีกเลี่ยง overhead ที่ไม่จำเป็นระหว่างการพัฒนา
- transform(code, id):
- นี่คือหัวใจของปลั๊กอิน มันจะดักจับโค้ด (`code`) และ ID (`id`) ของแต่ละโมดูล
- Conditional Check: `if (!id.endsWith('.js'))` ทำให้แน่ใจว่าการแปลงจะใช้กับไฟล์ JavaScript เท่านั้น ซึ่งจะป้องกันการประมวลผลไฟล์ประเภทอื่น (เช่น CSS หรือ HTML) ที่อาจทำให้เกิดข้อผิดพลาดหรือพฤติกรรมที่ไม่คาดคิด
- Package.json Access:
- `resolve(process.cwd(), 'package.json')` สร้างพาธแบบเต็มไปยังไฟล์ `package.json` `process.cwd()` จะคืนค่าไดเรกทอรีที่ทำงานปัจจุบัน ทำให้แน่ใจว่าใช้พาธที่ถูกต้องไม่ว่าจะรันคำสั่งจากที่ใด
- `JSON.parse(readFileSync(packageJsonPath, 'utf-8'))` อ่านและแยกวิเคราะห์ไฟล์ `package.json` `readFileSync` อ่านไฟล์แบบซิงโครนัส และ `'utf-8'` ระบุการเข้ารหัสเพื่อจัดการกับอักขระ Unicode ได้อย่างถูกต้อง การอ่านแบบซิงโครนัสเป็นที่ยอมรับได้ที่นี่เพราะมันเกิดขึ้นเพียงครั้งเดียวเมื่อเริ่มการแปลง
- Banner Generation:
- ``const banner = `/**\n * Project: ${packageJson.name}\n * Version: ${packageJson.version}\n */\n`;`` สร้างสตริง banner โดยใช้ template literals (backticks) เพื่อฝังชื่อและเวอร์ชันของโปรเจกต์จากไฟล์ `package.json` ได้อย่างง่ายดาย ลำดับ `\n` จะแทรกบรรทัดใหม่เพื่อจัดรูปแบบ banner ให้ถูกต้อง
- Code Transformation: `return banner + code;` จะเพิ่ม banner เข้าไปที่ด้านหน้าของโค้ด JavaScript เดิม นี่คือผลลัพธ์สุดท้ายที่คืนค่าโดยฟังก์ชัน transform
การผสานรวมปลั๊กอิน
นำเข้าปลั๊กอินไปยังไฟล์ `vite.config.js` ของคุณและเพิ่มเข้าไปในอาร์เรย์ `plugins`:
// vite.config.js
import bannerPlugin from './banner-plugin';
export default {
plugins: [
bannerPlugin(),
],
};
การรัน Build
ตอนนี้ ให้รัน `npm run build` (หรือคำสั่ง build ของโปรเจกต์ของคุณ) หลังจากที่การบิวด์เสร็จสมบูรณ์ ให้ตรวจสอบไฟล์ JavaScript ที่สร้างขึ้นในไดเรกทอรี `dist` คุณจะเห็น banner อยู่ที่ด้านบนสุดของแต่ละไฟล์
เทคนิคปลั๊กอินขั้นสูง
นอกเหนือจากการแปลงโค้ดแบบง่ายๆ แล้ว ปลั๊กอินของ Vite ยังสามารถใช้เทคนิคขั้นสูงเพื่อเพิ่มขีดความสามารถได้อีกด้วย
โมดูลเสมือน (Virtual Modules)
โมดูลเสมือนช่วยให้ปลั๊กอินสามารถสร้างโมดูลที่ไม่มีอยู่จริงเป็นไฟล์บนดิสก์ได้ ซึ่งมีประโยชน์สำหรับการสร้างเนื้อหาแบบไดนามิกหรือการให้ข้อมูลการกำหนดค่าแก่แอปพลิเคชัน
// virtual-module-plugin.js
export default function virtualModulePlugin(options) {
const virtualModuleId = 'virtual:my-module';
const resolvedVirtualModuleId = '\0' + virtualModuleId; // ขึ้นต้นด้วย \0 เพื่อป้องกันไม่ให้ Rollup ประมวลผล
return {
name: 'virtual-module-plugin',
resolveId(id) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId;
}
},
load(id) {
if (id === resolvedVirtualModuleId) {
return `export default ${JSON.stringify(options)};`;
}
},
};
}
ในตัวอย่างนี้:
- `virtualModuleId` คือสตริงที่แสดงตัวระบุของโมดูลเสมือน
- `resolvedVirtualModuleId` ถูกนำหน้าด้วย `\0` เพื่อป้องกันไม่ให้ Rollup ประมวลผลเป็นไฟล์จริง นี่เป็นข้อตกลงที่ใช้ในปลั๊กอินของ Rollup
- `resolveId` ดักจับการ resolve โมดูลและคืนค่า ID ของโมดูลเสมือนที่ resolve แล้วหาก ID ที่ร้องขอตรงกับ `virtualModuleId`
- `load` ดักจับการโหลดโมดูลและคืนค่าโค้ดของโมดูลหาก ID ที่ร้องขอตรงกับ `resolvedVirtualModuleId` ในกรณีนี้ มันจะสร้างโมดูล JavaScript ที่ส่งออก `options` เป็น default export
การใช้โมดูลเสมือน
// vite.config.js
import virtualModulePlugin from './virtual-module-plugin';
export default {
plugins: [
virtualModulePlugin({ message: 'Hello from virtual module!' }),
],
};
// main.js
import message from 'virtual:my-module';
console.log(message.message); // Output: Hello from virtual module!
การแปลงไฟล์ Index HTML
hook `transformIndexHtml` ช่วยให้คุณสามารถแก้ไขไฟล์ `index.html` ได้ เช่น การแทรกสคริปต์, สไตล์, หรือ meta tags ซึ่งมีประโยชน์สำหรับการเพิ่มการติดตามการวิเคราะห์, การกำหนดค่า metadata ของโซเชียลมีเดีย, หรือการปรับแต่งโครงสร้าง HTML
// inject-script-plugin.js
export default function injectScriptPlugin() {
return {
name: 'inject-script-plugin',
transformIndexHtml(html) {
return html.replace(
'