Przegląd
Używanie kompilatora Closure Compiler z compilation_level
ADVANCED_OPTIMIZATIONS
zapewnia lepsze współczynniki kompresji
niż kompilacja z SIMPLE_OPTIMIZATIONS
lub WHITESPACE_ONLY
. Kompilacja z użyciem ADVANCED_OPTIMIZATIONS
zapewnia dodatkową kompresję dzięki bardziej agresywnym metodom przekształcania kodu i zmiany nazw symboli. Jednak to bardziej agresywne podejście oznacza, że musisz zachować większą ostrożność podczas korzystania z ADVANCED_OPTIMIZATIONS
, aby mieć pewność, że kod wyjściowy działa tak samo jak kod wejściowy.
W tym samouczku wyjaśniamy, jak działa poziom kompilacji ADVANCED_OPTIMIZATIONS
i co możesz zrobić, aby mieć pewność, że Twój kod będzie działać po skompilowaniu za pomocą ADVANCED_OPTIMIZATIONS
. Wprowadza też pojęcie extern, czyli symbolu zdefiniowanego w kodzie zewnętrznym w stosunku do kodu przetwarzanego przez kompilator.
Zanim przeczytasz ten samouczek, zapoznaj się z procesem kompilowania kodu JavaScript za pomocą jednego z narzędzi kompilatora Closure, np. aplikacji kompilatora opartej na Javie.
Uwaga dotycząca terminologii: flaga wiersza poleceń --compilation_level
obsługuje częściej używane skróty ADVANCED
i SIMPLE
, a także bardziej precyzyjne ADVANCED_OPTIMIZATIONS
i SIMPLE_OPTIMIZATIONS
.
W tym dokumencie używamy dłuższych form, ale w wierszu poleceń można używać tych nazw zamiennie.
- Jeszcze lepsza kompresja
- Jak włączyć ADVANCED_OPTIMIZATIONS
- Na co uważać podczas korzystania z ustawienia ADVANCED_OPTIMIZATIONS
Jeszcze lepsza kompresja
Przy domyślnym poziomie kompilacji SIMPLE_OPTIMIZATIONS
kompilator Closure zmniejsza rozmiar kodu JavaScript, zmieniając nazwy zmiennych lokalnych. Istnieją jednak symbole inne niż zmienne lokalne, które można skrócić, a także inne sposoby zmniejszenia kodu niż zmiana nazw symboli. KompilacjaADVANCED_OPTIMIZATIONS
wykorzystuje pełen zakres możliwości zmniejszania kodu.
Porównaj wyniki dla SIMPLE_OPTIMIZATIONS
i ADVANCED_OPTIMIZATIONS
w przypadku tego kodu:
function unusedFunction(note) { alert(note['text']); } function displayNoteTitle(note) { alert(note['title']); } var flowerNote = {}; flowerNote['title'] = "Flowers"; displayNoteTitle(flowerNote);
Kompilacja z SIMPLE_OPTIMIZATIONS
skróci kod do postaci:
function unusedFunction(a){alert(a.text)}function displayNoteTitle(a){alert(a.title)}var flowerNote={};flowerNote.title="Flowers";displayNoteTitle(flowerNote);
Kompilacja z ADVANCED_OPTIMIZATIONS
w pełni skraca kod do tej postaci:
alert("Flowers");
Oba skrypty wyświetlają alert z tekstem "Flowers"
, ale drugi skrypt jest znacznie mniejszy.
Poziom ADVANCED_OPTIMIZATIONS
wykracza poza proste skracanie nazw zmiennych na kilka sposobów, m.in.:
- bardziej agresywne zmienianie nazw:
Kompilacja z użyciem tylko
SIMPLE_OPTIMIZATIONS
zmienia nazwy parametrównote
funkcjidisplayNoteTitle()
iunusedFunction()
, ponieważ są to jedyne zmienne w skrypcie, które są lokalne dla funkcji.ADVANCED_OPTIMIZATIONS
zmienia też nazwę zmiennej globalnejflowerNote
. - usuwanie martwego kodu;
Kompilacja z użyciem
ADVANCED_OPTIMIZATIONS
całkowicie usuwa funkcjęunusedFunction()
, ponieważ nie jest ona nigdy wywoływana w kodzie. - wstawianie funkcji;
Kompilacja z użyciem
ADVANCED_OPTIMIZATIONS
zastępuje wywołaniedisplayNoteTitle()
pojedynczymalert()
, które tworzy treść funkcji. Zastąpienie wywołania funkcji jej treścią jest nazywane „wstawianiem”. Gdyby funkcja była dłuższa lub bardziej skomplikowana, wstawienie jej w kodzie mogłoby zmienić jego działanie, ale kompilator Closure Compiler stwierdza, że w tym przypadku wstawienie jest bezpieczne i pozwala zaoszczędzić miejsce. Kompilacja zADVANCED_OPTIMIZATIONS
wstawia też stałe i niektóre zmienne, jeśli uzna, że może to zrobić bezpiecznie.
Ta lista zawiera tylko przykłady przekształceń zmniejszających rozmiar, które może wykonać kompilacja ADVANCED_OPTIMIZATIONS
.
Włączanie ustawienia ADVANCED_OPTIMIZATIONS
Aby włączyć ADVANCED_OPTIMIZATIONS
w aplikacji Closure Compiler, dodaj flagę wiersza poleceń --compilation_level ADVANCED_OPTIMIZATIONS
, jak w tym poleceniu:
java -jar compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js hello.js
Na co uważać podczas korzystania z ustawienia ADVANCED_OPTIMIZATIONS
Poniżej znajdziesz listę typowych niezamierzonych efektów ustawienia ADVANCED_OPTIMIZATIONS oraz działań, które możesz podjąć, aby ich uniknąć.
Usuwanie kodu, który chcesz zachować
Jeśli skompilujesz tylko funkcję poniżej za pomocą ADVANCED_OPTIMIZATIONS
, kompilator Closure Compiler wygeneruje puste dane wyjściowe:
function displayNoteTitle(note) { alert(note['myTitle']); }
Ponieważ funkcja nigdy nie jest wywoływana w kodzie JavaScript przekazywanym do kompilatora, kompilator Closure zakłada, że ten kod nie jest potrzebny.
W wielu przypadkach takie zachowanie jest pożądane. Jeśli na przykład skompilujesz kod razem z dużą biblioteką, kompilator Closure Compiler może określić, których funkcji z tej biblioteki faktycznie używasz, i odrzucić te, których nie używasz.
Jeśli jednak zauważysz, że kompilator Closure Compiler usuwa funkcje, które chcesz zachować, możesz temu zapobiec na 2 sposoby:
- Przenieś wywołania funkcji do kodu przetwarzanego przez kompilator Closure Compiler.
- Dołącz externs dla funkcji, które chcesz udostępnić.
W kolejnych sekcjach omówimy szczegółowo każdą z tych opcji.
Rozwiązanie: przenieś wywołania funkcji do kodu przetwarzanego przez kompilator Closure Compiler
Jeśli skompilujesz tylko część kodu za pomocą kompilatora Closure Compiler, może dojść do usunięcia niechcianego kodu. Możesz na przykład mieć plik biblioteki, który zawiera tylko definicje funkcji, oraz plik HTML, który zawiera bibliotekę i kod wywołujący te funkcje. W takim przypadku, jeśli skompilujesz plik biblioteki za pomocą ADVANCED_OPTIMIZATIONS
, kompilator Closure usunie wszystkie funkcje biblioteki.
Najprostszym rozwiązaniem tego problemu jest skompilowanie funkcji razem z tą częścią programu, która je wywołuje.
Na przykład kompilator Closure Compiler nie usunie elementu displayNoteTitle()
podczas kompilowania tego programu:
function displayNoteTitle(note) { alert(note['myTitle']); } displayNoteTitle({'myTitle': 'Flowers'});
W tym przypadku funkcja displayNoteTitle()
nie jest usuwana, ponieważ kompilator Closure Compiler widzi, że jest ona wywoływana.
Innymi słowy, możesz zapobiec usunięciu niechcianego kodu, umieszczając punkt wejścia programu w kodzie przekazywanym do kompilatora Closure. Punkt wejścia programu to miejsce w kodzie, w którym rozpoczyna się wykonywanie programu. Na przykład w programie z notatką o kwiatach z poprzedniej sekcji ostatnie 3 wiersze są wykonywane natychmiast po wczytaniu kodu JavaScript w przeglądarce. To punkt wejścia do tego programu. Aby określić, który kod należy zachować, kompilator Closure Compiler zaczyna od tego punktu wejścia i śledzi przepływ sterowania programu od tego miejsca.
Rozwiązanie: uwzględnij externs dla funkcji, które chcesz udostępnić
Więcej informacji o tym rozwiązaniu znajdziesz poniżej i na stronie poświęconej funkcjom zewnętrznym i eksportom.
Niespójne nazwy usług
Kompilator Closure Compiler nigdy nie zmienia literałów ciągów w kodzie, niezależnie od używanego poziomu kompilacji. Oznacza to, że kompilacja z ADVANCED_OPTIMIZATIONS
traktuje właściwości inaczej w zależności od tego, czy kod uzyskuje do nich dostęp za pomocą ciągu znaków. Jeśli połączysz odwołania do usługi w postaci ciągów znaków z odwołaniami w notacji kropkowej, kompilator Closure Compiler zmieni nazwy niektórych odwołań do tej usługi, ale nie wszystkich. W związku z tym kod prawdopodobnie nie będzie działać prawidłowo.
Na przykład:
function displayNoteTitle(note) { alert(note['myTitle']); } var flowerNote = {}; flowerNote.myTitle = 'Flowers'; alert(flowerNote.myTitle); displayNoteTitle(flowerNote);
Ostatnie 2 instrukcje w tym kodzie źródłowym robią dokładnie to samo. Jeśli jednak skompresujesz kod za pomocą ADVANCED_OPTIMIZATIONS
, otrzymasz:
var a={};a.a="Flowers";alert(a.a);alert(a.myTitle);
Ostatnia instrukcja w skompresowanym kodzie powoduje błąd. Bezpośrednie odwołanie do właściwości myTitle
zostało zmienione na a
, ale odwołanie w cudzysłowie do myTitle
w funkcji displayNoteTitle
nie zostało zmienione. W rezultacie ostatnie stwierdzenie odnosi się do usługi myTitle
, która już nie istnieje.
Rozwiązanie: zachowaj spójność w nazwach usług
To dość proste rozwiązanie. W przypadku dowolnego typu lub obiektu używaj wyłącznie składni z kropką lub ciągów w cudzysłowie. Nie mieszaj składni, zwłaszcza w odniesieniu do tej samej właściwości.
W miarę możliwości używaj też składni z kropką, ponieważ umożliwia ona lepsze sprawdzanie i optymalizację. Dostęp do właściwości w postaci ciągu znaków w cudzysłowie stosuj tylko wtedy, gdy nie chcesz, aby kompilator Closure Compiler zmieniał nazwy, np. gdy nazwa pochodzi ze źródła zewnętrznego, takiego jak zdekodowany JSON.
Osobne kompilowanie dwóch części kodu
Jeśli podzielisz aplikację na różne fragmenty kodu, możesz je skompilować osobno. Jeśli jednak 2 fragmenty kodu wchodzą ze sobą w jakąkolwiek interakcję, może to sprawić trudności. Nawet jeśli Ci się to uda, dane wyjściowe z tych 2 uruchomień kompilatora Closure nie będą ze sobą zgodne.
Załóżmy na przykład, że aplikacja jest podzielona na 2 części: część, która pobiera dane, i część, która je wyświetla.
Oto kod do pobierania danych:
function getData() { // In an actual project, this data would be retrieved from the server. return {title: 'Flower Care', text: 'Flowers need water.'}; }
Oto kod do wyświetlania danych:
var displayElement = document.getElementById('display'); function displayData(parent, data) { var textElement = document.createTextNode(data.text); parent.appendChild(textElement); } displayData(displayElement, getData());
Jeśli spróbujesz skompilować te 2 fragmenty kodu oddzielnie, napotkasz kilka problemów. Najpierw kompilator Closure Compiler usuwa funkcję getData()
z powodów opisanych w sekcji Usuwanie kodu, który chcesz zachować. Po drugie, kompilator Closure Compiler generuje błąd krytyczny podczas przetwarzania kodu, który wyświetla dane.
input:6: ERROR - variable getData is undefined displayData(displayElement, getData());
Kompilator nie ma dostępu do funkcji getData()
podczas kompilowania kodu, który wyświetla dane, więc traktuje getData
jako niezdefiniowaną.
Rozwiązanie: zbierz cały kod strony
Aby zapewnić prawidłową kompilację, skompiluj cały kod strony w ramach jednego procesu kompilacji. Kompilator Closure może przyjmować jako dane wejściowe wiele plików JavaScript i ciągów znaków JavaScript, więc możesz przekazywać kod biblioteki i inny kod razem w jednym żądaniu kompilacji.
Uwaga: to podejście nie sprawdzi się, jeśli musisz łączyć skompilowany i nieskompilowany kod. Wskazówki dotyczące radzenia sobie w takiej sytuacji znajdziesz w artykule Uszkodzone odwołania między skompilowanym a nieskompilowanym kodem.
Niedziałające odwołania między skompilowanym a nieskompilowanym kodem
Zmiana nazwy symbolu w ADVANCED_OPTIMIZATIONS
spowoduje przerwanie komunikacji między kodem przetworzonym przez kompilator Closure Compiler a innym kodem. Kompilacja zmienia nazwy funkcji zdefiniowanych w kodzie źródłowym. Każdy kod zewnętrzny, który wywołuje Twoje funkcje, przestanie działać po skompilowaniu, ponieważ nadal odwołuje się do starej nazwy funkcji. Podobnie odwołania w skompilowanym kodzie do symboli zdefiniowanych zewnętrznie mogą zostać zmienione przez kompilator Closure Compiler.
Pamiętaj, że „niekompilowany kod” obejmuje każdy kod przekazywany do funkcji eval()
jako ciąg znaków. Kompilator Closure nigdy nie zmienia literałów ciągów znaków w kodzie, więc nie modyfikuje ciągów znaków przekazywanych do instrukcji eval()
.
Pamiętaj, że są to powiązane, ale odrębne problemy: utrzymywanie komunikacji z zewnętrznym kodem skompilowanym i utrzymywanie komunikacji z zewnętrznego kodu skompilowanego. Te oddzielne problemy mają wspólne rozwiązanie, ale każda strona ma swoje niuanse. Aby w pełni wykorzystać możliwości kompilatora Closure Compiler, musisz wiedzieć, w jakiej sytuacji się znajdujesz.
Zanim przejdziesz dalej, zapoznaj się z informacjami o zewnętrznych funkcjach i eksportach.
Rozwiązanie do wywoływania kodu zewnętrznego z skompilowanego kodu: kompilowanie z użyciem plików externs
Jeśli używasz kodu dostarczonego na stronę przez inny skrypt, musisz mieć pewność, że kompilator Closure nie zmieni nazw odwołań do symboli zdefiniowanych w tej bibliotece zewnętrznej. Aby to zrobić, dołącz do kompilacji plik zawierający externs dla biblioteki zewnętrznej. Dzięki temu kompilator Closure Compiler będzie wiedzieć, których nazw nie kontrolujesz, a więc nie możesz ich zmienić. Kod musi używać tych samych nazw co plik zewnętrzny.
Typowe przykłady to interfejsy API, takie jak OpenSocial API i Google Maps API. Jeśli na przykład Twój kod wywołuje funkcję OpenSocial opensocial.newDataRequest()
bez odpowiednich plików zewnętrznych, kompilator Closure Compiler przekształci to wywołanie w a.b()
.
Rozwiązanie problemu wywoływania skompilowanego kodu z kodu zewnętrznego: implementacja funkcji extern
Jeśli masz kod JavaScript, którego używasz ponownie jako biblioteki, możesz użyć kompilatora Closure Compiler, aby zmniejszyć tylko bibliotekę, a jednocześnie umożliwić niekompilowanemu kodowi wywoływanie funkcji w bibliotece.
W takiej sytuacji rozwiązaniem jest wdrożenie zestawu funkcji zewnętrznych definiujących publiczny interfejs API biblioteki. Twój kod będzie zawierać definicje symboli zadeklarowanych w tych plikach zewnętrznych. Oznacza to wszystkie klasy lub funkcje, o których wspominają zewnętrzne funkcje. Może to również oznaczać, że klasy implementują interfejsy zadeklarowane w plikach zewnętrznych.
Te zewnętrzne funkcje są przydatne nie tylko dla Ciebie, ale też dla innych. Użytkownicy Twojej biblioteki będą musieli je uwzględnić podczas kompilowania kodu, ponieważ z ich perspektywy biblioteka jest skryptem zewnętrznym. Pomyśl o nich jako o umowie między Tobą a Twoimi klientami. Obie strony potrzebują kopii.
W tym celu podczas kompilowania kodu uwzględnij też pliki externs. Może się to wydawać nietypowe, ponieważ często myślimy o deklaracjach zewnętrznych jako o „pochodzących z innego miejsca”, ale jest to konieczne, aby poinformować kompilator Closure Compiler, które symbole udostępniasz, aby nie zostały zmienione.
Ważne zastrzeżenie: możesz otrzymać diagnostykę „duplicate definition” (zduplikowana definicja) dotyczącą kodu definiującego symbole zewnętrzne. Kompilator Closure zakłada, że każdy symbol w pliku zewnętrznym jest dostarczany przez zewnętrzną bibliotekę, i nie jest w stanie zrozumieć, że celowo dostarczasz definicję. Te diagnostyki można bezpiecznie pomijać, a pomijanie można traktować jako potwierdzenie, że rzeczywiście realizujesz interfejs API.
Dodatkowo kompilator Closure Compiler może sprawdzić, czy definicje są zgodne z typami deklaracji zewnętrznych. Dzięki temu uzyskasz dodatkowe potwierdzenie, że Twoje definicje są prawidłowe.