Просунуті оптимізації
| Пошук по форуму |
| Розширений пошук |
| До сторінки. |
Розширені оптимізації google closure compiler включаються опцією --compilation_level ADVANCED_OPTIMIZATIONS.
Слово "просунуті" (advanced) тут, мабуть, не зовсім підходить. Кардинальна відмінність цих оптимізації від звичайних (simple) - у тому, що вони небезпечні.
Щоб ними користуватися – треба вміти це робити.
Основний принцип просунутого режиму
Якщо в безпечному режимі перейменовуються тільки змінні всередині функцій, то в "просунутому" - на короткі імена замінюється все.
Якщо в безпечному режимі видаляється недосяжний код після return , то в просунутому - взагалі весь код, який не викликається в явному вигляді.
Наприклад, якщо запустити просунуту оптимізацію на такому коді:
Рядок запуску компілятора:
. То результат буде – порожній файл. Google Closure Compiler побачить, що функція test не використовується і з чистою совістю виріже її.
А в наступному скрипті функція збережеться:
Тут у скрипті є явний виклик функції, тому вона збереглася.
Звичайно, є способи зберегти функції, виклик яких відбувається поза скриптом, і ми їх обов'язково розглянемо.
Режим "Advanced" не передбачає збереження глобальних змінних, навпаки - він перейменовує (і видаляє) усі символи, крім деяких зарезервованих.
Інакше кажучи, просунутий режим (ADVANCED_OPTIMIZATIONS), на відміну від простого (SIMPLE_OPTIMIZATIONS - за замовчуванням), взагалі не дбає про доступність коду ззовні та збереження цілісності посилання щодо зовнішніх скриптів.
Єдине, що він гарантує - це внутрішню цілісність посилань, іто - за дотримання низки умов та практик програмування.
Власне, за рахунок такого агресивного підходу досягається додатковий ефект оптимізації та стиснення скриптів.
Тобто, просунутий режим - це не просто "покращений звичайний", а принципово інший, небезпечний і підхід до стиснення.
У цьому є корінна відмінність Google Closure Compiler від компіляторів типу YUI Compressor і ShrinkSafe.
Щоб ефективно стискати Google Closure Compiler у просунутому режимі, потрібно розуміти, що і як він робить. Це ми зараз обговоримо.
Збереження посилальної цілісності
Щоб використовувати стислий скрипт, ми маємо можливість викликати функції під тими іменами, які їм дали.
Тобто, перед нами стоїть завдання збереження посилальної цілісності, яка полягає в тому, щоб забезпечити доступність потрібних функцій для звернень по вихідному імені ззовні скрипта.
Існує два способи збереження зовнішньої цілісності посилань для вибраних символів: екстерни та експорти. Ми подробиці розглянемо обидва, почнемо з екстернів, т.к. вони простіші.
Екстерн- символ, який значиться у спеціальному списку компілятора. Він має бути визначений поза скриптом, у файлі екстернів.
Компілятор ніколи не перейменовує екстерни. Наприклад:
Після просунутого стиску:
Як бачите, перейменованою виявилася лише змінна event. Таке перейменування явно безпечно, т.к. event – локальна змінна.
Чому компілятор не торкнувся решти? Спробуємо інший варіант:
Тепер компілятор перейменував і blabla і megaProperty.
Справа в тому, що назви, використані раніше, були у внутрішньому списку екстернів компілятора. Цей перелікохоплює основні об'єкти браузерів і знаходиться (під ім'ям externs.zip) в корені архіву compiler.jar або в директорії externs svn.
Компілятор ніколи не перейменовує символи зі списку екстернів, окрім випадку, коли так названа локальна змінна.
Ще один приклад:
Як бачите, внутрішня змінна innerHTML не просто перейменована - вона заінлайнована (замінена на значення). Так як ця змінна локальна, будь-які дії всередині функції з нею безпечні.
А властивість innerHTML не зворушена, як і об'єкт window - оскільки вони у списку екстернів і є локальними змінними.
Це призводить до наступного побічного ефекту. Іноді властивості, які слід стиснути, не стискаються. Наприклад:
Як видно, властивість age стиснулася, а name і type – ні. Це побічний ефект екстернів: name та type – у списку об'єктів браузера, і компілятор просто намагається не наламати дров.
Тому зазначимо ще одне корисне правило оптимізації:
Назви своїх властивостей не повинні співпадати із зарезервованими словами (екстернами). Тоді вони добре стискатимуться.
Для завдання списку екстернів їх достатньо перерахувати у файлі та вказати цей файл прапором --externs.
Використання такого файлу при стисненні (опція --externs myexterns.js ) призведе до того, що всі звернення до символів dojo і dojo._scopeMap будуть не стиснуті, а залишені "як є".
Стискаюче перейменування екстернів
До речі, буває зручно дозволити перейменування деяких екстернів. Це робиться через привласнення їхньої проміжної змінної-неекстерну.
В основному, це працює з глобальними змінними та статичними методами.
Наприклад, ми хочемо стиснути document у наступному фрагменті:
Для цього надамо documentпроміжної змінної doc :
Компілятор не має права стискати document , а всі звернення до doc будуть стиснуті, т.к ім'я змінної doc буде замінено більш коротке. В результаті, в стислому коді document з'явиться лише один раз, а всі інші звернення (через doc) будуть стиснуті.
Експорт - програмний хід, що базується на наступному правилі поведінки компілятора.
Компілятор замінює звернення до властивостей через лапки на точку, і не чіпає назву властивості.
Наприклад, window['User'] перетвориться на window.User , але не далі.
Таким чином можна експортувати потрібні функції та об'єкти:
Звернемо увагу - сама функція SayWidget була перейменована в a. Але потім - експортована як window.SayWidget і таким чином доступна зовнішнім скриптам.
Додамо пару методів у прототип:
Метод setSayHandler експортований та доступний для зовнішнього виклику.
Сам рядок експорту виглядає досить безглуздо. На вигляд - привласнюємо властивість самому собі. Але логіка стиснення google closure compiler працює так, що така конструкція є експортом. Праворуч перейменування якості setSayHandler відбувається, а зліва - ні.
До речі, google closure compiler більше любить іншу організацію властивостей об'єкта при ОВП, але про це трохи пізніше.
Розглянемо наступний код:
Як бачите, перше звернення до властивості blabla стислося, а друге (як і всі аналогічні) - перетворилося на синтаксис через точку. В результаті отримали некоректну поведінку коду.
Отже, використовуючи сучасний режим оптимізації, плануйте поведінку коду після стиснення, особливо при зверненні до властивостей через квадратні дужки: object[methodName] .
goog.exportSymbol та goog.exportProperty
У бібліотеціGoogle Closure Library для експорту є спеціальна функція goog.exportSymbol. Викликається так:
Ця функція по суті працює так само, як і розглянутий вище рядок з присвоєнням якості, але при необхідності створює необхідні об'єкти.
Вона аналогічна коду:
Тобто якщо шлях до об'єкта не існує - exportSymbol створить потрібні порожні об'єкти.
Функція goog.exportProperty експортує властивість об'єкта:
Рядок вище - те саме, що і:
Справді, навіщо, якщо можна зробити простим присвоєнням?
Основна мета цих функцій – у взаємодії з Google Closure Compiler. Вони дають інформацію компілятору про експорти, яку може використовувати.
Наприклад, є недокументована внутрішня опція externExportsPath, яка генерує із усіх експортів файл екстернів. Таким чином, можна розповсюджувати відкомпільований javascript-файл як зовнішню бібліотеку, з файлом екстернів для зручного зовнішнього зв'язування.
Крім того, експорт через ці функції зручний і наочний.
Якщо ви використовуєте просунутий режим оптимізації, можна підключити файл base.js з Google Closure Library просто щоб працювали ці функції. Оптимізатор при просунутому стисненні виріже з base.js (майже) все зайве, крім функцій експорту, тому overhead буде мінімальним.
Відмінності експорту від екстерну
Між експортом та екстерном є дещо спільне. І те, й інше дає можливість доступу до об'єктів під вихідним ім'ям, до перейменування.
Але в іншому це зовсім різні речі.
Інлайнінг змінних та функцій
Ще один важливий принцип роботи просунутого режиму полягає в інлайнінгу констант та функцій.
Інлайнінг- це заміна всіх дзвінків функції на кодцієї функції і, аналогічно, заміна всіх звернень до константи її значення.
Інлайнінг функції
Після стиснення у просунутому режимі:
Функція test не є екстерном і не експортується, тому немає жодних вимог до її збереження.
Тому компілятор замінив виклик test(1) на тіло функції. З іншого боку, у цьому прикладі, сталося об'єднання констант: " this is my test number " +1 .
Інлайнінг константи
Константи інлайнуються аналогічно до функцій.
Важливо розуміти, що інлайнінг відбувається лише тоді, коли призводить до зменшення розміру коду.
Якщо в звичайному режимі стиснення стиль програмування - "якнайбільше локальних змінних, якнайбільше внутрішніх функцій", то при використанні просунутого режиму все з точністю навпаки.
Функції треба якнайкраще відокремлювати один від одного. Потік виконання повинен бути якомога зрозумілішим компілятору.
Воно зручне, т.к. дозволяє всередині зовнішньої функції оголошувати будь-які методи, тимчасові змінні, робити дії завантаження і т.п.
Такий стиль використовується у більшості javascript-бібліотек на момент написання цієї статті.
Але тепер глянемо на результат компіляції. В звичайному режимі:
Це приблизно те, що ми очікували від безпечного режиму. Непотрібний спосіб залишився, локальні характеристики перейменовані.
А тепер просунутий режим:
Отриманий код не те, що недостигнуть: залишений свідомо непотрібний метод, він просто перестав працювати після стиснення. Google Closure Compiler не розібрався, який символ куди йде, і некоректно стиснув код. Це схоже на недоробку творців компілятора, але нам хочеться не просто щоб працювало. Хочеться видалення недосяжного коду та інші фічі.
Компіляція цього коду у звичайному режимі не дасть особливих сюрпризів. А ось просунутий режим, навпаки, дуже здивує:
Google Closure Compiler не тільки розібрався у структурі та видалив зайвий метод – він заінлайнував функції, щоб підсумковий розмір вийшов мінімальним.
Як кажуть, переваги є.
Просунутий режим оптимізації стискає всі властивості та методи, окрім екстернів, що знаходяться у списку.
Це є принциповою відмінністю та покращенням, в порівнянні з іншими пакувальниками.
Крім того, відмова від збереження зовнішньої цілісності посилань дозволяє заінлайнувати константи і функції, вирізати непотрібний код і зробити ряд інших корисних оптимізацій.
Звичайно, для того, щоб це вирізання працювало, бібліотека має бути написана у правильному стилі, з розумінням процесу оптимізації. Прикладом бібліотеки є Google Closure Library.
У великих проектах воно відмінно працює в поєднанні системою залежностей.
Для того, щоб зробити символ доступним зовні, використовується експорт.
Щоб не перейменовувати звернення до зовнішнього символу, він поміщається до списку екстернів.
Взагалі, судячи з вигляду javascript'ів на сайтах, створених Google, сам Google тисне свої скрипти саме просунутим режимом оптимізації.
На всіthis, що використовуються для доступу до властивостей, компілятор видає попередження: JSC_USED_GLOBAL_THIS: неперевершена використання глобального цього об'єкта line. . Це страшно?
Видає таке попередження на
у цьому прикладі із статті:
якщо ваш скрипт юзає якусь бібліотеку, то не треба окремо створювати файл екстернів просто вкажіть файл бібліотеки
як оголосити екстерни всередині коду (не через зовнішній файл)?
Дякуємо за працю з написання цього циклу статей! Крута штука цей GCC - дійсно, в порівнянні з YUI Compressor - як автомобіль в порівнянні з великим! Ось тільки є невелике нарікання.
Було завдання оголосити змінну в глобальному контексті так, щоб вона стала доступною ззовні. коли пишу:
GCC її стискає. Коли прописую в екстерни
, Але в результаті в стислому варіанті з'являється зайве "window.myVariable" замість виглядає більш коротко "var myVariable", що злегка напружує.
Також напружує схожа проблема з об'єктом event, який доступний у глобальному контексті в IE при настанні будь-якої події - його теж доводиться писати з приставкою "window." - Тільки тоді GCC його розуміє і не стискає. У результаті потім у скомпресованому скрипті доводиться руками вишукувати та вичищати ці приставочки "window.", оскільки regexp`ам таку делікатну справу довіряти поки що боюся.
Компіля звичайно хороший, але змінюють стиль кодування, наприклад, у вже існуючому великому проекті досить складно через такий підхід. Чому б не зробити якісь директиви компіляції? Щось типу такого #Ifdef #endif
Щоб для певних ділянок коду, не робити такого стиснення.
function my(elem) return 0; > window['my']=my