Require.js – AMD w praktyce

Już kilkukrotnie poruszałem temat tworzenia modułów (czasem nawet „klas”) w JavaScripcie. Tym razem coś nowego – asynchroniczne definiowanie modułów. W skrócie AMD (ang. asynchronous module definition). Coraz więcej znanych frameworków i bibliotek wykorzystuje właśnie to podejście (choćby Dojo i jQuery).

W tym wpisie spróbuję pokazać dlaczego powstało takie rozwiązanie oraz przedstawić kilka prostych zastosowań.

Na początek warto:

  • mieć jakieś podstawy JavaScript
  • zarezerwować 5 minut

Za wiele <script /> w kodzie?

Czy tworząc bogate aplikacje internetowe oparte o JavaScript nie masz czasem przytłaczającego wrażenia, że połowa kodu HTML to zaczynają być znaczniki załączające kolejne pliki `*.js’? Czy taki widok jest dla Ciebie codziennością:

[html]<html>
<body>
<script src="jquery.js"></script>
<script src="1.js"></script>
<script src="http://example.com/a.js"></script>

<script src="z.js"></script>
</body></html>[/html]

W przypadku takiej strony, im jest ona bardziej rozbudowana, tym więcej plików trzeba załączyć. Jeśli mamy wiele podstron sytuacja powtarza się na wielu z nich. Dodatkowo nie było możliwości określenia zależności między konkretnymi „modułami” (plikami, które często były całymi frameworkami czy bibliotekami).

Jak sobie z tym radziliśmy?

Kilka razy pokazywałem już jak można zmniejszyć liczbę odwołań do serwera i rozmiar plików JS, oto moje autorskie rozwiązanie:

[html]<html>
<head>
<script src="script.php?files=file_1,file_2" type="text/javascript"></script>
</head>
<body></body></html>[/html]

Dokładny opis, jak miałoby to działać znajdziesz w tych wpisach (wiedza ta nie jest niezbędna do zrozumienia tego wpisu):

Nadal jednak trzeba było ładować skrypty „globalnie”. Nie było możliwości, aby konkretny załączony plik js określił jakich innych plików potrzebuje. Wymaganie, o którym ciągle wspominam brzmi wręcz jak mechanizmy `include’, `using’ czy `import’ z języków uznawanych powszechnie za bardziej dojrzałe (C, C#, Java, itp.). Czy podobny mechanizm w języku takim jak JavaScript jest osiągalny?

Nowe podejście – AMD

Powyższe przykłady zaprowadziły nas do nowego rozwiązania, które musiałby pozwalać:

  1. tworzyć moduły zawarte w pojedynczych plikach,
  2. załączać (w automatyczny sposób) te pliki odczytując w jakiś sposób ich wzajemne zależności „lokalnie” (moduł sam mówi czego jeszcze potrzebuje),
  3. rozbić wielkie biblioteki na mniejsze fragmenty, tak aby ładować tylko to, czego naprawdę potrzebujemy,
  4. móc korzystać z modułów zawartych w różnych bibliotekach bez ładowania całych bibliotek (obsługa grafiki z biblioteki X, obsługa zdarzeń z ABC).

Brzmi to pięknie. Czy da się to jednak osiągnąć w JavaScript? Może w nowszych wersjach, bo przecież teraz nie ma jeszcze odpowiednich mechanizmów w tym języku – pewnie pomyślało wielu. A jednak. Można. Dzisiejsze mechanizmy wystarczą.

AMD – przykład wykorzystania

W przykładach wykorzystam darmową bibliotekę Require.js.

[html]<!DOCTYPE html>
<html>
<head>
<title>Require.js – Przykład wykorzystania AMD [yarpo.pl]</title>
<meta charset="utf-8" />
<script src="require.js"></script>
<script>
require([‚modules/HelloWorldAlerter’], function(HelloWorldAlerter)
{
var alerter = new HelloWorldAlerter();
alerter.run();
});
</script>
</head>
<body></body></html>[/html]

demo online
pobierz działający kod (należy uruchamiać na serwerze WWW)

W powyższym przykładzie wykorzystałem funkcję `require’, która została zaimplementowana wewnątrz biblioteki `Require.js’. Przyjmuje ona 2 argumenty:

  1. tablica modułów, jakie będą wykorzystywane
  2. funkcję, która ma zostać wykonana, gdy wszystkie moduły zostaną załadowane

Być może zauważyłeś, że przekazana funkcja posiada w tym wypadku jeden argument – `HelloWorldAlerter’. Jest to konstruktor modułu. Gdyby podano więcej modułów, liczba parametrów funkcji także zwiększałaby się:

[javascript]require([‚modules/A’, ‚modules/B’, ‚widgets/AXT’, ‚Utils’], function(modulA, bModule, AXT, InnaNazwaBoNazwaToTylkoNazwa)
{
var objectA = new modulA()
, objectB = new bModule()
, oWidget = new AXT()
, oUtils = new InnaNazwaBoNazwaToTylkoNazwa()
, oUtils2 = new arguments[3](2); // za pomocą obiektu `arguments’ oraz z przekazaniem wartości do konstruktora

objectA.run();
objectB.run();
oWidget.create();
oUtils.run();
oUtils2.run();
});[/javascript]

demo online
kod

Zwróć uwagę, że parametry funkcji mogą mieć dowolną nazwę. To Ty decydujesz jak lokalnie – w tej funkcji będą się nazywać ładowane moduły (można nawet odwoływać się do nich za pomocą obiektu `arguments‚). Polecam jednak stosowanie pewnej stałej konwencji.

Jak tworzyć własne moduły?

Temat ten poruszyłem w osobnym wpisie dotyczącym tworzenia modułów JavaScript za pomocą AMD.

Jeśli chciałbyś zgłębić wiedzę na ten temat polecam:

  • zajrzeć w kod przykładów on-line dołączonych do tego posta (bądź pobrać kody źródłowe [1],[2])
  • poczytać na stronach biblioteki Require.js (dokumentacja)
  • popatrzeć jak wygląda kod znanych bibliotek np. Dojo (kod z SVN)

Powodzenia 🙂

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *