Blog webdeveloperski Patryk yarpo Jar

Tworzenie modułów JS zgodnych z AMD (require.js)

Autor wiadomości Styczeń 6, 2013

W niedawnym wpisie pokazałem jak wykorzystać bibliotekę require.js. W tym skupię się na tym, jak tworzyć własne moduły JavaScript zgodne z AMD (ang. asynchronous module definition).

Aby zacząć warto mieć:

  • serwer www (może być WAMP)
  • podstawy JS (przyda się też minimalna wiedza o AMD)
  • 5 minut 😉

Najprostszy moduł

Na początek stworzymy coś bardzo prostego. Będzie to moduł `Obliczenia', posiadający 4 metody (podstawowe operacje matematyczne). Nie będzie po niczym dziedziczył, ani wykorzystywał innych modułów.

define('modules/Obliczenia', function()
{
    return function()
    {
        this.dodaj = function(a, b)
        {
            return a + b;
        }
        this.odejmij = function(a, b)
        {
            return a - b;
        }
        this.pomnoz = function(a, b)
        {
            return a * b;
        }
        this.podziel = function(a, b)
        {
            return a / b;
        }
    };
});

demo online pobierz kod

Kluczowa jest tu funkcja `define' będąca zdefiniowana w bibliotece require.js.

Przyjmuje ona tu dwa parametry:

  1. nazwa modułu, będąca przy okazji ścieżką do pliku, w którym znajduje się ten moduł (czyli plik `Obliczenia.js' znajduje się w katalogu `modules').
  2. funkcja, która powinna zwrócić obiekt lub konstruktor obiektu (tak jak w powyższym przykładzie - funkcję)

Aby wykorzystać taki moduł należy użyć takiego kodu:

require(['modules/Obliczenia'], function(Obliczenia)
{
	var mat = new Obliczenia();

	console.log("2 + 2 = " + mat.dodaj(2,2));
	console.log("5 - 2 = " + mat.odejmij(5,2));
	console.log("2 * 3 = " + mat.pomnoz(2,3));
	console.log("6 / 3 = " + mat.podziel(6,3));
});

O tym, jak wykorzystywać funkcję require pisałem w osobnym wpisie.

Moduł z zależnościami

Do tego przypadku stworzymy prosty model świata. Jest sobie pojazd (moduł `mechanika/Pojazd'), który posiada silnik (moduł `mechanika/Silnik'). Gdy idziesz do sklepu kupić pojazd (najczęściej) nie musisz osobno kupić silnika. Załóżmy zatem, że kiedy wywołamy moduł `mechanika/Pojazd' nie powinniśmy przejmować się załączaniem `mechanika/Silnik'. Jak to zrobić? AMD pomoże:

Pojazd:

define('mechanika/Pojazd', ['mechanika/Silnik'], function(Silnik)
{
    return function()
    {
        var silnik = new Silnik()
        ,   uruchomiony = false;

        this.uruchom = function()
        {
            if (false === uruchomiony)
            {

                alert('Pojazd: Uruchamiam się');
                silnik.odpal();
                uruchomiony = true;
            }
            else
            {
                alert('Pojazd: Już jestem uruchomiony!');
            }
        }
        this.jedz = function()
        {
            if (true === uruchomiony)
            {
                alert('Pojazd: Jadę!');
            }
            else
            {
                alert('Pojazd: Nie mogę jechać. Najpierw mnie uruchom!');
            }
        }
        this.zatrzymaj = function()
        {
            if (false === uruchomiony)
            {
                alert('Pojazd: Po co mnie zatrzymujesz, skoro nie jestem uruchomiony');
            }
            else
            {
                alert('Pojazd: Zatrzymuję się');
                silnik.zgas();
                uruchomiony = false;
            }
        }
    };
});

demo online | pobierz kod

W przeciwieństwie do wcześniejszych przykładów  funkcja `define' przyjmuje tu 3 parametry. Nowym parametrem jest tablica zależności:

define(..., ['mechanika/Silnik'], ...

Jest to lista modułów, jakie są wymagane do poprawnego zbudowania definiowanego modułu. W tym konkretnym wypadku jest to jeden moduł. Jednak lista mogłaby być dużo dłuższa, np:

define(..., ['mechanika/Silnik', 'mechanika/Kolo', 'elektronika/Radio'],
    function(Silnik, Kolo, Radio)

Jak być może zauważyłeś z powyższego fragmentu kolejne pozycje z listy zależności są odwzorowywane w liście parametrów przekazywanych do funkcji (zasada jest taka sama jak w funkcji `require').

Silnik:

define('mechanika/Silnik', function()
{
    return function()
    {
        var odpalony = false;

        this.odpal = function()
        {
            if (false === odpalony)
            {
                alert('Silnik: Odpalam silnik');
                odpalony = true;
            }
            else
            {
                alert('Silnik: Już jestem odpalony!');
            }
        }
        this.zgas = function()
        {
            if (false === odpalony)
            {
                alert('Silnik: Nie mogę zgasić, bo nie jestem odpalony');
            }
            else
            {
                alert('Silnik: gaszę');
                odpalony = false;
            }
        }
    };
});

Kod HTML/JS tworzący pojazd:

<html>
<head>
    <script type="text/javascript" src="require.js"></script>
    <script type="text/javascript">
    require(['mechanika/Pojazd'], function(Pojazd)
    {
        var wehikul = new Pojazd();

        wehikul.uruchom();
        wehikul.jedz();
        wehikul.zatrzymaj();
        wehikul.zatrzymaj();
    });
    </script>
</head>
<body></body>
</html>

demo online | pobierz kod

W powyższym fragmencie istotnym jest zauważanie, że nigdzie jawnie nie załączamy modułu `mechanika/Silnik'. Dzięki AMD moduły same potrafią załadować swoje zależności. Dzięki czemu plik index.html nie pęka od kolejnych linii <script />.

Jeśli samo działanie kodu nie przekonuje Cię zajrzyj w zakładkę "Sieć" w narzędziach webdeveloperskich Twojej przeglądarki (przeważnie F12, w FF polecam Firebug):
Zakładka Sieć

Jak widzisz, choć jawnie w kodzie załączyliśmy tylko `require.js' pozostałe pliki zostały dzięki mechanizmom zawartym w bibliotece Require.js dołączone w odpowiedniej kolejności.

Komentarze (4) Trackbacks (0)
  1. Ostatnio nadziałem się na require.js. Okazało się, że ma problemy z wczytywaniem modułów zoptymalizowanych przez swój własny optymalizator (nie umie czytać plików, gdzie jest więcej niźli jedno define). Rozwiązanie jest strasznie głupie, bo trzeba… drugi raz wywołać require. Nie ukrywam – nie podobało mi się to i znalazłem ciut inne rozwiązanie – curl. nie, nie ten 😉 https://github.com/cujojs/curl
    to małe cudeńko to loader zgodny z AMD i dodatkowo mniejszy od require.js. Co ciekawe, pisany w taki sposób, żeby być w pełni zgodnym ze swym starszym bratem a – co dla mnie ważniejsze – z jego optymalizatorem.
    Mniejszy rozmiar, brak problemów ze zoptymalizowanymi skryptami (require.js, RLY?!), bardzo przystępne API… no cóż. Jak dla mnie wybór oczywisty 😉

  2. Hm to dość dziwne. Wiesz może czy twórcy R.JS nie uważają tego przypadkiem za jakąś zaletę ich biblioteki? Może ma to swoje dobre strony? (choć niebyt potrafię zrozumieć jakie)

    Podobnie nie rozumiem dlaczego zrezygnowano z bardzo fajnego sposobu prezentowanego wcześniej w Dojo ( http://www.yarpo.pl/2011/10/11/tworzenie-klas-w-dojo/ ):

    dojo.provide(‘yarpo.MySecondClass’);

    dojo.require(‘yarpo.MyFirstClass’);

    dojo.declare(‘yarpo.MySecondClass’, [yarpo.MyFirstClass],
    {
    constructor : function()
    {
    console.log(“działa MySecondClass”, arguments);
    }
    });

    Moim zdaniem takie rozwiązanie jest dużo bardziej czytelne. Choć z kolei define i podanie modułów jako parametrów pozawala na większą optymalizację kodu.

    Długo jednak się przekonywałem do takiego zapisu 🙂

  3. Raczej za zaletę tego uważać nie mogą i nazywają “missing feature” 😉 Ot, require.js za dużo robi, stąd ten błąd.
    Nie jestem pewien, ale require i define chyba wzięły się ze standardu CommonJS (patrz: require w node), stąd przyjęto taką a nie inną składnię

  4. Możliwe. Wydaje mi się, że:

    define(‘modul/A’) {
    require(‘modul/B’, B);

    var o = new B();
    }

    byłoby czytelniejsze.


Leave a comment

 

Brak trackbacków.