Вступление
При написании Single Page Application разработчики в большинстве случаев сталкиваются с одной очень распространенной проблемой, а именно — создание lazyLoad модулей и их последующая загрузка на сторону клиента. Т.е. по какому-то действию или по переходу по URL (в большинстве случаев) мы должны загрузить определенный набор зависимостей — JavaScript, CSS, HTML и т.д. В реалиях современной Front-End разработки это будет большущий JavaScript файл. В этой статье я хочу поделиться своим опытом и показать как реализовать lazyLoad модули для AngularJS и тем самым уменьшить общий объем кода при первой загрузке приложения.
Почему AngularJS 1.x
Наверное, у тебя, уважаемый читатель, появился резонный вопрос: «Стоп, какой AngularJS 1.x, ведь совсем недавно был релиз Angular v5.2». Вопрос более чем уместен. Все просто, довольно много проектов, которые используют AngularJS 1.x и при этом себя хорошо чувствуют. Есть целый ряд отраслей для которых переход на новую версию очень затратный как в человеко-часах, так и в денежном эквиваленте. AngularJS 1.x еще очень востребован на рынке.
Велосипеды, которые уже есть
Так уж повелось, что в мире Front-End разработки
require.ensure
и без использования ocLazyLoad, то получим примерно такую ошибку:Структура проекта
Итак, приступим. Оригинальный код проекта, для которого мне нужно было реализовать lazyLoad модули, я не могу предоставлять в общий доступ. Поэтому я сделал небольшое приложение. Я старался сделать так, чтобы приложение не выглядело как звездолет в котором непонятно, что откуда берется и вообще как это все работает. Основное назначение — это сделать рабочий прототип, который будет доступен онлайн (не только исходники). Ниже вы можете видеть структуру проекта для веток
require-ensure
и system-import
. Для ветки import-es6
мы внесем небольшое дополнение.project-root
+-- src
¦ +-- core
¦ ¦ +-- bootstrap.js
¦ +-- pages
¦ ¦ +-- home
¦ ¦ ¦ +-- about
¦ ¦ ¦ ¦ +-- about.module.js
¦ ¦ ¦ ¦ +-- about.view.html
¦ ¦ ¦ +-- index
¦ ¦ ¦ ¦ +-- index.module.js
¦ ¦ ¦ ¦ +-- index.view.html
¦ ¦ ¦ +-- home.module.js
¦ ¦ ¦ +-- home.module.routing.js
¦ ¦ ¦ +-- home.module.states.js
+-- app.js
+-- index.html
Прежде, чем мы приступим к работе с кодом, давайте обсудим два важных момента, которые влияют на то, будет ли наш модуль lazyLoad или нет:
- Чтобы модуль стал lazyLoad его не нужно указывать как зависимость для других модулей.
- Модуль не должен быть импортирован нигде, кроме того роута для которого вы хотите сделать этот модуль lazyLoad.
Если не очень понятно, не волнуйтесь — на практике сразу станет ясно, что к чему.
require.ensure() + $ocLazyLoad
require.ensure
был предложен командой webpack еще в его первых версиях. Этот метод позволяет разработчикам динамически создавать отдельные файлы (чанки в контексте терминологии webpack) с какой-то частью кода, которые будут впоследствии загружены по требованию на стороне клиента. Данный подход является менее предпочтительным для создания динамически загружаемых модулей, но если сильно хочеться, то ничего страшного в этом нет. Этот метод подойдет прекрасно тем, кто хочет сделать lazyLoad модули без больших затрат на рефакторинг. Ниже вы можете видеть пример использования require.ensure
для загрузки index.module.js
:Для
about.module.js
код будет идентичным, за исключением путей к модулю и некоторых других параметров. Ниже вы можете видеть пример использования require.ensure
для загрузки about.module.js
:Как вы могли заметить из кода вся магия происходит вот в этой сроке:
$ocLazyLoad.load(module.HOME_ABOUT_MODULE);
Есть другой вариант указания модуля — через объект:
$ocLazyLoad.load({
name: "home.module"
});
Но в таком случае, мы себя ограничиваем в свободе действий. Если мы захотим переименовать модуль, то нужно будет менять код в нескольких местах. Также есть большая вероятность допустить ошибку при написании имени модуля. Я вам настоятельно рекомендую не использовать этот подход.
Хотел бы обратить ваше внимание на один важный нюанс в отношении
about.module.js
и последующей загрузки на сторону клиента. Посмотрите на скрин ниже:При переходе по
Home/About
ссылке подгружаются сразу два файла: index.module.chunk.js
и about.module.chunk.js
. Так происходит потому что home.about
URL является дочерним по отношению к home
URL. Об этом стоит помнить. Забегая немного наперед, в последнем разделе мы добавим еще один модуль с новым URL, и увидим, что для него будет грузиться только один файл и больше ничего.System.import + $ocLazyLoad
Я долго думал писать про этот подход или нет. Считаю, что нужно о нем поговорить.
System.import это другая конструкция от команды webpack, которую впоследствии запретили, но этот подход продолжают предлагать как вариант реализации. Более того, эта конструкция продолжает работать и в новых версиях webpack. Подозреваю, что это сделали из соображений совместимости. Если вы используете эту конструкцию в своем проекте, то у меня плохие новости — она в статусе deprecated. Кода в этом разделе не будет. Идем дальше.
Dynamic imports + $ocLazyLoad
Возможно, вы уже слышали, что Chrome 63 и Safari Technology Preview 24 выкатили обновления и теперь разработчикам доступны динамические импорты. Да, да, те самые динамические импорты, которые были предложены в спецификации. Еще в далеком 2016 команда webpack внедрила поддержку динамических импортов.
В этом разделе мы добавим еще один модуль в корень директории
pages
, чтобы убедиться в правильной работе lazyLoad. Структура для ветки import-es6
представлена ниже:project-root
+-- src
¦ +-- core
¦ ¦ +-- bootstrap.js
¦ +-- pages
¦ ¦ +-- blog
¦ ¦ ¦ +-- blog.module.js
¦ ¦ ¦ +-- blog.service.js
¦ ¦ ¦ +-- blog.view.html
¦ ¦ +-- home
¦ ¦ ¦ +-- about
¦ ¦ ¦ ¦ +-- about.module.js
¦ ¦ ¦ ¦ +-- about.view.html
¦ ¦ ¦ +-- index
¦ ¦ ¦ ¦ +-- index.module.js
¦ ¦ ¦ ¦ +-- index.view.html
¦ ¦ ¦ +-- home.module.js
¦ ¦ ¦ +-- home.module.routing.js
¦ ¦ ¦ +-- home.module.states.js
+-- app.js
+-- app.routing
+-- app.states.js
+-- index.html
Если вы не используете у себя в проекте Babel или TypeScript, то все заведется с коробки без лишних танцев с бубном. Но и вы и я знаем, что в реалиях современного Front-End очень сложно писать код без Babel или TypeScript. Поговорим про Babel. Для начала нам нужно установить дополнительный плагин для Babel, который понимает синтаксис динамических импортов: syntax-dynamic-import. Иначе мы получим ошибку:
Далее нам нужно добавить
.babelrc
с настройками:Теперь вторая неприятная ошибка, которую вы можете видеть ниже:
Да, вы не ошиблись, ESLint тоже не понимает динамических импортов. Чтобы это поправить нужно установить специальный парсер для ESLint babel-eslint и тогда все заработает как по маслу. Добавим
.eslintrc
с настройками:Что ж, пришло время попробовать динамические импорты на деле. Протестируем их на новом модуле:
Как вы могли заметить из кода, команда webpack добавила несколько приятных фишек к динамическим импортам. Есть возможность указать название итогового чанка, который создаст webpack, а также мы можем указать способ загрузки. Подробнее про это читайте здесь. На видео ниже вы можете наблюдать работу динамических импортов:
Component vs. Template
Для загрузки модулей использовалось свойство
component
. Можно использовать вместо свойства component
свойство под именем template
. Работают они практически одинаково, с одним лишь нюансом. Если вы опечатались в названии компоненты или по каким-то другим причинам компонента недоступна, то вы получите ошибку в консоли. С template
вы такой ошибки не получите. И можно очень долго искать в чем проблема.Полезные ссылки
- How to route to AngularJS 1.5 components
- Lazy loading in UI-Router
- Исходный код на GitHub
- Проект на Heroku
Вместо заключения
lazyLoad модули в контексте AngularJS приложения — это отличная возможность сделать ваше приложение более легковесистым, более отзывчивым и более распределенным. Времена меняются, требования к приложениям растут, а с ними растут и объемы кода, которые мы доставляем на клиент. Если раньше было достаточно собрать весь код в единый файл, отдать его конечному пользователю и все было круто, то сейчас это непозволительная роскошь. Наблюдается тенденция разделения приложения в зависимости от URL с выделением общего кода.
На этом все. Спасибо за внимание. Кто дочитал до конца, отдельное спасибо.
P.S. Если у вас был подобный опыт в реализации lazyLoad модулей для AngularJS приложения — поделитесь им в комментариях.
Комментарии (6)
Focushift
11.01.2018 19:47Можно ли таким образом завернуть сервисы?
var_bin Автор
11.01.2018 19:50В принципе, данный подход можно использовать и для сервисов и для контроллеров. В документации к ocLazyLoad есть примеры
PaulMaly
Так с чем связана ошибка при простом использовании require.ensure с Angular? Много раз его использовал с другими фреймворками и ни разу не сталкивался.
var_bin Автор
Если в кратце, то вся проблема в сервисе
$injector
. Учитывая как webpack обрабатываетrequire.ensure
,$injector
в свою очередь не находит сам модуль для которого мы применяемrequire.ensure
. ocLazyLoad берет решение этой проблемы на себя.PaulMaly
Понятно, я так и понял, что это специфичная для Angular проблема. require.ensure не всегда удобен, так как можно легко сломать статический анализатор Webpack, когда его используешь. Спасибо за разъяснение.