• Strona główna
  • Curriculum Vitae
  • O mnie
  • Mapa strony
  • Kontakt
Niebieski Pomarańczowy Zielony Różowy Fioletowy

Kompresja JavaScript

Opublikowane 29 kwietnia 2010. Autor: Kamil Brenk. Wizyt: 1 485.

Kategorie: PHP
Tematyka: Apache, JavaScript, kompresja gzip, Minify JavaScript, optymalizacja serwisów, PHP, PHP PEAR, praktyczne skrypty, Protokół HTTP, wydajność serwisów internetowych, Wyrażenia regularne

kwi 29

Był już wpis o kompresowaniu stylów CSS, czas więc zabrać się za kolejne optymalizacje strony. Tym razem bierzemy się za kompresowanie JavaScript.

Wpis będzie oparty o strukturę klasy stworzonej przy okazji wspomnianego kompresora CSS. Kompresja kodu zostanie natomiast wykonywana przez JavaScript’s Packer (by Dean Edwards).

Zaczynamy kodzić!

Zacznijmy od pokazania interfejsu, który został już wcześniej stworzony, a który wykorzystamy także i przy tym kompresorze.

1
2
3
4
5
6
7
8
9
10
11
interface CodeCompressor {

    public function addFiles($url_files);
    public function addFile($url_file);
   
    public function cleanCode($code);
    public function compressCode($code);
   
    public function showCode();
   
}

Sama klasa wygląda więc następująco:

1
2
3
4
5
class JSCompressor implements CodeCompressor {

    // kod

}

Tak jak wcześniej, tak i teraz klasa zawiera szereg ustawień konfiguracyjnych:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * Domyślne ustawienia konfiguracyjne dla klasy
**/

private $config = array(
   
    'charset'               => 'utf-8',         // kodowanie znaków
    'compress_code'         =>  true,           // kompresja kodu

    'gzip_contents'         =>  true,           // kompresja gzip
    'gzip_level'            =>  6,              // poziom kompresji gzip
       
    'cache_enabled'         =>  true,           // buforowanie po stronie serwera
    'cache_location'        =>  'tmp/',         // folder dla cache
    'use_flush_key'         =>  true,           // własnoręczne usuwanie cache, ?flush=FILE_ID
       
    'use_cache_browser'     =>  true,           // buforowanie po stronie klienta
    'time_cache_browser'    =>  3600            // czas trzymania w buforze (sekundy)
   
);

Jak więc widać powyżej, klasa zawiera tablicę z ustawieniami konfiguracyjnymi. Przy późniejszym wywoływaniu instancji klasy możemy podać jej tablicę z własnymi ustawieniami.

W klasie zdefiniowano kilka innych właściwości przechowujących kod, adresy skompresowanych plików czy uchwyt do obiektu odpowiadającego za cache:

1
2
3
4
5
6
7
8
9
10
11
// zmienna przechowująca złączony kod JavaScript
private $js_code;
   
// tablica z adresami plików do złączenia i optymalizacji
private $files_require = array();
   
// tablica przechowująca adresy plików, które już przetworzono
private $files_loaded = array();
   
// uchwyt dla klasy buforującej wynik
private $cache_lite;

Ustawienia konfiguracyjne są odpowiednio nakładane na powyższe przez konstruktor klasy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public function __construct($config=array()) {
   
    // jeśli nie zdefiniowano ustawień - pozostaw domyślne
    if (!is_array($config)) return false;
       
    // przypisz ustawienia konfiguracyjne do właściwości klasy
    foreach ($config as $name => $type) {
           
        if (in_array($name, $this->config)) {
            $this->config[$name] = $config[$name];
        }
           
    }
       
       
    // uruchom buforowanie plików
    $options = array(
        'caching' => $this->config['cache_enabled'],
        'cacheDir' => $this->config['cache_location']
    );

    $this->cache_lite = new Cache_Lite($options);
       
       
    // samodzielne czyszczenie buforu
    // dodaj ?flush=FILE_ID do adresu, aby usunąć cache
    if (
        $this->config['cache_enabled']
        and $this->config['use_flush_key']
        and !empty($_GET['flush'])
    ) {
        $this->flushCache($_GET['flush']);
    }
   
}

Jak widać wyżej, struktura jest bardzo podobna do struktury klasy CSSCompressor omawianej kilka wpisów wstecz.

Dalej definiujemy metody flushCache, addFiles, addFile, checkModifiedFiles, _lastModifiedCache, _getCacheName, showCode oraz outputHeaders. Nie opiszę ich tutaj, ponieważ nie wprowadzono do nich żadnych zmian i są jednakowe z metodami klasy CSSCompressor.

Nawiasem mówiąc, wcześniej mogłem stworzyć klasę ogólną do kompresji kodu, a następnie tylko dziedziczyć z niej i ewentualnie przeciążać wybrane metody.

Trudno, teraz już na to za późno :-)

Właściwa kompresja JavaScript

Zmiany nastąpiły w metodach odpowiadających za kompresję kodu. Tutaj zadanie jest zlecane zewnętrznej klasie, której autorem jest Dean Edwards. Nie chciałem zabierać się za to samemu, gdyż kompresja taka wymaga dokładnej analizy kodu JavaScript i trochę jest z tym roboty.

Metoda ta wygląda następująco:

1
2
3
4
5
6
7
8
9
public function compressCode($code) {

    // JavaScript Compressor by Dean Edwards
    $packer = new JavaScriptPacker($code, 0, true, true);
   
    // zwróć skompresowany kod
    return $packer->pack();
   
}

Klasa odpowiadająca za pobieranie kodu z plików nie uległa zbytnio zmianie.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private function getCode($files) {
   
    // zmienna przechowująca kod
    $full_code = null;
   
    // pobierz i zoptymalizuj kod z załączonych plików
    foreach ($files as $id => $url_file) {
           
        // pobierz kod źródłowy - błędy zostaną 'stłumione' i pominięte
        // zabezpiecz przed wielokrotnym pobieraniem tego samego kodu
        if (
            !in_array($url_file, $this->files_loaded)
            and $code = @file_get_contents($url_file)
        ) {
           
            // odnotuj dołączenie pliku
            array_push($this->files_loaded, $url_file);
               
            // dodaj średnik na końcu pliku, jeśli nie istnieje
            if (substr($code, -1) !== ';') $code .= ';';
               
            // wykonaj kompresję
            if ($this->config['compress_code']) $code = $this->compressCode($code);
               
            // dodaj kod do zwrócenia
            $full_code .= $code;
            unset($code);
           
        }
       
    }
       
    // zwróć wynik działania
    return $full_code;
   
}

Jedyną zmianą tutaj jest zaimplementowanie funkcji dodającej średnik na końcu pobranego kodu z pliku, jeśli go tam jeszcze nie ma.

Jest to zabezpieczenie przed występowaniem błędów typu:

plik1.js:

1
document.getElementById('search-field').value = 'wpisz tekst'

plik2.js:

1
if (price === 32) promocja();

W połączeniu (bez średnika) otrzymalibyśmy następujący kod (błędny):

1
document.getElementById('search-field').value = 'wpisz tekst'if (price === 32) promocja();

Dodając średnik na końcu każdego pliku ustrzeżemy się przed sytuacjami tego typu.

Kilka przykładów

Przykład 1 – kod JavaScript w zewnętrznym pliku:

Kod HTML:

1
2
3
4
5
<input type="button" id="but1" value="button1">
<input type="button" id="but2" value="button2">
<input type="button" id="but3" value="button3">

<script type="text/javascript" src="./js1.php?load=file1,file2,file3"></script>

Plik js1.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
require_once 'class.JSCompressor.php';

// utwórz instancję klasy
$css_compress = new JSCompressor();

// dodaj pliki js do złączenia
if (!isset($_GET['load'])) die('Nie wybrano żadnych plików ze skryptami.');

// przerób na tablicę
$files = explode(',', $_GET['load']);

foreach ($files as $id => &$file) {
    $file = trim($file) . '.js';
}

// ustaw do kompresji
$css_compress->addFiles($files);

// wyświetl wynik
$css_compress->showCode('infile');

Korzystamy tutaj z domyślnych ustawień klasy (włączona kompresja, buforowanie, cache, etc). Domyślne ustawienia moim zdaniem są optymalnymi i przeze mnie zalecanymi.

Przykład 2 – wewnętrzny kod JavaScript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php

require_once 'class.JSCompressor.php';

// konfiguracja
$_config = array(

    'compress_code'         =>  true,       // status kompresji kodu
   
    'cache_enabled'         =>  true,       // buforowanie po stronie serwera
    'cache_location'        =>  'tmp/',     // folder dla cache
   
    'use_cache_browser'     =>  true,       // buforowanie po stronie klienta
    'time_cache_browser'    =>  3600,       // czas trzymania w buforze (sekundy)
   
    'gzip_contents'         =>  true,       // kompresja gzip
    'gzip_level'            =>  6           // poziom kompresji gzip

);

// utwórz instancję klasy
$js_compress = new JSCompressor($_config);

// dodaj pliki js do złączenia
$js_compress->addFile('./file1.js');
$js_compress->addFile('./file2.js');
$js_compress->addFile('./file3.js');

?>

<input type="button" id="but1" value="button1">
<input type="button" id="but2" value="button2">
<input type="button" id="but3" value="button3">

<script type="text/javascript">
<?= $js_compress->showCode('inline'); ?>
</script>

W ten sposób uzyskujemy kod, który możemy wprowadzić bezpośrednio do dokumentu (który jest także buforowany i zapisany na dysku!).

Podsumowanie

No i to by było na tyle, mamy gotowy kompresor JavaScript!

Nie było to najtrudniejsze zadanie, tym bardziej, że wszystkie bardziej logiczne funkcje zlecane są zewnętrznej klasie (opracowanej przez kogoś dużo lepszego ode mnie).

Moja klasa dodaje jedynie kolejną warstwę abstrakcji do istniejącej już klasy, implementując przy tym buforowanie, cachowanie, gzip i kilka innych dodatków. Na moje potrzeby jest to w zupełni wystarczające. Mam też nadzieję, iż komuś jeszcze przyda się stworzony tutaj kod :-)

Chciałbym tutaj także wspomnieć o zaleceniu, które mówi o umieszczaniu kodu JavaScript na dole strony. Więcej info w poradach Yahoo: Best Practices for Speeding Up Your Web Site.

Przydatne linki
  • Kompresja JavaScript – pokaż przykład
  • Pełny kod źródłowy biblioteki + przykłady (40 KB)

Podobne wpisy

  • Kompresja CSS
  • Boilerplate 2.0
  • Pobieranie adresów URL z innej strony
  • Jak pobierać zewnętrzne zasoby?
  • Kompresja w Apache (deflate)

Komentarze (3)

  1. Michal Wachowski 30 kwietnia 2010

    Bez cache’owania taka kompresja nie ma racji bytu…
    Sam pozostałem przy łączeniu w jeden plik i gzipie (o czym wiesz :D)

  2. Kamil Brenk 25 marca 2011

    Michał, ale tu jest i było zaimplementowane buforowanie :-)

  3. Michal Wachowski 25 marca 2011

    Widzę. Z cache’owaniem – jest jak najbardziej ok, bez – średnio to widzę.
    Ale to moje subiektywne blabla.



Dodaj komentarz

XHTML: Możesz użyć następujących tagów
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Kamil Brenk Blog

PHP, JavaScript, SQL, HTML

  • Informacje o blogu

    Kamil Brenk

    Blog o tworzeniu aplikacji na potrzeby sieci Web.

    Praktyczne przykłady, porady i sztuczki. PHP, SQL, AJAX, JavaScript, HTML i pochodne.

    Kanał RSS

    • Najnowsze
    • Komentarze
    • Popularne
    • Gramatyka w HTML i CSS
    • PHP kontra Microsoft Office, part I
    • Cross-Domain JavaScript: CORS
    • Wysyłanie wiadomości SMS w PHP
    • Boilerplate 2.0
    • Własne selektory w jQuery
    • Kamil Brenk: @Michał:1) jak już otrzymam dyplom to zrobię serię o...
    • Michal Wachowski: Po pierwsze - tyle czekania i tylko to? A bu! :) Po drugie -...
    • Kamil Brenk: @CapaciousCore: języki kompilowane są szybsze niż...
    • CapaciousCore: @Kamil Brenk wiem, że komentarze i post nie są uber świeże....
    • Kamil Brenk: @CapaciousCore: post i komentarze napisane ponad rok temu;...
    • CapaciousCore: Przebrnąłem przez te wszystkie komentarze i mam trochę...
    • Kamil Brenk: @arhiman: dzięki za komentarz :)A to dziwne co piszesz, bo...
    • Przyszłość PHP
    • Niestandardowe czcionki na stronie
    • Gramatyka w PHP, część 1
    • Umowa i zaliczka dla freelancera
    • Projekt aplikacji po stronie klienta
    • Własny mechanizm Feed
    • jQuery.extends dla PHP
  • Szukajka
    Wpisz co chcesz wyszukać na stronie…
  • Kategorie
    • Apache
    • Freelancer
    • Front-end Development
    • HTML5 & CSS3
    • Inne
    • JavaScript
    • Książki
    • PHP
    • Po godzinach
    • Pozycjonowanie
    • Protokół HTTP
    • SQL
    • Wyrażenia regularne
  • Moje serwisy
    • Testy zawodowe
    • Miłość, uczucia i seks
  • Czytane blogi
    • Wojciech Sznapka
    • Wojciech Soczyński
    • Michał Wachowski
    • Tomasz Kowalczyk
    • JavaScript po polsku | Code42
  • Archiwum
    • Luty 2012
    • Listopad 2011
    • Październik 2011
    • Wrzesień 2011
    • Sierpień 2011
    • Lipiec 2011
    • Maj 2011
    • Kwiecień 2011
    • Marzec 2011
    • Luty 2011
    • Styczeń 2011
    • Grudzień 2010
    • Listopad 2010
    • Październik 2010
    • Wrzesień 2010
    • Sierpień 2010
    • Lipiec 2010
    • Czerwiec 2010
    • Maj 2010
    • Kwiecień 2010
    • Marzec 2010
    • Luty 2010
    • Styczeń 2010
  • Strona główna
  • Curriculum Vitae
  • O mnie
  • Mapa strony
  • Kontakt

Kamil Brenk © 2010. All rights reserved.

Designed by FTL Wordpress Themes brought to you by Smashing Magazine.

Do góry ∧