إرسال 500 كيلوبايت من جافاسكريبت غير مُحسّن لمتصفح المستخدم ليس مجرد بطء، بل هو جريمة ضد تجربة المستخدم. في هذا الدليل العملي، سنبني تطبيق Vue 3 من الصفر للإنتاج، مع التركيز على الأداء والأمان والبنية القابلة للتطوير دون الوقوع في فخاخ الـ Over-Engineering.
هذه البنية تجعل من السهل إضافة ميزات جديدة دون التأثير على الميزات الحالية. مثلاً، إذا أردنا إضافة ميزة جديدة مثل 'الإعدادات'، سنضيف مجلد settings داخل features ونضع كل الكود المتعلق بها هناك. هذا أيضاً يجعل من السهل إزالة ميزة كاملة دون ترك آثار في المشروع.
الـ Composition API في Vue 3 هو واحد من أقوى الميزات، لكنه غالباً ما يُساء استخدامه. رأيت الكثير من المطورين ينشئون composables ضخمة تحتوي على عشرات الدوال، مما يجعلها غير قابلة لإعادة الاستخدام وصعبة الصيانة. القاعدة التي أتبعها هي: كل composable يجب أن يفعل شيئاً واحداً فقط، ويفعله بشكل جيد.
// src/features/auth/composables/useAuth.js
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { login as authLogin } from '../services/authService'
export function useAuth() {
const isLoading = ref(false)
const error = ref(null)
const router = useRouter()
const login = async (credentials) => {
isLoading.value = true
error.value = null
try {
const user = await authLogin(credentials)
// حفظ المستخدم في الـ localStorage أو الـ Pinia
localStorage.setItem('user', JSON.stringify(user))
// إعادة التوجيه بعد تسجيل الدخول
router.push({ name: 'dashboard' })
} catch (err) {
error.value = err.message
} finally {
isLoading.value = false
}
}
return {
isLoading,
error,
login
}
}في هذا المثال، استخدمنا composable واحد فقط للتعامل مع عملية تسجيل الدخول. لاحظ أننا فصلنا منطق الـ API في خدمة منفصلة (authService.js)، مما يجعل الكود أكثر قابلية للاختبار وإعادة الاستخدام. أيضاً، استخدمنا ref بدلاً من reactive عندما نحتاج إلى قيم بدائية مثل Boolean أو String، لأن هذا يجعل الكود أكثر وضوحاً وأسهل في التصحيح.
الكثير من المطورين يستخدمون Pinia لكل شيء، حتى للمتغيرات التي لا تحتاج إلى إدارة مركزية. هذا خطأ كبير يؤدي إلى تعقيد غير ضروري وتقليل الأداء. القاعدة التي أتبعها هي: استخدم الـ Local State للمتغيرات التي لا تحتاج إلى مشاركتها بين مكونات متعددة، واستخدم Pinia فقط عندما تحتاج إلى حالة مشتركة بين مكونات بعيدة عن بعضها في شجرة المكونات.
في هذا المشروع، سنستخدم Pinia فقط لإدارة حالة المستخدم والمعلومات المشتركة بين الميزات. مثلاً، سننشئ store للمستخدم لتخزين معلوماته بعد تسجيل الدخول، وسنستخدمه في جميع الميزات التي تحتاج إلى هذه المعلومات.
// src/store/userStore.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
user: null,
token: null
}),
getters: {
isAuthenticated: (state) => !!state.user,
getUserName: (state) => state.user?.name
},
actions: {
setUser(user) {
this.user = user
},
setToken(token) {
this.token = token
},
logout() {
this.user = null
this.token = null
localStorage.removeItem('user')
}
}
})لاحظ أننا استخدمنا getters للوصول إلى حالة المستخدم بدلاً من الوصول المباشر إلى الـ state. هذا يجعل الكود أكثر قابلية للصيانة ويسهل تغيير بنية الـ state لاحقاً دون كسر المكونات التي تستخدمها. أيضاً، أضفنا action للـ logout لمسح حالة المستخدم من الـ store والـ localStorage.
العديد من المطورين يركزون على كتابة الكود دون التفكير في كيفية أدائه في الإنتاج. الحقيقة هي أن تطبيق Vue 3 يمكن أن يكون بطيئاً جداً إذا لم يتم تحسينه بشكل صحيح. في هذا القسم، سنتحدث عن بعض التقنيات التي استخدمتها في مشاريع حقيقية لتحسين أداء التطبيقات بشكل كبير.
أولاً، دعنا نتحدث عن الـ Lazy Loading. في معظم المشاريع، يتم تحميل جميع مكونات التطبيق عند تحميل الصفحة الأولى، حتى لو لم يتم استخدامها. هذا يؤدي إلى تحميل ملفات كبيرة ووقت تحميل بطيء. الحل هو استخدام الـ Lazy Loading لتحميل المكونات فقط عند الحاجة إليها.
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
name: 'home',
component: () => import('../features/home/Home.vue')
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import('../features/dashboard/Dashboard.vue'),
meta: { requiresAuth: true }
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
// إضافة guard للتحقق من المصادقة
router.beforeEach((to, from, next) => {
const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
const isAuthenticated = localStorage.getItem('user')
if (requiresAuth && !isAuthenticated) {
next({ name: 'home' })
} else {
next()
}
})
export default routerفي هذا المثال، استخدمنا دالة import الديناميكية لتحميل المكونات فقط عند الحاجة إليها. هذا يقلل من حجم الملف الرئيسي ويحسن وقت التحميل بشكل كبير. أيضاً، أضفنا guard للتحقق من المصادقة قبل السماح للمستخدم بالوصول إلى صفحات معينة.
الـ Code Splitting هو واحد من أقوى تقنيات تحسين الأداء، لكنه غالباً ما يُساء فهمه. الفكرة هي تقسيم الكود إلى قطع صغيرة يتم تحميلها فقط عند الحاجة إليها. في Vue 3، يمكننا القيام بذلك بسهولة باستخدام Vite وRollup.
في ملف vite.config.js الذي رأيناه سابقاً، أضفنا manualChunks لتقسيم الاعتماديات الخارجية إلى ملفات منفصلة. هذا يقلل من حجم الملف الرئيسي ويحسن وقت التحميل. لكن يمكننا الذهاب أبعد من ذلك بتقسيم الكود بناءً على الميزات.
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
build: {
rollupOptions: {
output: {
manualChunks: {
// تقسيم الاعتماديات الخارجية
vendor: ['vue', 'vue-router', 'pinia'],
// تقسيم الميزات إلى ملفات منفصلة
auth: ['./src/features/auth/index.js'],
dashboard: ['./src/features/dashboard/index.js']
}
}
}
}
})بهذه الطريقة، سيتم تحميل كود ميزة تسجيل الدخول فقط عندما يزور المستخدم صفحة تسجيل الدخول، وسيتم تحميل كود لوحة التحكم فقط عند زيارة لوحة التحكم. هذا يقلل من حجم الملفات التي يتم تحميلها في كل صفحة ويحسن أداء التطبيق بشكل كبير.
الكثير من المطورين يركزون على كتابة الكود دون التفكير في الأمان حتى مرحلة متأخرة من المشروع. هذا خطأ كبير يمكن أن يؤدي إلى اختراقات أمنية خطيرة. في هذا القسم، سنتحدث عن بعض أفضل الممارسات لحماية تطبيق Vue 3 من الهجمات الشائعة.
أولاً، دعنا نتحدث عن الـ Cross-Site Scripting (XSS). Vue 3 تحمي تلقائياً من هذا النوع من الهجمات عن طريق تحويل كل البيانات النصية إلى نص عادي قبل عرضها في القالب. لكن هذا لا يعني أننا يمكننا تجاهل الأمان تماماً. يجب علينا دائماً تطهير البيانات المدخلة من المستخدم قبل تخزينها أو عرضها.
// src/shared/utils/sanitize.js
import DOMPurify from 'dompurify'
export function sanitizeInput(input) {
if (!input) return ''
return DOMPurify.sanitize(input)
}
// استخدام في المكون
import { sanitizeInput } from '@/shared/utils/sanitize'
const userInput = ref('')
const sanitizedInput = computed(() => sanitizeInput(userInput.value))في هذا المثال، استخدمنا مكتبة DOMPurify لتطهير البيانات المدخلة من المستخدم. هذه المكتبة تزيل أي أكواد جافاسكريبت أو HTML ضارة من المدخلات، مما يحمي التطبيق من هجمات XSS. لاحظ أننا استخدمنا computed property لعرض البيانات المطهرة بدلاً من البيانات الأصلية.
عند التواصل مع الـ API، يجب علينا حماية التطبيق من هجمات CSRF (Cross-Site Request Forgery) و CORS (Cross-Origin Resource Sharing). لحماية التطبيق من هجمات CSRF، يجب علينا إضافة رموز CSRF إلى كل طلب HTTP. لحماية التطبيق من مشاكل CORS، يجب علينا تكوين السيرفر لقبول الطلبات فقط من النطاقات المصرح بها.
// src/shared/services/apiClient.js
import axios from 'axios'
import { useUserStore } from '@/store/userStore'
const apiClient = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
headers: {
'Content-Type': 'application/json'
}
})
// إضافة interceptor لإضافة رمز CSRF ورمز المصادقة
apiClient.interceptors.request.use((config) => {
const userStore = useUserStore()
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content
if (userStore.token) {
config.headers.Authorization = `Bearer ${userStore.token}`
}
if (csrfToken) {
config.headers['X-CSRF-Token'] = csrfToken
}
return config
})
export default apiClientفي هذا المثال، أنشأنا عميل axios مخصص لإرسال الطلبات إلى الـ API. أضفنا interceptor لإضافة رمز المصادقة ورمز CSRF إلى كل طلب. هذا يحمي التطبيق من هجمات CSRF ويمنحنا تحكم كامل في الطلبات المرسلة إلى السيرفر.
بعد بناء التطبيق واختباره، يأتي الجزء الأكثر أهمية: النشر للإنتاج. الكثير من المطورين يعتقدون أن النشر هو مجرد رفع الملفات إلى السيرفر، لكن الحقيقة هي أن هناك الكثير من التفاصيل التي يجب الاهتمام بها لضمان أن التطبيق يعمل بشكل صحيح وآمن في البيئة الحقيقية.
أولاً، يجب علينا بناء التطبيق للإنتاج باستخدام أمر npm run build. هذا الأمر سينشئ نسخة محسنة من التطبيق جاهزة للنشر. لكن قبل النشر، يجب علينا اختبار النسخة النهائية للتأكد من أنها تعمل بشكل صحيح.
# بناء التطبيق للإنتاج
npm run build
# اختبار النسخة النهائية محلياً
npm install -g serve
serve -s distبعد بناء التطبيق، سنجد مجلد dist يحتوي على جميع الملفات اللازمة للنشر. يمكننا رفع هذه الملفات إلى أي سيرفر ويب مثل Nginx أو Apache. لكن قبل ذلك، يجب علينا تكوين السيرفر بشكل صحيح لضمان أن التطبيق يعمل بشكل جيد.
server {
listen 80;
server_name yourdomain.com;
root /var/www/vue3-app/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# ضغط الملفات الثابتة
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# تحسينات للأداء
expires 1y;
add_header Cache-Control "public";
add_header ETag "";
}في هذا المثال، قمنا بتكوين Nginx لتقديم تطبيق Vue 3 بشكل صحيح. لاحظ أننا استخدمنا try_files لتوجيه جميع الطلبات إلى index.html، مما يسمح لـ Vue Router بالتعامل مع التوجيه من جانب العميل. أيضاً، قمنا بتمكين ضغط Gzip وتقنيات التخزين المؤقت لتحسين الأداء.
بعد نشر التطبيق، يجب علينا مراقبة أدائه وأخطائه لضمان تجربة مستخدم سلسة. هناك العديد من الأدوات التي يمكننا استخدامها لهذا الغرض، مثل Sentry لمراقبة الأخطاء وGoogle Analytics لمراقبة الأداء.
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'
// إعداد Sentry لمراقبة الأخطاء
import * as Sentry from '@sentry/vue'
import { Integrations } from '@sentry/tracing'
const app = createApp(App)
if (import.meta.env.PROD) {
Sentry.init({
app,
dsn: import.meta.env.VITE_SENTRY_DSN,
integrations: [
new Integrations.BrowserTracing({
routingInstrumentation: Sentry.vueRouterInstrumentation(router),
tracingOrigins: ["localhost", /^https:\/\/yourdomain\.com/]
})
],
tracesSampleRate: 1.0
})
}
app.use(createPinia())
app.use(router)
app.mount('#app')في هذا المثال، قمنا بإعداد Sentry لمراقبة الأخطاء في التطبيق. لاحظ أننا قمنا بتهيئة Sentry فقط في بيئة الإنتاج باستخدام متغير البيئة PROD. هذا يضمن أننا لا نرسل بيانات الأخطاء من بيئة التطوير إلى Sentry. أيضاً، قمنا بإعداد تتبع التوجيهات باستخدام BrowserTracing لمراقبة أداء التوجيهات في التطبيق.
بعد أكثر من عشر سنوات في تطوير الويب وبناء عشرات التطبيقات باستخدام Vue، تعلمت أن النجاح في الإنتاج لا يتعلق بالكود فقط، بل بطريقة التفكير. إليك بعض النصائح الذهبية التي ستوفر عليك مئات الساعات من الصداع:
وأخيراً، تذكر أن Vue 3 هي مجرد أداة. الأداة الجيدة في يد المطور السيئ ستنتج تطبيقاً سيئاً، والأداة البسيطة في يد المطور الجيد ستنتج تطبيقاً رائعاً. ركز على فهم المفاهيم الأساسية وكيفية عمل الأشياء خلف الكواليس، وليس فقط على كتابة الكود.