Статьи

JavaScript следующего поколения с AMD и RequireJS

Я не знаю о вас, но я всегда ищу лучшие способы организовать мой JavaScript. Недавно (всего неделю или около того назад) я обнаружил, что кажется лучшим паттерном. В этом руководстве я познакомлю вас с AMD : определение асинхронного модуля и RequireJS . Держись крепко; это будет дикая поездка!

Этот учебник включает в себя скринкаст, доступный для пользователей Tuts + Premium.


Если вы некоторое время писали клиентский JavaScript, вы могли заметить проблему <sarcasm> small </ sarcasm> с JavaScript. Единственный или, по крайней мере, стандартный способ получения нескольких файлов JavaScript на странице состоит в том, чтобы включить несколько тегов сценария:

1
2
3
<script src=»file1.js»></script>
<script src=»file2.js»></script>
<script src=»file3.js»></script>

Есть несколько проблем с этим методом.

Во-первых, это просто неудобно. Весь JavaScript во всех сценариях загружается в глобальное «пространство имен». Конечно, это может вводить в заблуждение новичков, которые могут легко — и простительно — думать, что переменные с одинаковыми именами в разных файлах не будут конфликтовать. Но более того, это опыт для опытных разработчиков JavaScript с, возможно, десятками файлов JavaScript. Некоторые файлы будут зависеть от других, которые могут зависеть от других и т. Д. И вы несете ответственность за игровую площадку: вы должны убедиться, что все они прибывают в правильном порядке, и чтобы никто не забивал кого-либо. Это может быть сложным или трудоемким, в зависимости от проекта.

Во-вторых, и что более важно, когда браузер загружает и выполняет файлы JavaScript, это все, что он делает; это блокирует загрузку другого контента, пока это не будет сделано. Вот почему вы слышали, что рекомендуется размещать ваши теги <script> в конце вашего <body>, а не в <head>, как это было раньше: таким образом, весь ваш другой контент (HTML, CSS) , изображения и т. д.) будут видны пользователю до загрузки и выполнения JavaScript. Используя один из многих доступных загрузчиков скриптов , вы сможете оптимизировать этот процесс в максимально возможной степени. RequireJS — один из таких загрузчиков, и вы получите все преимущества его использования с AMD. Подробнее об этом читайте в первой главе (и, конечно, во всем остальном!) Высокопроизводительного JavaScript от Николаса С. Закаса.



Для этого есть предлагаемое решение, частично в форме определения асинхронного модуля (AMD). Это решает проблему зависимости (некоторые сценарии зависят от других), глобальную проблему «пространства имен» (где весь код находится в глобальном «пространстве имен» по умолчанию) и проблему блокировки.

Идея такова: что если бы у нас был стандартный способ написать «кусок» функциональности JavaScript? Правильный способ организовать библиотеку, фреймворк, набор вспомогательных функций или скрипт? Мы будем называть эти модули наборов кодов. Тогда что, если бы был стандартный способ загрузки наших модулей на страницу? Лучший способ, чем использовать теги сценария?

Вот что я собираюсь показать вам в этом уроке. Определение асинхронного модуля — это спецификация для определения модулей JavaScript. Я уже говорил выше, что AMD — это только часть решения. Это потому, что модуль бесполезен, если мы не можем загрузить его на страницу и использовать. Итак, это другая часть: нам нужно требовать наши модули (загружать их), а затем использовать их. Хотя эта часть не является спецификацией, подобной AMD, мы увидим, что все в порядке.

Как видно из заголовка, это руководство посвящено библиотеке RequireJS . Но, как вы быстро узнаете, это гораздо больше. Вот несколько общих моментов, которые я хочу, чтобы вы запомнили, проходя этот урок:

  • AMD и требуемые модули — это две разные вещи: AMD — это спецификация со строгой реализацией; требование модулей вообще не является стандартом и имеет различные реализации.
  • RequireJS — это только одна из многих библиотек, которые реализуют спецификации AMD и метод требующих модулей. Там нет ничего, что говорит, что библиотека должна реализовывать оба.

Видя, что AMD — это спецификация, которая не изменится, независимо от того, какую библиотеку вы используете для загрузки модулей, давайте сначала рассмотрим, как создавать модули; мы сделаем это, не ссылаясь ни на какие библиотеки. Затем мы рассмотрим, как мы можем использовать RequireJS для использования наших модулей.

Последнее замечание: вы можете быть знакомы с шаблоном модуля в JavaScript. Модули, о которых мы говорим сегодня, не таковы; они довольно похожи, в том смысле, что мы можем использовать замыкание, чтобы получить «частную» область видимости, но это другой шаблон. Просто к вашему сведению.


Прежде чем мы рассмотрим какой-то фактический синтаксис, давайте обсудим создание модуля в резюме (да, я знаю, что вам не терпится что-то кодировать, но основа важна). Во-первых, каждый созданный вами модуль будет находиться в отдельном файле; Помимо упрощения процесса разработки, вы увидите, что загрузчики модулей, такие как RequireJS, используют имя файла в качестве имени модуля. Конечно, вы можете жестко закодировать имя для вашего модуля внутри файла, но это плохая идея. Это потому, что после разработки вы захотите использовать инструмент оптимизации, чтобы поместить все ваши модули в один файл для лучшей загрузки. Инструмент оптимизации даст вашим модулям имя, исходя из их имени файла. Если вы добавили имя для модулей в файле, это может вызвать путаницу.

Итак, имя. Следующее, что будет у ваших модулей — это необязательный список других модулей, от которых он зависит. Помимо упрощения разбиения вашего кода на управляемые модули, эта поддержка зависимостей делает две вещи:

  • Это устраняет проблему необходимости (в правильном порядке!) Нескольких тегов сценариев. Ваша библиотека загрузки загрузит их для вас.
  • Это позволяет вашим модулям взаимодействовать, даже не приближаясь к этому глобальному «пространству имен».

Затем есть единственная обязательная часть модуля: спецификация называет его фабрикой . Это либо обычный старый объект, либо функция, которая возвращает значение. Если ваша фабрика является функцией — более распространенный случай — вы не получите функцию при работе с модулем; вы получите значение, которое возвращает функция. Это значение может быть любым значением JavaScript; вы, вероятно, обнаружите, что используете объект чаще всего, но вы можете вернуть функцию Constructor, строку или что угодно.

Готовы к некоторому коду?


Итак, давайте сделаем модуль. В частности, давайте создадим модуль, который будет обрабатывать присоединение и отсоединение событий DOM. Начнем с создания нашего примера проекта: просто папка с index.html (к которой мы еще вернемся) и папка с именем «js». Поскольку в конечном итоге вы соберете большую коллекцию модулей, важно сохранить их хорошо организовано: поэтому у нас будет папка «dom» внутри «js». В «dom» создайте файл с именем events.js и откройте его в events.js текстовом редакторе.

Спецификация AMD определяет одну функцию под названием define . Как вы могли догадаться из нашего обсуждения выше, он принимает три параметра: имя (необязательно), массив зависимостей (необязательно) и фабрику. У нас здесь нет никаких зависимостей, и на самом деле нет никакой подготовительной работы, которую мы должны сделать, поэтому наша фабрика может быть просто объектом:

01
02
03
04
05
06
07
08
09
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
define({
    addEvent: function (el, evt, fn) {
        if (el.addEventListener) {
            this.addEvent = function (el, evt, fn) {
                el.addEventListener(evt, fn, false);
                return el;
            };
        } else if (el.attachEvent) {
            this.addEvent = function (el, evt, fn) {
                el.attachEvent(«on» + evt, fn);
                return el;
            };
        } else {
            this.addEvent = function (el, evt, fn) {
                el[«on» + evt] = fn;
                return el;
            };
        }
        return this.addEvent(el, evt, fn);
    },
    removeEvent: function (el, evt, fn) {
        if (el.removeEventListener) {
            this.removeEvent = function (el, evt, fn) {
                el.removeEventListener(evt, fn, false);
                return el;
            };
        } else if (el.detachEvent) {
            this.removeEvent = function (el, evt, fn) {
                el.removeEvent(«on» + evt, fn);
                return el;
            };
        } else {
            this.removeEvent = function (el, evt, fn) {
                el[«on» + evt] = fn;
                return el;
            };
        }
        return this.removeEvent(el, evt, fn);
    }
});

Не может быть проще Если вы ранее создавали функции обработки событий, вы точно знаете, что мы делаем: в зависимости от возможностей DOM-событий браузера мы переопределяем addEvent или removeEvent при первом запуске.

Теперь вы можете подумать: «Почему бы просто не назначить этот объект переменному event ?». Вот как вы могли бы это сделать, если бы вы не использовали AMD, но тогда вам придется помнить об импорте сценария. Но теперь давайте использовать модуль, который мы только что создали.

Давайте создадим файл с именем main.js в папке «js»; если бы это был большой проект, то именно там начинаются все действия. Важно отметить, что то, что мы собираемся сделать , не является частью спецификации AMD. Это часть RequireJS, и это не определенный API. С учетом сказанного, давайте посмотрим, как это работает!

Итак, откройте этот файл main.js Давайте начнем с этого:

1
2
3
require([«dom/events»], function (event) {
  
});

Что это делает? Мы вызываем функцию require , которая является частью RequireJS. Он принимает два параметра: массив зависимостей и функцию обратного вызова, где происходит действие. Вы можете подумать, что это выглядит довольно похоже на то, как работает функция define . Ну, это так, но не спешите, где я, и подумайте, что они более тесно связаны, чем на самом деле. Другие загрузчики JavaScript, работающие с модулями AMD, могут сделать это немного по-другому. Это потому, что define — это спецификация, а require — нет ; я уже говорил об этом?

Как видите, каждая зависимость — это строка в массиве зависимостей. Обратите внимание, как мы указали на ваш модуль событий: «dom / events». Это название нашего модуля. помните, это происходит от пути к нашему файлу модуля. Вы можете думать о «dom /» как о пространстве имен для всех модулей, связанных с DOM.

Итак, давайте сделаем простое событие:

1
2
3
4
5
6
require([«dom/events»], function (events) {
    var elem = document.getElementById(«target»);
    events.addEvent(elem, «click», function () {
        alert(«clicked»);
    });
});

Мы должны поместить что-то в наш файл index.html чтобы все это работало. Как это выглядит?

01
02
03
04
05
06
07
08
09
10
<!DOCTYPE html>
<html>
    <head>
        <title>Learn Require.js</title>
        <script data-main=»js/main» src=»js/require.js»></script>
    </head>
    <body>
        <h1 id=»target»>Learning Require.js</h1>
    </body>
</html>

Это, конечно, было бы хорошим временем для загрузки RequireJS (просто скачайте простой require.js , require.js никаких других специальных дополнений ). Поместите его в папку «js».

Вы можете быть смущены, когда увидите, что наш единственный скрипт-скрипт require.js . А как насчет загрузки main.js чтобы приложение (эй! Оно маленькое, но это приложение) работало? Что ж, видите, что атрибут HTML5 data- * на теге script? Это говорит RequireJS, что загружать после завершения загрузки. Итак, мы говорим ему загрузить js/main.js В случае RequireJS это также устанавливает параметр baseUrl в ту же папку, в которой находится загружаемый скрипт. Это наша папка «js». Это имеет отношение к тому, как нам нужны наши модули внутри main.js : поскольку при main.js настоящее время нет доступных модулей, называемых «dom / events», он предполагает, что это путь (а также имя) и ищет это внутри baseUrl : js/dom/events.js .

Ну, это все, что вам нужно сделать: откройте этот файл index.html и щелкните заголовок:


Большой! Это сработало!


Теперь, когда у нас есть рабочий модуль, давайте перейдем к следующему шагу. Прямо сейчас наш модуль «dom / events» является просто литералом объекта, потому что это все, что ему нужно. Но у меня есть идея, которая сделает его более полезным для разработчиков, которые его используют: не было бы неплохо, чтобы некоторые сахарные методы были названы в честь общих событий: «click», «mouseover», «mouseout», «keypress», и т.д. Конечно, мы могли бы кодировать каждый из них в отдельности, но давайте будем умными и добавим их динамически. Это потребует, чтобы наш модуль был чем-то большим, чем просто литерал объекта. Измените events.js так, чтобы он выглядел так:

1
2
3
4
5
6
7
define(function () {
    var eventObject = { /* the object we had previously */ };
  
    // we&#39;ll add our sugar methods dynamically in here.
  
    return eventObject;
});

Мы собираемся создать массив со всеми именами методов, которые мы хотим добавить; поскольку эти имена совпадают с именами событий DOM, наша задача в два раза проще: перебрать массив и добавить каждый сахарный метод.

Цикл над массивом … (пожалуйста, погладьте свой подбородок здесь). Если бы все браузеры, которые мы хотим поддерживать, были современными, мы могли бы использовать Array.prototype.forEach для легкой итерации. Но IE 6 — 8 не поддерживает это. Конечно, мы могли бы просто использовать цикл for, но в конечном итоге эти старые браузеры умрут, поэтому мы могли бы подготовиться к этому сейчас. Давайте Array.prototype.forEach модуль утилит массива, который будет делать то же самое, что и Array.prototype.forEach . Если forEach доступен, мы будем использовать его, в противном случае мы будем использовать версию реализации, показанную в документации Mozilla .

Итак, создайте папку «utils» в нашей папке «js» и откройте файл с именем array.js :

01
02
03
04
05
06
07
08
09
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
define(function () {
  
    var nativeForEach = function (list, callback, thisArg) {
        [].forEach.call(list, callback, thisArg);
    },
    customForEach = function (list, callback, thisArg) {
        var T, k = 0, O, len, kValue;
  
        if ( list == null ) {
            throw new TypeError( » this is null or not defined» );
        }
  
        O = Object(list);
        len = O.length >>> 0;
  
        if ( {}.toString.call(callback) != «[object Function]» ) {
            throw new TypeError( callback + » is not a function» );
        }
  
        if ( thisArg ) {
            T = thisArg;
        }
  
        while( k < len ) {
            if ( k in O ) {
                kValue = O[k];
                callback.call(T, kValue, k, O);
            }
            k++;
        }
    };
  
    return {
        forEach: (function () {
            if ({}.toString.call([].forEach) === «[object Function]») {
                return nativeForEach;
            } else {
                return customForEach;
            }
        }())
    };
});

Опять же, у нас есть модуль без жестко заданного имени и без зависимостей. В нашей фабричной функции мы создаем две функции: nativeForEach , которая просто nativeForEach наши аргументы в собственный Array.prototype.forEach ; и customForEach , который использует версию реализации MDN. Затем мы возвращаем объект с помощью метода forEach . Это вызывающая себя анонимная функция, которая Array.prototype.forEach существует ли Array.prototype.forEach как функция, и если она существует, она использует это. В противном случае он использует наш метод.

Итак, теперь, когда у нас есть модуль утилит массива, давайте использовать его в нашем модуле событий DOM. Это означает, что утилиты массивов будут зависеть от нашего модуля событий dom. Мы должны добавить это в:

1
2
3
4
5
6
7
define([«utils/array»], function (array) {
    var eventObject = { /* the object we had previously */ };
  
    // we&#39;ll add our sugar methods dynamically in here.
  
    return eventObject;
});

Обратите внимание, что даже если это находится в папке «dom», модуль utils/array все еще находится с baseUrl который был определен ранее. Теперь наш модуль dom/events не будет доступен в нашем main.js пока он не загрузит utils/array . Опять же, нет необходимости в дополнительных тегах скрипта и не нужно беспокоиться о порядке загрузки.

Так? Давайте уже использовать наши утилиты массивов!

01
02
03
04
05
06
07
08
09
10
11
define([«utils/array»], function (array) {
    var eventObject = { /* the object we had previously */ };
  
    array.forEach([«click», «mouseover»,»mouseout», «keypress»], function(evt) {
        eventObject[evt] = function (el, fn) {
            this.addEvent(el, evt, fn);
        };
    });
      
    return eventObject;
});

Это так просто. И, конечно, вы можете включить другие события в этот массив, и они тоже будут добавлены. Теперь мы можем вернуться к нашему файлу main.js и использовать наши новые методы:

1
2
3
event.click(elem, function () {
    alert(«clicked»);
});

Я думаю, что вы уже умеете создавать свои собственные модули с помощью Asynchronous Module Definition. И вам будет приятно узнать, что спецификация действительно настолько проста: вы знаете почти все, что нужно знать о AMD, и, конечно, вы знаете достаточно, чтобы начать использовать ее сейчас.

Тем не менее, есть много чего, что может сделать RequireJS, и, поскольку это главная звезда этого шоу, давайте дадим ему то время, которое он заслуживает.


Вы, наверное, думаете: «Ну, это все довольно блестяще, но я никогда не видел ни одного из этих модулей раньше в моей жизни; Вы ожидаете, что я откажусь от всех библиотек и фреймворков, которые я знаю и люблю использовать эту систему? »

Вовсе нет — потому что мы можем загружать обычные старые файлы JavaScript как зависимости и define и require . Вы относитесь к ним так же, как к модулю: укажите путь к файлу (без расширения файла) относительно baseUrl , и вы в деле. Конечно, это не мешает этим сценариям использовать глобальное пространство имен, если это то, что они делают, и вы ничего не передадите на свою фабрику в качестве параметра. Это просто более простой способ убедиться, что нужные скрипты загружены до того, как вам понадобится их использовать.

Например, наш файл utils/array.js может не utils/array.js модуль AMD; это может быть просто глобальный объектный литерал с именем arrayUtils который содержит наши служебные методы. Если бы это было так, мы бы потребовали его таким же образом, но использовали бы его так же, как любую другую глобальную переменную; ничего не передается функции в качестве параметра:

1
2
3
4
5
6
7
require([«utils/array»], function () {
    var arr = [1,2,3];
  
    arrayUtils.forEach(arr, function (item) {
        // action here
    });
});

Это так просто!


RequireJS имеет довольно много опций для настройки, поэтому мы настроим вас на следующие.

Прежде всего, вы захотите узнать, что вы конфигурируете RequireJS, просто передав объект параметров в require.config() . Но какие варианты вы можете установить? Вот некоторые из самых полезных:

Мы уже говорили о том, как устанавливается эта опция, когда вы используете атрибут data-main при загрузке require.js ; если data-main — это my_scripts/start_here.js , то baseUrl будет my_scripts . Однако вы можете перезаписать это в файле скрипта, установив baseUrl: "my_path" . Это также хорошая идея, если вы предпочитаете загружать ваш скрипт из его собственного тега script (потому что при этом не устанавливается baseUrl ):

1
2
<script src=»js/require.js»></script>
<script src=»js/main.js»></script>

Если вы сделаете это, вы должны включить это в начало main.js :

1
require.config({ baseUrl: «./js» });

И все будет работать правильно.

Иногда вам не захочется указывать полный путь к модулю в ваших массивах зависимостей. Например, вы можете оставить один модуль в my_libs/utils/v1/2/0/functional.js , и это будет просто уродливо. Таким образом, вы можете использовать опцию paths для «псевдонима» ваших скриптов.

1
2
3
4
5
require.config({
    paths : {
        «utils/functional» : «my_libs/utils/v1/2/0/functional»
    }
});

Обратите внимание, что реальный путь должен быть записан так, как если бы он был в массиве зависимостей: поэтому, если они не начинаются с протокола (например, http:// ) или /, они будут найдены относительно baseUrl . Но, как и массив deps, мы можем хорошо использовать внешние скрипты:

1
2
3
4
5
6
require.config({
    paths : {
        «utils/functional» : «my_libs/utils/v1/2/0/functional»,
        jQuery: «https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min»
    }
});

Теперь мы можем использовать «jQuery» в нашем массиве deps!

Обратите внимание, что, хотя это полный путь, я взял «.js» с конца; расширение файла требуется, когда вы помещаете URL-адрес прямо в массив зависимостей, но когда вы помещаете его здесь, в параметре конфигурации paths , его там быть не должно. Не забывайте об этом маленьком раздражении, и у вас все будет хорошо.

Сколько секунд RequireJS должен ждать перед загрузкой скрипта? По умолчанию установлено значение 7, но вы можете изменить его, если хотите.

RequireJS довольно гибок, поэтому есть несколько других свойств, которые вы не будете использовать большую часть времени. Тем не менее, вы можете проверить их в документах .


Да, RequireJS имеет архитектуру плагинов, что делает его еще более полезным. Как вы увидите, все плагины в RequireJS связаны с загрузкой файлов. Мы просто рассмотрим один плагин здесь: текстовый плагин. Но есть и другие, если вы интересуетесь .

Текстовый плагин позволяет загружать файлы, которые не являются файлами JavaScript (но являются текстовыми файлами, т.е. без изображений); это, вероятно, будет HTML или CSS. Я уверен, что вы можете подумать о местах, где это пригодится: больше не нужно строить огромные куски DOM в JavaScript; просто требуется файл HTML. Кроме того, это отлично подходит для использования систем шаблонов, таких как Mustache.js или Underscore.js : вместо помещения шаблона в <script type = ”text / template> или прямо в JavaScript, просто поместите его в отдельный файл и используйте текстовый плагин. Просто скачайте текстовый плагин , поместите его в ту же папку, что и ваш основной файл JavaScript. Теперь давайте использовать это.

Давайте попробуем использовать его с шаблоном Underscore.js. Вот что я добавлю в свой main.js :

01
02
03
04
05
06
07
08
09
10
11
12
require([«text!templates/list.html», «libs/underscore»], function (listTpl) {
    // Underscore is loaded globally.
    var context = {
        people : {
            «Fredrick» : «Back-end Developer»,
            «Victoria» : «Front-end Developer»,
            «Hamilton» : «Designer»,
            «Georgea» : «Content Strategist»
        }
    };
    document.body.innerHTML = _.template(listTpl, context);
});

Обратите внимание, как нам требуется наш файл шаблона: мы добавляем к нему префикс text! так что RequireJS знает, как использовать текстовый плагин для загрузки этого ресурса. У нас есть расширение файла там; это требуется. Все, что находится в ваших текстовых файлах, будет загружено в виде строки. Что находится в нашем файле templates/list.html ?

1
2
3
4
5
<ul>
    <% _.each(people, function (job, name) { %>
        <li><strong><%= name %></strong>: <%= job %></li>
    <% });
</ul>

В этом примере мы просто _.template() эту строку в _.template() вместе с объектом context . И мы получили результат, который мы ожидали:


Существуют и другие плагины для RequireJS: Джеймс Берк, автор RequireJS, также написал плагины, которые загружают скрипты в определенном порядке, загружают файлы CoffeeScripts и загружают пакеты интернационализации. Вы можете проверить их, если вам интересно, на странице загрузки .

Написание собственных плагинов возможно, но это довольно сложная вещь. Я укажу вам на документы по написанию плагинов, которые дадут вам все, что вам нужно знать.


Конечно, использование модулей AMD и RequireJS значительно упрощает разработку. Однако теперь у вас есть рюкзак, полный файлов JavaScript, которые загружает ваша страница. Конечно, есть только один тег сценария, но эти другие сценарии загружаются в виде отдельных файлов, которые все складываются. Итак, перед запуском сайта вы захотите использовать инструмент оптимизации RequireJS. Этот скрипт может выполнять две задачи; в первую очередь он объединит соответствующие файлы сценариев и сократит их. Он также может оптимизировать ваш CSS, удаляя комментарии и вставляя @import s (и, возможно, минимизируя).

Инструмент оптимизации RequireJS написан на JavaScript, поэтому для его работы вам понадобится либо Node.js, либо Java & Rhino. Поверьте мне, использование Java & Rhino — ужасное решение. Придерживайтесь Node, если можете; Есть инструкции по установке на любой платформе по всему Интернету. Я покажу вам только способ Node для этого, потому что я бы несколько раз медленно наносил себе удары, чем использовал Java и Rhino (пожалуйста, узнайте гиперболу).

Вы можете прочитать на сайте о том, как оптимизировать один файл за один раз. Но мы собираемся для большого материала: давайте посмотрим, как оптимизировать весь проект. Конечно, вы захотите сделать больше, чем просто оптимизацию RequireJS при подготовке проекта к запуску, так что вы можете запустить его как часть сценария сборки (Nettuts + недавно имел несколько сценариев для сценариев сборки ; проверьте их, если вы ‘ не знакомы с ними). Для начала скачайте файл r.js. Не имеет значения, куда вы положили этот файл, но вам нужно знать путь к нему. Сайт RequireJS рекомендует поместить его в тот же каталог, в котором находятся ваши проекты, так почему бы вам не сделать это?

Мы собираемся проверить это с примером проекта, который вы получили в этой загрузке; это просто комбинация примеров фрагментов, которые мы видели до сих пор, все работают вместе. Итак, давайте создадим профиль сборки для нашего проекта. Это просто файл JSON, похожий на наш объект конфигурации, но со свойствами, специфичными для сборки. Итак, создайте файл в папке «js»; документация RequireJS обычно вызывает файл app.build.js , поэтому мы сделаем то же самое. Начните с этой оболочки:

1
2
3
({
  
})

И давайте посмотрим, какие свойства мы ему дадим:

  • appDir : это корневой каталог вашего проекта. Здесь я отмечу, что все пути в вашем профиле сборки относятся к самому файлу (поэтому, если вы перемещаете файл, вы должны изменить пути). Поскольку мы помещаем файл в наш каталог «js», вот что у нас есть в файле:

    1
    appDir: «../»
  • baseURL : этот baseUrl точно такой же, как baseUrl который устанавливается, когда RequireJS запускается в браузере. Конечно, поскольку ваш код JavaScript не запускается при использовании инструмента оптимизации, инструмент не знает, что такое базовый URL. Это базовое свойство URL относится к свойству appDir ( appDir : я временно забыл это в скринкасте). Итак, мы дадим это так:

    1
    baseUrl: «./js»
  • paths : опять же, как с require.config ; так как наш JavaScript здесь не работает, мы должны установить все наши пути и в файле app.build.js . В нашем случае нам понадобится

    1
    2
    3
    paths: {
        «underscore» : «lib/underscore-min»
    }
  • modules : вот важный: свойство modules — это массив всех модулей, которые мы хотели бы оптимизировать. По сути, все зависимости этого модуля будут включены в файл модуля, а также любые зависимости (и так далее). Мы сделаем это:

    1
    2
    3
    modules: [
        { name : «main» }
    ]

    Вы, вероятно, думаете здесь о двух вещах: во-первых, в main.js нет модуля; это просто обязательный звонок. Во-вторых, зачем иметь объект только с name ? Почему бы просто не ввести имена в виде строк? Ну, хотя main не является модулем, мы все равно можем оптимизировать его таким образом; все зависимости будут сохранены в одном файле. Кроме того, есть и другие опции, которые мы можем включить, которые зависят от модуля. Отсюда и объект. Итак, каковы эти другие свойства? Хорошо, вот два, которые вы можете найти полезными: include: ["module/here", "maybe/another"] будет включать эти модули и их зависимости в одном файле. Также есть exclude: ["module/here"] ; это предотвращает добавление модуля, который обычно включается в файл. Вы можете сделать это, если модуль уже будет включен в другой встроенный файл и, следовательно, уже будет загружен в среду.

  • optimizeCss — инструмент оптимизации оптимизирует любые найденные файлы CSS. Здесь есть три варианта: «нет», который будет игнорировать файлы CSS; «Стандартный», который удаляет все комментарии, вставляет все @import и удаляет разрывы строк; и «standard.keepLines», который удаляет только комментарии и встроенные @import s. «Standard.keepLines» является значением по умолчанию, но я собираюсь установить это:

    1
    optimizeCss: «standard»

    Не попадайтесь в ту же ловушку, которую я делал, изучая это: «ss» в CSS не пишется с заглавной буквы.

  • dir : еще одно: свойство dir сообщает инструменту оптимизации, куда поместить построенный проект. Я буду использовать

    1
    dir: «../../sample-build»

    Таким образом, у нас будет папка «sample-build» прямо рядом с нашей папкой «sample_project». В этом новом оптимизированном проекте будет все: вы сможете запустить его без проблем.

Итак, вот наш законченный app.build.js :

01
02
03
04
05
06
07
08
09
10
({
    appDir: «../»,
    baseUrl: «js»,
    dir: «../../sample-build»,
    optimizeCSS: «standard»,
    paths : {
        «underscore» : «libs/underscore-min»
    },
    modules: [ { name: «main» } ]
})

Есть и другие свойства для более сложных вещей. Если вам интересно, вы можете открыть этот файл r.js найти набор свойств по умолчанию:

01
02
03
04
05
06
07
08
09
10
buildBaseConfig = {
       appDir: «,
       pragmas: {},
       paths: {},
       optimize: «uglify»,
       optimizeCss: «standard.keepLines»,
       inlineText: true,
       isBuild: true,
       optimizeAllPluginResources: false
   };

Теперь давайте оптимизируем этот проект! Войдите в терминал, cd в папку «js» и попробуйте это:

1
node /path/to/r.js -o app.build.js

Вы, вероятно, получите вывод, выглядящий примерно так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
Optimizing (standard) CSS file: /Users/andrew/Sites/sample-build/css/custom.css
Optimizing (standard) CSS file: /Users/andrew/Sites/sample-build/css/main.css
  
Tracing dependencies for: main
Uglifying file: /Users/andrew/Sites/sample-build/js/app.build.js
Uglifying file: /Users/andrew/Sites/sample-build/js/dom/events.js
Uglifying file: /Users/andrew/Sites/sample-build/js/libs/underscore-min.js
Uglifying file: /Users/andrew/Sites/sample-build/js/main.js
Uglifying file: /Users/andrew/Sites/sample-build/js/require.js
Uglifying file: /Users/andrew/Sites/sample-build/js/text.js
Uglifying file: /Users/andrew/Sites/sample-build/js/utils/array.js
  
js/main.js
—————-
js/utils/array.js
js/dom/events.js
js/text.js
text!templates/list.html
js/libs/underscore-min.js
js/main.js

Инструмент оптимизации нашел все CSS-файлы в нашем проекте и оптимизировал их, «uglified» (с помощью компрессора UglifyJS ) всех наших файлов JavaScript, и они скомпилировали все соответствующие файлы в main.js

Такой проницательный читатель, как вы (да, вы), может задаться вопросом, как мы можем выбросить все наши модули в один файл, поскольку они были идентифицированы по именам файлов. Что ж, если вы main.js в нашем оптимизированном main.js , вы увидите это:

1
2
define(«utils/array»,[],function(){ /* .. */ });
define(«dom/events»,[«utils/array»],function(a){ /* .. */ });

Инструмент оптимизации дал нашим модулям жестко заданные имена. Вы заметите, что есть также это:

1
2
define(«underscore»,function(){})
define(«main»,function(){})

Пустые модули? Хорошо, подумайте об этом на секунду, и вы поймете, что это только два файла, в которых нет реальных модулей: один — это библиотека, которая влияет на глобальную область видимости, а другой — наш вызов require . RequireJS может отлично работать с ними, но имейте в виду, что чем больше используемых вами сценариев, не соответствующих спецификации AMD, тем больше пустых модулей вы получите. Я предполагаю, что инструмент оптимизации включает их, так что загрузчик не ищет файлы с этими именами, когда эти файлы требуются. Поэтому я бы не стал их удалять.


Я мог бы показать вам гораздо больше с помощью Asynchronous Module Definition и RequireJS. Однако, прочитав это, я почти уверен, что вы получили хорошее представление о чем-то большем, чем просто основы. Если вы заинтересованы в получении дополнительной информации, вот список вещей, которые вы можете проверить:

  • У большинства библиотек JavaScript есть событие ready или нечто подобное, которое срабатывает, когда DOM загружен, и им можно манипулировать. У RequireJS тоже есть одна: вы можете передать require.ready функцию, и она выполнит эту функцию, когда придет время. Вы также можете поместить эту функцию в объект config как свойство reader .

  • Вы можете использовать операторы require внутри операторов require :

    1
    2
    3
    4
    5
    6
    7
    require([«some/module1», «some/modules2»], function () {
        // code here, optionally
        require([«some/module3», «some/module4»], function () {
            // code here
        });
        // code here, optionally
    });
  • Для создания многоязычных приложений есть довольно удобный плагин RequireJS для интернационализации .

  • Существует сборка RequireJS со встроенным jQuery .

  • Эти слайды из выступления Джона Ханна о AMD довольно аккуратны.

  • AlmondJS — минимальная реализация AMD API для использования после оптимизированных сборок (написано Джеймсом Бёрком, разработчиком RequireJS).