استكشف بنية إضافات Vite وتعلم كيفية إنشاء إضافات مخصصة لتحسين سير عمل التطوير لديك. أتقن المفاهيم الأساسية بأمثلة عملية لجمهور عالمي.
إزالة الغموض عن بنية إضافات Vite: دليل عالمي لإنشاء إضافات مخصصة
أحدثت Vite، أداة البناء فائقة السرعة، ثورة في تطوير الواجهات الأمامية. وتعود سرعتها وبساطتها إلى حد كبير إلى بنية إضافاتها القوية. تسمح هذه البنية للمطورين بتوسيع وظائف Vite وتكييفها مع احتياجات مشاريعهم الخاصة. يقدم هذا الدليل استكشافًا شاملًا لنظام إضافات Vite، مما يمكّنك من إنشاء إضافاتك المخصصة وتحسين سير عمل التطوير لديك.
فهم المبادئ الأساسية لـ Vite
قبل الخوض في إنشاء الإضافات، من الضروري فهم مبادئ Vite الأساسية:
- الترجمة عند الطلب (On-Demand Compilation): تقوم Vite بترجمة الكود فقط عند طلبه من المتصفح، مما يقلل بشكل كبير من وقت بدء التشغيل.
- وحدات ESM الأصلية: تستفيد Vite من وحدات ECMAScript الأصلية (ESM) في التطوير، مما يلغي الحاجة إلى التجميع (bundling) أثناء التطوير.
- بناء الإنتاج المعتمد على Rollup: بالنسبة لعمليات بناء الإنتاج، تستخدم Vite أداة Rollup، وهي أداة تجميع مُحسّنة للغاية، لإنشاء كود فعال وجاهز للإنتاج.
دور الإضافات في النظام البيئي لـ Vite
صُممت بنية إضافات Vite لتكون قابلة للتوسيع بشكل كبير. يمكن للإضافات أن:
- تحول الكود (على سبيل المثال، ترجمة TypeScript، إضافة المعالجات المسبقة).
- تقدم ملفات مخصصة (على سبيل المثال، التعامل مع الأصول الثابتة، إنشاء وحدات افتراضية).
- تعدل عملية البناء (على سبيل المثال، تحسين الصور، إنشاء service workers).
- توسع واجهة سطر الأوامر (CLI) الخاصة بـ Vite (على سبيل المثال، إضافة أوامر مخصصة).
الإضافات هي المفتاح لتكييف Vite مع متطلبات المشاريع المختلفة، من التعديلات البسيطة إلى التكاملات المعقدة.
بنية إضافات Vite: نظرة متعمقة
إضافة Vite هي في الأساس كائن JavaScript له خصائص محددة تحدد سلوكه. دعنا نفحص العناصر الرئيسية:
إعدادات الإضافة
ملف `vite.config.js` (أو `vite.config.ts`) هو المكان الذي تقوم فيه بتكوين مشروع Vite الخاص بك، بما في ذلك تحديد الإضافات التي سيتم استخدامها. يقبل الخيار `plugins` مصفوفة من كائنات الإضافات أو دوال تعيد كائنات إضافات.
// vite.config.js
import myPlugin from './my-plugin';
export default {
plugins: [
myPlugin(), // Invoke the plugin function to create a plugin instance
],
};
خصائص كائن الإضافة
يمكن أن يحتوي كائن إضافة Vite على عدة خصائص تحدد سلوكه خلال المراحل المختلفة لعملية البناء. إليك تفصيل للخصائص الأكثر شيوعًا:
- name: اسم فريد للإضافة. هذا الحقل مطلوب ويساعد في تصحيح الأخطاء وحل التعارضات. مثال: `'my-custom-plugin'`
- enforce: يحدد ترتيب تنفيذ الإضافة. القيم الممكنة هي `'pre'` (تعمل قبل الإضافات الأساسية)، `'normal'` (الافتراضي)، و `'post'` (تعمل بعد الإضافات الأساسية). مثال: `'pre'`
- config: يسمح بتعديل كائن إعدادات Vite. يستقبل إعدادات المستخدم والبيئة (الوضع والأمر). مثال: `config: (config, { mode, command }) => { ... }`
- configResolved: يُستدعى بعد حل إعدادات Vite بالكامل. مفيد للوصول إلى كائن الإعدادات النهائي. مثال: `configResolved(config) { ... }`
- configureServer: يوفر الوصول إلى مثيل خادم التطوير (شبيه بـ Connect/Express). مفيد لإضافة برمجيات وسيطة (middleware) مخصصة أو تعديل سلوك الخادم. مثال: `configureServer(server) { ... }`
- transformIndexHtml: يسمح بتحويل ملف `index.html`. مفيد لإدخال السكريبتات، الأنماط، أو علامات meta. مثال: `transformIndexHtml(html) { ... }`
- resolveId: يسمح باعتراض وتعديل عملية حل الوحدات (module resolution). مفيد لمنطق حل الوحدات المخصص. مثال: `resolveId(source, importer) { ... }`
- load: يسمح بتحميل وحدات مخصصة أو تعديل محتوى الوحدات الحالية. مفيد للوحدات الافتراضية أو المحملات المخصصة. مثال: `load(id) { ... }`
- transform: يحول الكود المصدري للوحدات. مشابه لإضافة Babel أو PostCSS. مثال: `transform(code, id) { ... }`
- buildStart: يُستدعى في بداية عملية البناء. مثال: `buildStart() { ... }`
- buildEnd: يُستدعى بعد اكتمال عملية البناء. مثال: `buildEnd() { ... }`
- closeBundle: يُستدعى بعد كتابة الحزمة (bundle) على القرص. مثال: `closeBundle() { ... }`
- writeBundle: يُستدعى قبل كتابة الحزمة على القرص، مما يسمح بالتعديل. مثال: `writeBundle(options, bundle) { ... }`
- renderError: يسمح بعرض صفحات أخطاء مخصصة أثناء التطوير. مثال: `renderError(error, req, res) { ... }`
- handleHotUpdate: يسمح بالتحكم الدقيق في التحديث السريع للوحدات (HMR). مثال: `handleHotUpdate({ file, server }) { ... }`
خطافات الإضافة (Hooks) وترتيب التنفيذ
تعمل إضافات Vite من خلال سلسلة من الخطافات التي يتم تشغيلها في مراحل مختلفة من عملية البناء. فهم ترتيب تنفيذ هذه الخطافات أمر بالغ الأهمية لكتابة إضافات فعالة.
- config: تعديل إعدادات Vite.
- configResolved: الوصول إلى الإعدادات النهائية.
- configureServer: تعديل خادم التطوير (في وضع التطوير فقط).
- transformIndexHtml: تحويل ملف `index.html`.
- buildStart: بداية عملية البناء.
- resolveId: حل معرفات الوحدات (module IDs).
- load: تحميل محتوى الوحدة.
- transform: تحويل كود الوحدة.
- handleHotUpdate: التعامل مع التحديث السريع للوحدات (HMR).
- writeBundle: تعديل الحزمة الناتجة قبل كتابتها على القرص.
- closeBundle: يُستدعى بعد كتابة الحزمة الناتجة على القرص.
- buildEnd: نهاية عملية البناء.
إنشاء أول إضافة Vite مخصصة لك
دعنا ننشئ إضافة Vite بسيطة تضيف لافتة (banner) إلى أعلى كل ملف JavaScript في بناء الإنتاج. ستحتوي هذه اللافتة على اسم المشروع وإصداره.
تنفيذ الإضافة
// 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' يجعلها مخصصة للإنتاج فقط، مما يتجنب العبء غير الضروري أثناء التطوير.
- transform(code, id):
- هذا هو جوهر الإضافة. يعترض كود كل وحدة (`code`) ومعرفها (`id`).
- التحقق الشرطي: `if (!id.endsWith('.js'))` يضمن أن التحويل ينطبق فقط على ملفات JavaScript. هذا يمنع معالجة أنواع الملفات الأخرى (مثل CSS أو HTML)، والتي قد تسبب أخطاء أو سلوكًا غير متوقع.
- الوصول إلى Package.json:
- `resolve(process.cwd(), 'package.json')` يبني المسار المطلق لملف `package.json`. تعيد `process.cwd()` دليل العمل الحالي، مما يضمن استخدام المسار الصحيح بغض النظر عن مكان تنفيذ الأمر.
- `JSON.parse(readFileSync(packageJsonPath, 'utf-8'))` يقرأ ويحلل ملف `package.json`. تقرأ `readFileSync` الملف بشكل متزامن، ويحدد `'utf-8'` الترميز للتعامل مع أحرف Unicode بشكل صحيح. القراءة المتزامنة مقبولة هنا لأنها تحدث مرة واحدة في بداية التحويل.
- إنشاء اللافتة (Banner):
- ``const banner = `/**\n * Project: ${packageJson.name}\n * Version: ${packageJson.version}\n */\n`;`` تنشئ سلسلة اللافتة. تستخدم القوالب الحرفية (backticks) لتضمين اسم المشروع وإصداره بسهولة من ملف `package.json`. تُدرج التسلسلات `\n` أسطرًا جديدة لتنسيق اللافتة بشكل صحيح. يتم تخطي `*` باستخدام `\*`.
- تحويل الكود: `return banner + code;` يضيف اللافتة إلى بداية كود JavaScript الأصلي. هذه هي النتيجة النهائية التي تعيدها دالة التحويل.
دمج الإضافة
استورد الإضافة في ملف `vite.config.js` وأضفها إلى مصفوفة `plugins`:
// vite.config.js
import bannerPlugin from './banner-plugin';
export default {
plugins: [
bannerPlugin(),
],
};
تشغيل عملية البناء
الآن، قم بتشغيل `npm run build` (أو أمر بناء مشروعك). بعد اكتمال البناء، تفحص ملفات JavaScript التي تم إنشاؤها في مجلد `dist`. سترى اللافتة في أعلى كل ملف.
تقنيات الإضافات المتقدمة
بالإضافة إلى تحويلات الكود البسيطة، يمكن لإضافات Vite الاستفادة من تقنيات أكثر تقدمًا لتعزيز قدراتها.
الوحدات الافتراضية (Virtual Modules)
تسمح الوحدات الافتراضية للإضافات بإنشاء وحدات لا وجود لها كملفات فعلية على القرص. هذا مفيد لإنشاء محتوى ديناميكي أو توفير بيانات التكوين للتطبيق.
// virtual-module-plugin.js
export default function virtualModulePlugin(options) {
const virtualModuleId = 'virtual:my-module';
const resolvedVirtualModuleId = '\0' + virtualModuleId; // Prefix with \0 to prevent Rollup from processing
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` يعترض عملية حل الوحدات ويعيد معرف الوحدة الافتراضية المحلول إذا كان المعرف المطلوب يطابق `virtualModuleId`.
- `load` يعترض عملية تحميل الوحدات ويعيد كود الوحدة إذا كان المعرف المطلوب يطابق `resolvedVirtualModuleId`. في هذه الحالة، ينشئ وحدة JavaScript تصدر `options` كتصدير افتراضي.
استخدام الوحدة الافتراضية
// 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
يسمح الخطاف `transformIndexHtml` بتعديل ملف `index.html`، مثل إدخال السكريبتات، الأنماط، أو علامات meta. هذا مفيد لإضافة تتبع التحليلات، تكوين بيانات الوسائط الاجتماعية، أو تخصيص بنية HTML.
// inject-script-plugin.js
export default function injectScriptPlugin() {
return {
name: 'inject-script-plugin',
transformIndexHtml(html) {
return html.replace(
'