Require.js вредит моему мозгу. Некоторые фундаментальные вопросы о том, как он загружает скрипты/модули

Предположим, это мой config.js или main.js:

require.config({
    // paths are analogous to old-school <script> tags, in order to reference js scripts
    paths: {
        jquery: "libs/jquery-1.7.2.min",
        underscore: "libs/underscore-min",
        backbone: "libs/backbone-min",
        jquerymobile: "libs/jquery.mobile-1.1.0.min",
        jquerymobilerouter: "libs/jquery.mobile.router.min"
    },
    // configure dependencies and export value aliases for old-school js scripts
    shim: {
        jquery: ["require"],
        underscore: {
            deps: ["jquery"],
            exports: "_"
        },
        backbone: {
            deps: ["underscore", "jquery"],
            exports: "Backbone"
        },
        jquerymobilerouter: ["jquery", "backbone", "underscore"],
        jquerymobile: ["jquery", "jquerymobilerouter", "backbone", "underscore"]
    }
});
require(["jquery", "backbone", "underscore", "app/app.min", "jquerymobilerouter", "jquerymobile"], function ($, Backbone, _, App) {
    console.log($);
    console.log(Backbone);
    console.log(_);
    $("body").fadeIn(function () {
        App.init();
    });
});
  1. Если я правильно понимаю, параметр конфигурации paths позволяет вам ссылаться на скрипты, как тег <script> в HTML. Предполагая, что это так, нужно ли мне по-прежнему использовать псевдонимы сценариев, таких как jQuery, с помощью $ или подчеркивания с помощью _ в моем фактическом заявлении запроса ниже? Кажется странным, что мне пришлось бы это делать, учитывая, что если вы ссылаетесь на jQuery со стандартным тегом <script>, $ можно использовать во всем сценарии автоматически. Разве не должно быть то же самое с использованием paths?

  2. Я новичок в параметре конфигурации shim, который, как я понимаю, заменил устаревший плагин order!. Что на самом деле делает свойство exports? Похоже, псевдоним для скрипта не создается; например, если я установлю exports для подчеркивания на "whatever", а затем попробую console.log(whatever), оно не будет определено. Так в чем смысл?

  3. Как правильно использовать такие скрипты, как jQuery, «глобально»? То есть, как правильно использовать псевдоним $ в моем модуле App.js или любом другом модуле в моей папке «приложение»? Должен ли я требовать jQuery в каждом отдельном модуле и псевдониме $ каждый раз? Или то, как я сделал это здесь, правильно?

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


person J. Ky Marsh    schedule 14.06.2012    source источник
comment
Я хотел бы отправить скриншот этого вопроса каждому новому пользователю в качестве примера того, как правильно задать вопрос.   -  person Mike Robinson    schedule 14.06.2012
comment
@MikeRobinson да, это правда. В последнее время у меня нет времени на ответы, но на такой хороший вопрос я постараюсь ответить на него.   -  person Nicola Peluchetti    schedule 14.06.2012


Ответы (2)


  1. Пути сообщают require.js, где искать, когда вам нужна эта зависимость.

    Например, у меня есть такие настройки:

    "paths": { 
        "jquery": "require_jquery"
    },
    "shim": {
        "jquery-cookie"  : ["jquery"],
        "bootstrap-tab"  : ["jquery"],
        "bootstrap-modal": ["jquery"],
        "bootstrap-alert": ["jquery"]
    },
    

    это означает, что каждый раз в модуле я делаю

    define( ['jquery']
    

    requirejs загружает файл require_jquery из основного пути вместо того, чтобы пытаться загрузить jquery.js. В вашем случае он загрузит исходный файл jQuery, который затем будет доступен по всему миру. Лично мне такой подход не нравится, и по этой причине в файле require_jquery.js я делаю:

    define( ["jquery_1.7.2"], function() {
        // Raw jQuery does not return anything, so return it explicitly here.
        return jQuery.noConflict( true );
    } );
    

    это означает, что jQuery будет определен только внутри моих модулей. (Это потому, что я пишу плагины для Wordpress, и поэтому я могу включать свою собственную версию jQuery, не касаясь внешней версии)

  2. Экспорт (чтение из документов просто должно быть именем используемого вами модуля, чтобы его можно было обнаружить, если загрузка прошла правильно. Здесь объясняется. Поэтому, если вы хотите установить экспорт для подчеркивания, это должно быть _

  3. jQuery должен быть глобальным, как я объяснил, если вы просто импортируете его, файл выполняется, а jQuery является глобальным

РЕДАКТИРОВАТЬ - чтобы ответить на комментарии.

  1. да, я имею в виду, что вы должны экспортировать $ или jQuery для jQuery и _ для магистрали. Из того, что я получил из документов, это необходимо только в некоторых крайних случаях и не будет необходимо для библиотек, которые объявляют себя в глобальном пространстве имен как jQuery.

    Я думаю, что requirejs нуждается в них, когда ему нужно отказаться от загрузки jQuery из CDN. я думаю, что requirejs сначала пытается загрузить jQuery из CDN, а затем проверяет, правильно ли он загружен, проверяя, существует ли «экспортированная» переменная, и если это не так, она загружает ее из локальной файловой системы (если вы конечно, были настроены запасные варианты). Это то, что необходимо, когда requirejs не видит возвращающегося 404.

  2. jQuery доступен глобально, потому что он объявлен глобальным. Если вы просто загрузите и выполните сценарий jQuery, вы получите две глобальные переменные, $ и jQuery (или вы можете сделать, как я, и избежать этого). Внутри функции define() вы можете использовать псевдоним jQuery как хотите.

    define( [ 'jquery' ], function( jq ) {
        // jq is jquery inside this function. if you declared it 
        // globally it will be also available as $ and jQuery
    } );
    
person Nicola Peluchetti    schedule 14.06.2012
comment
Это расчистило пути для меня, так что спасибо. Однако есть еще два вопроса: 1.) Когда вы говорите, что экспорт должен быть именем модуля, вы имеете в виду, каков фактический псевдоним библиотеки? Как $ для jQuery, например? Означает ли это, что вы не можете просто назначить произвольное значение экспорта? Я прочитал документы Requirejs, и они совсем не помогли. 2.) Я не уверен, что слежу за глобальной доступностью jQuery, как в вашем примере. Доступен ли он глобально, ПОТОМУ ЧТО я использовал псевдоним пути или просто из-за того, как он указан в блоке определения? - person J. Ky Marsh; 15.06.2012
comment
@J.KyMarsh, я изменил свой ответ. - person Nicola Peluchetti; 15.06.2012
comment
@Дж. Кай Марш: Я с тобой. Документация по RequireJS, хоть и очень большая, мне мало помогает. - person flu; 06.02.2013

Просто чтобы прояснить любую путаницу вокруг exports, предполагается, что любая библиотека прокладок прикрепляет свойство к глобальному контексту (window или root) или изменяет уже существующее глобальное свойство (например, плагин jQuery). Когда requireJS получает команду для загрузки зависимостей с оболочкой, он проверяет глобальный контекст на наличие свойства, соответствующего значению exports этой конфигурации оболочек, и, если находит его, возвращает его как значение этого модуля. Если он не находит его, то загружает связанный скрипт, ждет его выполнения, затем находит глобальный символ и возвращает его.

Важно помнить, что если конфигурация оболочки не содержит значение exports, любой метод init в этой конфигурации НЕ будет выполняться. Загрузчик зависимостей должен найти значение для модуля (это то, что указывает exports), прежде чем этот модуль можно будет инициализировать, поэтому свойство требуется, если для этого модуля существует прокладка init.

обновление: я также должен указать, что если рассматриваемый модуль вызывает define где-либо, любая конфигурация прокладки, которая у вас есть для этого модуля, будет проигнорирована. Это на самом деле вызвало у меня некоторые головные боли, потому что я хотел использовать конфигурацию прокладки для вызова метода jQuery jQuery.noConflict(true), чтобы отменить глобификацию jQuery и сохранить его только для модулей, которые в нем нуждаются, но не смог заставить его работать. (См. обновление внизу для получения информации о том, как легко сделать это, используя конфигурацию карты вместо конфигурации прокладки.)

обновление 2: недавний вопрос о группе google requireJS заставил меня понять, что мое объяснение может немного вводить в заблуждение, поэтому я хотел бы уточнить. RequireJS будет повторно использовать шиммированную зависимость, только если она была загружена с помощью requireJS хотя бы один раз. То есть, если у вас просто есть тег <script> на странице хостинга (скажем, например, подчеркивание), вот так:

<script src='lib/underscore.js'></script>
<script src='lib/require.js' data-main='main.js'></script>

... и у вас есть что-то подобное в вашей конфигурации requireJS:

paths: {
    'underscore': 'lib/underscore'
},
shim: {
    'underscore': {
        exports: '_'
    }
}

Затем, когда вы в первый раз сделаете define(['underscore'], function (_) {}); или var _ = require('underscore');, RequireJS повторно загрузит библиотеку подчеркивания, а не повторно использует ранее определенную window._, потому что, насколько известно requireJS, вы никогда раньше не загружали подчеркивание. Конечно, он может проверить, определен ли уже _ в корневой области, но у него нет возможности убедиться, что _, который уже есть, совпадает с тем, который определен в вашей конфигурации paths. Например, и prototype, и jquery присваивают себе значение window.$ по умолчанию, и если requireJS предполагает, что 'window.$' является jQuery, тогда как на самом деле это прототип, вы окажетесь в плохой ситуации.

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

 <script src='lib/underscore.js'></script>
 <script src='lib/require.js' data-main='main.js'></script>
 <script src='lib/underscore.js'></script>

Где второй экземпляр подчеркивания загружается с помощью requireJS.

По сути, библиотека должна быть загружена через requireJS, чтобы requireJS знал о ней. Однако в следующий раз, когда вам потребуется подчеркивание, requireJS скажет: «Эй, я уже загрузил это, так что просто верните значение exports и не беспокойтесь о загрузке другого скрипта».

Это означает, что у вас есть два реальных варианта. Один из них я бы назвал анти-шаблоном: просто не используйте requireJS для выражения зависимостей для глобальных скриптов. То есть, пока библиотека прикрепляет глобальный контекст к корневому контексту, вы сможете получить к нему доступ, даже если эта зависимость не требуется явно. Вы можете понять, почему это анти-шаблон - вы просто устранили большинство преимуществ использования загрузчика AMD (явный список зависимостей и переносимость).

Другой, лучший вариант — использовать requireJS для загрузки всего до такой степени, что единственный фактический тег сценария, который вы должны создать самостоятельно, — это тот, который изначально загружает requireJS. Вы можете использовать прокладки, но в 95% случаев не так уж сложно вместо этого добавить в скрипт оболочку AMD. Может потребоваться немного больше усилий, чтобы преобразовать все ваши не-AMD-библиотеки в совместимые с AMD, но как только вы сделаете одну или две, все станет намного проще — я могу взять любой универсальный плагин jQuery и преобразовать его в модуль AMD. менее чем за минуту. Обычно достаточно просто добавить

define(['jquery'], function (jQuery) {

вверху и

    return jQuery;
});

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

(function ($) {
    // plugin code here
})(jQuery);

И это хорошая идея, чтобы обратить внимание на предполагаемый объем. Вы, безусловно, можете напрямую сопоставить «jquery» с $, предполагая, что плагин не ожидает найти jQuery вместо $. Это всего лишь базовая оболочка AMD — более сложные обычно пытаются определить, какой тип загрузчика используется (commonJS, AMD или обычные глобальные) и используют другой метод загрузки в зависимости от результата. Вы можете легко найти примеры этого за несколько секунд в Google.

Обновление: обходной путь, который я использовал для поддержки использования jQuery.noConflict(true) с RequireJS, сработал, но потребовал очень небольшой модификации исходного кода jQuery, и с тех пор я нашел гораздо лучший способ выполнить то же самое без изменения jQuery. К счастью, то же самое сделал и Джеймс Берк, автор RequireJS, который добавил его в документацию по RequireJS: http://requirejs.org/docs/jquery.html#noconflictmap

person Isochronous    schedule 26.02.2013
comment
Это может быть одним из самых полезных объяснений использования прокладки RequireJS. Свойство 'exports:' часто путают с указанием того, куда RequireJS должен прикрепить скрипт, хотя на самом деле это место, где он ищет его, как указание готово. Спасибо! - person Micros; 13.12.2013
comment
Рад, что был полезен! Я провел много времени, борясь с requireJS, поэтому мне пришлось изучить его особенности, и я подумал, что если поделиться тем, что я узнал, это может помочь кому-то еще избежать тех же головных болей, через которые прошел я. - person Isochronous; 08.01.2014