تجميع متقدم

نظرة عامة

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

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

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

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

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

ضغط أفضل

باستخدام مستوى التجميع التلقائي من SIMPLE_OPTIMIZATIONS، يجعل Closure Compiler جافا سكريبت أصغر عن طريق إعادة تسمية المتغيرات المحلية. ومع ذلك، هناك رموز غير المتغيرات المحلية التي يمكن اختصارها، وهناك طرق لتقليص الرمز بخلاف إعادة تسمية الرموز. إنّ التجميع باستخدام 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

تتطلّب كل من واجهة مستخدم خدمة Closure Compiler وواجهة برمجة التطبيقات للخدمة والتطبيق طرقًا مختلفة لضبط السمة compilation_level على السمة ADVANCED_OPTIMIZATIONS.

كيفية تفعيل ADVANCED_OPTIMIZATIONS في واجهة مستخدم خدمة Closure Compiler

لتفعيل ADVANCED_OPTIMIZATIONS لواجهة مستخدم خدمة "Compiler"، انقر على زر الاختيار "إعدادات متقدمة".

كيفية تفعيل ADVANCED_OPTIMIZATIONS في واجهة برمجة تطبيقات خدمة Closure Compiler

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

#!/usr/bin/python2.4

import httplib, urllib, sys

params = urllib.urlencode([
    ('code_url', sys.argv[1]),
    ('compilation_level', 'ADVANCED_OPTIMIZATIONS'),
    ('output_format', 'text'),
    ('output_info', 'compiled_code'),
  ])

headers = { "Content-type": "application/x-www-form-urlencoded" }
conn = httplib.HTTPSConnection('closure-compiler.appspot.com')
conn.request('POST', '/compile', params, headers)
response = conn.getresponse()
data = response.read()
print data
conn.close()

كيفية تفعيل ADVANCED_OPTIMIZATIONS في تطبيق Closure Compiler

لتفعيل 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']);
}

ونظرًا لأن الدالة لا يتم استدعاؤها مطلقًا في جافا سكريبت التي تمرِّرها إلى برنامج التجميع، يفترض Closure Compiler أن هذه الشفرة غير مطلوبة!

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

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

  • انقل استدعاءات الدالة إلى الرمز الذي تمت معالجته بواسطة 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 عند نقطة الدخول هذه ويتتبع تدفق التحكم في البرنامج للأمام من هناك.

الحل: تضمين العناصر الخارجية للدوال التي ترغب في عرضها

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

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

لا يؤدي تجميع البرمجيات الختامية إلى تغيير أي أحرف حرفية للسلسلة في شفرتك، بغض النظر عن مستوى التجميع الذي تستخدمه. وهذا يعني أن التجميع باستخدام 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 قبول العديد من ملفات جافا سكريبت وسلاسل جافا سكريبت كإدخال، بحيث يمكنك تمرير رمز المكتبة والشفرات الأخرى معًا في طلب تجميع واحد.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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