تجميع متقدّم

نظرة عامة

يوفّر استخدام Closure Compiler مع compilation_level من ADVANCED_OPTIMIZATIONS معدّلات ضغط أفضل من التجميع باستخدام SIMPLE_OPTIMIZATIONS أو WHITESPACE_ONLY. تحقّق عملية التجميع باستخدام ADVANCED_OPTIMIZATIONS ضغطًا إضافيًا من خلال التعامل بشكل أكثر صرامة مع طرق تحويل الرموز وإعادة تسمية العناصر. ومع ذلك، يعني هذا النهج الأكثر صرامة أنّه يجب توخّي المزيد من الحذر عند استخدام ADVANCED_OPTIMIZATIONS للتأكّد من أنّ الرمز الناتج يعمل بالطريقة نفسها التي يعمل بها الرمز المدخل.

يوضّح هذا البرنامج التعليمي وظيفة ADVANCED_OPTIMIZATIONS مستوى التجميع والإجراءات التي يمكنك اتّخاذها للتأكّد من أنّ الرمز يعمل بعد تجميعه باستخدام ADVANCED_OPTIMIZATIONS. ويقدّم أيضًا مفهوم extern: وهو رمز يتم تعريفه في تعليمات برمجية خارجية عن التعليمات البرمجية التي يعالجها المترجم.

قبل قراءة هذا البرنامج التعليمي، يجب أن تكون على دراية بعملية تجميع JavaScript باستخدام إحدى أدوات Closure Compiler، مثل تطبيق المجمّع المستند إلى Java.

ملاحظة حول المصطلحات: تتوافق علامة سطر الأوامر --compilation_level مع الاختصارات الأكثر استخدامًا ADVANCED وSIMPLE، بالإضافة إلى الاختصارات الأكثر دقة ADVANCED_OPTIMIZATIONS وSIMPLE_OPTIMIZATIONS. يستخدم هذا المستند الشكل الأطول، ولكن يمكن استخدام الأسماء بشكل متبادل في سطر الأوامر.

  1. ضغط أفضل
  2. كيفية تفعيل ADVANCED_OPTIMIZATIONS
  3. الأمور التي يجب الانتباه إليها عند استخدام ADVANCED_OPTIMIZATIONS
    1. إزالة رمز تريد الاحتفاظ به
    2. أسماء المواقع الإلكترونية غير المتسقة
    3. تجميع جزأين من الرمز بشكل منفصل
    4. المَراجع غير الصالحة بين الرمز البرمجي المجمَّع وغير المجمَّع

ضغط أفضل

باستخدام مستوى التجميع التلقائي SIMPLE_OPTIMIZATIONS، يقلّل Closure Compiler حجم JavaScript من خلال إعادة تسمية المتغيرات المحلية. هناك رموز أخرى غير المتغيرات المحلية يمكن اختصارها، وهناك طرق أخرى لتقليص حجم الرمز غير إعادة تسمية الرموز. يستفيد التجميع باستخدام ADVANCED_OPTIMIZATIONS من النطاق الكامل لاحتمالات تصغير الرمز.

قارِن بين النتائج الخاصة بـ SIMPLE_OPTIMIZATIONS وADVANCED_OPTIMIZATIONS للرمز التالي:

function unusedFunction(note) {
  alert(note['text']);
}

function displayNoteTitle(note) {
  alert(note['title']);
}

var flowerNote = {};
flowerNote['title'] = "Flowers";
displayNoteTitle(flowerNote);

يؤدي التجميع باستخدام SIMPLE_OPTIMIZATIONS إلى تقصير الرمز البرمجي ليصبح على النحو التالي:

function unusedFunction(a){alert(a.text)}function displayNoteTitle(a){alert(a.title)}var flowerNote={};flowerNote.title="Flowers";displayNoteTitle(flowerNote);

يؤدي التجميع باستخدام ADVANCED_OPTIMIZATIONS إلى تقصير الرمز بالكامل ليصبح على النحو التالي:

alert("Flowers");

ينتج كلا النصين البرمجيين تنبيهًا يقرأ "Flowers"، ولكن النص البرمجي الثاني أصغر بكثير.

يتجاوز المستوى ADVANCED_OPTIMIZATIONS عملية الاختصار البسيط لأسماء المتغيرات بعدة طرق، بما في ذلك:

  • إعادة تسمية أكثر صرامة:

    يؤدي التجميع باستخدام SIMPLE_OPTIMIZATIONS فقط إلى إعادة تسمية مَعلمات note للدالتَين displayNoteTitle() وunusedFunction()، لأنّ هذه المَعلمات هي المتغيّرات الوحيدة في النص البرمجي التي تكون محلية لدالة. تعيد ADVANCED_OPTIMIZATIONS أيضًا تسمية المتغير العمومي flowerNote.

  • إزالة الرموز البرمجية غير النشطة:

    تؤدي عملية التجميع باستخدام ADVANCED_OPTIMIZATIONS إلى إزالة الدالة unusedFunction() بالكامل، لأنّه لا يتم استدعاؤها مطلقًا في الرمز.

  • تضمين الدوال البرمجية:

    يؤدي التجميع باستخدام ADVANCED_OPTIMIZATIONS إلى استبدال استدعاء displayNoteTitle() بالدالة alert() الفردية التي تشكّل نص الدالة. يُعرف استبدال استدعاء الدالة بنص الدالة باسم "التضمين". إذا كانت الدالة أطول أو أكثر تعقيدًا، قد يؤدي تضمينها إلى تغيير سلوك الرمز، ولكن يحدّد Closure Compiler أنّ تضمينها في هذه الحالة آمن ويوفّر مساحة. تتضمّن عملية التجميع مع ADVANCED_OPTIMIZATIONS أيضًا تضمين الثوابت وبعض المتغيّرات عندما تحدّد أنّه يمكنها إجراء ذلك بأمان.

هذه القائمة هي مجرد عينة من عمليات التحويل التي تقلّل الحجم والتي يمكن أن تنفّذها عملية تجميع ADVANCED_OPTIMIZATIONS.

كيفية تفعيل ADVANCED_OPTIMIZATIONS

لتفعيل ADVANCED_OPTIMIZATIONS في تطبيق Closure Compiler، أدرِج علامة سطر الأوامر --compilation_level ADVANCED_OPTIMIZATIONS، كما في الأمر التالي:

java -jar compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js hello.js

أمور يجب الانتباه إليها عند استخدام ADVANCED_OPTIMIZATIONS

في ما يلي بعض الآثار الجانبية الشائعة غير المقصودة لخيارات ADVANCED_OPTIMIZATIONS، والخطوات التي يمكنك اتّخاذها لتجنُّبها.

إزالة الرمز الذي تريد الاحتفاظ به

إذا كنت ستجمّع الدالة أدناه فقط باستخدام ADVANCED_OPTIMIZATIONS، سينتج عن Closure Compiler ناتج فارغ:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}

بما أنّ الدالة لا يتم استدعاؤها أبدًا في JavaScript الذي تمرّره إلى Closure Compiler، يفترض Closure Compiler أنّ هذا الرمز غير مطلوب.

في كثير من الحالات، يكون هذا السلوك هو ما تريده بالضبط. على سبيل المثال، إذا كنت تجمّع الرمز مع مكتبة كبيرة، يمكن لبرنامج Closure Compiler تحديد الدوال التي تستخدمها فعليًا من تلك المكتبة والتخلص من الدوال التي لا تستخدمها.

في المقابل، إذا تبيّن لك أنّ Closure Compiler يزيل دوال تريد الاحتفاظ بها، يمكنك اتّباع إحدى الطريقتَين التاليتَين لمنع حدوث ذلك:

  • انقل طلبات الدوال إلى الرمز البرمجي الذي تتم معالجته بواسطة Closure Compiler.
  • أدرِج ملفات externs للدوال التي تريد عرضها.

تتناول الأقسام التالية كل خيار بمزيد من التفصيل.

الحل: نقل استدعاءات الدوال إلى الرمز البرمجي الذي تتم معالجته بواسطة Closure Compiler

قد تواجه مشكلة إزالة الرمز البرمجي غير المرغوب فيه إذا كنت تترجم جزءًا فقط من الرمز البرمجي باستخدام Closure Compiler. على سبيل المثال، قد يكون لديك ملف مكتبة يحتوي على تعريفات الدوال فقط، وملف HTML يتضمّن المكتبة ويحتوي على الرمز البرمجي الذي يستدعي هذه الدوال. في هذه الحالة، إذا كنت تجمّع ملف المكتبة باستخدام ADVANCED_OPTIMIZATIONS، سيزيل Closure Compiler جميع دوال المكتبة.

أبسط حل لهذه المشكلة هو تجميع الدوال مع جزء البرنامج الذي يستدعي هذه الدوال. على سبيل المثال، لن يزيل Closure Compiler displayNoteTitle() عندما يترجم البرنامج التالي:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}
displayNoteTitle({'myTitle': 'Flowers'});

لا تتم إزالة الدالة displayNoteTitle() في هذه الحالة لأنّ Closure Compiler يرى أنّه يتم استدعاؤها.

بعبارة أخرى، يمكنك منع إزالة الرمز غير المرغوب فيه من خلال تضمين نقطة الدخول الخاصة ببرنامجك في الرمز الذي ترسله إلى Closure Compiler. نقطة دخول البرنامج هي الموضع في الرمز البرمجي الذي يبدأ البرنامج التنفيذ منه. على سبيل المثال، في برنامج ملاحظات الزهور من القسم السابق، يتم تنفيذ الأسطر الثلاثة الأخيرة بمجرد تحميل JavaScript في المتصفح. هذه هي نقطة الدخول إلى هذا البرنامج. لتحديد الرمز الذي يجب الاحتفاظ به، يبدأ Closure Compiler من نقطة الدخول هذه ويتتبّع مسار التحكّم في البرنامج من هناك.

الحل: تضمين Externs للوظائف التي تريد عرضها

يمكنك الاطّلاع على مزيد من المعلومات حول هذا الحل أدناه وفي الصفحة حول المتغيرات الخارجية وعمليات التصدير.

أسماء المواقع غير متّسقة

لا تغيّر عملية تجميع Closure Compiler أبدًا السلاسل الحرفية في الرمز، بغض النظر عن مستوى التجميع الذي تستخدمه. وهذا يعني أنّ عملية التجميع باستخدام ADVANCED_OPTIMIZATIONS تتعامل مع السمات بشكل مختلف حسب ما إذا كان الرمز البرمجي يصل إليها باستخدام سلسلة. إذا جمعت بين مراجع السلسلة إلى سمة ومراجع النقطة، يعيد Closure Compiler تسمية بعض المراجع إلى تلك السمة وليس البعض الآخر. نتيجةً لذلك، من المحتمل ألا يتم تنفيذ الرمز بشكل صحيح.

على سبيل المثال، خذ الرمز التالي:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}
var flowerNote = {};
flowerNote.myTitle = 'Flowers';

alert(flowerNote.myTitle);
displayNoteTitle(flowerNote);

إنّ آخر عبارتين في رمز المصدر هذا تؤديان المهمة نفسها تمامًا. ومع ذلك، عند ضغط الرمز باستخدام ADVANCED_OPTIMIZATIONS، ستحصل على ما يلي:

var a={};a.a="Flowers";alert(a.a);alert(a.myTitle);

تؤدي الجملة الأخيرة في الرمز المضغوط إلى حدوث خطأ. تمت إعادة تسمية المرجع المباشر إلى السمة myTitle ليصبح a، ولكن لم تتم إعادة تسمية المرجع المقتبس إلى myTitle ضمن الدالة displayNoteTitle. نتيجةً لذلك، يشير البيان الأخير إلى سمة myTitle لم تعُد متوفّرة.

الحلّ: الحفاظ على اتّساق أسماء المواقع

هذا الحل بسيط جدًا. بالنسبة إلى أي نوع أو عنصر، استخدِم صيغة النقطة أو السلاسل المقتبسة حصريًا. لا تخلط بين الصيغ، خاصةً عند الإشارة إلى السمة نفسها.

ننصحك أيضًا باستخدام صيغة النقطة متى أمكن ذلك، لأنّها تتيح إجراء عمليات تحقّق وتحسين أفضل. لا تستخدِم الوصول إلى السمة كسلسلة مقتبسة إلا عندما لا تريد أن يعيد Closure Compiler تسمية السمة، مثلاً عندما يكون الاسم من مصدر خارجي، مثل JSON الذي تم فك ترميزه.

تجميع جزأين من الرمز بشكل منفصل

إذا قسّمت تطبيقك إلى أجزاء مختلفة من الرمز البرمجي، قد تحتاج إلى تجميع الأجزاء بشكل منفصل. ومع ذلك، إذا كان هناك تفاعل بين جزأين من الرمز، قد يؤدي ذلك إلى حدوث صعوبة. وحتى إذا نجحت في ذلك، لن يكون الناتج من عمليتَي تشغيل Closure Compiler متوافقًا.

على سبيل المثال، لنفترض أنّ أحد التطبيقات مقسّم إلى جزأين: جزء يستردّ البيانات، وجزء يعرض البيانات.

في ما يلي الرمز البرمجي لاسترداد البيانات:

function getData() {
  // In an actual project, this data would be retrieved from the server.
  return {title: 'Flower Care', text: 'Flowers need water.'};
}

في ما يلي رمز لعرض البيانات:

var displayElement = document.getElementById('display');
function displayData(parent, data) {
  var textElement = document.createTextNode(data.text);
  parent.appendChild(textElement);
}
displayData(displayElement, getData());

إذا حاولت تجميع هذين الجزأين من الرمز بشكل منفصل، ستواجه عدة مشاكل. أولاً، يزيل Closure Compiler الدالة getData()، للأسباب الموضّحة في إزالة الرمز الذي تريد الاحتفاظ به. ثانيًا، يُنتج Closure Compiler خطأً فادحًا عند معالجة الرمز الذي يعرض البيانات.

input:6: ERROR - variable getData is undefined
displayData(displayElement, getData());

بما أنّ المترجم البرمجي لا يمكنه الوصول إلى الدالة getData() عند ترجمة الرمز الذي يعرض البيانات، فإنّه يتعامل مع getData على أنّه غير محدّد.

الحلّ: تجميع كل الرموز لصفحة واحدة

لضمان التجميع السليم، يجب تجميع كل الرموز لصفحة معًا في عملية تجميع واحدة. يمكن أن يقبل Closure Compiler ملفات JavaScript وسلاسل JavaScript متعددة كمدخلات، ما يتيح لك تمرير رموز المكتبة ورموز أخرى معًا في طلب تجميع واحد.

ملاحظة: لن تنجح هذه الطريقة إذا كنت بحاجة إلى دمج الرمز المجمَّع وغير المجمَّع. راجِع المراجع المعطّلة بين الرموز البرمجية المجمّعة وغير المجمّعة للحصول على نصائح حول كيفية التعامل مع هذه الحالة.

المراجع غير الصالحة بين الرمز البرمجي المجمَّع وغير المجمَّع

سيؤدي تغيير اسم الرمز في ADVANCED_OPTIMIZATIONS إلى إيقاف التواصل بين الرمز الذي تتم معالجته بواسطة Closure Compiler وأي رمز آخر. تعيد عملية التجميع تسمية الدوال المحدّدة في رمز المصدر. سيحدث خطأ في أي رمز خارجي يستدعي دوالك بعد إجراء عملية التجميع، لأنّه سيظل يشير إلى اسم الدالة القديم. وبالمثل، قد يغيّر Closure Compiler المراجع في الرمز البرمجي المجمّع إلى الرموز المحدّدة خارجيًا.

يُرجى العِلم أنّ "التعليمات البرمجية غير المجمّعة" تشمل أي تعليمات برمجية يتم تمريرها إلى الدالة eval() كسلسلة. لا يغيّر Closure Compiler أبدًا سلاسل الحروف في الرمز، لذا لا يغيّر السلاسل التي يتم تمريرها إلى عبارات eval().

يُرجى العِلم أنّ هذه المشاكل مرتبطة ولكنها مختلفة: الحفاظ على التواصل بين الرمز البرمجي الذي تم تجميعه والرمز البرمجي الخارجي، والحفاظ على التواصل بين الرمز البرمجي الخارجي والرمز البرمجي الذي تم تجميعه. لهذه المشاكل المنفصلة حل مشترك، ولكن هناك فروق دقيقة في كل جانب. للاستفادة إلى أقصى حد من Closure Compiler، من المهم معرفة حالة الاستخدام.

قبل المتابعة، ننصحك بالتعرّف على المتغيرات الخارجية وعمليات التصدير.

حلّ لاستدعاء رموز برمجية خارجية من رموز برمجية تم تجميعها: التجميع باستخدام Externs

إذا كنت تستخدم رمزًا بريًا تم توفيره في صفحتك من خلال نص برمجي آخر، عليك التأكّد من أنّ Closure Compiler لا يعيد تسمية مراجعك إلى الرموز المحدّدة في تلك المكتبة الخارجية. لإجراء ذلك، عليك تضمين ملف يحتوي على تعريفات العناصر الخارجية للمكتبة الخارجية في عملية التجميع. سيؤدي ذلك إلى إخبار Closure Compiler بالأسماء التي لا يمكنك التحكّم فيها وبالتالي لا يمكن تغييرها. يجب أن يستخدم الرمز الأسماء نفسها التي يستخدمها الملف الخارجي.

ومن الأمثلة الشائعة على ذلك واجهات برمجة التطبيقات مثل OpenSocial API وGoogle Maps API. على سبيل المثال، إذا كان الرمز البرمجي يستدعي دالة OpenSocial opensocial.newDataRequest()، بدون ملفات externs المناسبة، سيحوّل Closure Compiler هذا الاستدعاء إلى a.b().

حلّ لاستدعاء الرموز البرمجية المجمّعة من رموز برمجية خارجية: تنفيذ Externs

إذا كان لديك رمز JavaScript تعيد استخدامه كمكتبة، قد تحتاج إلى استخدام Closure Compiler لتقليص حجم المكتبة فقط مع السماح للرمز غير المجمَّع باستدعاء الدوال في المكتبة.

الحل في هذه الحالة هو تنفيذ مجموعة من الرموز الخارجية التي تحدّد واجهة برمجة التطبيقات العامة لمكتبتك. سيوفّر الرمز الخاص بك تعريفات للرموز المعرَّفة في هذه الملفات الخارجية. وهذا يعني أي فئات أو وظائف يشير إليها الموظفون الخارجيون. وقد يعني أيضًا أن تتضمّن صفوفك واجهات معرَّفة في الملفات الخارجية.

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

لتحقيق ذلك، تأكَّد من تضمين تعريفات externs في عملية تجميع الرمز البرمجي. قد يبدو هذا الأمر غير معتاد، لأنّنا غالبًا ما نعتقد أنّ تعريفات extern تأتي من مكان آخر، ولكن من الضروري إخبار Closure Compiler بالرموز التي تعرضها حتى لا تتم إعادة تسميتها.

من المهم هنا ملاحظة أنّك قد تتلقّى رسائل تشخيصية بشأن "تعريف مكرّر" حول الرمز الذي يحدّد الرموز الخارجية. يفترض Closure Compiler أنّ أي رمز في الملفات الخارجية يتم توفيره من خلال مكتبة خارجية، ولا يمكنه حاليًا فهم أنّك توفّر تعريفًا عن قصد. يمكنك إيقاف هذه البيانات التشخيصية بأمان، ويمكنك اعتبار عملية الإيقاف تأكيدًا على أنّك تستوفي متطلبات واجهة برمجة التطبيقات.

بالإضافة إلى ذلك، قد يتحقّق Closure Compiler من تطابق تعريفاتك مع أنواع التصريحات الخارجية. يوفّر ذلك تأكيدًا إضافيًا على أنّ تعريفاتك صحيحة.