Pull to refresh

Сборщик проектов на Angular и RequireJS и некоторые мысли по сборке

Reading time 8 min
Views 14K
Что самое неудобное в сборщиках проекта? Правильно! То, что нужно сборку писать самому. Изучать grunt/gulp/webpack, шаманить с плагинами, думать, как разбить конфиг на модули, когда он вырастает до нескольких сотен строчек, потом несколько месяцев радоваться, что всё работает, а когда в проекте появляется критическое изменение, опять лезть в это болото.

Мне тоже всё это порядком надоело, поэтому написал сборщик, лишенный этих недостатков. Его gulpfile.js выглядит так:

var gulp = require('gulp');
var arjs = require('arjs-builder')();

gulp.task('build', arjs.build);
gulp.task('test', arjs.test);
gulp.task('default', arjs.run);

Скопировали себе проект, и больше никогда туда не лезете, и навсегда забываете что такое сборка.
Единственное, что придется выучить, — это три команды:

gulp #компилит, поднимает локальные серверы
gulp build #билдит проект
gulp test #запускает тесты

Открываете localhost:7000 и наслаждаетесь локальной версией сайта, а в папке build
уже лежит сбилженная версия.

— А как же темплейты, их же надо в js внедрять?
— Конечно! Все внедрено как положено.
— А я стили пишу на less, sass, stylus, их же надо компилить?
— Пишите как писали, всё чудесным образом будет работать.
— А картинки в CSS инклудить?
— Так давно всё в CSS. All included как в пятизвездочном отеле.
— А разбить сбилженный файл на модули?
— Проверьте папку build. Всё по модулям? С уникальными именами, основанными на содержимом файла? Вот, а вы волновались!
— А вот еще там что-то…
— И это тоже работает.

Но как такое возможно? Это мы и рассмотрим в статье. А в конце еще расскажу, почему всё-таки RequireJS

Структура проекта


Всегда чем-то приходится жертвовать. В нашем случае это ограничения, накладываемые на структуру проекта и непосредственно результат сборки. Но тут не плакать, а радоваться надо, потому что мы пожертвовали таким злом, как «мне нужна максимальная гибкость и контроль, я всё напишу сам». Оглянемся по сторонам и положа руку на сердце придем к выводу, что большинство разработчиков слабо представляют архитектуру фронтенда сингл-пейдж приложения, не говоря уже о том, как его правильно собирать (в один файл, по модулям, синхронно грузить, асинхронно, быстродействие никто не замерял, никто ничего не анализировал). Да и нужно ли это веб-разработчику? Ради этого он устраивался на работу?

Ближе к теме. Сборщик предназначен для проектов, основанных на RequireJS и AngularJS. Впрочем, он работает с проектами и на чистом JS и теоретически с любыми другими фреймворками.

Рабочая директория c вашим приложением будет иметь такую структуру:
projects/
 ├──project1/
 ├──project2/
 ├──project3/
 ├──files/
 ├──vendor/
 ├──compiled/
 ├──build/
 ├──index.html
 ├──lib.js
 └──lib.css
.bowerrc
bower.json
package.json
gulpfile.js

project1..3 — папки с проектами. Проект описывает обособленный раздел сайта или отдельный сайт. Проекты могут быть написаны на разных фреймворках или без них. Для проектов может использоваться как общий index.html так и собственный. В приложении среднего уровня будет минимум три проекта:
  • main — основной сайт
  • admin — админка
  • old-browser — заглушка для старых браузеров, написанная на самом примитивном JS

В больших приложениях может появиться проект lib в котором будут собраны основные модули, использующиеся в других проектах (роутинг, авторизация, работа с ресурсами...).

В отдельные проекты стоит выносить новую версию сайта (чтобы организовать плавный переход), сборки для A/B-тестирования, временные лендинг-пейджи и т. п.

В files кладутся файлы, которые не будут вставляться в CSS (кандидаты на перенос на файловый сервер). Остальные — будут преобразованы в base64 и вставлены в стили, даже если это 2ГБ видео. Потому что проект описывает только интерфейс, контент должен браться из других мест.
В vendor копируются библиотеки из bower.
В compiled складываются временные файлы для локальной работы (стили, вендорные библиотеки).
В build собранные проекты, готовые для заливки на сервер.

Пример index.html можно найти здесь. Он, так же как gulpfile.js, не меняется никогда.
— А как же мне добавить туда свой скрипт или подключить аналитику?
— Через массив scripts в конфиге или в соответствующем модуле в проекте. В демо-приложение специально добавил пример с аналитикой и мета-тегами.
Абстрактное маркетинговое агентство просит добавить Яндекс.Метрику, googleAnalitycs, googleTagManager, счетчики vkontakte, mailru, doubleClick..., и каждому нужно поместить свой скрипт в индекс, и чтобы повыше других, чтобы он там создал свой говно-айфрейм или говно-картинку в один пиксель. А не офигели ли вы, господа? Думаете можно просто так срать в индекс? Теперь все эти выскочки заключены в отдельный модуль и подключаются, только если в конфиге стоит разрешение. Мы же не хотим наблюдать кучу левых запросов при локальной разработке?

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

Теперь рассмотрим упрощенную структуру отдельного проекта:
_config/
 ├──default.yaml
 ├──dev.yaml
 └──production.yaml
module1/
 ├──_tests
 ├──someFolder1/
 │   ├──some.js
 │   ├──style.sass
 │   └──template.html
 ├──someFolder2/
 └──config.js
module2/
module3/
bootstrap.js
requireconfig.js

Пойдем с конца.
requireconfig.js — подключает библиотечные файлы к проекту
bootstrap.js — подключает файлы проекта. Стандартная схема работы require.js
module1..3 — модули проекта. Во время сборки каждый модуль собирается в отдельный файл
В _tests лежат тесты, относящиеся к файлам в текущей папке. В качестве тестов рассматриваются файлы, оканчивающиеся на spec.js.
В config.js описываются зависимости соответствующего модуля
someFolder может иметь любую структуру (главное, чтобы все зависимости были описаны)
Если рядом с файлом скрипта лежит template.html или style.сss/.sass/.scss/.less/.stylus, то они будут добавлены в кеш шаблонов или скомпиленый файл стилей. Очень простое правило. Сборщик не тащит что попало, а берет только то, что относится к файлу скрипта в той последовательности, в которой загружаются скрипты.

Самое интересное — _config
Здесь хранятся конфигурации для различных окружений. Поддерживаются форматы json, json5, hjson, cson, yaml. Как только IDE начнут поддерживать json5, переведу все примеры на него и оставлю единственным поддерживаемым форматом. В default описываются общие для всех окружений настройки. Остальные конфиги намерживаются на дефолтный.

Чтобы собрать проект с нужным конфигом нужно указать его имя первым параметром
gulp --qa
gulp build --production

По умолчанию берется dev.

Структура конфигурационного файла:
{
    public: { ... }, //параметры, доступные в приложении через project.config
    localhost: {
        webserver: { ... }, //настройки локальных веб-серверов
        manifest: { ... } //подключаемые ресурсы для локальной работы
    },
    build: {
        /* общие настройки для всех модулей */
        ...
        manifest: { ... }, //подключаемые ресурсы для собранного проекта
        modules: { ... }, //индивидуальные настройки для каждого модуля, например чтобы vendor сжимался с параметром mangle: true, а стили основного модуля загружались бы отдельным файлом
        copy: { ... } //скопировать в билд какие-то файлы, например robots.txt
    },
    vendor: { ... }, //настройки для сборки вендоров. Например, можно собрать bootstrap или angularStrap со своим набором компонентов
}

Конфиг, вероятно, самое сложное, что есть в сборщике. Чтобы его представить, лучше посмотреть примеры приложений посложнее, попроще и документацию.

Почему всё таки RequireJS?


Конечно, чтобы модули загружались по мере необходимости, а не все разом… Скажет кто-то. Но нет.

Собранный проект имеет такую структуру:
vendor-f8acc4024d.js #800КБ
vendor-9dcd7dad8d.css #100КБ
common-95dafc6502.js #200КБ
homepage-2979ff1937.js #30КБ
news-ebf043aeac.js #10КБ
...

Если пораскинуть мозгами, то можно прийти к выводу, что в любом проекте vendor (модуль с библиотечными компонентами) будет иметь самый большой вес около 1МБ (никак ни меньше 500КБ). При этом ленивую загрузку организовать не получится, т. к. все библиотеки должны быть загружены до старта проекта. Конечно, можно его разбить на файлы для каждой библиотеки, но тогда их загрузка забьет все потоки браузера и отложит загрузку остальных файлов проекта, что не принесет значительного выигрыша. Спасает то, что из всех модулей vendor меняется реже всего, поэтому почти всегда он будет браться из кеша.

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

Следом идут 10—20 модулей для разделов и различной бизнес-логики. Они меняются часто, но весят копейки. Более того, в сумме их вес сравним с весом всех библиотек.

Для ускорения загрузки и быстрого отображения элементов имеет смысл вынести стилевые файлы из вендора и главного модуля в отдельные модули (сборка позволяет настроить включение CSS в JS и порядок загрузки индивидуально для каждого модуля). В примере на рисунке ниже можно было бы вынести стили модуля payment, т. к. в них включены тяжелые иконки платежных систем.

А теперь посмотрим, как будет происходить первая загрузка проекта (файлы имеют небольшой вес т. к. включен gzip):
network

Хорошо заметно, что пока грузится vendor, успевают загрузиться почти все ресурсы проекта. Т. е. в ленивой загрузке модулей нет никакого смысла. Более того, это замедлит работу сайта, т. к. придется ждать загрузки какого-нибудь 10КБ-го файла, если пользователь перейдет в другой раздел. Другими словами, ни для больших ни для маленьких проектов RequireJS НЕ НУЖЕН!

Так зачем же он используется? Исключительно для разработки.

Основное его преимущество в том, что можно легко подключать зависимости и заимствовать компоненты из разных проектов. Например, в админке используется тот же роутинг, те же ресурсы, та же обработка ошибок. Зачем копипастить, если можно просто подключить нужные модули из основного проекта? Это позволяет развертывать новые проекты максимально быстро.

Второстепенное преимущество в том, что для локальной работы не нужен сборщик (только запущенный сервер). Скрипты не компилятся. Результат доступен моментально, а в больших проектах это многого стоит. А как же Babel, ES6, Coffee, TypeScript? А никак. Сборка создавалась для использования в больших и средних проектах в продакшене. Если у вас университетская исследовательская работа или домашняя страничка, зачем вам вообще сборка? А если всё это в серьезном проекте, да в продакшене… Положим еще раз руку на сердце, вы просто изучаете новые технологии за счет работодателя. По той же причине не поддерживаются никакие html-препроцессоры (хотя это сделать достаточно просто). JS и HTML знают любые веб-разработчики, в отличие от остальных технологий, которые сегодня популярны, а завтра о них все забыли.

Так как же всё это запустить?


Устанавливаете глобально bower и gulp:
npm i -g bower
npm i -g gulp

Скачиваете упрощенный или полноценный демо проект, набираете в его папке команды:
npm i
bower i
gulp

Открываете localhost:7000, Любуетесь.

Набираете:
gulp build --production
gulp

Открываете localhost:7200, Любуетесь собранной версией для продакшена, которая появится в папке build

Что-то не заработало?
Значит npm не загрузил или коряво загрузил модули. Если проблема с Karma, убедитесь, что она и все её плагины находятся в одной папке node_modules. Если не находит node-sass, скорее всего вы недавно обновляли ноду — переустановите все плагины. Нет прав на изменение папки — попробуйте другую консоль. При установке сплошные ошибки, но всё работает/не работает — у вас Винда.

Планы на будущее


  • Собирать билд можно будет вовсе без RequireJS. Сейчас лишние 17КБ терпимы (по сравнению с весом всего проекта).
  • Упрощение АПИ.
  • Поддержка других фреймворков (не в одиночку).
  • Быстродействие… Хотя и сейчас все процессы происходят в оперативной памяти, а livereload компилит только стили изменившегося модуля.
  • Поддержка многоязычных проектов.
  • Генерация документации из кода.
  • Написать статью об архитектуре проекта на AngularJS. Сейчас была затронута лишь общая структура, которая не объясняет какие модули должны быть в проекте и как они должны быть написаны.
  • Вообще, всё переписать на C++, избавившись от гульпа и проч. (вероятно, не в этой жизни)

Спасибо за внимание, готов выслушать критику.
Tags:
Hubs:
+5
Comments 38
Comments Comments 38

Articles