Когда проектов становится чуть больше чем один, возникает необходимость как-то переиспользовать не только отдельные модули с кодом, но и сами UI-компоненты. Вариантов решения проблемы много — от традиционного копипаста, до настройки отдельного проекта с тестами, документацией и даже блекджеком.
Проблема в том, что второй вариант требует значительных усилий по подготовке и каждый такой проект уникальный — со своим инструментарием в котором каждому новому разработчику нужно разбираться заново. В конце июля, команда Angular предложила свое, комплексное, решение этой проблемы добавив в angular/cli новую команду для создания библиотек — library.
Давайте посмотрим, что из этого получилось.
Для тестов, взята самая свежая из стабильных версий angular/cli — 6.1.5 (04.09.2018)
Идеальный мир
В идеальном мире все должно быть удобно. Так, для библиотеки компонент я бы выделил три важных момента
- Единообразность проектов и быстрый старт
- Удобство разработки
- Удобство распространения
Итак начем со старта
Для того что бы создать свою библиотеку нам нужно сделать два шага — создать новый проект и добавить к нему библиотеку. Сначала создадим новый проект:
npx @angular/cli@latest new mylibapp
Я использую npx что бы не устанавливать cli глобально и избегать npm run конструкций. Если у вас npm версии 5.2 или новее — попробуйте. Подробнее почитать можно здесь
После выполнения команды, мы увидим стандартный (для 6 ангуляра, который отличается от 5ой версии) проект в котором будут созданы два под-проекта — основной mylibapp и mylibapp-e2e. Сам angular проект теперь описывается в angular.json.
Библиотеки, как видим, пока нет.
И вот он первый нюанс. Наше название уже занято основным проектом, и назвать библиотеку так же уже не выйдет. Поэтому, если вы хотите назвать библиотеку my-super-library, сначала нужно создать проект, который должен называться как-то по-другому. Например, my-super-library-project. И только потом, создавать библиотеку с желаемым названием.
Теперь создадим третий под-проект и сгенерируем библиотеку.
cd mylibapp
npx ng generate library mylib --prefix mlb
Указывать префикс не обязательно, но очень желательно что бы не пересекаться с другими библиотеками.
Как видно, теперь, третьим под-проектом добавилась наша наша библиотечка. Она имеет свой отдельный package.json, tsconfig и karma.conf.js, что позволяет настраивать ее без боязни задеть остальные проекты. Кстати, при желании мы можем добавить еще одну библиотеку и она тоже будет отдельным подпроектом. Но вот почему библиотеку нельзя было выделить совсем отдельным проектом (как например в .Net) я не знаю. И если e2e проект не сложно удалить руками, то основной проект — уже нет. В итоге в репозитории появляется лишний код, что не очень хорошо.
Теперь давайте посмотрим, какие инструменты мы получаем сразу. Это связка tslint + codelyzer, karma + jasmine и protractor для e2e. Т.е. стандартный набор angular проекта, ничего специфичного для библиотеки нам не подвезли. Это немного странно, так как какой-то инструмент для просмотра компонент и рендера их в документацию (например storybook) просто must have. Но ладно, будем считать, что тут нам просто оставили пространство для маневра.
Давайте запустим тесты и линтер что бы убедиться, что все работает.
npm test mylib
npx ng lint mylib
У меня все прошло без проблем, но для тестирования был использован Chrome, что тоже странно. Я ничего против него не имею, но на билд серверах его на 90% не будет. Почему не использовали тот же Puppeteer — не понятно.
Подведем итоги:
Плюсы
- Быстрый старт нового проекта
- Единообразный подход
Минусы
- Лишний код в проекте
- Очевидные вещи нужно допиливать руками
Пока ничего критичного, продолжаем копать дальше.
Разработка
Кое-какие компоненты у нас уже есть "из коробки", давайте на них посмотрим. Поскольку никаких специальных инструментов у нас нет, будем использовать основной проект (вот он оказывается зачем нужен). Для этого нам нужно сбилдить библиотеку, сделать импорт библиотечного модуля и запустить основной проект.
npx ng build mylib
import { MylibModule } from "mylib";
...
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule, MylibModule
],
providers: [],
bootstrap: [AppComponent]
})
npm start
После того как все выполнится, мы увидим наш компонент из библиотеки. Но снова есть нюанс — watch режим для библиотеки пока не сделали, нужно каждый раз запускать билд библиотеки самостоятельно? Watch появится только в angular/cli 6.2+. И не из коробки, для этого придется добавить новый флаг в tsconfig.json
tsconfig.json
"angularCompilerOptions": {
"enableResourceInlining": true,
}
А после этого запускать билд c флагом watch:
ng build mylib --watch
Если же вы в силу каких-то причин будете использовать cli младше 6.2, билдить придется самостоятельно, что, прямо скажем, — плохо.
Теперь давайте добавим новый компонент. Для этого нужно выполнить стандартную команду generate component. Из-за того, что библиотека не наш основной проект, приходится использовать флаг проекта, что тоже немного раздражает (а вот если бы библиотека была самостоятельным решением...).
npx ng generate component some-nice-image --project mylib
Теперь под mylib/src создадим папку assets, добавим картинку и снова пересоберем библиотеку что бы увидеть резульат. И тут нас ждет еще один сюрприз — картинки нет. Оказывается, что ресурсы, используемые в библиотеке не попадают в билд автоматически, их нужно копировать самостоятельно (или вот так). И вроде бы не страшно, но все равно как-то не правильно.
Зато, из-коробки должен работать tree-shaking. Давайте создадим еще один компонент в библиотеке но не будем использовать его в основном проекте. Собираем основной проект в продакш режиме
npx build --prod
И видим, что размер бандла не изменился. Tree-Shaking с библиотеками действительно работает!
Теперь неплохо было бы попробовать поставить какую-то зависимость. Поскольку каждый проект имеет свой собственный package.json нам нужно сначала перейти в папку библиотеки и выполнять команду npm install
npm i -D @drag13/when-do
npm i @drag13/round-to
Я специально поставил их по-разному что бы проверить как потом с этим справится упаковщик. Все ставится без проблем. Пробуем собрать и получаем предупреждение
Distributing npm packages with 'dependencies' is not recommended. Please consider adding drag13/round-to to 'peerDependencies'or remove it from 'dependencies
Распространение npm пакетов с зависимостями не желательно. Пожалуйста, подумайте, что бы добавить зависимость drag13/round-to к peerDependencies или вообще убрать ее из зависимостей
а, затем, и ошибку:
Dependency drag13/round-to must be explicitly whitelisted
Зависимость drag13/round-to должна быть явно добавлена в белый список
Вот это уже интересно, by design, библиотека не хочет иметь прямых зависимостей. Пробуем переместить нашу зависимость в секцию peerDependencies и собраться заново – вуаля, все работает. Но это значит порядок установки сторонних библиотек теперь другой. Сначала ставим зависимость на основной модуль, потом, ручками добавляем в секцию peerDependencies библиотеки.
Остальное работает так же, как и в обычном Angular проекте.
Коротко подведем итоги:
Плюсы:
- Работаем в знакомом окружении со знакомыми командами
- Есть tree-shaking из коробки
Минусы:
- Для "посмотреть компонент" нужно использовать целый проект
- Пока еще нет watch режима
- Ресурсы нужно копировать вручную или настраивать билд-процесс самостоятельно.
И, наконец, переходим к публикации
Публикация
Вот тут все прямо хорошо. Для публикации angular/cli использует уже неплохо зарекомендовавший себя ng-packgr который самостоятельно собирает наш код в пригодный для публикации npm пакет оставляя за бортом настройку package.json файла (а это не мало), минификацию, упаковку в разные форматы (например в UMD).
Для того, чтобы опубликовать свой пакет (или посмотреть что там внутри) нужно выполнить три команды
npx ng build --prod
cd dist/mylib
npm publish
Если вы не хотите паблишить, замените команду publish на pack
В результате у меня получилось следующее:
Для начала давайте заглянем в package.json, который выглядит совсем не так оригинальный package.json нашей библиотеки.
Как видим, packagr не стал удалять наши devDependencies, хотя некоторые так делают. Кроме того теоретически радует количество форматов, которые описаны в package.json (пусть я и половины их не знаю).
Внутри пакет содержит минифицированный и не минифицированный бандл в формате UMD, и еще несколько бандлов внутреннего формата angular (fesm5, fesm2015). Но, главное, теперь об этом не будет болеть голова разработчиков что просто замечательно.
Перейдем к выводам
Плюсы:
- Удобство
- Продуманность
Итого
Решение получилось интересное, но сырое. Старт и публикация очень удобны, но к разработке пока есть вопросы. Особенно растраивает, что сейчас библиотека не является самостоятельным проектом by design, а скорее дополнением к основному проекту с возможностью публикации.
С другой стороны, проделан большой кусок работы, функционал постоянно развивается, и я уверен, что со временем, мы получим отличный инструмент для разработки.
Автор: Drag13