chevron-left chevron-right

Jak zoptymalizować ładowanie plików JS z zewnętrznych źródeł?

Projektując strony internetowe zawsze dochodzimy do momentu, w którym trzeba się pochylić nad problemem optymalizacji projektu tak, aby był szybko ładowany w przeglądarce użytkownika. Jednym z wielu problemów, które się wtedy napotyka, jest temat związany z ładowaniem plików z zewnętrznych źródeł, np. skryptów Google Analytics czy też skryptów Facebooka.

Najczęściej wykorzystywanym narzędziem do sprawdzania optymalizacji strony internetowej jest Google PageSpeed Insights. Z pomocą tego narzędzia można się dowiedzieć, jak dobrze zoptymalizowana jest strona dla urządzeń mobilnych oraz dla komputerów.

Wykorzystaj pamięć podręczną przeglądarki - opis problemu

Wykorzystaj pamięć podręczną przeglądarki - przykład błędu

Przy budowaniu stron internetowych wykorzystujemy czcionki, obrazki, pliki JS czy style CSS. W szczególności obrazki i czcionki są najcięższymi wagowo plikami. Gdy użytkownik wchodzi na stronę, to bez odpowiedniej konfiguracji serwera będzie musiał pobierać te same pliki za każdym razem jak odwiedzi daną stronę. W celu poprawienia wydajności ładowania strony wykorzystuje się pamięć podręczną przeglądarki.

Cachowanie plików wewnętrznych strony internetowej

Aby zoptymalizować ładowanie plików pochodzących z serwera (typu Apache) na której znajduje się strona, musimy odpowiednio skonfigurować plik .htaccess. Plik .htaccess pozwala nam kontrolować zachowanie się serwera w różny sposób. Możemy w pliku zadeklarować reguły przekierowania linków do strony, wykorzystanie protokołu SSL na stronie czy też sterować zachowaniem kompilatora PHP. Poza wcześniej wymienionymi rzeczami, można również sterować cachowaniem plików, tym samym optymalizując prędkość ładowania stron internetowych. Przykładowa konfiguracja może wyglądać następująco:

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
<IfModule mod_expires.c>
    ExpiresActive on
 
    ExpiresDefault                          "access plus 1 month"
    ExpiresByType text/cache-manifest       "access plus 0 seconds"
    ExpiresByType text/html                 "access plus 0 seconds"
    ExpiresByType text/xml                  "access plus 0 seconds"
    ExpiresByType application/xml           "access plus 0 seconds"
    ExpiresByType application/json          "access plus 0 seconds"
    ExpiresByType application/rss+xml       "access plus 1 hour"
    ExpiresByType application/atom+xml      "access plus 1 hour"
    ExpiresByType image/x-icon              "access plus 1 week"
    ExpiresByType image/gif                 "access plus 1 month"
    ExpiresByType image/png                 "access plus 1 month"
    ExpiresByType image/jpeg                "access plus 1 month"
    ExpiresByType video/ogg                 "access plus 1 month"
    ExpiresByType audio/ogg                 "access plus 1 month"
    ExpiresByType video/mp4                 "access plus 1 month"
    ExpiresByType video/webm                "access plus 1 month"
    ExpiresByType text/x-component          "access plus 1 month"
    ExpiresByType application/x-font-ttf    "access plus 1 month"
    ExpiresByType font/opentype             "access plus 1 month"
    ExpiresByType application/x-font-woff   "access plus 1 month"
    ExpiresByType image/svg+xml             "access plus 1 month"
    ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
    ExpiresByType text/css                  "access plus 1 year"
    ExpiresByType application/javascript    "access plus 1 year"
 
</IfModule>

Powyższa konfiguracja sprawdza czy moduł mod_expires.c został załadowany na serwerze, a następnie dla każdego typu pliku ustawia odpowiedni czas wygaśnięcia ważności cache'a. W przypadku, gdyby jakiś typ nie został zdefiniowany na liście, to ustawia domyślną wartość na jeden miesiąc.

Sprawa jednak się komplikuje w momencie, gdy dostajemy informację jak na obrazku powyżej, która odnosi się do plików ładowanych z zewnątrz. Wtedy nasza konfiguracja w pliku .htaccess nie przyniesie oczekiwanego skutku. W takiej sytuacji są możliwe dwa podejścia (a może więcej?) czyli:

  1. Nic z tym nie robimy i ignorujemy ten błąd,
  2. Lub, próbujemy to jakoś obejść.

Pochylimy się nad drugą opcją czyli spróbujemy obejść problem za pomocą CRONa.

Jak cachować pliki z zewnętrznych źródeł?

Przyjrzyjmy się obrazkowi, który wcześniej zamieściłem. Można zauważyć, że znajdują się tam m.in. pliki pochodzące z Facebooka, Google Analytics oraz Google Adsense. Są one zaciągane z zewnętrznych serwerów, co może spowodować wolniejsze ładowanie strony internetowej. Aby poprawić tą sytuację możemy skopiować zawartość tych plików JavaScript i zapisać lokalnie na serwerze. Dlatego zamiast ładować plik z zewnętrznego serwera, np.: https://www.google-analytics.com/analytics.js możemy załadować plik z naszej domeny: https://twoja-domena.pl/scripts/analytics.js. Tym samym uzyskujemy kontrolę nad cachowaniem danego pliku.

Niestety, takie rozwiązanie ma swoje wady. Mianowicie, plik z kodem nie zostanie zaktualizowany w przypadku jakichkolwiek zmian w przyszłości. To może sprawić, że z powodu przyszłych zmian kod przestanie działać, co może przynieść duże straty dla właściciela strony (brak reklam z Adsense czy brak statystyk) lub może zmniejszyć przyjemność udostępniania artykułów w sieciach społecznościowych (brak aktualnego kodu FB).

Na szczęście, można temu zapobiec. Pomocny okaże się CRON, czyli system kolejkowania zadań, które mogą zostać uruchomione o określonej porze. Zadaniami mogą być skrypty, komendy lub programy uruchomione z określonymi parametrami.

Przykładowy skrypt CRON, który będzie aktualizował skrypt Google Analytics zapisany lokalnie na serwerze wygląda następująco:

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?
 
// Link do pliku na serwerze zdalnym
$remoteFile = 'https://www.google-analytics.com/analytics.js';
// Ścieżka absolutna do pliku lokalnego na serwerze
$localfile = '/home/username/public_html/scripts/analytics.js';
 
$fp = @fsockopen('https://www.google-analytics.com', '80', 0, 'Wystąpił błąd połączenia z serwerem Google Analytics', 10);
 
if (!$fp) {
    // Gdyby wystąpił błąd połączenia zwróć zapisaną wersję skryptu,
    // jeśli istnieje
    if (file_exists($localfile)) {
        readfile($localfile);
    }
} else {
    // Nagłówki zapytania
    $header = "GET https://www.google-analytics.com/analytics.js HTTP/2.0\r\n";
    $header .= "Host: https://www.google-analytics.com\r\n";
    $header .= "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:56.0) Gecko/20100101 Firefox/56.0\r\n";
    $header .= "Accept: */*\r\n";
    $header .= "Accept-Language: pl,en-US;q=0.7,en;q=0.3\r\n";
    $header .= "Connection: keep-alive\r\n";
    $header .= "Referer: https://twoja-domena.pl\r\n\r\n";
 
    fputs($fp, $header);
 
    $response = '';
 
    // Odczytaj odpowiedź z serwera
    while ($line = fread($fp, 4096)) {
        $response .= $line;
    }
 
    // Zamknij połączenie
    fclose($fp);
 
    // Usuń nagłówki odpowiedzi
    $pos = strpos($response, "\r\n\r\n");
    $response = substr($response, $pos + 4);
 
    // Zwróć odpowiedź z serwera
    echo $response;
 
    // Jeśli lokalny plik nie istnieje, to go utwórz
    if (!file_exists($localfile)) {
        fopen($localfile, 'w');
    }
 
    // Jeśli możliwy jest zapis do pliku,
    // to zapisz odpowiedź w pliku.
    if (is_writable($localfile) && ($fp = fopen($localfile, 'w'))) {
        fwrite($fp, $response);
        fclose($fp);
    }
}

Powyższy skrypt PHP otwiera połączenie z serwerem zewnętrznym tak jakby zrobiła to przeglądarka (dzięki nagłówkom) a następnie zapisze odpowiedź z serwera do pliku z lokalną wersją skryptu. W przypadku, gdyby połączenie się nie udało, to zwróci poprzednio zapisaną wersję Google Analytics, a w logach serwera zostanie zapisana informacja o błędzie. Dzięki temu będzie można łatwo odkryć potencjalny problem ze zadaniem CRONa.

Tak utworzony plik ze skryptem PHP wystarczy dodać do listy zadań wykonywanych codzienne o określonej porze, dzięki czemu będziemy mieć zawsze aktualną wersję skryptu. Uruchamianie tego skryptu częściej niż raz dziennie w większości przypadków nie przyniesie nam większych korzyści. Możnaby się zastanowić, czy nie odpytywać rzadziej, ale to już zależy od przypadku. Bezpieczniej jest sprawdzać codziennie czy wystąpiły zmiany.

Więcej na temat zadań CRON i ich konfiguracji można się dowiedzieć z tej strony

Podsumowanie

Z pomocą tego tekstu będziesz w stanie podbić swój wynik w PageSpeed Insights o kilka punktów redukując liczbę plików serwowanych z zewnątrz. W razie potrzeby możesz też przygotować wersję zbundlowaną, czyli połączoną w jeden plik z innymi skryptami JS, które są ładowane na Twojej stronie. Tym samym skutecznie redukując liczbę zapytań na serwer w celu pobrania plików JavaScript.

Zapraszam do komentowania i dzielenia się swoimi doświadczeniami!