Spec-Zone.ru › RequireJS

Почему веб-модули?

  • Проблема
  • Решение
  • API загрузки скриптов
  • Асинхронность против синхронности
  • Загрузка скриптов: XHR
  • Загрузка скриптов: Веб-рабочие потоки
  • Загрузка скриптов: document.write()
  • Загрузка скриптов: head.appendChild(script)
  • Обертывание функций

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

Проблема

  • Веб-сайты превращаются в веб-приложения
  • Сложность кода растет по мере увеличения сайта
  • Сборка становится сложнее
  • Разработчик хочет отдельные JS-файлы/модули
  • Развертывание требует оптимизированного кода в одном или нескольких HTTP-запросах

Решение

Фронтенд-разработчикам нужно решение с:

  • Неким аналогом #include/import/require
  • Возможностью загрузки вложенных зависимостей
  • Простотой использования для разработчика, но при этом поддержкой инструмента оптимизации, помогающего при развертывании

API загрузки скриптов

В первую очередь необходимо решить вопрос с API загрузки скриптов. Вот несколько кандидатов:

  • Dojo: dojo.require("some.module")
  • LABjs: $LAB.script("some/module.js")
  • CommonJS: require("some/module")

Все они отображаются как загрузка some/path/some/module.js. В идеале мы могли бы выбрать синтаксис CommonJS, поскольку он, вероятно, станет более распространённым со временем, и мы хотим повторно использовать код.

Мы также хотим некий синтаксис, позволяющий загружать обычные файлы JavaScript, которые существуют сегодня — разработчик не должен переписывать весь свой JavaScript, чтобы получить преимущества загрузки скриптов.

Однако нам нужно что-то, что хорошо работает в браузере. CommonJS require() — это синхронный вызов, ожидается, что он сразу вернёт модуль. Это не работает в браузере.

Асинхронность против синхронности

Этот пример должен проиллюстрировать основную проблему для браузера. Предположим, у нас есть объект Employee, и мы хотим, чтобы объект Manager наследовался от объекта Employee. Принимая этот пример, мы можем закодировать его так, используя наш API загрузки скриптов:

var Employee = require("types/Employee");

function Manager () {
    this.reports = [];
}

//Error if require call is async
Manager.prototype = new Employee();

Как указывает комментарий выше, если require() асинхронный, этот код не будет работать. Однако синхронная загрузка скриптов в браузере убивает производительность. Что делать?

Загрузка скриптов: XHR

Заманчиво использовать XMLHttpRequest (XHR) для загрузки скриптов. Если используется XHR, то мы можем изменить текст выше — мы можем использовать регулярное выражение для поиска вызовов require(), убедиться, что мы загружаем эти скрипты, а затем использовать eval() или элементы script, у которых текст тела установлен на текст скрипта, загруженного через XHR.

Использование eval() для оценки модулей — плохая практика:

  • Разработчикам объяснили, что eval() — это плохо.
  • В некоторых средах eval() запрещен.
  • Отладка сложнее. Firebug и инспектор WebKit имеют соглашение //@ sourceURL=, которое помогает дать имя выполняемому тексту, но эта поддержка не является универсальной для всех браузеров.
  • Контекст eval() различается в разных браузерах. Вы можете использовать execScript в IE, чтобы помочь в этом, но это означает больше движущихся частей.

Использование тегов script с заданным текстом тела — плохая практика:

  • Во время отладки номер строки ошибки не соответствует исходному файлу.

XHR также имеет проблемы с междоменными запросами. Некоторые браузеры теперь поддерживают междоменные XHR, но это не универсально, а IE решил создать отдельный объект API для междоменных вызовов — XDomainRequest. Больше движущихся частей и больше вещей, которые могут пойти не так. В частности, необходимо убедиться, что не отправляются никакие нестандартные HTTP-заголовки, в противном случае может быть выполнен ещё один запрос «предварительной проверки», чтобы убедиться, что доступ к междоменным ресурсам разрешен.

Dojo использовал XHR-базовый загрузчик с eval(), и, хотя он работает, он стал источником разочарования для разработчиков. Dojo имеет загрузчик xdomain, но он требует модификации модулей с помощью шага сборки, чтобы использовать оберточную функцию, так что теги script src="" могут быть использованы для загрузки модулей. Существует много крайних случаев и движущихся частей, которые создают нагрузку на разработчика.

Если мы создаём новый загрузчик скриптов, мы можем сделать лучше.

Загрузка скриптов: Веб-рабочие потоки

Веб-рабочие потоки могут быть ещё одним способом загрузки скриптов, но:

  • Он не имеет сильной кросс-браузерной поддержки
  • Это API обмена сообщениями, и скрипты, вероятно, захотят взаимодействовать с DOM, поэтому это означает просто использование рабочего потока для извлечения текста скрипта, но передача текста обратно в основное окно, а затем использование eval/script с текстовым телом для выполнения скрипта. Это имеет все те же проблемы, что и XHR, упомянутые выше.

Загрузка скриптов: document.write()

document.write() можно использовать для загрузки скриптов — он может загружать скрипты с других доменов, и он соответствует тому, как браузеры обычно потребляют скрипты, поэтому он позволяет легко отлаживать.

Однако в примере Асинхронность против синхронности мы не можем просто выполнить этот скрипт напрямую. В идеале мы могли бы узнать зависимости require() до выполнения скрипта и убедиться, что эти зависимости загружены первыми. Но мы не имеем доступа к скрипту до его выполнения.

Кроме того, document.write() не работает после загрузки страницы. Отличный способ повысить видимую производительность вашего сайта — это загружать код по требованию, когда пользователь его нуждается для следующего действия.

Наконец, скрипты, загруженные с помощью document.write(), будут блокировать отображение страницы. При поиске наилучшей производительности вашего сайта это нежелательно.

Загрузка скриптов: head.appendChild(script)

Мы можем создавать скрипты по требованию и добавлять их в head:

var head = document.getElementsByTagName('head')[0],
    script = document.createElement('script');

script.src = url;
head.appendChild(script);

Существует немного больше деталей, чем просто приведенный выше фрагмент, но это основная идея. Этот подход имеет преимущество перед document.write(), так как он не будет блокировать отображение страницы и работает после загрузки страницы.

Однако он по-прежнему имеет проблему Асинхронность против синхронности: в идеале мы могли бы узнать зависимости require() до выполнения скрипта и убедиться, что эти зависимости загружены первыми.

Обертывание функций

Таким образом, нам нужно знать зависимости и убедиться, что мы их загружаем перед выполнением нашего скрипта. Лучший способ сделать это — создать наш API загрузки модулей с функциями-обёртками. Вот так:

define(
    //The name of this module
    "types/Manager",

    //The array of dependencies
    ["types/Employee"],

    //The function to execute when all dependencies have loaded. The
    //arguments to this function are the array of dependencies mentioned
    //above.
    function (Employee) {
        function Manager () {
            this.reports = [];
        }

        //This will now work
        Manager.prototype = new Employee();

        //return the Manager constructor function so it can be used by
        //other modules.
        return Manager;
    }
);

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

require(["some/script.js"], function() {
    //This function is called after some/script.js has loaded.
});

Этот тип синтаксиса был выбран, потому что он лаконичен и позволяет загрузчику использовать загрузку типа head.appendChild(script).

Он отличается от обычного синтаксиса CommonJS из-за необходимости работы в браузере. Были предложения использовать обычный синтаксис CommonJS с загрузкой типа head.appendChild(script), если процесс сервера преобразует модули в формат передачи с функцией-обёрткой.

Я считаю, что важно не принуждать к использованию процесса сервера во время выполнения для преобразования кода:

  • Это делает отладку странной, номера строк будут отличаться от исходного файла, поскольку сервер вставляет функцию-обёртку.
  • Это требует большего количества компонентов. Разработка фронтенда должна быть возможна с помощью статических файлов.

Более подробную информацию о причинах проектирования и вариантах использования этого формата обертывания функций, называемого Asynchronous Module Definition (AMD), можно найти на странице Почему AMD?

© jQuery Foundation and other contributors
Licensed under the MIT License.
http://requirejs.org/docs/why.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API