• Strona główna
  • Curriculum Vitae
  • O mnie
  • Przykład: Gramatyka w PHP
  • Przykład: Kompresja CSS
  • Przykład: Kompresja JavaScript
  • Przykład: Skracanie linków
  • Przykład: Wykrywanie serwera HTTP
  • Przykład: Własna bramka SMS
  • Mapa strony
  • Kontakt
Niebieski Pomarańczowy Zielony Różowy Fioletowy

JavaScript w modułach

Opublikowane 23 lipca 2013. Autor: Kamil Brenk. Wizyt: 6 919.

Kategorie: JavaScript

lip 23

Ponad dwa lata temu opisywałem sposoby wczytywania plików JavaScript, gdzie wymieniłem kilka fajnych bibliotek do asynchronicznego doczytywania kodu.

Jakiś czas później trafiłem także na RequireJS, który ma podobną funkcjonalność i całkowicie zmienił moje podejście do pisanego kodu JavaScript…

Modularne tworzenie kodu

Wspomniany RequireJS to biblioteka, która implementuje interfejs AMD (Asynchronous module definition). Podejście to propaguje tworzenie re-używalnych (zna ktoś polski odpowiednik tego słówka? :)) kawałków kodu, które odpowiadają tylko za jedną funkcjonalność (np. slider, fader, datapicker) i łatwo je przenosić do innych projektów.

To z kolei przynosi kolejne korzyści, jak choćby:

  • Uproszczenie kodu. Tworząc nawet najbardziej skomplikowany system, możesz rozbić jego poszczególne części na małe kawałki. Traktując każdą funkcjonalność jako osobny byt, być może uda Ci się nie spieszyć całego projektu :-)
  • Ograniczenie zmiennych globalnych. Zmienne globalne to zło – powodują trudne do wykrycia błędy, jak choćby przypadkowe przeciążanie funkcji czy własności. Wykorzystując w projekcie AMD, używasz tylko dwóch zmiennych globalnych – require i define (o tym dalej).
  • Możliwość wielokrotnego użycia kodu. Szalenie ważna cecha, jeśli cenisz swój czas i pieniądz. Tworząc konkretny moduł (np. kalendarz, zegar, kalkulator, slider) możesz go wykorzystać w dowolnie innym projekcie – wystarczy dostosować CSS i odpowiednio skonfigurować/dopisać brakujące funkcjonalności.
  • Wczytujesz tylko to co potrzebujesz. Z różnych statystyk można wyczytać, że strony ważą coraz więcej. Wiadomo, Internet jest coraz szybszy, więc niektórzy zaczynają zapominać o optymalizacji. To ciency gracze, bowiem zapominają przy tym o mobilnym Internecie (który jest przyszłością) i limitach panujących w tym świecie.

    AMD pozwala na wczytywanie tylko tych modułów, które są aktualnie używane. W dodatku, jak nazwa wskazuje – doczytywane są one asynchronicznie. To nowe podejście, bowiem wcześniej najczęściej łączyło się wszystkie pliki w jeden wielki i każdorazowo go wczytywało (co też ma swoje zalety i czasem się przydaje, zwłaszcza przy mniejszych serwisach).

  • Wczytujesz tylko kiedy potrzebujesz. Cecha podobna do powyższej, lecz tym razem w kontekście lazy loadingu. AMD umożliwia wczytanie odpowiednich modułów tylko w razie potrzeby, np. dopiero po otworzeniu okienka modalnego możesz wczytać moduł do kalendarza, które będzie można wywołać z poziomu tego okna.

AMD/RequireJS zrewolucjonizowało podejście do pisanego kodu JavaScript. I choć moduły w JavaScript były znane i używane wiele lat wcześniej to AMD narzucił pewien standard, do którego dostosowuje się coraz więcej web-developerów i bibliotek przez nich pisanych (m. in. jQuery, Dojo, MooTools, Underscore.js, Backbone, etc).

To z kolei rokuje pisaniem lepszego kodu.

Tworzymy pierwszy moduł w JavaScript

Tak jak wcześniej wspomniałem, będziemy mieli do czynienia tylko z dwoma globalnymi funkcjami:

  • require – asynchronicznie wczytuje wskazany moduł, a po tego dokonaniu wykonuje kod zawarty w funkcji zwrotnej, np.
    1
    2
    3
    require(['jquery'], function(jQuery) {
        jQuery(document.body).html('It\'s working!');
    });

    Co się dzieje? Wczytujemy moduł jQuery, a następnie wypluwamy do body jakiś napis.

  • define – definiuje nowy moduł, jak choćby powyższe jQuery. Nazwa pliku będzie stanowić przy okazji nazwę modułu – plusem jest lepsza organizacja kodu i tworzenie modułów o konkretnych funkcjonalnościach nadających się do wielokrotnego użytku.
    1
    2
    3
    4
    5
    define(function() {
        return {
            'doSomething': ...
        };
    });

    Co się dzieje? Definiujemy nowy moduł i zwracamy obiekt z własnością doSomething.

  • w przestrzeni globalnej jest jeszcze obiekt requirejs umożliwiający konfigurację biblioteki.

Mając już podstawową wiedzę o działaniu RequireJS, możesz napisać pierwszy przykład. Do tego celu musisz stworzyć szablonowy plik HTML (razem z elementami: html, head, body), folder scripts/ z pobranym require.js oraz w dokumencie załączyć bibliotekę RequireJS:

1
<script data-main="scripts/app" src="scripts/require.js"></script>

Tak będzie wyglądał natomiast nasz plik scripts/app.js:

1
document.body.innerHTML = 'It\'s working!';

Przykład 1.

Ten brzydki przykład pokazuje pierwszą funkcjonalność RequireJS, jaką jest możliwość wczytywania pliku głównego aplikacji zaraz po wczytaniu require.js. Wystarczy dodać niestandardowy atrybut data-* wskazujący na plik rozruchowy (data-main), który w zależności od potrzeb będzie wczytywał kolejne moduły. Nie ma potrzeby dodawać rozszerzenia plików, jeśli jest nim standardowe rozszerzenie dla JavaScriptu (*.js).

RequireJS i jQuery

Kiedy już znasz działanie RequireJS, pewnie chciałbyś zacząć go używać z jakąś biblioteką, jak choćby jQuery. Aby jednak móc to zrobić, biblioteka powinna być zdefiniowana jako moduł. jQuery (i wiele innych bibliotek) już to robi – sprawdza, czy w przestrzeni globalnej widnieje funkcja define i jeśli tak jest, nie zanieczyszcza przestrzeni globalnej :-)

Zatem ponownie wczytajmy require.js w dokumencie:

1
<script data-main="scripts/app" src="scripts/require.js"></script>

A następnie wczytajmy jQuery i odpalmy nasz wypasiony kod:

1
2
3
4
5
6
7
8
9
10
11
12
requirejs.config({
    'paths': {
        'jquery': [
            '//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min',
            'jquery'
        ]
    }
});

require(['jquery'], function(jQuery) {
    jQuery(document.body).text('It\'s working!');
});

Przykład 2.

Dzieje się tutaj kilka rzeczy:

  • Najpierw odwołujemy się do obiektu requirejs, konfigurując jego ustawienia. Ustawiamy ścieżkę, skąd ma zostać pobrane jQuery.
  • Ścieżka do jQuery została przekazana dwukrotnie w tablicy. Taki zapis mówi: pobierz moduł z pierwszego adresu, a jeśli nie zadziała, pobierz z kolejnego adresu. Jeśli więc tworząc stronę zabraknie dostępu do Sieci, jQuery pobierze się z lokalnej lokalizacji.
  • Na koniec wywołujemy globalną funkcję require, która powoduje wczytanie modułu (jeśli jeszcze nie jest wczytany) i wywołanie kodu z funkcji zwrotnej (który wymaga do działania wczytanego modułu).
RequireJS i zależności

Żaden z tworzonych modułów nie zaśmieca już przestrzeni globalnej. Jak zatem odwołać się z modułu A do modułu B? W tym celu wymyślono zależności pomiędzy modułami.

W naszym przykładzie pomińmy tworzenie kolejnego, takiego samego pliku HTML :-) Zabierzmy się za napisanie prostego modułu, którego celem będzie odwrócenie tekstu. Oczywiście wykorzystajmy do tego niezawodne jQuery! :D (dla lepszego pokazania współpracy kilku modułów)

scripts/jquery.object-size.js:

1
2
3
4
5
define(['jquery'], function(jQuery) {
    return function(object) {
        return jQuery.map(object, function(n, i) { return i}).length;
    }
});

Nowością w powyższym kodzie jest tablica przed definicją modułu. To właśnie w tej tablicy wskazujemy nazwy modułów, które należy najpierw wczytać, nim zostanie wykonany kod tworzonego modułu. Zależne moduły zostaną w tej samej kolejności przekazane jako argumenty funkcji definiującej nowy moduł.

Do modułów możemy się także odwoływać przez użycie funkcji require:

1
2
3
4
5
6
7
define(['require'], function(require) {
    var jQuery = require('jquery');

    return function(object) {
        return jQuery.map(object, function(n, i) { return i}).length;
    }
});

Zmianie ulega również plik główny aplikacji, app.js:

1
2
3
4
5
6
7
8
9
10
11
requirejs.config({
    'paths': {
        'jquery': '//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min'
    }
});

require(['jquery', 'jquery.object-size'], function(jQuery, objectSize) {
    var myObject = {'a': 1, 'b': 2, 'c': 3};

    jQuery(document.body).text('Object ' + JSON.stringify(myObject) + ' size: ' + objectSize(myObject));
});

Ponownie definiujemy ścieżkę do jQuery, a następnie wczytujemy dwa moduły: jquery oraz jquery.object-size. Pierwszym jest biblioteka jQuery, której użyliśmy tutaj do operacji DOM, drugim funkcja do obliczania wielkości obiektu.

Przykład 3.

Moduły z własną nazwą

Wcześniej wspomniałem, że każdy moduł powinien być w osobnym pliku. To najlepsze rozwiązanie, bowiem rozwijanie takiego kodu będzie proste nawet po dłuższej przerwie. Niemniej jednak pojawia się poważny problem – co z wydajnością takiego rozwiązania? Przecież każdy z tych modułów to osobne żądanie HTTP, a skoro na starcie potrzebujemy XX modułów to dlaczego by ich nie połączyć w jednym pliku?

I właśnie tutaj przydaje się nazywanie modułów. Oczywiście nie musimy tego robić ręcznie (a nawet nie powinniśmy). Mamy od tego r.js oraz obszerną instrukcję jak sobie z tym poradzić (być może omówię w kolejnym wpisie, jeśli będzie taka potrzeba).

Jeśli jednak chciałbyś z jakiegoś powodu robić to ręcznie, możesz użyć następującej składni:

1
2
3
define('nazwa-modulu', function() {
    return function() {};
});

Przykład 4.

Współpraca ze starym kodem

Ok, spodobało Ci się to podejście i chciałbyś zastosować je w swojej firmie. Jednak co zrobić ze starym kodem, niekompatybilnym z RequireJS? Wyrzucić lata pracy?

Najlepszym rozwiązaniem jest przerobić kod na modularny – jeśli to był dobry kod, zapewne wystarczy dodać definicję i zależności modułu (2 linijki). Jeśli słaby – lepiej przepisać od nowa lub to komuś zlecić :-)

Gorzej w przypadku kodu, który nie jest naszego autorstwa. Przecież nie będziemy modyfikować komuś kodu – musielibyśmy to robić przy każdej jego aktualizacji. Rozwiązaniem jest odpowiednie skonfigurowanie RequireJS z wykorzystaniem własności shim.

W naszym nieidealnym świecie potrzebujemy użyć funkcji objectSize, którą trzymamy w pliku jquery.object-size.js:

1
2
3
function objectSize(object) {
    return jQuery.map(object, function(n, i) { return i}).length;
}

Funkcja jest napisana przez innego programistę i nie masz praw do jej modyfikacji :-) Co więcej, używa ona globalnego, znajomo brzmiącego obiektu jQuery. Niemniej jednak w naszym projekcie jQuery to tylko moduł, do którego dostęp jest utrudniony (nie zaśmieca globalnej przestrzeni). Co zrobić, jak żyć?

Wystarczy właściwa konfiguracja RequireJS:

1
2
3
4
5
6
7
8
requirejs.config({
    'paths': {
        'jquery': '//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min'
    },
    'shim': {
        'jquery.object-size': ['jquery']
    }
});

By chwilę później móc użyć:

1
2
3
4
require(['jquery.object-size'], function(objectSize) {
    var myObject = {'a': 1, 'b': 2, 'c': 3};
    document.body.innerHTML = 'Object ' + JSON.stringify(myObject) + ' size: ' + objectSize(myObject);
});

Problem rozwiązany. Przykład 5.

Podobnie można poradzić sobie z niedostosowanymi do RequireJS pluginami do jQuery. Przykład 6.

Co tutaj się dzieje? W konfiguracji dodajemy obiekt shim, w którym podajemy niekompatybilne z RequireJS pliki. W nazwie własności używamy nazwy pliku, natomiast jako wartość podajemy tablicę z zależnymi modułami, które staną się globalne w obrębie tego pliku (w tym przypadku jQuery).

Moduły w JavaScript

RequireJS nie jest jedyną biblioteką implementującą AMD. Masz do wyboru wiele innych opcji, m. in. curl.js, Yabble, PINF. RequireJS jest natomiast najpopularniejszy (co przekłada się na jego wsparcie w bibliotekach i dostępność modułów).

Również grupa CommonJS stworzyła standard dla modułów i paczek, a ich składnia jest dość szeroko wspierana (także przez jQuery).

Co warto jeszcze wiedzieć?

Modularne podejście do programowania nie jest żadną nowością – JavaScript co najwyżej zapożyczył sprawdzoną w innych językach funkcjonalność. W niektórych językach, jak Java, moduły są wbudowane w domyślną bibliotekę. Nawet w PHP mamy require do zaciągania kolejnych plików! :D (choć temu akurat daleko do modułów).

Co ważne, także i w ECMAScript 6 (Harmony) najprawdopodobniej pojawią się moduły. Doskonale to pokazuje w jakim kierunku zmierza świat JavaScriptu.

Komentarze (5)

  1. DarV 27 lipca 2013

    ” re-używalnych” wielokrotnego użytku ; D ?

  2. Kamil Brenk 28 lipca 2013

    @DarV: bardziej mi chodziło o jednowyrazowy odpowiednik dla reusable, ale wielokrotnego użytku również może być :)

  3. Michal 30 sierpnia 2013

    Fajny temat, akurat myślałem o nauce JavaScriptu, a ten artykuł motywuje :)

  4. hyh 29 grudnia 2013

    Czy pliki podane w src=”” dalej są wgrywane przy ładowaniu strony? Chodzi tylko o interpretowanie gdy będą potrzebne, tak?

  5. Kamil Brenk 29 grudnia 2013

    @hyh: są wczytywane. Dlatego jak widzisz wyżej, wczytujemy tylko require.js i main.js, a kolejne pliki doczytywane są dopiero w momencie ich wywołania, np.:

    1
    2
    if (/** ... **/)
        require('nazwapliku', myCallback);


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
    • Liczniki w CSS
    • Wyprzedaż książek o programowaniu!
    • Niestandardowy placeholder
    • JavaScript w modułach
    • Co dalej z blogiem?
    • Interaktywna mapa w HTML i CSS
    • Olsztyn: Jak wyseparować zawartość zassaną przez file_get_content?
    • ERMLAB: Od czegoś trzeba zacząć :) Wiele osób właśnie stawia na...
    • david: co nalezy wkleić na stronę aby plik ze stylami był ladowany...
    • krynicz: Nie jestem pewien czy dobrze to rozumiem: wpisujemy OG w...
    • yaro: Jak zmienić re_write znak "_" na "-"?
    • Piotr: stworzyłem prostą stronkę w PHP, czy jest możliwość aby...
    • MichalR: Super sprawa... bardzo przydatne.. dzieki i pozdrawiam..
    • Niestandardowe czcionki na stronie
    • Sposoby wczytywania JavaScript
    • Gramatyka w PHP, część 1
    • Umowa i zaliczka dla freelancera
    • Wysyłanie wiadomości SMS w PHP
    • Projekt aplikacji po stronie klienta
    • Własny mechanizm Feed
  • 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
    • Filip Górczyński
  • Strona główna
  • Curriculum Vitae
  • O mnie
  • Przykład: Gramatyka w PHP
  • Przykład: Kompresja CSS
  • Przykład: Kompresja JavaScript
  • Przykład: Skracanie linków
  • Przykład: Wykrywanie serwera HTTP
  • Przykład: Własna bramka SMS
  • Mapa strony
  • Kontakt

Kamil Brenk © 2010. All rights reserved.

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

Do góry ∧