Как да създадете съвместима с AOT / JIT Angular 4 библиотека с външни SCSS / HTML шаблони

TL; DR или библиотечни изисквания

Създадох проект за семе на ъглови библиотеки, който може да ви бъде интересен в случай, ако:

  • Искате да създадете компонентна библиотека за Angular 4 и да я публикувате в NPM.
  • Вие искате вашата библиотека да е готова за по-нататъшно компилиране на AOT и JIT.
  • Искате библиотеката ви да е готова за по-нататъшно използване директно в браузърите (да речем UMD пакет, зареждан от SystemJS).
  • Искате да пишете стилове на компоненти във външни SCSS файлове.
  • Искате да напишете шаблони на компоненти във външни HTML файлове.
  • Искате да имате режим на гледане за изграждане на библиотеки (включително изграждане на AOT).

Този проект съдържа пример за библиотека на TickTock. Самата библиотека е малка и единственото, което прави е да показва текущото време. Но най-важното е, че проектът съдържа среда за многократна употреба за Angular библиотеки, която позволява да се изграждат, тестват, обличат, документират, изследват и публикуват.

Фрустрацията на Back-End Developer

Аз съм разработчик, който през последните няколко години работи по разработването на RESTful API и по този начин не участва много в предните части на проектите. Така че тези няколко години фронтови новини ме подминаха.

След като реших да разработя свой собствен проект за домашни любимци, за мен беше интересно да се гмурна в предния свят (както бях преди, когато jQuery управляваше този свят :)

И аз бях разочарован ...

React, AngularJS, Angular 2, Angular 4, Vue, Ember, Webapck, Rollup, Babel, Prepack, Node.js, NPM, Прежда, Gulp, Grunt, Karma, Jasmine, Protractor, Istanbul, Mocha, Chai, Sinon, TypeScript, ECMAScript, ES5, ES2015, TSlint, Codelyzer, AMD, UMD, CommonJS, Compodoc, SystemJS, компилация преди време, компилация Just-in-Time, Redux, Flux, RxJS, Promises, Observables, SCSS, Flex ... Дойде на ! Спри се! Това е твърде много за онзи, който си помисли, че ако знаете jQuery, вие сте нинджа на предния край: D Какво да изберете? Как да ги използваме заедно? Много възможни комбинации, човече!

Whaaaat?

Разбира се, няма да обхващам всички тези теми тук. И няма да кажа, че все още съм ги покрил напълно.

Това, което ще направя, е да споделя малко от моя опит (фрустрации и отговори, които намерих за себе си) в една конкретна област - разработване на библиотека на компоненти за Angular 4.

Отговорите и решенията, които ще предложа по-долу, разбира се, не са перфектни и не са "единствените". Това е просто нещо, което отговори на въпросите ми и работи за мен в моя проект. Така че се надявам, че може да ви се сторят полезни и за вас. И също така се надявам да ми простиш, ако откриеш някаква некоректност по-долу и вземеш под внимание моя нов старт в предния свят.

Въпросите

Когато реших да преместя един от вътрешния си модул Angular 4 в отделна библиотека npm, за да го използвам повторно в много бъдещи проекти, се сблъсках със следните въпроси, които не бяха толкова тривиални за мен.

1. Какво да експортирате и в какъв формат?

Ако искам библиотеката ми да е съвместима с AOT / JIT и да може да се използва в надстройки на браузър чрез SystemJS в какви формати и структура трябва да я експортирам?

  • Трябва ли да е само един файл на JS пакет или трябва да са много некомплектовани JS файлове със запазена структура на папките? Или трябва да са и двете?
  • Трябва ли да експортирам * .js.map, * .d.ts файлове? Какво по дяволите са * .metadata.json и * .ngfactory.ts файлове и трябва ли да ги експортирам също?
  • Трябва ли да включвам Angular core в пакети JS или трябва да го прескоча? Ако трябва да го прескоча тогава как да го направя?
  • Какъв стандарт JS да използвам: ES5 или ES2015?

2. Как да експортирате?

Нека се преструваме, че имаме първи отговори на първия въпрос. Как трябва да го изградя тогава? Изглежда, че има много възможности:

  • Уебпакет или свиване?
  • TypeScript компилатор (tsc) или ъглов компилатор (ngc)?
  • „Ngc + Webpack“ или „ngc + събиране“?
  • ngtools / webpack Плъгин за Webpack без ngc?
  • rollup-plugin-angular-aot Плъгин за сгъване без ngc?

3. Как да използвате външни SCSS стилове и HTML шаблони?

Има няколко добри примера за начинаещи ъглови библиотеки (любимият ми е Yo генератор за ъглови библиотеки), но те не поддържат външни SCSS стилове или HTML шаблони или режим на гледане за AOT изграждания. Но исках да имам мои шаблони, стилове и свързана с компоненти логика отделно.

Проблемът е, че Webpack ви позволява да имате външни SCSS и HTML шаблони с режим на гледане, но той е пакет по своето естество и по този начин произвежда пакети, които не са съвместими с AOT. Angular Compiler от своя страна осигурява AOT-приятелски изход (всички файлове отделно с * .metadata.jsons), но все още не поддържа режим на гледане и също така не поддържа външни шаблони на SCSS.

4. Поток на развитие

Като разработчик искам да разполагам с повече или по-малко автоматизирана разработка на работния процес. Така че трябваше да мога да променя източниците на библиотека и да ги тествам незабавно в браузър, без да правя ръчно прехвърляне, свързване, копиране и всички тези неща.

Как да осигурите режим на гледане за AOT-изграждане?

Както беше споменато в предишния въпрос, Angular Compiler все още не поддържа часовник режим. Имах нужда от решение тук.

Как да свържем локално развитата библиотека с местния проект?

Разбира се, има npm-връзка за това, но има грешка, която кара npm да открадне зависимостите на библиотеката от нея и да ги премести в папката node_modules на проекта. В резултат на това всички скриптове за строители в библиотечния пакет стават нефункционални.

Отговорите

Можете да прегледате проекта за засяване на ъглови библиотеки в GitHub, за да видите пълния работен пример и изходните файлове. Предоставих и описателен README там, така че трябва да е лесно да се разбере цялата идея.

Но засега нека се съсредоточим върху въпросите, изброени по-горе.

1. Какво да експортирате и в какъв формат?

1.1 Файлове, които се използват директно от браузърите

Тъй като исках моята библиотека да е готова за консумация от браузъра от SystemJS, определено трябваше да я експортирам като един JS файл (пакет).

SystemJS разбира различни формати на модули като ESM (ECMAScript Module), AMD (асинхронна дефиниция на модул), CJS (CommonJS) и т.н. Така че в общи линии бих могъл да избера всеки от тях.

Но има и още един формат, който се нарича UMD (Universal Module Definition). Този формат изглежда е много подходящ за предоставяне на библиотека като пакет, тъй като в крайна сметка имплементира както AMD, така и CommonJS формати. В резултат нашият пакет може да се изразходва дори от задния край от Node.

Така че получаваме първия си файл, който трябва да бъде експортиран:

index.umd.js

За удобство на потребителите библиотеката трябва да предостави и изрядна версия на пакета, за да намали времето за изтегляне за потребителя:

index.umd.min.js

Добре, ние се погрижихме за крайните потребители, нека да се погрижим и за разработчиците, които биха могли да искат отстраняване на грешки в пакета. За щастие имаме JavaScript SourceMaps. По принцип това е начин да се преобразува комбиниран / компилиран / минимизиран файл обратно в неизградено състояние.

Така че има още два файла за експортиране:

index.umd.js.map,
index.umd.min.js.map

След като библиотеката бъде публикувана в npm, папката dist с надстройките на вашата библиотека също става достъпна на UNPKG сървъра. Можете да намерите примери за реални файлове на споменатите по-горе файлове:

Тъй като имаме нашия пакет в мрежата, може да играете с него на Plunker (този подход не е пример в реалния свят и трябва да се използва само за тестване или демонстрационни цели, тъй като използва TypeScript компилация и изграждане на проекти от страна на браузъра, който е доста бавно и отнемащо трафик).

1.1.1 Изключете ъгловата сърцевина от снопа

Може да е доста очевидно, но трябва да имаме предвид, че пишем библиотека за проект Angular. Тъй като самият проект трябва да има Angular core като зависимост, библиотеката не трябва да включва Angular източници в пакетите, които произвежда. За целта трябва да настроим връстници на връстници в нашия package.json файл.

{
  ...
  "peerDependitions": {
    "@ ъглов / общ": "^ 4.0.0",
    "@ angular / core": "^ 4.0.0"
  }
  ...
}

Пълна версия на package.json в хранилище с ъглови библиотеки.

И след това изключете пакетите от връстници от пакета, като поискате от пакета Webpack да го направи в webpack-umd.config.ts.

импортиране * като angularExternals от 'webpack-angular-externals';
импортиране * като rxjsExternals от 'webpack-rxjs-externals';
експортиране по подразбиране {
  ...
  външни: [angularExternals ()],
  ...
}

Пълна версия на webpack-umd.config.ts в хранилище с ъглова библиотека-семена.

1.2 Файлове, които трябва да бъдат използвани от ъгловите проекти на гърба

Едно от изискванията, които библиотеката ви трябва да изпълни, е да бъде съвместима с AOT. Това е така, защото вие като разработчик на библиотека никога няма да разберете какви проекти ще се опитат да консумират вашата библиотека: с JIT или AOT видове компилация или дори и с двете. И вероятно не искате да свиете потребителската си аудитория.

Също според официалната ъглова документация е за предпочитане използването на JIT при разработване и AOT в производството.

Това не са единствените причини да поддържат компилация AOT. Самият AOT е чудесна опция за минимизиране на общото тегло на библиотеката, ускоряване на процеса на изобразяване и изключение, свързано с шаблон, по време на компилиране, а не по време на изпълнение в браузърите. Повече за AOT над предимствата на JIT можете да прочетете в официалната документация и например в статията на Минко Гечев.

Ок, предполагам, че сега вече е ясно, че се нуждаем от тази поддръжка за компилация преди време. И така, как бихме го спечелили?

Имаме нужда от Angular Compiler (или ngc), за да направим това за нас. NGC е вид обвивка над TSC (TypeScriptCompiler), но взема предвид спецификите на Angular при компилиране на * .ts файлове (вижте допълнителни файлове, които произвежда по-долу).

Нека забравим за момент за NGC и помислим защо изобщо се нуждаем от тази транспилация от TypeScript в JavaScript. Тъй като потребителите на проекти на библиотеката могат да бъдат писани както на TypeScript, така и на JavaScript, ние трябва да направим нашите компилации да бъдат TypeScript-агностични. С други думи, ние трябва да транспилираме източниците от * .ts в * .js формат, използвайки един от текущите стандарти: ES5 или ES2015 (известен още като ES6). По този начин не само проектите на TypeScript ще могат да консумират вашата библиотека, но и тези с JavaScript. Можете да прочетете повече за ES5 и ES2015 (известен още като ES6) в статията на Джон Папа. В нашия случай ще произвеждаме ES5 версия на JavaScript файлове заедно с ES2015 полифили, за да осигурим по-голяма поддръжка на „не толкова модерните“ браузъри.

https://johnpapa.net/es5-es2015-typescript/

Добре, да се върнем към Angular Compiler. За съжаление в момента тя не е достатъчно документирана (за сравнение, да кажем, TSC). И следователно следната информация може да бъде трудно за разбиране и объркване.

NGC приема като вход файлове TypeScript на компонента и услугата и произвежда следните файлове като изход:

  • * .js - [произведено от tsc, не забравяйте, че ngc е обвивка] състави JavaScript представяне на компонент / услуга * .ts файл от входа.
  • * .d.ts - [произведени от tsc, не забравяйте, че ngc е обвивка] декларационни файлове. Тъй като * .ts файлове с типове се прехвърлят в * .js файлове, които не поддържат типизации, TypeScript компилаторът трябва да постави цялата информация за отделни файлове * .d.ts, за да може да използва тези * .js файлове в TypeScript проекти по-късно. Между другото, има проект DefinitelyTyped с много дефиниции на типове, които вече са допринесли за много JS не-TypeScript библиотеки.
  • * .ngfactory.ts - фабрика за компоненти. С други думи, екземпляр на компонента по време на изпълнение - комбинация от оригиналния клас клас и JavaScript представяне на HTML шаблона на компонента. Оригиналният клас компоненти все още се препраща вътрешно от генерираната фабрика.
  • * .css.shim.ts - компилирана версия на CSS стиловете на компонента.
  • * .metadata.json - метаданни, свързани с текущия компонент (или NgModule). Това е вид JSON представяне на обектите, които предаваме на декораторите @Component @NgModule. Този файл съдържа информацията, която ще се нуждае от NGC на проекта (не от библиотеката), която е била в оригиналната библиотека * .ts файлове, но не е била включена във файловете * .d.ts.

Както Чък Яздевски спомена: „Всички библиотеки трябва да включват файла * .metadata.json заедно с всички файлове * .d.ts, които произвеждат, в противен случай няма да работят правилно с ngc. Ако нямаме тази информация, не можем да генерираме фабриките за библиотеката “.

Също така Минко Гечев пише в статията си: „Angular Compiler се нуждае от метаданни за компонентите, за да компилира техните шаблони. Да предположим, че в нашето приложение използваме библиотека на компоненти на трети страни. Как компилаторът Angular AoT познава метаданните на дефинираните компоненти, ако те се разпространяват като обикновен JavaScript? Не става За да можете да компилирате предварително приложение, препращайки към външна ъглова библиотека, библиотеката трябва да бъде разпределена с * .metadata.json, произведен от компилатора. “

Също така не е необходимо да доставяме * .ngfactory.ts файловете на нашата библиотека, тъй като това е нещо, което ще бъде компилирано и произведено от NGC от страна на проекта (а не от библиотеката) въз основа на файлове * .metadata.json, които трябва да бъдат предоставени от библиотеката. Както е обяснено тук: „Действителното изпълнение на AOT очаква AOT да се изпълнява винаги на ниво проект и никога на ниво библиотека (въпреки че буквата не е точно вярна; можете да използвате инструмента AOT в библиотеката, за да произвеждате„ метаданни “файлове ). С този дизайн можете да консумирате библиотека, написана (например) с Angular 2.3.3, дори ако проектът ви използва Angular 2.4.5 (малко по-различен AOT изход) или Angular 4.x (много различен AOT изход).

Добре, нека да обобщим За да може нашата библиотека да се използва от ъглови проекти с компилация от AOT / JIT, трябва да произведем следните четири файла от всеки * .ts файл:

** / *. js - преведена версия на * .ts файл в стандарт ES5.
** / *. js.map - JavaScript карта за приятно отстраняване на грешки.
** / *. d.ts - декларационни файлове за запазване на информация за типовете.
** / *. metadata.json - метаданни, необходими за по-нататъшно компилиране на AOT.

1.2.1 Конфигурация на ъгловия компилатор

И така, как да стартираме ngc? Извършва се чрез npm скриптове. Обърнете внимание на парчето файл package.json:

{
  "скриптове": {
    ...
    "ngcompile": "node_modules / .bin / ngc -p tsconfig-aot.json"
    ...
  }
  "devDependitions": {
    ...
    "@ angular / компилатор": "^ 4.0.0",
    "@ angular / compiler-cli": "^ 4.0.0"
    ...
  }
}

Пълна версия на package.json в хранилище с ъглови библиотеки.

Както може да забележите Angular Compiler приема конфигурационен файл tsconfig-aot.json като параметър. Ето къде се случва цялата ngc конфигурация. Тук ние молим компилатора да произведе JS файлове в стандарт ES5, да пропусне ngfactory поколение, да произведе изходни карти и декларационни файлове и така нататък:

{
  "compilerOptions": {
    "target": "es5", // Посочване на стандарт на ES
    "module": "es2015",
    "sourceMap": true, // Поискайте tsc да генерира * .map.js файлове
    "декларация": true, // Поискайте tsc да генерира * .d.ts файлове
    "outDir": "dist", // Посочете изходната папка за файловете
    ...
  }
  "files": [// Посочете входния файл за tsc / ngc
    "./tmp/src-inlined/index.ts"
  ],
  "angularCompilerOptions": {// Конфигурация на специфичния ъглов компилатор
    "genDir": "dist",
    "skipTemplateCodegen": true, // Не произвеждайте * .ngfactory.ts
    ...
  }
}

Пълна версия на tsconfig-aot.json в хранилище с ъглови библиотеки.

2. Как да експортирате?

Открих следната схема на строителство, която работи за мен.

ъглова библиотека-семен поток изграждане

Можете да разгледате раздела „Еволюция на изграждането на ъглова библиотека-семена“ в края на тази статия за други възможни решения, които по някаква причина не могат да се поберат на всички мои изисквания.

2.1 Използване на глътка

Отличителната характеристика на този подход е вграждане на всички външни HTML шаблони със техните SCSS стилове (компилирани в CSS) в свързани * .component.ts файлове чрез Gulp. Като направите това в края, ще получите временна папка с чисти ** / *. Ts файлове, които могат лесно да бъдат компилирани от Angular компилатор. Gulp също ще ни помогне да симулираме режим на гледане за Angular компилатор (повече за това по-късно).

2.2 Паралелни конструкции

Друго предимство на този подход е, че ESM, UMD и тестовите конструкции са напълно независими и могат да се извършват успоредно или отделно една по една:

# Изпълнете всички компилации наведнъж.
прежда изграждане
# Изпълнете само изграждането на ESM.
изграждане на прежда: esm
# Изпълнете само UMD изграждане.
прежда изграждане: umd

Прочетете повече за процеса на изграждане в README за ъглови библиотеки.

2.3. Сбор или Webpack

Сборът и Webpack са два страхотни пакета, които гледах.

Както можете да видите от схемите „Развитие на изграждане на ъглова библиотека-семена“ по-долу, реших първо да използвам Rollup. Привлече ме простотата на конфигурация и родната поддръжка на TreeShaking.

Както Webpack, така и сборът има свързани с TypeScript / Ъглови приставки (rollup-plugin-typecript, rollup-plugin-angular, ngtools / webpack, awesome-typecript-loader и т.н.).

Но лично аз реших да използвам Webpack досега, тъй като ми се струва по-зрял, добре документиран и с по-голяма подкрепа от общността.

За пореден път това е моят личен избор. Ако предпочитате да използвате Rollup като свой библиотечен пакет, тогава две добри статии могат да ви бъдат полезни.

След като нещо, което мога да ви предложа: не използвайте и двете едновременно :) Дръжте технологията си възможно най-тънка. След като приключих с Rollup, правейки UMD пакет и Webpack правейки подобен пакет, но за тестове на Karma - това беше червен флаг за мен, така че преминах към Webpack и за двата случая.

3. Как да използвате външни SCSS стилове и HTML шаблони?

Наличието на Gulp в арсенала на библиотеката прави възможно извършването на компилация на SCSS преди вграждането на шаблони. Вграждането на шаблони се извършва с помощта на gulp-inline-ng2-tempalte библиотека, което от своя страна ви позволява да осигурите функция за процесор на шаблон.

Разгледайте примера на gulpfile.js в хранилище с ъглови библиотеки за повече подробности.

4. Поток на развитие

За да отстраните грешката си в браузъра, трябва да имате Angular проект, който да консумира вашата библиотека, да изградите приложението и да го покажете. За ваше удобство всичко това трябва да се случи автоматично във фонов режим, така че след като промените изходния код на библиотеката, трябва незабавно да видите промените в браузъра.

Има няколко начина да отидете тук:

  • Използвайте вашия истински проект за библиотека-потребител и свържете библиотеката си към него чрез команда за връзка с прежда (вижте по-долу).
  • Използвайте демонстрационни приложения, които са предоставени за ваше удобство, като част от хранилището с ъглови библиотеки.
  • Използвайте Angular-CLI, за да генерирате библиотечно-потребителски проект за вас и след това използвайте прежда за прежда, за да свържете библиотеката си към него.

Предоставяне на режим на гледане за AOT компилатор за по-добро разработване на опит

Добре, така че сега Angular Compiler все още не поддържа часовник режим. Но използването на Gulp в ъглови библиотеки-семена ни дава възможност да симулираме режим на гледане. Разгледайте пример gulpfile.js (файл със скриптове на Gulp):

Той следи за всички промени в файла в src папка и след като бъде открита всяка промяна на файла, принуждава AOT компилатора да прекомпилира библиотеката.

Разбира се, това изглежда като решение, но може да ви спести часове на развитие, като стартира процеса на компилация автоматично за вас.

Пълната версия на gulpfile.js може да бъде намерена в ъглово библиотечно семе хранилище.

Използване на демо приложения

Можете да се възползвате от режимите на гледане както за изграждане на библиотеки, така и за демо-проекти, за да видите промените в изходния код на вашата библиотека веднага в браузъра си.

За целта трябва да:

  1. Отворете два екземпляра на конзолата.
  2. Стартирайте изграждането на библиотека в режим на гледане в първия екземпляр на конзолата, като стартирате прежда build: watch (при условие, че сте в коренната папка с ъглови библиотеки).
  3. Стартирайте изграждането на демонстрационни проекти (JIT версия) в режим на гледане, като стартирате стартиране на прежда във втория екземпляр на конзолата (при условие, че сте в папка ъгъл-библиотека-семена / демонстрация).

В резултат, след като промените изходния код на библиотеката, той ще бъде автоматично прекомпилиран и от своя страна демо-проектът на JIT ще бъде автоматично преизграден и вие ще можете да видите тези промени във вашия браузър моментално.

За повече подробности относно демонстрационните проекти, структурата на папките им и командите npm, моля, разгледайте демонстрационните проекти за ъглови библиотеки README.

Използване на прежда връзка

Тъй като семената на ъглови библиотеки използват Прежди като мениджър на пакети по подразбиране, не е нужно да се справяме с бъг npm-link, който кара npm да открадне зависимостите на библиотеката от него и да ги премести в папката node_modules на проекта. (в резултат всички скриптове за строители в библиотечния пакет стават нефункционални).

Така че можете просто да стартирате връзка за прежда, за да свържете библиотеката си към проекта:

# Изпълнете това в папката на вашата библиотека
прежда връзка
# Изпълнете това в папката на вашия проект (заменете името на библиотеката)
прежда връзка ъглова-библиотека-семена

Но в случай, че стартирате проекта си на Angular CLI, може да срещнете друга грешка, която няма да ви позволи да имате папка node_modules вътре във вашата свързана библиотека и вашата AOT (prod) надстройка ще се провали с грешка: „ГРЕШКА при грешка при решаване на символ стойности статично. Функция за обаждане ɵmakeDecorator “.

Ето предложение за решение. Така че можете просто да добавите следния ред към файла tsconfig.json на AngularCLI:

"пътеки": {"@ angular / *": ["../node_modules/@angular/*"]}

Прочетете повече за разработването на работния процес на библиотеката и демонстрационните проекти, които семената на ъглови библиотеки предлагат в свързания раздел README.

Допълнителни функции на проекта

Освен основната функционалност за изграждане на библиотека, проектът за семената на ангуларната библиотека съдържа следните функции:

  • Тестване с Карма и Жасмин.
  • Доклад за тестово покритие през Истанбул.
  • Обвързване с TSLint и Codelyzer за статичен анализ на кода.
  • Проучване на вашето изграждане чрез Sourcemap Explorer, който ви показва визуализация на карта на сайта, за да ви помогне да отстраните грешката откъде идва целият код.
  • Генериране на документация през Compodoc. Разгледайте примера с документацията.
  • Хостинг на документация чрез GitHub Pages.
  • Непрекъсната интеграция с Travis CI.
  • Значка за покритие на кода чрез Codecov като напомняне за покриване на код с тестове.

Послепис # 1 Еволюция на изграждането на ъглови библиотеки

Това са само няколко примера за това как би могла да бъде изградена ъгловата библиотека в зависимост от изискванията. Открих изискването да има външни SCSS и HTML файлове най-проблемният, тъй като той донесе с него различни видове обкръжения, като копиране на шаблони в папка dist или използване на Gulp за вграждане на шаблони в * .component.ts файлове и т.н.

еволюция на изграждането на ъглова библиотека-семенаъглова библиотека-семен поток изграждане

Но в резултат вярвам, че потокът на сглобяване стана повече или по-малко прав, с възможност за паралелно извършване на всякакви сборки (за тестове, за UMD, за ESM).

Послепис # 2

Надявам се, че сте намерили нещо полезно за вас в тази статия. Разбира се, тя не покрива изцяло темата, но може да намерите допълнителна информация, като проучите README и прегледате самите източници. Всичко това е написано с една цел - да помогне на разработчиците да отделят повече време за развитието на самата библиотека, а след това за фокусиране върху нейната среда за изграждане. Късмет!