Статьи

Ленивые Угловые Модули

Время запуска имеет решающее значение для надлежащего взаимодействия с пользователем, и именно здесь многие JavaScript SPA действительно терпят неудачу. Даже если вы можете (и обязательно должны) применять механизмы минимизации кода и пакетирования, вы не захотите загружать все ваше приложение сразу. Это просто не полезно и не требуется. К сожалению, на данный момент Angular изначально не поддерживает ленивую загрузку. Таким образом, многие разработчики начали создавать собственные реализации и обходные пути, чтобы обойти такой недостаток. Эта статья направлена ​​на изучение этих реализаций, а также на объединение их с другой интересной и горячо обсуждаемой темой: правильная модульность.

Я пишу эту статью поэтапно, добавляя больше материала по мере продвижения в теме. Обновления будут включать существующие подходы других разработчиков, а также примеры исходного кода и подробное объяснение деталей реализации. Просто следите за моей  RSS-фидом  или  учетной записью Twitter,  чтобы узнать, когда появятся обновления. 

Основанная на функциях против MVC основанная организация

Не только Angular, но и другие фреймворки также предлагают типичный MVC вид структурирования и организации вашего кода, имея

  • controller папка
  • model папка
  • view папка

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



Наоборот: структурирование на основе MVC, сгенерированное официальным генератором Angular Yeoman

Таким образом, наличие «основанной на функциях» организации и группировки кода имеет тенденцию быть более полезным и поддерживаемым в долгосрочной перспективе. К сожалению, многие примеры Angular, даже официальные, продвигают структуру папок на основе MVC (на данный момент). Возьмите проект angular-seed  или  официальный генератор Yeoman .
Это, кажется, изменится, хотя. В последнем официальном  руководстве по лучшим практикам Angular на GitHub  предлагается «сгруппировать ваш код в связанные пакеты» и ссылаться на   демонстрационную версию приложения Angular, которая является примером кода, который сопровождает разработку  Mastering Web Application с  книгой AngularJS (обязательно прочитайте!).

То, что вы читаете и слышите в сообществе, не всегда может быть настолько последовательным, как вы хотели бы. В любом случае, мой подход явно следует за функциональным сокращением, так как он уже доказал свою плодотворность при разработке JavaScript SPA с JavaScriptMVC / CanJS, а также в настольном приложении, разработанном с помощью PRISM.net.

Проекты, на которые следует обратить внимание на правильную организацию, основанную на функциях, являются источником книги Angular:  ссылка на GitHub . Изображение ниже иллюстрирует это далее.



Функциональная организация кода по примеру приложения angular.

Кроме того,  Джон Папа провел в TechEd этого года сессию,  посвященную созданию модульных приложений Angular со стеком .Net. В то время как бэкэнд менее интересен для этой статьи, в  своем интерфейсе  он хорошо следит за такой функциональной организацией.

Реализация подхода на основе возможностей в Angular

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

  • app.controllers, у  app.services вас есть
  • app.usersapp.users.edit

Кроме того,  Джон Папа  структурирует каждый модуль, чтобы он находился в отдельной папке.



Пример: внутренняя структура модуля (Джон Папа)

..и содержит специальный файл , который содержит только определения и возможных зависимостей нагрузки углового модуля:  <modulename>.module.js.

(function () {
    'use strict';
    angular.module('app.dashboard', []);
})();

Официальный угловой демо  следует почти идентичный подход , но вместо того, чтобы иметь файл с именем  <modulename>.module.js он определяет его как  <modulename>.js.



Пример: внутренняя структура модуля (по официальной демонстрации приложения Angular)

Внутренне этот файл определяет имя модуля и зависимости:

angular.module('admin', ['admin-projects', 'admin-users']);

Подход, принятый обоими, звучит разумно, и это то, что я в итоге отразил с помощью RequireJS (как мы увидим позже). Единственная переменная здесь — это  имя углового модуля, который повторяется в виде жестко закодированных строк  среди подмодулей. В связи с этой темой я нашел интересную статью Avi Haiat об  AngularJS и RequireJS для очень больших приложений . Он придерживается подхода, при котором каждый модуль имеет по крайней мере следующие файлы:

namespace.js — определяет имя модуля как модуль AMD, беря корневое пространство имен ( ../namespace) и объединяет его с именем текущего модуля.

define([
    '../namespace'
], function(rootNamespace) {
    'use strict';

    return rootNamespace + '.moduleone';
});

module.js — это фактическое определение модуля Angular, импортирующего определенное пространство имен.

define([
    'angular',
    './namespace'
], function(angular, namespace){
    'use strict';

    // module definition
    return angular.module(namespace, []);
});

module.require.js — используется для сбора всех файлов, необходимых для RequireJS для правильной загрузки модуля. Он в основном объединяет все файлы модуля, так что любой пользователь должен просто «потребовать» этот файл, чтобы получить все.

define([
    './module',
    ..,
], function(){ 
});

Таким образом, вы можете изменить имена модулей в одной точке без необходимости редактировать несколько файлов с жестко закодированными строками. Хотя с теоретической точки зрения это звучит великолепно, я пока не уверен, окупятся ли дополнительные расходы на дополнительные файлы только для переноса пространства имен. Посмотрим…

Состояние искусства Ленивый Загрузка в JavaScript

В настоящее время  RequireJS  определенно является современным в асинхронной (и ленивой) загрузке файлов JavaScript. Он основан на  API AMD (определение асинхронного модуля) .

Основное использование состоит в определении модуля AMD, скажем myModule.js

define(['jquery', './logger.js'], function($, logger){
    // do something

    return {
        doSomething: function(){
            logger.log('Hi');
        }
    }
});

… что вы можете «потребовать» где-то еще.

require(['./myModule.js'], function(myModule){
    // do something interesting with it 
});

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

function someFunction(param){
    if(param > 40){
        require(['./myModule.js'], function(){
            // this callback will be invoked once the dependency has been loaded.
        });
    }
}

Это оказывается весьма полезным для нашей реализации отложенной угловой загрузки.

Ленивая загрузка и внедрение зависимостей

Но у Angular уже есть механизм «внедрения зависимостей». Почему я хотел бы также использовать RequireJS? У этих двух целей разные цели:  RequireJS загружает ваши физические файлы сценариев  в браузер, а механизм внедрения зависимостей Angular загружает логический артефакт времени выполнения по его имени .

Чтобы механизм DI Angular работал правильно, весь код JavaScript должен быть загружен и известен платформе. Angular анализирует код для определения контроллеров, сервисов, директив и т. Д. И вводит их в других точках по запросу. Например, вы определяете свой сервис:

// definition of a service on an existing Angular module
myModule.factory('consolelogger', function(){
    return function(msg){
        console.log(msg)
    }   
});

Затем в другом месте вы указываете свою  consolelogger зависимость.

myModule.controller('MyController', ['consolelogger', function(consolelogger){
    consolelogger('hi there');
}]);

В DI Angular вам не нужно указывать местоположение файла, а просто имя, с которым вы определили свой артефакт!

Таким образом, DI Angular хорош для тестирования и лучшей модульности, в то время как RequireJS (или альтернативы, такие как  $ scriptjs  и т. Д.) — определенно инструмент для отложенной загрузки по требованию. Но обратите внимание, мы не можем просто лениво загрузить некоторые файлы JavaScript с помощью RequireJS после запуска нашего приложения Angular, потому что контейнер DI просто не распознает эти недавно созданные артефакты. Вместо этого нам нужно вручную сообщить Angular о тех новых файлах, которые поступили. Подробнее об этом позже.

Угловой и RequireJS для отложенной загрузки

Существующие подходы для ленивой загрузки

Когда я искал подходящую оценку реализации отложенной загрузки в Angular, (немного погуглив ) я прокомментировал  AngularJS Googel Group . Комментарии от других, в основном, показали ресурсы, которые я уже нашел в Интернете

Рекомендации