Если вы нашли крутой компонент в npm, но она с приставкой ng, ngx, angular и так далее, то не стоит расстраиваться по этому поводу. Есть много решений, чтобы этот компонент оказался у вас. В данной статье рассмотрим решение, которое официально поддерживается Angular Team, а именно - Angular Elements.
Для практики выберем любой компонент из Awesome Angular.
Я же выбрал простой, но очень интересный — ngx-avatar. Который, в свою очередь, выводит аватары из различных социальных сетей или же просто выводит инициалы пользователя.
Примерно вот так:
И Аpi у него простой, вот небольшой пример:
<ngx-avatar facebookId="1508319875"></ngx-avatar>
<ngx-avatar src="assets/avatar.jpg"></ngx-avatar>
<ngx-avatar name="John Doe"></ngx-avatar>
И так, создадим для этого Angular проект и подключим библиотеку.
ng new avatar-lib --minimalN
npm i ngx-avatar --save
Подключим пакет в наш модуль.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
// Import your AvatarModule
import { AvatarModule } from 'ngx-avatar';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
// Specify AvatarModule as an import
AvatarModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Теперь создадим простой компонент, так как по умолчанию `AvatarModule` не экспортирует свой единственный компонент наружу.
ng g c avatar
import { Component, ViewEncapsulation, Input } from '@angular/core';
@Component({
selector: 'app-avatar',
template: `
<ngx-avatar [name]="name"></ngx-avatar>
`,
styles: [],
encapsulation: ViewEncapsulation.ShadowDom
})
export class AvatarComponent {
@Input() name: string;
constructor() { }
}
Для простоты мы использовали лишь один из входящих параметров.
Создадим из всего этого универсальный компонент. Для того чтобы заработали наши элементы необходимо добавить Angular Elements в наш проект.
ng add @angular/elements
Кроме всего прочего, эта команда так же включит в секцию scripts облегченную версию для регистрации Custom Elements.
"scripts": [{
"input": "node_modules/document-register-element/build/document-register-element.js"
}]
Получившийся в итоге app.module.ts:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { AvatarComponent } from './avatar.component';
import { AvatarModule } from 'ngx-avatar';
@NgModule({
declarations: [AvatarComponent],
imports: [AvatarModule, BrowserModule],
entryComponents: [AvatarComponent]
})
export class AppModule {
constructor(private injector: Injector) {
// Создаем наш кастомный Angular Element
const avatarComponent = createCustomElement(AvatarComponent, {
injector
});
// И обьявляем наш элемент
customElements.define('avatar-lib', avatarComponent);
}
ngDoBootstrap() {}
}
Отлично! Теперь нам все это добро нужно собрать. Для этого создадим свой сборщик, который будет упаковывать наш компонент в один js файл:
const fs = require('fs-extra');
const concat = require('concat');
(async function build() {
const files = [
'./dist/avatar-lib/runtime.js',
'./dist/avatar-lib/polyfills.js',
'./dist/avatar-lib/scripts.js',
'./dist/avatar-lib/main.js'
];
await fs.ensureDir('elements');
await concat(files, 'elements/avatar-lib.js');
})();
Все готово! осталось только собрать. Добавим такой скрипт в наш package.json.
"build:elements": "ng build --prod --output-hashing none && node build.js"
Ну и все! Получился у нас такой вот js файл `avatar-lib.js` весом в ~221kB и ~60кб в gzip. Естественно сюда включены еще много с angular/core. Сама либа ngx-avatar весит около 16.8kB и 5.4kB в gzip. Чтобы гораздо уменьшить вес avatar-lib.js нам бы понадобился Ivy Compiler, но это тема для другой статьи. (Надеюсь к этому моменту зарелизят Ivy, или же буду собирать вручную из того что сейчас доступно).
Что же дал нам Angular Elements?
Это всего лишь удобное api для реализации Web Components. На самом деле, можно было обойтись и без него. Посмотрите что можно было бы делать ранее: статья Jia Li (активно сопровождает сейчас Zone.js).
Внедряем наш компонент в другие фреймворки.
Сначала во Vue. Подключаем любым удобным для вас способом и внедряем через стандартные vue бингинги.
<avatar-lib :name = ‘myName’></avatar-lib>
Так же можно было бы организовать Output события.
Демо на Vue: https://github.com/Jamaks/angular-element/tree/master/ang-el-vue
Настало время для React! Все так же просто:
<avatar-lib name = {this.myName}></avatar-lib>
Что же касается поддержки браузеров на данный момент:
https://angular.io/guide/elements#browser-support-for-custom-elements
https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define
Конечно же, использовать ли такой метод внедрения или нет остается решать вам. Я бы хотел чтобы UI компоненты не имели привязки к чему либо и были без подобных суффиксов: ng-, ngx-, v-, react-, rc- и так далее.
Бонус!
Вдохновившись размером компонента (ngx-avatar), попытался все таки скормить ngtsc, ngcс и потом собрать с помощью rollup. Но попытки оказались безуспешными, так как выбранный компонент требовал много внешних модулей. Отчаявшись, сделал некоторое подобие данного компонента, и результаты приятно удивили — на данный момент (7.1.2) получилась библиотека ~96кб и ~26кб в gzip. Естественно туда легли много зависимостей, да и моя конфигурация rollup оставляет желать лучшего. Но все таки это не 3кб, которые нам показывали при презентации Ivy. Остается ждать когда внедрят ngcc в Webpack (cli) и напишут документацию.
Так же небольшие эксперименты и инсайды из мира Angular можете найти по ссылке.
Автор: jamak