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

دعنا نتحدث عن كلا الجانبين من هذه العملة.

لماذا التنسيق أهم مما تظن

رأيت فرقًا تضيع ساعات في مراجعات الكود تتجادل حول tabs مقابل spaces، والفواصل المنقوطة أو عدمها، وأين توضع الأقواس المعقوصة. هذا وقت لا يُقضى في تسليم المميزات. التنسيق المتسق يزيل هذه النقاشات تمامًا.

لكن الأمر ليس فقط عن انسجام الفريق. الكود المنسق بشكل موحد يجعل اكتشاف الأخطاء أسهل. تأمل هذا:

javascript

استدعاء sendNotification() هذا يعمل في كل مرة، وليس فقط للمسؤولين — المسافة البادئة مضللة. مع الأقواس المعقوصة الإلزامية (التي يفرضها معظم المنسقين)، يصبح هذا الخطأ واضحًا:

javascript

أفضل ممارسات التنسيق

اختر أسلوبًا وقم بأتمتته. لا تعتمد على البشر للتنسيق بشكل متسق. استخدم Prettier — لديه آراء قوية، وهذا هو المطلوب. اضبطه ليعمل عند الحفظ، ولن تفكر في التنسيق مرة أخرى أبدًا.

المسافة البادئة: مسافتان هي اتفاقية JavaScript/TypeScript. دليل أسلوب Google ودليل Airbnb وStandard كلهم متفقون على هذا.

الفواصل المنقوطة: استخدمها. نعم، لدى JavaScript خاصية ASI (Automatic Semicolon Insertion)، لكن لها مشاكل موثقة جيدًا. فقط أضف الفواصل المنقوطة.

طول السطر: حافظ على الأسطر أقل من 80-100 حرف. الأسطر الطويلة تسبب التمرير الأفقي وتجعل المقارنات أصعب في القراءة.

كيف يعمل التصغير فعلاً

التصغير هو عكس التنسيق — يجعل الكود أصغر ما يمكن للإنتاج. إليك ما يفعله مُصغّر مثل Terser:

  • إزالة المسافات والتعليقات — كل تلك المسافات وعلامات التبويب وفواصل الأسطر وتعليقات // TODO: fix later؟ تختفي.
  • تقصير أسماء المتغيراتuserAccountBalance يصبح a. calculateMonthlyPayment يصبح b. المتغيرات المحلية فقط — لن يُعيد تسمية الأشياء التي قد يشير إليها كود خارجي.
  • حذف الكود الميت — إذا لم يتم استدعاء دالة أبدًا، يتم حذفها.
  • الحساب المسبق للثوابتconst TAX_RATE = 0.2; total * (1 + TAX_RATE) يصبح total*1.2.

النتيجة؟ عادةً تقليل 50-70% من حجم الملف. إليك مقارنة قبل/بعد:

قبل (مقروء، 147 بايت):

javascript

بعد (مصغر، 62 بايت):

javascript

نفس الوظيفة، أصغر بـ 58%.

لا تنسَ خرائط المصدر

الكود المصغر غير مقروء، مما يجعل تتبع أخطاء الإنتاج مؤلمًا. خرائط المصدر تحل هذا بربط الكود المصغر بمصدرك الأصلي. كل أداة بناء حديثة تولدها — تأكد من رفعها إلى خدمة تتبع الأخطاء (Sentry، Bugsnag، إلخ).

سير العمل العملي

سير العمل العملي

في التطوير: اكتب كودًا منسقًا وسهل القراءة. استخدم Prettier + ESLint للتنسيق والفحص التلقائي. في الإنتاج: صغّر كل شيء من خلال أداة البناء (webpack، Vite، esbuild).

إعداد Prettier + ESLint في 60 ثانية

إذا لم تقم بأتمتة التنسيق بعد، إليك أسرع إعداد:

javascript

ثم أضف سكربت تنسيق إلى package.json: "format": "prettier --write src/**/*.{js,ts,jsx,tsx}". شغّله مرة واحدة لتنسيق قاعدة الكود بأكملها، واضبط محررك للتنسيق عند الحفظ. قد يكون الفرق الأولي ضخمًا، لكن بعد ذلك، انتهت نقاشات التنسيق إلى الأبد.

Tree Shaking مقابل التصغير

كثيرًا ما يخلط الناس بين هاتين التقنيتين، لكنهما تقنيتا تحسين مختلفتان تعملان معًا:

  • التصغير يجعل الملفات الفردية أصغر بإزالة المسافات وتقصير الأسماء وحساب التعبيرات مسبقًا. لا يزيل التصديرات غير المستخدمة.
  • Tree shaking يزيل الكود غير المستخدم عبر الوحدات. إذا استوردت { debounce } فقط من lodash-es، فإن tree shaking يزيل كل شيء آخر من الحزمة.

كلاهما ضروري لبناء الإنتاج. المجمّعات الحديثة مثل Vite وesbuild تفعل كليهما تلقائيًا — فقط تأكد من استخدام استيرادات ES modules (import) بدلاً من CommonJS (require) حتى يتمكن tree shaking من تحليل رسم التبعيات الخاص بك.

مطبات التصغير الشائعة

1. الاعتماد على function.name في الإنتاج. المصغرات تعيد تسمية الدوال، لذا myFunction.name سيعيد "a" أو شيئًا عديم الفائدة بالمثل في الإنتاج. إذا كنت بحاجة لأسماء دوال ثابتة (للتسجيل أو تتبع الأخطاء أو الانعكاس)، استخدم معرفات نصية صريحة بدلاً من ذلك.

2. تصغير كود يستخدم eval(). eval() يمكنه الإشارة إلى أي متغير بالاسم، لذا المصغّر لا يمكنه إعادة تسمية أي شيء بأمان في ذلك النطاق. معظم المصغرات ستتخطى إعادة التسمية في الدوال التي تحتوي على eval()، لكن هذا يحد من التحسين بشكل كبير. تجنب eval() تمامًا إن أمكن.

3. عدم اختبار البناء المصغر. بعض الأخطاء تظهر فقط بعد التصغير — خاصة حول تشويه أسماء الخصائص. دائمًا شغّل مجموعة اختباراتك على بناء الإنتاج، وليس فقط بناء التطوير.

قياس حجم الحزمة

التصغير مهم فقط إذا كنت تعرف من أين تبدأ. إليك طرقًا سريعة للتحقق من حجم حزمتك:

javascript

هذه الأدوات تولد خرائط شجرية مرئية تُظهر بالضبط أي التبعيات تلتهم حزمتك. قد تتفاجأ — وجدت مرة مشروعًا حيث بيانات اللغات في moment.js شكّلت 500KB من الحزمة النهائية. التحويل إلى dayjs وفّر 490KB فورًا.

Prettier مقابل ESLint: ليسا نفس الشيء

حسنًا، أحتاج أن أقول هذا لأنني أرى هذا الخلط *في كل مكان*: Prettier وESLint ليسا نفس الأداة. حتى أنهما لا يقومان بنفس العمل. ومع ذلك، أرى باستمرار مطورين يعاملونهما كأنهما قابلان للتبادل. دعني أوضح.

Prettier هو منسق كود. يهتم بكيف *يبدو* كودك — المسافات، فواصل الأسطر، الفواصل المنقوطة، الفواصل الزائدة، نمط علامات الاقتباس، المسافات البادئة. هذا كل شيء. لا يعرف ولا يهتم ما إذا كان كودك يعمل فعلاً. يمكن أن يكون لديك دالة تحذف قاعدة بياناتك بالكامل وسيتأكد Prettier فقط من أنها ذات مسافات بادئة جميلة.

ESLint، من ناحية أخرى، هو فاحص كود. يهتم بـ*جودة* الكود — المتغيرات غير المستخدمة، الكود الذي لا يمكن الوصول إليه، الأخطاء المحتملة، معالجة الأخطاء المفقودة، مشاكل إمكانية الوصول. يلتقط الأشياء التي ستلدغك في الساعة 2 صباحًا يوم السبت عندما يتم استدعاؤك.

لكن هنا المشكلة — ESLint *أيضًا* لديه بعض قواعد التنسيق المدمجة، وهنا يصبح الأمر فوضويًا. إذا شغّلت الأداتين بدون تكوينهما بشكل صحيح، ستتقاتلان. Prettier ينسق كودك بطريقة، ESLint يشتكي أنه يجب أن يكون بطريقة أخرى، تصلحه لـ ESLint، Prettier يعيد تنسيقه... حلقة لا نهائية من الإحباط.

الحل بسيط جدًا: استخدم eslint-config-prettier. يوقف كل قواعد ESLint التي تتعارض مع Prettier، فيركز ESLint على جودة الكود ويتولى Prettier التنسيق. لا مزيد من المعارك.

javascript

لاحظ كيف أن "prettier" هو العنصر الأخير في مصفوفة extends؟ هذا حاسم — يجب أن يتجاوز أي قواعد تنسيق من الإضافات المذكورة فوقه. رأيت فرقًا تقضي ساعات في تتبع تعارضات ESLint/Prettier لتكتشف أن الترتيب كان خاطئًا. ثق بي في هذا.

إعدادي الموصى به: دع Prettier يتعامل مع كل شيء شكلي، وقم بتكوين قواعد ESLint التي تلتقط الأخطاء فعلاً. لا تضيع ESLint في الشكوى من الفواصل المنقوطة عندما يتعامل Prettier مع ذلك بالفعل.

النقاش الكبير: Tabs مقابل Spaces (محسوم)

حسنًا، دعنا نتحدث عن الحرب المقدسة للبرمجة. Tabs أو spaces؟ رأيت صداقات تنتهي بسبب هذا النقاش. رأيت خيوط Slack تستمر *لأيام*. شخصيًا شهدت مطورًا كبيرًا يكتب صفحة من 2000 كلمة على Confluence يدافع عن tabs. كان رائعًا وغير ضروري تمامًا.

لنلقِ نظرة على البيانات الفعلية. استطلاع مطوري Stack Overflow أظهر باستمرار أن spaces أكثر شعبية — تقريبًا 60-65% من المطورين يفضلون spaces. تحليل GitHub الخاص للمستودعات العامة يحكي قصة مشابهة.

لكن هذا هو المثير: يختلف بشكل كبير حسب اللغة. Go يستخدم tabs — هذا ليس حتى نقاشًا، gofmt يفرض tabs ولا أحد يجادل مع gofmt. Python يستخدم 4 spaces — PEP 8 يقول ذلك، ولا تجادل مع PEP 8 أيضًا. JavaScript وTypeScript؟ المجتمع استقر إلى حد كبير على 2 spaces، وعمليًا كل دليل أسلوب رئيسي يوافق.

حجة إمكانية الوصول لـ tabs مقنعة فعلاً — tabs تسمح لكل مطور بضبط العرض المرئي المفضل لديه، وهذا مهم للمطورين ذوي الإعاقات البصرية. هذا سبب حقيقي ومشروع لتفضيل tabs.

لكن تعرف ماذا؟ هذا هو الجواب *الصحيح فعلاً*، وأعني ذلك بصدق: استخدم ما يفرضه المنسق الخاص بك وتوقف عن الجدال. إذا كان مشروعك يستخدم Prettier مع tabWidth: 2 وuseTabs: false، فأنت تستخدم 2 spaces. نقطة. المنسق يتخذ القرار، أنت تقبله، وتنفق طاقتك على أشياء مهمة فعلاً — مثل ما إذا كان تطبيقك ينهار عندما يدخل شخص ما إيموجي في صندوق البحث.

الحياة أقصر من أن تُقضى في جدالات التنسيق. دع الروبوتات تقرر.

التصغير الحديث: esbuild وSWC وثورة السرعة

لسنوات، كان Terser ملك تصغير JavaScript. حل محل UglifyJS، كان مجربًا في المعارك، وعمل بشكل رائع. المشكلة الوحيدة؟ بطيء. بطيء *جدًا* على قواعد الكود الكبيرة. ولا أعني بطء "اذهب لتحضير قهوة" — أعني بطء "أعد التفكير في اختياراتك المهنية بينما تحدق في خط أنابيب CI".

ثم ظهر esbuild وغيّر كل شيء. مكتوب بلغة Go، esbuild أسرع 10-100 مرة من Terser. لا أبالغ — المقاييس شبه هزلية. مشروع يستغرق Terser فيه 30 ثانية للتصغير؟ esbuild ينجزه في 300 ملي ثانية. أول مرة رأيته، ظننت صدقًا أن شيئًا معطل لأنه انتهى بهذه السرعة.

SWC منافس آخر، مكتوب بلغة Rust. ليس بنفس سرعة esbuild للتصغير الصرف، لكنه سلسلة أدوات أكثر اكتمالاً — يتعامل مع التحويل والتجميع والتصغير كلها في واحد. إذا كنت تستخدم Next.js، فأنت تستخدم SWC بالفعل تحت الغطاء.

إليك مقارنة تقريبية على مشروع متوسط الحجم (~500 ملف JS):

ToolLanguageMinification TimeNotes
TerserJavaScript~25sBattle-tested, most compatible
esbuildGo~0.3sBlazing fast, some edge cases
SWCRust~0.8sFull toolchain, great ecosystem

هل فرق السرعة مهم فعلاً؟ بصراحة، لمشروع صغير ببناء مدته 5 ثوانٍ، على الأرجح لا. لكن عندما تشغل CI/CD على monorepo كبير وخط أنابيب البناء يعمل 50 مرة في اليوم؟ تلك الدقائق تتراكم بسرعة. رأيت فرقًا تقتطع 10+ دقائق من أوقات CI فقط بالانتقال من Terser إلى esbuild.

الخبر الجيد هو أنك إذا كنت تستخدم Vite، فأنت تحصل بالفعل على esbuild لبناء التطوير وRollup (مع Terser أو esbuild) للإنتاج. Next.js يستخدم SWC. Angular يجرب esbuild أيضًا. النظام البيئي يتحرك بسرعة هنا.

كلمة تحذير: esbuild وSWC لا ينتجان دائمًا مخرجات مطابقة لـ Terser بايت ببايت. في حالات نادرة، تحسينات Terser الأكثر عدوانية تنتج حزمًا أصغر قليلاً. لكننا نتحدث عن ربما 1-2% فرق — يستحق تمامًا تحسين السرعة 100 ضعف في معظم الحالات.

تصغير CSS وHTML أيضًا

تحدثنا عن JavaScript، لكن لا تتجاهل تصغير CSS وHTML. جديًا، رأيت مشاريع حيث حزمة CSS كانت *أكبر* من JavaScript. خاصة إذا كنت تستخدم إطار عمل utility-first مثل Tailwind (قبل أن يقوم PurgeCSS بعمله).

لـ CSS، cssnano هي الأداة المفضلة. تفعل أكثر من مجرد إزالة المسافات — تدمج أيضًا القواعد المكررة، وتحول الألوان إلى صيغ أقصر (#ff0000 يصبح #f00 أو حتى red)، وتزيل الخصائص الزائدة، وتحسن تعبيرات calc(). على ورقة أنماط نموذجية، يمكنك توقع 30-50% توفير.

css

لـ HTML، html-minifier-terser يزيل المسافات بين العلامات، ويزيل علامات الإغلاق الاختيارية، ويصغر CSS وJS المضمنين، ويزيل تعليقات HTML، ويطوي السمات المنطقية. فعال بشكل مدهش — رأيت 20-30% تقليل في الصفحات الغنية بـ HTML.

والآن الجزء الجيد: إذا كنت تستخدم Angular أو React أو Vue أو أي إطار عمل حديث تقريبًا مع خطوة بناء، هذا يحدث بالفعل تلقائيًا. أداة البناء الخاصة بك تتعامل مع تصغير CSS وHTML كجزء من بناء الإنتاج. لكن معرفة ما يحدث تحت الغطاء مفيد حقًا — خاصة عندما تحتاج لتتبع لماذا HTML الإنتاج لا يطابق مصدرك، أو عندما تحاول تحسين آخر مسار تصيير حرج.

تنسيق الكود في خطوط أنابيب CI/CD

اسمع، هنا المشكلة مع "التنسيق عند الحفظ" — يعمل فقط إذا كان *كل شخص في الفريق* قد ضبطه بشكل صحيح. وقد لدغني هذا من قبل. تعرف السيناريو: شخص ينضم للفريق، يستنسخ المستودع، يبدأ بالتعديلات، ويرسل PR بـ 400 تغيير تنسيق ممزوجة مع 5 أسطر من الكود الفعلي. مراجعة ذلك الـ PR كابوس.

الحل؟ افرض التنسيق في CI. اجعل من المستحيل دمج كود غير منسق. إليك الطريقة:

الخطوة 1: أضف فحص CI. أضف prettier --check . إلى خط أنابيب CI. ينتهي بكود 1 إذا كان أي ملف لا يطابق تنسيق Prettier. بدون حجج، بدون نقاشات — CI هو القانون.

javascript

الخطوة 2: أضف خطافات pre-commit. التقاط مشاكل التنسيق في CI أمر رائع، لكن الأفضل التقاطها *قبل* أن يتم عمل commit للكود. هنا يأتي دور Husky وlint-staged.

javascript
javascript
javascript

جمال lint-staged أنه يعمل فقط على الملفات التي غيرتها فعلاً، وليس قاعدة الكود بأكملها. فخطاف pre-commit يأخذ 1-2 ثانية بدلاً من 30. لن يقوم أحد بتعطيل خطاف يأخذ ثانية واحدة.

رأيت فرقًا تنتقل من "التنسيق مصدر دائم للاحتكاك في PRs" إلى "لا نفكر أبدًا في التنسيق حرفيًا" في غضون أسبوع من إعداد هذا. إنه أحد تلك الأشياء حيث إعداد الـ 15 دقيقة يؤتي ثماره ألف مرة.

دراسات حالة حقيقية لحجم الحزمة

لنتحدث عن أرقام حقيقية، لأن النصيحة المجردة عن "إبقاء الحزم صغيرة" ليست مفيدة جدًا بدون سياق. رأيت هذه السيناريوهات بالضبط تحدث في مشاريع إنتاج، والفروقات صادمة حقًا.

الحالة 1: lodash مقابل lodash-es. هذه الكلاسيكية. إذا فعلت import _ from 'lodash' واستخدمت فقط _.debounce، فأنت تستورد المكتبة بأكملها — 71.5 KB مصغر + مضغوط. انتقل إلى import { debounce } from 'lodash-es' ومع tree shaking، أنت تنظر إلى حوالي 1.5 KB فقط لتلك الدالة. هذا تقليل 98%. تحقق بنفسك على Bundlephobia.

الحالة 2: moment.js مقابل dayjs. كان Moment.js المعيار الذهبي للتعامل مع التواريخ لسنوات، لكنه يأتي بـ 72.1 KB مصغر + مضغوط — وهذا يشمل كل بيانات اللغات افتراضيًا. Day.js لديه API شبه متطابق لكنه بـ 2.9 KB. ليس خطأ مطبعي. 72 KB مقابل 3 KB لنفس الوظائف تقريبًا. فريق Moment.js أنفسهم الآن يوصون ببدائل.

الحالة 3: مكتبات الأيقونات. هذا يفاجئ الناس دائمًا. إذا فعلت import { FaHome } from 'react-icons/fa'، فلا مشكلة — هذا يدعم tree shaking. لكن بعض مكتبات الأيقونات غير مهيأة لـ tree shaking، وينتهي بك الأمر باستيراد مئات أيقونات SVG عندما تحتاج 5 فقط. رأيت استيرادات أيقونات تضيف 200+ KB للحزم. تحقق دائمًا أن مكتبة أيقوناتك تدعم tree shaking، أو استورد الأيقونات فرديًا.

الحالة 4: مكتبات تنسيق التواريخ. احتجت لتنسيق تاريخ واحد في منطقة زمنية محددة؟ رأيت مرة مشروعًا يستورد moment-timezone (النسخة الكاملة مع كل بيانات المناطق الزمنية) — هذا 97 KB مصغر + مضغوط — فقط لتنسيق تاريخ واحد. API الأصلي Intl.DateTimeFormat يتعامل مع معظم تنسيقات المناطق الزمنية أصلاً بدون تكلفة على الحزمة.

الدرس هنا ليس "لا تستخدم التبعيات أبدًا" — هذا سيكون سخيفًا. الدرس هو: اعرف ما تستورده وكم يكلف. شغّل npx source-map-explorer build/static/js/*.js أو تحقق من Bundlephobia قبل إضافة تلك المكتبة الجديدة اللامعة. مستخدموك على اتصالات الجوال البطيئة سيشكرونك.

جربه بنفسك

تحتاج لتنظيف كود فوضوي بسرعة؟ الصقه في JavaScript Formatter للحصول على مخرجات فورية ومقروءة. مستعد لتقليصه للإنتاج؟ JavaScript Minifier يقلصه إلى الحد الأدنى. وإذا لم تكن متأكدًا أن كودك صالح أصلاً، مرره أولاً عبر JavaScript Validator.