تالیف پیشرفته

بررسی اجمالی

استفاده از کامپایلر بسته با سطح compilation_level ADVANCED_OPTIMIZATIONS نرخ فشرده سازی بهتری را نسبت به کامپایل با SIMPLE_OPTIMIZATIONS یا WHITESPACE_ONLY می دهد. کامپایل با ADVANCED_OPTIMIZATIONS با استفاده از روش‌هایی که کد را تغییر می‌دهد و نمادها را تغییر می‌دهد، فشرده‌سازی بیشتری به دست می‌آورد. با این حال، این رویکرد تهاجمی تر به این معنی است که هنگام استفاده از ADVANCED_OPTIMIZATIONS باید مراقب باشید تا مطمئن شوید که کد خروجی همانند کد ورودی کار می کند.

این آموزش نشان می دهد که سطح کامپایل ADVANCED_OPTIMIZATIONS چه می کند و چه کاری می توانید انجام دهید تا مطمئن شوید کد شما پس از کامپایل با ADVANCED_OPTIMIZATIONS کار می کند. همچنین مفهوم خارجی را معرفی می کند: نمادی که در کدی خارج از کد پردازش شده توسط کامپایلر تعریف می شود.

قبل از خواندن این آموزش، باید با فرآیند کامپایل جاوا اسکریپت با یکی از ابزارهای Closure Compiler ( واسط کاربری سرویس کامپایلر، API سرویس کامپایلر یا برنامه کامپایلر ) آشنا شوید.

نکته ای در مورد اصطلاحات: پرچم خط فرمان --compilation_level از اختصارات رایج ADVANCED و SIMPLE و همچنین دقیق تر ADVANCED_OPTIMIZATIONS و SIMPLE_OPTIMIZATIONS می کند. این سند از فرم طولانی تر استفاده می کند، اما نام ها ممکن است به جای یکدیگر در خط فرمان استفاده شوند.

  1. فشرده سازی حتی بهتر
  2. نحوه فعال کردن ADVANCED_OPTIMIZATIONS
  3. هنگام استفاده از ADVANCED_OPTIMIZATIONS چه نکاتی را باید رعایت کرد
    1. حذف کدی که می خواهید نگه دارید
    2. نام های دارایی متناقض
    3. کامپایل دو قسمت از کد به طور جداگانه
    4. مراجع شکسته بین کد کامپایل شده و کامپایل نشده

فشرده سازی حتی بهتر

با سطح کامپایل پیش‌فرض SIMPLE_OPTIMIZATIONS ، کامپایلر بسته با تغییر نام متغیرهای محلی، جاوا اسکریپت را کوچک‌تر می‌کند. با این حال، نمادهای دیگری غیر از متغیرهای محلی وجود دارد که می‌توان آنها را کوتاه کرد، و راه‌هایی برای کوچک کردن کد به جز تغییر نام نمادها وجود دارد. کامپایل با 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() که بدنه تابع را می سازد، جایگزین می کند. این جایگزینی فراخوانی تابع با بدنه تابع به عنوان "inlining" شناخته می شود. اگر تابع طولانی‌تر یا پیچیده‌تر بود، درون‌خط‌کردن آن ممکن است رفتار کد را تغییر دهد، اما کامپایلر Closure تشخیص می‌دهد که در این مورد درون‌خط‌سازی ایمن است و باعث صرفه‌جویی در فضا می‌شود. کامپایل با ADVANCED_OPTIMIZATIONS همچنین ثابت ها و برخی متغیرها را در زمانی که مشخص می کند می تواند این کار را به صورت ایمن انجام دهد، داخل می کند.

این فهرست فقط نمونه‌ای از تبدیل‌های کاهش‌دهنده اندازه است که مجموعه ADVANCED_OPTIMIZATIONS می‌تواند انجام دهد.

نحوه فعال کردن ADVANCED_OPTIMIZATIONS

UI سرویس Closure Compiler، سرویس API و برنامه کاربردی، همگی روش‌های مختلفی برای تنظیم compilation_level روی ADVANCED_OPTIMIZATIONS دارند.

نحوه فعال کردن ADVANCED_OPTIMIZATIONS در رابط کاربری Closure Compiler Service

برای فعال کردن ADVANCED_OPTIMIZATIONS برای رابط کاربری سرویس کامپایلر بسته، روی دکمه رادیویی «پیشرفته» کلیک کنید.

نحوه فعال کردن ADVANCED_OPTIMIZATIONS در API سرویس کامپایلر بسته

برای فعال کردن ADVANCED_OPTIMIZATIONS برای API سرویس کامپایلر بسته، یک پارامتر درخواست به نام compilation_level با مقدار ADVANCED_OPTIMIZATIONS مانند برنامه پایتون زیر اضافه کنید:

#!/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 ارسال می کنید، از حذف کد ناخواسته جلوگیری کنید. نقطه ورود یک برنامه، جایی در کد است که برنامه شروع به اجرا می کند. به عنوان مثال، در برنامه گل یادداشت از قسمت قبل، سه خط آخر به محض بارگذاری جاوا اسکریپت در مرورگر اجرا می شود. این نقطه ورود برای این برنامه است. برای تعیین کدهایی که باید نگه دارید، 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() را به دلایلی که در Removal of Code You Want to Keep توضیح داده شده حذف می کند. دوم، Closure Compiler هنگام پردازش کدی که داده ها را نمایش می دهد، یک خطای مرگبار ایجاد می کند.

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

از آنجایی که کامپایلر هنگام کامپایل کدی که داده ها را نمایش می دهد به تابع getData() دسترسی ندارد، getData را تعریف نشده تلقی می کند.

راه حل: همه کدها را برای یک صفحه با هم کامپایل کنید

برای اطمینان از کامپایل مناسب، تمام کدهای یک صفحه را با هم در یک اجرای کامپایل کامپایل کنید. کامپایلر Closure می تواند چندین فایل جاوا اسکریپت و رشته های جاوا اسکریپت را به عنوان ورودی بپذیرد، بنابراین می توانید کد کتابخانه و سایر کدها را با هم در یک درخواست کامپایل ارسال کنید.

توجه: اگر نیاز به ترکیب کدهای کامپایل شده و کامپایل نشده دارید، این روش کار نخواهد کرد. برای راهنمایی در مورد مدیریت این وضعیت، به مراجع شکسته بین کد کامپایل شده و کامپایل نشده مراجعه کنید.

مراجع شکسته بین کد کامپایل شده و کامپایل نشده

تغییر نام نماد در ADVANCED_OPTIMIZATIONS ارتباط بین کد پردازش شده توسط Closure Compiler و هر کد دیگری را قطع می کند. Compilation توابع تعریف شده در کد منبع شما را تغییر نام می دهد. هر کد خارجی که توابع شما را فراخوانی می کند پس از کامپایل شکسته می شود، زیرا همچنان به نام تابع قدیمی اشاره دارد. به طور مشابه، ارجاعات در کد کامپایل شده به نمادهای تعریف شده خارجی ممکن است توسط Closure Compiler تغییر یابد.

به خاطر داشته باشید که "کد کامپایل نشده" شامل هر کدی است که به eval() به عنوان رشته ارسال می شود. Closure Compiler هرگز کلمات رشته را در کد تغییر نمی دهد، بنابراین Closure Compiler رشته های ارسال شده به دستورات eval() را تغییر نمی دهد.

توجه داشته باشید که اینها مشکلات مرتبط اما متمایز هستند: حفظ ارتباط کامپایل شده به خارجی و حفظ ارتباط خارجی به کامپایل شده. این مشکلات جداگانه یک راه حل مشترک دارند، اما تفاوت های ظریف در هر طرف وجود دارد. برای استفاده حداکثری از Closure Compiler، مهم است که بدانید کدام مورد را دارید.

قبل از ادامه، ممکن است بخواهید با خارجی ها و صادرات آشنا شوید.

راه حل فراخوانی کد خارجی از کد کامپایل شده: کامپایل با Externs

اگر از کد ارائه شده به صفحه خود توسط اسکریپت دیگری استفاده می کنید، باید مطمئن شوید که Closure Compiler نام مراجع شما را به نمادهای تعریف شده در آن کتابخانه خارجی تغییر نمی دهد. برای انجام این کار، یک فایل حاوی اکسترن های کتابخانه خارجی را در کامپایل خود قرار دهید. این به Closure Compiler می گوید که کدام نام ها را کنترل نمی کنید و بنابراین نمی توان آنها را تغییر داد. کد شما باید از همان نام هایی استفاده کند که فایل خارجی استفاده می کند.

نمونه‌های رایج این مورد APIهایی مانند OpenSocial API و Google Maps API هستند. به عنوان مثال، اگر کد شما تابع OpenSocial opensocial.newDataRequest() را فراخوانی کند، بدون خارجی های مناسب، Closure Compiler این فراخوانی را به ab() تبدیل می کند.

راه حل فراخوانی کد کامپایل شده از کد خارجی: پیاده سازی خارجی ها

اگر کد جاوا اسکریپتی دارید که مجدداً به عنوان کتابخانه از آن استفاده می کنید، ممکن است بخواهید از Closure Compiler برای کوچک کردن کتابخانه استفاده کنید و در عین حال به کدهای کامپایل نشده اجازه دهید تا توابع موجود در کتابخانه را فراخوانی کنند.

راه حل در این شرایط پیاده سازی مجموعه ای از خارجی ها است که API عمومی کتابخانه شما را تعریف می کنند. کد شما تعاریفی را برای نمادهای اعلام شده در این قسمت های خارجی ارائه می دهد. این به معنای هر کلاس یا عملکردی است که افراد خارجی شما ذکر می کنند. همچنین ممکن است به این معنا باشد که کلاس‌های شما رابط‌های اعلام شده در اکسترن‌ها را پیاده‌سازی کنند.

این اکسترن ها برای دیگران نیز مفید هستند، نه فقط برای خودتان. مصرف کنندگان کتابخانه شما در صورتی که در حال کامپایل کردن کد خود هستند، باید آنها را درج کنند، زیرا کتابخانه شما از دیدگاه آنها یک اسکریپت خارجی را نشان می دهد. خارجی ها را به عنوان قرارداد بین خود و مصرف کنندگان خود در نظر بگیرید، هر دوی شما به یک کپی نیاز دارید.

برای این منظور، مطمئن شوید که هنگام کامپایل کردن کد، اکسترن ها را نیز در کامپایل قرار دهید. این ممکن است غیرعادی به نظر برسد، زیرا ما اغلب تصور می‌کنیم که اکسترن‌ها «از جای دیگری می‌آیند»، اما لازم است به Closure Compiler بگوییم کدام نمادها را در معرض نمایش قرار می‌دهید تا تغییر نام ندهند.

یکی از اخطارهای مهم در اینجا این است که ممکن است در مورد کدی که نمادهای خارجی را تعریف می کند، تشخیص "تعریف تکراری" دریافت کنید. Closure Compiler فرض می‌کند که هر نمادی در خارجی‌ها توسط یک کتابخانه بیرونی عرضه می‌شود و در حال حاضر نمی‌تواند بفهمد که شما عمداً تعریفی ارائه می‌کنید. سرکوب این تشخیص‌ها بی‌خطر است، و می‌توانید سرکوب را به‌عنوان تأییدیه‌ای در نظر بگیرید که واقعاً در حال انجام API خود هستید.

علاوه بر این، Closure Compiler ممکن است بررسی کند که تعاریف شما با انواع اعلان‌های خارجی مطابقت داشته باشد. این امر تأیید دیگری را ارائه می دهد که تعاریف شما درست است.