محمد قتيبة شيخانيأبني. أستكشف. أشارك

14من 18

تطوير الويب

أدوات البناء الحديثة: npm وVite وخطوة البناء

حتى الآن كتبتَ جافاسكربت «خالصةً» يفهمها المتصفّح مباشرةً: تحفظ الملفّ، تحدّث الصفحة، فترى أثره. لكنّك إن فتحت مشروع موقعٍ حديثٍ حقيقيٍّ وجدت أشياء غريبة: ملفّ package.json، ومجلّد node_modules الضخم، وأوامر مثل npm run build، وملفّاتٍ لا يفتحها المتصفّح كما هي. بين الشيفرة التي يكتبها المطوّر اليوم والشيفرة التي يفهمها المتصفّح خطوةٌ خفيّةٌ اسمها «خطوة البناء» (Build Step). هذا المقال يفتح هذا الصندوق الأسود: لماذا وُجد، وما أدواته، وكيف يجري عمليًّا، لأنه البوّابة التي لا بدّ منها قبل أن تدخل عالم أطر العمل الحديثة وتفهم كيف تُبنى المواقع اليوم فعلًا.

لماذا تحتاج «خطوة بناء» أصلًا؟

المتصفّح لا يفهم إلا ثلاث لغاتٍ: HTML وCSS وجافاسكربت. لكنّ المطوّر الحديث لا يكتب بها مباشرةً، بل يستعمل أدواتٍ ترفع إنتاجيّته: إطار عملٍ مثل React بلغته الخاصّة (JSX)، أو TypeScript الذي يضيف أمانًا إلى جافاسكربت، أو Sass الذي يوسّع CSS. المتصفّح لا يفهم أيًّا من هذه، فلا بدّ من «مترجمٍ» يحوّلها إلى اللغات الثلاث الأساسية. هذه هي المشكلة الأولى التي تحلّها خطوة البناء.[1]

والمشكلة الثانية مشكلة عدد. الموقع البسيط ملفٌّ أو اثنان، أمّا التطبيق الحديث فيتكوّن من مئات الملفّات الصغيرة، ويعتمد على عشرات المكتبات الجاهزة. لو طلب المتصفّح كل ملفٍّ على حدة لتباطأ الموقع تباطؤًا قاتلًا (تذكّر من مقال HTTP أن كل طلبٍ له كلفة). فتحتاج أداةً تجمع هذه الملفّات كلّها وتحزمها في حزمةٍ صغيرةٍ مُحسَّنة. هاتان الحاجتان (الترجمة والحزم) هما سبب وجود أدوات البناء، وهي اليوم جزءٌ لا ينفصل عن تطوير الواجهات الأمامية الحديث.

ولم يكن الأمر هكذا دائمًا. في بدايات الويب كان المطوّر يكتب ملفّ جافاسكربت ويربطه بوسم <script> في صفحته، وانتهى. لكن مع تعقّد التطبيقات تراكمت المشكلات: ملفّاتٌ كثيرةٌ يصعب ترتيبها، ومتغيّراتٌ تتصادم في النطاق العامّ، ومكتباتٌ تعتمد بعضها على بعضٍ بترتيبٍ حسّاس. فتطوّرت الحلول تدريجيًّا حتى وُلد نظام «الوحدات» الذي ينظّم الشيفرة، ووُلدت المُجمّعات التي تحزمها، ووُلدت أدوات الترجمة التي تتيح لغاتٍ أحدث. فأدوات البناء ليست تعقيدًا اختُرع لإرباكك، بل حلولٌ تراكمت لمشكلاتٍ حقيقيةٍ واجهها من سبقك.

npm: مدير الحزم وقلب المنظومة

مرّ بك npm سريعًا في مقال تجهيز البيئة بوصفه «مدير الحزم» الذي يأتي مع Node.js؛ وهنا أفتحه لك أعمق لأنه أساس كل ما يأتي. تخيّله «متجرًا» هائلًا يضمّ ملايين الحزم البرمجية المجّانية (يُسمّى السجلّ، registry)، تثبّت منه ما بناه غيرك وتبني فوقه بدل أن تكتب كل شيءٍ من الصفر.[2] وهو أضخم سجلّ برمجيّاتٍ في التاريخ، يضمّ ملايين الحزم تُنزَّل مليارات المرّات يوميًّا، من مكتباتٍ كبرى إلى أدواتٍ صغيرةٍ تؤدّي مهمّةً واحدة. هذا الكنز المجّانيّ هو ما يجعل المطوّر الواحد اليوم يبني في أيّامٍ ما كان يأخذ فريقًا أشهرًا.

وقلب أيّ مشروعٍ ملفٌّ اسمه package.json، بطاقة هويّة المشروع: يحمل اسمه وإصداره، والأهمّ قائمة «الاعتماديّات» (dependencies) التي يحتاجها، و«أوامر التشغيل» (scripts) المختصرة:

{
  "name": "my-app",
  "version": "1.0.0",
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  },
  "dependencies": {
    "react": "^18.2.0"
  }
}

فإذا نسخت مشروعًا على جهازٍ جديد، يكفي أمرٌ واحد، npm install، ليقرأ npm هذا الملفّ ويجلب كل الاعتماديّات إلى مجلّد node_modules. لهذا لا تُرفَع node_modules إلى Git (تستبعدها بـ.gitignore كما مرّ بك في مقال Git): أنت تنقل قائمة الأدوات لا الأدوات نفسها. وتُقسَم الاعتماديّات قسمين: ما يحتاجه الموقع ليعمل عند المستخدم (dependencies)، وما تحتاجه أنت أثناء التطوير فقط كأدوات البناء (devDependencies). وأمّا الرمز ^ قبل رقم الإصدار فيتبع نظام «الإصدار الدلاليّ» (SemVer): ثلاثة أرقامٍ (كبير.متوسّط.صغير) تخبرك حجم التغيير بين النسخ، فتسمح بالتحديثات الصغيرة الآمنة دون الكبيرة التي قد تكسر مشروعك.

وثمّة بدائل لـnpm تؤدّي الدور نفسه بكفاءةٍ أعلى أحيانًا، أشهرها pnpm وyarn، لكنّ npm يأتي افتراضيًّا مع Node.js فهو نقطة البداية، والمفاهيم نفسها تنطبق عليها جميعًا.

وإلى جانب package.json تجد ملفًّا اسمه package-lock.json يربك المبتدئ. الأوّل يقول «أريد إصدار React الثامن عشر أو أحدث منه»، أمّا الثاني فيسجّل الإصدار الدقيق الذي ثُبِّت فعلًا (مثل 18.2.0 بالضبط) ومعه إصدارات كل اعتماديّاته. فائدته أن يضمن أن يحصل كل من ينسخ مشروعك على النسخ نفسها حرفًا حرفًا، فلا يعمل المشروع على جهازك ويتعطّل على جهاز زميلك. لهذا تَرفع هذا الملفّ إلى Git (بخلاف node_modules)، فهو ضمانة «البناء القابل للتكرار».

كيف ترتبط الملفّات: الوحدات والاستيراد

وحتى يجمع المُجمِّع مئة ملفٍّ في حزمة، يجب أن يعرف كيف يرتبط بعضها ببعض. وهنا يأتي نظام «الوحدات» (Modules) في جافاسكربت الحديثة: تقسّم شيفرتك ملفّاتٍ صغيرةً كلٌّ منها مسؤولٌ عن وظيفةٍ واحدة، ثمّ «تصدّر» (export) منها ما تريد إتاحته، و«تستورد» (import) في ملفٍّ آخر ما تحتاجه:

// math.js
export function add(a, b) {
  return a + b;
}

// app.js
import { add } from './math.js';
console.log(add(2, 3));   // 5

هذا النظام يجعل شيفرتك منظّمةً قابلةً لإعادة الاستعمال، ويتيح للمُجمِّع أن يتتبّع «شجرة الاعتماديّات»: يبدأ من ملفٍّ رئيسيٍّ ويتبع كل import ليعرف ما يحتاجه المشروع فعلًا، فيحزم المستعمَل ويسقط المهمَل. وبالطريقة نفسها تستورد المكتبات الجاهزة من node_modules (مثل import React from 'react'). ففهم الاستيراد والتصدير شرطٌ لفهم كيف يفكّر المُجمِّع.

المُجمِّع: من مئة ملفٍّ إلى حزمةٍ واحدة

«المُجمِّع» (Bundler) هو الأداة التي تحلّ مشكلة العدد. تخيّله عاملًا يجمع كل ملفّات مشروعك (شيفرة، وأنماط، وصور) ويتتبّع كيف يستورد كلٌّ منها الآخر، ثمّ يحزمها في ملفّاتٍ قليلةٍ مُحسَّنة، فيقلّل عدد الطلبات ويزيل التكرار ويهيّئ التطبيق للنشر.[3] والمُجمِّع الكلاسيكيّ الذي ساد سنواتٍ هو webpack، قويٌّ ومرنٌ لكنّ إعداده كان معقّدًا يخيف المبتدئ، وبطيئًا في المشاريع الكبيرة.

وهنا ظهر «Vite» (تعني «سريع» بالفرنسية) فغيّر التجربة جذريًّا، وصار الخيار الافتراضيّ لمعظم المشاريع الحديثة.[4] سرّه أنه يفرّق بين وضعين. في وضع التطوير، يستفيد من «وحدات جافاسكربت الأصلية» (ES Modules) التي صارت المتصفّحات تفهمها، فلا يحزم شيئًا مقدّمًا بل يخدم الملفّات عند الطلب، فيبدأ خادمه في أقل من ثانيةٍ مهما كبر مشروعك (بينما كان webpack يأخذ عشرات الثواني). ومن أجمل ما يقدّمه «الاستبدال الساخن» (HMR): تعدّل سطرًا في شيفرتك فترى الأثر في المتصفّح فورًا دون إعادة تحميل الصفحة ودون أن تفقد حالتها. أمّا للنشر فيحزم كل شيءٍ في النسخة النهائية المُحسَّنة. ولا يبني Vite هذا كلّه من الصفر، بل يقف على أكتاف أدواتٍ سريعةٍ مكتوبةٍ بلغاتٍ مترجَمةٍ سريعة: يستعمل esbuild في التطوير وRollup في الإنتاج. لا يلزمك معرفة هذه الأسماء لتستعمل Vite، لكن اعرف أن السرعة التي تشعر بها ليست سحرًا، بل ثمرة هندسةٍ دقيقةٍ تحتها.

ماذا تفعل خطوة البناء فعلًا؟

حين تشغّل البناء، تجري سلسلة تحويلاتٍ على شيفرتك. أهمّها «الترجمة» (Transpilation): تحويل ما لا يفهمه المتصفّح إلى ما يفهمه، فتُترجَم JSX في React إلى جافاسكربت عاديّة، وTypeScript إلى جافاسكربت، وميزات اللغة الحديثة جدًّا إلى صيغةٍ أقدم تعمل في متصفّحاتٍ أكثر (تتولّى هذا أداةٌ مثل Babel). ثمّ «الحزم» الذي سبق وصفه. ثمّ «التصغير» (Minification): حذف المسافات والتعليقات وتقصير أسماء المتغيّرات، فيصغر حجم الملفّ دون تغيير عمله. ثمّ «هزّ الشجرة» (Tree-shaking): إسقاط الشيفرة التي لا تُستعمَل فعلًا من المكتبات، فلا يُحمَّل ما لا تحتاجه. وأخيرًا معالجة الأصول كالصور والخطوط وتحسينها.

ولا تقتصر هذه التحويلات على جافاسكربت، بل تطال CSS أيضًا. فإن كتبت بـSass (الذي مرّ بك في مقال CSS) تُرجِمَ إلى CSS عاديّ في البناء. وثمّة أداةٌ تُسمّى PostCSS تعالج أنماطك آليًّا، أشهر استعمالٍ لها «Autoprefixer» الذي يضيف البادئات التي تحتاجها بعض المتصفّحات دون أن تكتبها بيدك. كما يصغّر البناء ملفّات CSS ويزيل غير المستعمَل منها، تمامًا كما يفعل بجافاسكربت. فخطوة البناء تعتني بطبقات موقعك الثلاث، لا بالشيفرة وحدها.

ومن أدوات التوكيل التي ستلازمك أداتان للجودة لا للترجمة: «المدقّق» (Linter) مثل ESLint، الذي يفحص شيفرتك وأنت تكتب فينبّهك إلى الأخطاء والعادات السيّئة قبل أن تشغّلها؛ و«المنسّق» (Formatter) مثل Prettier، الذي يرتّب شيفرتك تلقائيًّا بأسلوبٍ موحّدٍ عند كل حفظ. مرّت بك هاتان بوصفهما إضافتين في محرّر VS Code، وهما هنا جزءٌ من توكيل المشروع نفسه، يضمن أن تبقى الشيفرة نظيفةً متّسقةً خاصّةً حين يعمل عليها فريقٌ كامل.

وحاجةٌ أخيرةٌ يحلّها التوكيل: «متغيّرات البيئة» (Environment Variables). فمشروعك يحتاج قيمًا تختلف بين جهازك والخادم الحيّ (كعنوان واجهة برمجةٍ أو مفتاحٍ سرّيّ)، ولا يصحّ أن تكتبها مباشرةً في شيفرتك المرفوعة. الحلّ ملفٌّ اسمه .env تضع فيه هذه القيم (وتستبعده من Git)، فيقرؤها أداة البناء ويحقنها وقت البناء. هكذا تفصل سرّك وإعداداتك عن شيفرتك، فلا يتسرّب مفتاحٌ ولا تختلط إعدادات التطوير بالإنتاج.

ومن هنا يتّضح الفرق بين «بناء التطوير» و«بناء الإنتاج». في التطوير تريد السرعة والوضوح: خادمٌ يبدأ فورًا، وشيفرةٌ مقروءةٌ يسهل تصحيحها، واستبدالٌ ساخنٌ يريك تغييراتك لحظيًّا. وفي الإنتاج تريد أصغر حجمٍ وأسرع تحميل: شيفرةٌ مصغّرةٌ محزومةٌ مهزوزةُ الشجرة، يخرجها Vite عادةً في مجلّدٍ اسمه dist هو ما ترفعه إلى الاستضافة. أنت تكتب لراحتك، والأداة تخرج للأداء.

وهذه المكاسب ليست تجميلًا؛ لها أثرٌ مقيسٌ في سرعة صفحتك. فكلّ كيلوبايتٍ يحذفه التصغير، وكلّ وحدةٍ مهملةٍ تسقطها هزّة الشجرة، يخفّفان ما يُنزّله المتصفّح ويعالجه قبل أن يرسم المحتوى، فيتحسّن ما تجمعه جوجل من مقاييس تجربة المستخدم تحت اسم «مؤشّرات الويب الأساسية» (Core Web Vitals)، كزمن رسم أكبر عنصرٍ في الصفحة (LCP). ولأنّ هذه المؤشّرات داخلةٌ في ترتيب نتائج البحث، فإنّ خطوة البناء التي قد تظنّها شأنًا تقنيًّا صرفًا تنعكس مباشرةً على سرعة موقعك وحظّه من الظهور.[5]

ولأن النسخة المنشورة مصغّرةٌ محزومةٌ يصعب قراءتها، قد تتساءل: كيف أصحّح خطأً فيها؟ هنا تأتي «خرائط المصدر» (Source Maps): ملفّاتٌ ينشئها البناء تربط كل سطرٍ في الشيفرة المصغّرة بموضعه الأصليّ في شيفرتك، فتشير إليك أدوات المطوّر في المتصفّح إلى السطر الحقيقيّ الذي كتبته لا إلى الفوضى المصغّرة. هكذا تنال أداء الإنتاج وراحة التصحيح معًا.

سير العمل عمليًّا

لنرَ هذا في خطواتٍ ملموسة. لإنشاء مشروعٍ حديثٍ بـVite، تكتب في الطرفية أمرًا واحدًا يهيّئ لك هيكل المشروع كاملًا (ويسألك عن إطار العمل الذي تريده):

npm create vite@latest my-app
cd my-app
npm install      # يجلب الاعتماديّات إلى node_modules
npm run dev      # يشغّل خادم التطوير السريع
npm run build    # يبني النسخة النهائية في مجلّد dist

الأمر الأوّل يولّد المشروع، والثاني يدخله، والثالث يجلب الأدوات، والرابع يشغّل خادم التطوير فتفتح موقعك على localhost وتطوّره بالاستبدال الساخن، والأخير يبني النسخة الجاهزة للنشر. لاحظ أن هذه الأوامر هي «أوامر التشغيل» نفسها التي رأيتَها في package.json، فـnpm run dev يشغّل ما عرّفته أنت تحت اسم dev. هذه الدورة (أنشئ، ثبّت، طوّر، ابنِ) هي إيقاع العمل اليوميّ لأيّ مطوّر واجهاتٍ حديث.

وأوامر التشغيل في package.json ليست محصورةً فيما يأتي جاهزًا؛ تعرّف أنت ما تشاء منها. فتضيف أمرًا لفحص جودة شيفرتك، أو لتشغيل اختباراتك، أو لتنسيقها، ثمّ تشغّله بـnpm run <الاسم>. وثمّة أمرٌ شقيقٌ مفيد هو npx: يشغّل أداةً من npm مرّةً دون تثبيتها دائمًا في مشروعك (ومنه npm create vite الذي يستدعي أداة الإنشاء عند الحاجة فقط). هكذا يصير package.json «لوحة تحكّمٍ» لمشروعك، تجمع فيها كل أوامرك المتكرّرة في أسماءٍ قصيرةٍ يتذكّرها فريقك.

وثلاثة التباساتٍ تصادفها أوّل مرّةٍ ويحسن أن تطمئنّ إليها. الأوّل ضخامة node_modules: مجلّدٌ قد يبلغ مئات الميغابايت لمشروعٍ صغير، وهذا طبيعيٌّ لأنه يضمّ كل اعتماديّة واعتماديّاتها؛ لا تقلق ولا ترفعه إلى Git، فهو يُعاد توليده بأمرٍ واحد. والثاني الفرق بين التثبيت «المحلّيّ» (في مشروعٍ بعينه) و«العامّ» (على جهازك كلّه بإضافة -g): آثِر المحلّيّ ليبقى لكل مشروعٍ أدواته بإصداراتها. والثالث عبارة «يعمل على جهازي»: غالبًا سببها اختلاف إصدارات الأدوات بين جهازين، وهنا تظهر قيمة package-lock.json الذي يوحّدها فيختفي السبب.

ولا تظنّ أنك «فقدت» بساطة الماضي حين كنت تفتح ملفّ HTML مباشرةً. للمشاريع الصغيرة البسيطة تبقى تلك الطريقة صحيحةً ولا تحتاج أدوات بناءٍ أصلًا؛ خطوة البناء ضريبةٌ تدفعها مقابل قوّةٍ تحتاجها فعلًا (إطار عمل، أو لغةٌ مترجَمة، أو مشروعٌ كبير). القاعدة نفسها التي تكرّرت في هذه السلسلة: لا تضِف أداةً قبل أن تفرضها حاجتك.

وقد بسّطت خطوة البناء النشر أيضًا بطريقةٍ أنيقة. فبدل أن ترفع مجلّد dist يدويًّا، تربط مستودعك على GitHub بمنصّة استضافةٍ حديثةٍ (مثل Netlify أو Vercel أو Cloudflare Pages)، فتشغّل هي أمر البناء تلقائيًّا في كل مرّةٍ تدفع فيها تغييرًا بـgit push، وتنشر النتيجة فورًا. هكذا يجتمع ما تعلّمته في مقال Git مع خطوة البناء في سير عملٍ واحد: تكتب، تدفع، يُبنى موقعك ويُنشَر دون تدخّلٍ منك. هذا النمط (المعروف بـJAMstack) صار شائعًا لأنه سريعٌ وآمنٌ ورخيص، وهو ثمرةٌ مباشرةٌ لاجتماع أدوات البناء مع نظام التحكّم بالإصدارات في تدفّقٍ واحد.

ولتتصوّر الترجمة بمثالٍ ملموس: تكتب في React سطرًا بلغة JSX يخلط الوسوم بالشيفرة (const el = <h1>مرحبًا</h1>)، وهو سطرٌ لا يفهمه المتصفّح إطلاقًا. تحوّله أداة البناء إلى استدعاء دالّةٍ صريحٍ بجافاسكربت عاديّة يفهمها كل متصفّح. أنت كتبت ما هو أوضح لك، والبناء أخرج ما هو مفهومٌ للآلة. هذا بالضبط ما يجري لكل سطرٍ في مشروعك الحديث، خلف الكواليس، في أجزاءٍ من الثانية.

من الأدوات إلى الأطر

فهمتَ الآن الطبقة التي تربك أكثر المبتدئين حين يفتحون مشروعًا حديثًا: npm الذي يجلب الأدوات، وpackage.json الذي يسرد حاجاتك، والمُجمِّع الذي يحزم مشروعك، وVite الذي يجعل التطوير سريعًا، وخطوة البناء التي تترجم شيفرتك وتحسّنها للنشر. لم تَعُد هذه الكلمات طلاسم، بل أدواتٌ تعرف لماذا وُجدت ومتى تستعملها.

ولنكن صادقين: هذه المنظومة أضافت تعقيدًا حقيقيًّا إلى ما كان يومًا بسيطًا كفتح ملفٍّ في المتصفّح، وكثيرٌ من المبتدئين يضيعون فيها قبل أن يكتبوا سطرًا مفيدًا. لكنّ الفرق أنك الآن تفهم سبب كل قطعةٍ منها، فلا تتعامل معها سحرًا تخشاه، بل أداةً تطيّعها. وأدوات مثل Vite صُمّمت أصلًا لتخفّف هذا العبء عن المبتدئ، فتمنحك مشروعًا جاهزًا للعمل بأمرٍ واحدٍ دون أن تضبط شيئًا بنفسك.

وهذه الطبقة ليست غايةً بذاتها، بل أساسٌ يقوم عليه ما هو أكبر. فالأدوات التي مرّ ذكرها (الترجمة والحزم والاستبدال الساخن) إنّما وُجدت أصلًا لتخدم تقنيةً غيّرت تطوير الواجهات: «أطر عمل جافاسكربت». والآن وقد صار التوكيل بين يديك وفهمت ما يجري خلف كل أمرٍ تكتبه، أنت جاهزٌ لها على بصيرةٍ لا على تخمين. وهذا موضوع المقال التالي: «أطر عمل جافاسكربت: React وVue وما بعدهما».

المصادر

  1. MDN: نظرة عامة على أدوات جانب العميل. لماذا يحتاج التطوير الحديث إلى خطوة بناء.
  2. npm Docs: عن npm. مدير الحزم والسجلّ وملفّ package.json.
  3. webpack: مفاهيم المُجمِّع. كيف يجمع المُجمِّع الملفّات ويحزمها.
  4. Vite: لماذا Vite. وحدات ES في التطوير والحزم في الإنتاج والاستبدال الساخن.
  5. web.dev: أكثر الطرق فاعليّةً لتحسين مؤشّرات الويب الأساسية. تقليل حجم الشيفرة المُرسَلة يحسّن مقاييس السرعة مثل LCP.

اترك تعليقاً

الحقول المميّزة بـ مطلوبة