Время запуска имеет решающее значение для надлежащего взаимодействия с пользователем, и именно здесь многие JavaScript SPA действительно терпят неудачу. Даже если вы можете (и обязательно должны) применять механизмы минимизации кода и пакетирования, вы не захотите загружать все ваше приложение сразу. Это просто не полезно и не требуется. К сожалению, на данный момент Angular изначально не поддерживает ленивую загрузку. Таким образом, многие разработчики начали создавать собственные реализации и обходные пути, чтобы обойти такой недостаток. Эта статья направлена на изучение этих реализаций, а также на объединение их с другой интересной и горячо обсуждаемой темой: правильная модульность.
Я пишу эту статью поэтапно, добавляя больше материала по мере продвижения в теме. Обновления будут включать существующие подходы других разработчиков, а также примеры исходного кода и подробное объяснение деталей реализации. Просто следите за моей RSS-фидом или учетной записью Twitter, чтобы узнать, когда появятся обновления.
Основанная на функциях против MVC основанная организация
Не только Angular, но и другие фреймворки также предлагают типичный MVC вид структурирования и организации вашего кода, имея
controller
папкаmodel
папкаview
папка- …
Хотя на первый взгляд это может показаться чистой и хорошо организованной структурой, вскоре вы обнаружите, что она не так полезна, как во время разработки или сопровождения. Когда вы в последний раз открывали свой проект и говорили «давайте добавим контроллер» ?? Вместо этого вы обычно реализуете или модифицируете некоторые функции.
Наличие структуры MVC подразумевает непрерывный, утомительный поиск соответствующего контроллера, модели и представления, которые принадлежат функции в этих трех отдельных папках.
Таким образом, наличие «основанной на функциях» организации и группировки кода имеет тенденцию быть более полезным и поддерживаемым в долгосрочной перспективе. К сожалению, многие примеры Angular, даже официальные, продвигают структуру папок на основе MVC (на данный момент). Возьмите проект angular-seed или официальный генератор Yeoman .
Это, кажется, изменится, хотя. В последнем официальном руководстве по лучшим практикам Angular на GitHub предлагается «сгруппировать ваш код в связанные пакеты» и ссылаться на демонстрационную версию приложения Angular, которая является примером кода, который сопровождает разработку Mastering Web Application с книгой AngularJS (обязательно прочитайте!).
То, что вы читаете и слышите в сообществе, не всегда может быть настолько последовательным, как вы хотели бы. В любом случае, мой подход явно следует за функциональным сокращением, так как он уже доказал свою плодотворность при разработке JavaScript SPA с JavaScriptMVC / CanJS, а также в настольном приложении, разработанном с помощью PRISM.net.
Проекты, на которые следует обратить внимание на правильную организацию, основанную на функциях, являются источником книги Angular: ссылка на GitHub . Изображение ниже иллюстрирует это далее.
Кроме того, Джон Папа провел в TechEd этого года сессию, посвященную созданию модульных приложений Angular со стеком .Net. В то время как бэкэнд менее интересен для этой статьи, в своем интерфейсе он хорошо следит за такой функциональной организацией.
Реализация подхода на основе возможностей в Angular
Angular уже располагает «модульной системой», которая обеспечивает какое-то пространство имен. Эта система естественным образом вписывается в функциональный подход, поэтому вместо таких модулей, как
app.controllers
, уapp.services
вас естьapp.users
,app.users.edit
…
Кроме того, Джон Папа структурирует каждый модуль, чтобы он находился в отдельной папке.
..и содержит специальный файл , который содержит только определения и возможных зависимостей нагрузки углового модуля: <modulename>.module.js
.
(function () { 'use strict'; angular.module('app.dashboard', []); })();
Официальный угловой демо следует почти идентичный подход , но вместо того, чтобы иметь файл с именем <modulename>.module.js
он определяет его как <modulename>.js
.
Внутренне этот файл определяет имя модуля и зависимости:
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 . Комментарии от других, в основном, показали ресурсы, которые я уже нашел в Интернете
- Ifeanyi Isitor на ленивой загрузке с
$scriptjs
. Он также добавил реализацию, используя RequireJS . - Беннадель о своем подходе к использованию RequireJS для отложенной загрузки .
- angularAMD — проект, который объединяет AngularJS + RequireJS в библиотеке для лучшего повторного использования.
Рекомендации
- Освоение разработки веб-приложений с AngularJS
- Официальный генератор Yeoman Angular
- John Papa TechEd 2014: создание многофункциональных приложений с AngularJS на ASP.net и хранилище демо-кода
- RequireJS домашняя страница
- Сообщение групп Google о отложенной загрузке
- Ленивая загрузка Ifeanyi Isitor с использованием $ scriptjs