AMD / Require JS - Как загружать файлы только по запросу

У меня есть вопросы по AMD/Require.js. Я новичок в модульном js и AMD, поэтому было бы здорово, если бы кто-нибудь помог мне с этим.

Допустим, у меня есть 2 отдельные области в моей системе, определенные как модули (функция "define()" - я использую Backbone, кстати)

  • Books
    • BookItemView.js
    • BookModel.js
    • BookCollection.js
  • DVDS
    • DvdItemView.js
    • DVDModel.js
    • DvdCollection.js

Я заметил, что если пользователь в настоящее время обращается к разделу «Книги» сайта, файлы из раздела DVD также включаются в Require.js. У меня следующий вопрос:

  • Есть ли способ включить только файлы, относящиеся к активному разделу сайта? Бывают ситуации, когда пользователь не заинтересован в просмотре списка DVD или ему даже не разрешено это делать. Я хотел бы, чтобы сервер не делал ненужных HTTP-запросов. Есть ли возможность сделать это с помощью Require.js?

Любые идеи были бы хороши.


person darksoulsong    schedule 30.12.2013    source источник


Ответы (4)


Это возможно, но сильно зависит от того, как вы структурируете свое JS-приложение. Кроме того, я предполагаю, что у вас есть приложение Backbone. Существуют разные подходы для разных фреймворков.

Например, у вас может быть app.js, который выглядит примерно так:

define([
    'DvdItemView', 'DvdModel', 'DvdCollection', 
    'BookItemView', 'BookModel', 'BookCollection'
], function(DvdView, DvdModel, DvdCol, BookView, BookModel, BookCol) {

   // router declarations go here

});

Поскольку вы «определяете» этот модуль как зависящий от вышеуказанных модулей, они всегда будут загружаться перед запуском кода маршрутизатора. Как указывает @alexndm, если вы запустите r.js, вы сможете объединить все свои модули в один.

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

routes: {
    '/books': 'booksList',
    '/books/:id': 'booksDetail',
    '/dvds': 'dvdList',
    '/dvds/:id': 'dvdDetail'
},

booksList: function() {
    if (!booksDepsLoaded) {
        // Here we go and load the appropriate files
        require(['BooksArea'], function(BooksArea) {
            // BooksArea.Model, BooksArea.Collection, etc...
            // do stuff to render a book list..
        })
    }
},

...

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

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

person Jon Jaques    schedule 30.12.2013
comment
Если вы ищете полезные шаблоны RequireJS, взгляните на Durandal.js. Они используют маршрутизатор на основе Sammy.JS, но концепции схожи. - person Jon Jaques; 31.12.2013
comment
Также см. здесь некоторые более сложные шаблоны с Marionette: gist.github.com/yethee/3729470 - person Jon Jaques; 31.12.2013

Проверьте require-lazy, небольшую библиотеку, которая подключается как во время выполнения, так и во время компиляции (r.js ) Я написал, чтобы сделать то, что вы просите. Основное использование будет:

(из сценария main.js :)

define(["lazy!modules/books", "lazy!modules/dvds"], function(books, dvds) {
    ...
});

Скрипты modules/books и modules/dvds будут как обычно. Переменные books и dvds в приведенной выше функции определения на самом деле являются обещаниями для реальных модулей. Сами модули не загружаются заранее. Затем где-то в main.js код решает, какое представление отображать:

if( view === "books" ) {
    books.get().then(function(realBooks) {
       ...
    });
}
else if( view === "dvds" ) {
    dvds.get().then(function(realDvds) {
       ...
    });
}

Он поддерживает grunt и bower тоже.

person Nikos Paraskevopoulos    schedule 31.12.2013

На самом деле это возможно сделать с немного другой структурой проекта.

У Джеймса Берка есть репозиторий, в котором подробно описано, как это сделать здесь, https://github.com/requirejs/example-multipage-shim

Основная идея заключается в том, что у вас есть несколько файлов «main.js», которые загружают зависимости для разных областей вашего сайта.

Итак, в вашем случае у вас могут быть файлы main-books и main-dvd.

В вашем HTML-коде для разных областей просто загрузите общие модули, которые вам понадобятся для обеих областей, а затем загрузите конкретный основной файл...

require(['./js/common'], function (common) {
    require(['app/main-books']);
});

Надеюсь, это поможет!

person jcreamer898    schedule 30.12.2013

при использовании require.js в продакшене рекомендуется запускать r.js http://requirejs.org/docs/optimization.html

Что он делает: - Объединяет связанные скрипты в слои сборки и минимизирует их с помощью UglifyJS (по умолчанию) или Closure Compiler (вариант при использовании Java). - Оптимизирует CSS, встраивая файлы CSS, на которые ссылается @import, и удаляя комментарии.

Это означает, что ВСЕ ваши скрипты будут объединены в один файл, и не будет никаких дополнительных HTTP-запросов.

person alexndm    schedule 30.12.2013
comment
THX, я знаю о r.js, но я стараюсь не объединять все в один файл, потому что этот проект, над которым я работаю, довольно большой, с сотнями файлов шаблонов, представлений, моделей и коллекций. Считаете ли вы, что загрузка их всех при инициализации будет лучшим подходом? - person darksoulsong; 31.12.2013
comment
Если у вас есть сотни файлов, я бы рекомендовал объединение. Такое количество отдельных запросов не будет идеальным. - person kinakuta; 31.12.2013