هل تريد تطبيق Vue 3 جاهز للإنتاج وليس مجرد demo؟ هذا الدليل العملي يفكك كل خطوة من تهيئة المشروع إلى النشر، مع التركيز على الأداء والأخطاء الحقيقية التي تقع فيها الفرق في الشركات الكبرى.
لاحظ هنا أننا استخدمنا التحميل الكسول للمكونات باستخدام `() => import()`. هذا يعني أن المكونات لن تُحمل إلا عند الحاجة إليها، مما يقلل من حجم الـ bundle الأولي. أيضاً، استخدمنا `meta.requiresAuth` لتحديد المسارات التي تحتاج إلى مصادقة، ثم استخدمنا الـ Navigation Guard للتحقق من حالة المصادقة قبل السماح بالوصول إلى هذه المسارات.
مشكلة شائعة أخرى هي إدارة الـ scroll position عند التنقل بين الصفحات. مثلاً، إذا كان المستخدم في منتصف صفحة طويلة ثم انتقل إلى صفحة أخرى ثم عاد، قد يريد العودة إلى نفس المكان الذي كان فيه. الـ `scrollBehavior` في Vue Router يسمح لك بتحديد هذا السلوك. في المثال أعلاه، إذا كان هناك `savedPosition`، فسيتم استخدامه، وإلا سيتم التمرير إلى أعلى الصفحة.
في الماضي، كان Vuex هو الخيار الوحيد لإدارة الحالة في تطبيقات Vue. لكن مع ظهور Composition API في Vue 3، أصبح Vuex يبدو ثقيلاً ومعقداً. هذا هو السبب في أن فريق Vue نفسه أوصى باستخدام Pinia بدلاً من Vuex للمشاريع الجديدة. Pinia أسهل في الاستخدام، وأكثر مرونة، ويعمل بشكل أفضل مع Composition API.
لنأخذ مثالاً عملياً. لنفترض أننا نبني تطبيقاً لإدارة المهام، ونريد إدارة حالة المهام. في Pinia، يمكنك إنشاء store بهذه الطريقة:
// stores/tasks.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useTasksStore = defineStore('tasks', () => {
const tasks = ref([])
const completedTasks = computed(() => tasks.value.filter(task => task.completed))
function addTask(task) {
tasks.value.push(task)
}
function toggleTask(id) {
const task = tasks.value.find(task => task.id === id)
if (task) {
task.completed = !task.completed
}
}
function removeTask(id) {
tasks.value = tasks.value.filter(task => task.id !== id)
}
return { tasks, completedTasks, addTask, toggleTask, removeTask }
})هذا الكود يبدو وكأنه مكون Vue عادي، لكنه store يمكن استخدامه في أي مكان في التطبيق. لاحظ أننا استخدمنا `ref` و `computed` من Vue، مما يجعل الكود مألوفاً لأي شخص يستخدم Composition API. أيضاً، Pinia يدعم الـ TypeScript بشكل أفضل من Vuex، مما يجعله خياراً أفضل للمشاريع الكبيرة.
مشكلة شائعة في إدارة الحالة هي الـ reactivity. مثلاً، إذا قمت بتعديل كائن داخل الـ store بشكل مباشر، فقد لا يتم تحديث واجهة المستخدم. في المثال أعلاه، استخدمنا `tasks.value.push(task)` بدلاً من تعديل الـ array مباشرة، وهذا يضمن أن الـ reactivity يعمل بشكل صحيح. أيضاً، استخدمنا `computed` لحساب المهام المكتملة، مما يعني أن هذه القيمة ستُحدث تلقائياً عند تغيير حالة المهام.
الأداء هو أحد أكبر التحديات في تطبيقات Vue 3 الكبيرة. عندما يصل عدد المكونات إلى المئات، قد تلاحظ أن التطبيق يصبح بطيئاً، خاصة عند التحديثات المتكررة. هناك عدة تقنيات لتحسين الأداء، وسنركز هنا على ثلاث منها: التحميل الكسول، استخدام `v-once`، وتحسين الـ reactivity.
أولاً، التحميل الكسول. كما ذكرنا سابقاً، يمكنك استخدام `() => import()` لتحميل المكونات فقط عند الحاجة إليها. لكن يمكنك أيضاً استخدام التحميل الكسول للمكونات الكبيرة داخل الصفحة نفسها. مثلاً، إذا كان لديك مكون كبير مثل جدول بيانات معقد، يمكنك تحميله بشكل كسول بعد تحميل الصفحة الرئيسية. هذا يقلل من حجم الـ bundle الأولي ويحسن الـ Time to Interactive.
ثانياً، استخدام `v-once`. إذا كان لديك مكون أو جزء من القالب لا يتغير أبداً، يمكنك استخدام `v-once` لتجنب إعادة تحليله في كل تحديث. مثلاً:
<template>
<div>
<header v-once>
<!-- هذا الجزء لن يتغير أبداً -->
<h1>{{ appName }}</h1>
</header>
<main>
<!-- هذا الجزء سيتغير -->
<p>{{ currentTime }}</p>
</main>
</div>
</template>ثالثاً، تحسين الـ reactivity. في بعض الأحيان، قد لا تحتاج إلى تحديث واجهة المستخدم عند تغيير بعض البيانات. مثلاً، إذا كان لديك مصفوفة كبيرة من البيانات وتريد تعديل عنصر واحد فيها، قد لا تريد أن يتم إعادة تحميل المكون بالكامل. في هذه الحالة، يمكنك استخدام `shallowRef` بدلاً من `ref` لتجنب تتبع الـ reactivity العميق. إليك مثال:
import { shallowRef } from 'vue'
const largeArray = shallowRef([...Array(10000).keys()])
function updateItem(index, newValue) {
// هذا التعديل لن يؤدي إلى إعادة تحميل المكون بالكامل
largeArray.value[index] = newValue
}مشكلة شائعة أخرى هي الـ Memory Leaks. مثلاً، إذا قمت بتسجيل مستمع للأحداث داخل مكون ثم لم تقم بإلغاء تسجيله عند تدمير المكون، فقد يؤدي ذلك إلى تسرب الذاكرة. لحسن الحظ، Vue يوفر الـ lifecycle hooks مثل `onMounted` و `onUnmounted` لإدارة هذه الحالات. إليك مثال:
import { onMounted, onUnmounted } from 'vue'
export default {
setup() {
let intervalId
onMounted(() => {
intervalId = setInterval(() => {
console.log('This runs every second')
}, 1000)
})
onUnmounted(() => {
clearInterval(intervalId)
})
}
}النشر ليس مجرد رفع الكود إلى السيرفر. هناك عدة خطوات يجب اتباعها لضمان أن التطبيق يعمل بشكل جيد في الإنتاج. أولاً، يجب تحسين الكود باستخدام أدوات مثل Terser لضغط الكود، و Brotli لضغط الملفات الثابتة. أيضاً، يجب إعداد الـ environment variables بشكل صحيح، وضمان أن الـ API endpoints تشير إلى السيرفر الصحيح في الإنتاج.
أول خطوة هي بناء التطبيق للإنتاج. Vite يأتي مع إعدادات جاهزة للإنتاج، لكن يمكنك تخصيصها حسب احتياجاتك. إليك مثال على إعدادات `vite.config.js` لتحسين الإنتاج:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { visualizer } from 'rollup-plugin-visualizer'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), visualizer()],
build: {
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
},
rollupOptions: {
output: {
manualChunks: {
vendor: ['vue', 'pinia', 'vue-router', 'axios']
}
}
}
}
})في هذا الإعداد، استخدمنا `terser` لضغط الكود وإزالة الـ `console.log` و الـ `debugger` من الكود النهائي. أيضاً، استخدمنا `manualChunks` لتقسيم الـ bundle إلى أجزاء أصغر، مما يحسن الأداء. إضافة `visualizer` تساعدك على رؤية حجم الـ bundle وتحديد الملفات الكبيرة التي قد تحتاج إلى تحسين.
بعد بناء التطبيق، الخطوة التالية هي نشره. هناك عدة خيارات للنشر، مثل Vercel، Netlify، أو حتى سيرفر خاص. سأشرح هنا كيفية نشر التطبيق على Vercel، لأنه سهل ومجاني للمشاريع الصغيرة. أولاً، قم بتثبيت Vercel CLI:
npm install -g vercelثم، قم بإنشاء ملف `vercel.json` في جذر المشروع لتحديد إعدادات النشر:
{
"version": 2,
"builds": [
{
"src": "package.json",
"use": "@vercel/static-build",
"config": { "distDir": "dist" }
}
],
"routes": [
{
"src": "/(.*)",
"dest": "/"
}
]
}أخيراً، قم بتشغيل الأمر التالي لنشر التطبيق:
vercelمشكلة شائعة في النشر هي إعداد الـ environment variables. مثلاً، إذا كان لديك API endpoint مختلف في التطوير والإنتاج، يجب عليك إعداد هذه المتغيرات بشكل صحيح. في Vercel، يمكنك إضافة المتغيرات من واجهة المستخدم أو باستخدام ملف `.env.production`. إليك مثال:
# .env.production
VITE_API_URL=https://api.example.com
VITE_APP_ENV=productionثم، في الكود، يمكنك الوصول إلى هذه المتغيرات باستخدام `import.meta.env`:
const apiUrl = import.meta.env.VITE_API_URLبعد بناء أكثر من 20 تطبيق Vue 3 للإنتاج، يمكنني تلخيص تجربتي في ثلاث نصائح ذهبية: أولاً، ابدأ بالتفكير في الإنتاج منذ اليوم الأول. لا تنتظر حتى نهاية المشروع لتكتشف أن الـ bundle size كبير جداً أو أن الأداء بطيء. ثانياً، استخدم Pinia لإدارة الحالة، فهو أسهل وأكثر مرونة من Vuex. ثالثاً، لا تخف من تقسيم الكود إلى مكونات صغيرة. المكون الكبير هو عدو الصيانة والأداء على حد سواء.
أيضاً، لا تنسَ مراقبة الأداء بعد النشر. استخدم أدوات مثل Lighthouse و Sentry لتتبع الأخطاء والأداء في الإنتاج. في أحد المشاريع، اكتشفنا بعد النشر أن أحد المكونات كان يسبب تجمد واجهة المستخدم على الهواتف القديمة. بعد التحقيق، وجدنا أن المشكلة كانت في استخدام `v-for` على مصفوفة كبيرة دون استخدام `key` بشكل صحيح. هذه الأشياء الصغيرة هي التي تصنع الفرق بين تطبيق جيد وتطبيق ممتاز.
أخيراً، تذكر أن بناء تطبيق Vue 3 للإنتاج ليس مجرد كتابة كود. إنه عن التفكير في البنية التحتية، والأداء، وتجربة المستخدم منذ البداية. إذا اتبعت الخطوات التي شرحناها في هذا المقال، ستتجنب الكثير من المشاكل الشائعة وستبني تطبيقاً سريعاً وقابلاً للصيانة.