Zusammenstellen einer Bibliothek von Winkelkomponenten als Webkomponenten

Viele Artikel werden über Winkelelemente geschrieben und lesen regelmäßig Berichte. Sie müssen nicht mehr mehrere vollwertige Winkel bereitstellen - sammeln Sie einfach die Webkomponenten und verwenden Sie sie auf Ihrer Seite.

In der Regel beschränken sich diese Materialien jedoch darauf, eine eher utopische Situation zu berücksichtigen: Wir erstellen ein separates Projekt, erstellen eine Winkelkomponente, konfigurieren das Projekt so, dass Elemente zusammengesetzt werden, und kompilieren schließlich mehrere JS-Dateien. Wenn Sie sie mit einer regulären Seite verbinden, erhalten Sie das gewünschte Ergebnis. Hurra, die Komponente funktioniert! ..

Bild

In der Praxis besteht darüber hinaus die Notwendigkeit, mehrere Komponenten aus einem vorgefertigten Arbeitswinkelprojekt herauszuziehen, vorzugsweise um dessen aktuelle Entwicklung und Verwendung nicht zu beeinträchtigen. Dieser Artikel stellte sich genau aufgrund einer dieser Situationen heraus: Ich wollte nicht nur einzelne Elemente des Projekts zusammenstellen, sondern auch die gesamte UI-Bibliothek auf Angular zu einer Reihe von Dateien mit nativen Webkomponenten kompilieren.

Modulvorbereitung


Lassen Sie uns zunächst daran erinnern, wie das Modul zum Kompilieren von Winkelelementen aussehen sollte.

@NgModule({ imports: [BrowserModule], entryComponents: [SomeComponent], }) export class AppModule { constructor(readonly injector: Injector) { const ngElement = createCustomElement(SomeComponent, { injector, }); customElements.define('some-component', ngElement); } ngDoBootstrap() {} } 

Wir brauchen:

  1. Fügen Sie den entryComponents die Komponente hinzu, die wir als Winkelelement erstellen möchten, und importieren Sie die für die Komponente erforderlichen Module.
  2. Erstellen Sie ein Winkelelement mit createCustomElement und einem Injektor.
  3. Deklarieren Sie eine Webkomponente in Ihrem Browser customElements.
  4. Überschreiben Sie die ngDoBootstrap-Methode, um sie zu leeren.

Das erste Element ist die Bezeichnung der Komponente selbst und ihrer Abhängigkeiten, und die anderen drei sind der Prozess, der erforderlich ist, damit die Webkomponente im Browser angezeigt wird. Diese Trennung ermöglicht es Ihnen, die Logik des separaten Erstellens eines Elements in einer abstrakten Oberklasse zu platzieren:

 export abstract class MyElementModule { constructor(injector: Injector, component: InstanceType<any>, name: string) { const ngElement = createCustomElement(component, { injector, }); customElements.define(`${MY_PREFIX}-${name}`, ngElement); } ngDoBootstrap() {} } 

Die Oberklasse kann eine vollwertige native Komponente aus dem Injektor, der Komponente und dem Namen zusammensetzen und registrieren. Das Modul zum Erstellen eines bestimmten Elements sieht folgendermaßen aus:

 @NgModule({ imports: [BrowserModule, MyButtonModule], entryComponents: [MyButtonComponent], }) export class ButtonModule extends MyElementModule { constructor(injector: Injector) { super(injector, MyButtonComponent, 'button'); } } 

In diesem Beispiel sammeln wir das Modul unserer Schaltfläche in den NgModule-Metadaten und deklarieren die Komponente aus diesem Modul in entryComponents. Außerdem erhalten wir den Injektor aus dem Angular Dependency Injection-Mechanismus.

Das Modul ist zur Montage bereit und gibt uns eine Reihe von JS-Dateien, die in eine separate Webkomponente gefaltet werden können. Auf diese Weise können wir mehrere Module erstellen und abwechselnd Webkomponenten daraus zusammensetzen.

Einige Komponenten zusammenstellen


Jetzt müssen wir den Bootstrap-Prozess der resultierenden Module einrichten. Am meisten gefällt mir die Idee, diese Logik in eine separate ausführbare Datei zu stellen, die für das Kompilieren eines bestimmten Moduls verantwortlich ist.

Die Struktur der Elemente sieht ungefähr so ​​aus:

Bild

Eine separate Kompilierungsdatei in der einfachsten Version würde folgendermaßen aussehen:

 enableProdMode(); platformBrowserDynamic() .bootstrapModule(ButtonModule) .catch(err => console.error(err)); 

Dieser Ansatz hilft dabei, die vorbereiteten Module einfach zu umgehen und die Projektstruktur mit Elements klar und einfach beizubehalten.

In den Build-Einstellungen von angle.json geben wir den Pfad der gesammelten Datei zu einem bestimmten temporären Ordner in dist an:

 "outputPath": "projects/elements/dist/tmp" 

Nach dem Zusammenbau des Moduls fallen eine Reihe von Ausgabedateien an.

Verwenden Sie für die Assembly selbst den üblichen Build-Befehl in angle-cli:

 ng run elements:build:production --main='projects/elements/src/${project}/${component}/compile.ts' 

Ein separates Element wird das Endprodukt sein, daher aktivieren wir die Produktionsflags mit Ahead-of-Time-Compilation und ersetzen anschließend den Pfad zur ausführbaren Datei, die aus dem Projekt und dem Namen der Komponente besteht.

Jetzt werden wir das Ergebnis in einer separaten Datei sammeln, die das endgültige Bündel unserer separaten Webkomponente darstellt. Verwenden Sie dazu die übliche Katze:

 cat dist/tmp/runtime.js dist/tmp/main.js > dist/tmp/my-${component}.js 

Es ist wichtig zu beachten, dass wir die Datei polyfills.js nicht in das Bundle jeder Komponente einfügen, da wir eine Duplizierung erhalten, wenn wir in Zukunft mehrere Komponenten auf einer Seite verwenden. Natürlich sollten Sie die Option outputHashing in angle.json deaktivieren.

Wir werden das resultierende Bundle von einem temporären Ordner in einen Ordner zum Speichern von Komponenten übertragen. Zum Beispiel so:

 cp dist/tmp/my-${component}.js dist/components/ 

Es bleibt nur noch alles zusammenzusetzen - und das Kompilierungsskript ist fertig:

 // compileComponents.js projects.forEach(project => { const components = fs.readdirSync(`src/${project}`); components.forEach(component => compileComponent(project, component)); }); function compileComponent(project, component) { const buildJsFiles = `ng run elements:build:production --aot --main='projects/elements/src/${project}/${component}/compile.ts'`; const bundleIntoSingleFile = `cat dist/tmp/runtime.js dist/tmp/main.js > dist/tmp/my-${component}.js`; const copyBundledComponent = `cp dist/tmp/my-${component}.js dist/components/`; execSync(`${buildJsFiles} && ${bundleIntoSingleFile} && ${copyBundledComponent}`); } 

Jetzt haben wir einen ordentlichen Vater mit einer Reihe von Webkomponenten:

Bild

Verbinden Sie Komponenten mit einer regulären Seite


Unsere zusammengestellten Webkomponenten können unabhängig voneinander in die Seite eingefügt werden und verbinden ihre JS-Bundles nach Bedarf:

 <my-elements-input id="input"> </<my-elements-input> <script src="my-input.js"></script> 

Um nicht die gesamte zone.js mit jeder Komponente zu ziehen, verbinden wir sie einmal am Anfang des Dokuments:

 <script src="zone.min.js"></script> 

Die Komponente wird auf der Seite angezeigt und alles ist in Ordnung.

Und fügen wir eine Schaltfläche hinzu:

 <my-elements-button size="l" onclick="onClick()"></my-elements-button> <script src="my-button.js"></script> 

Wir starten die Seite und ...

Bild

Oh, es ist kaputt!

Wenn wir in das Bündel schauen, finden wir dort eine so unauffällige Linie:

 window.webpackJsonp=window.webpackJsonp||[] 

Bild

Webpack-Patches-Fenster, um das Laden derselben Module nicht zu duplizieren. Es stellt sich heraus, dass sich nur die erste der Seite hinzugefügte Komponente zu customElements hinzufügen kann.

Um dieses Problem zu lösen, müssen wir ein benutzerdefiniertes Webpack verwenden:

  1. Fügen Sie dem Projekt ein benutzerdefiniertes Webpack mit folgenden Elementen hinzu:

    ng add @angular-builders/custom-webpack --project=elements

  2. Angular.json konfigurieren:

     "builder": "@angular-builders/custom-webpack:browser", "options": { "customWebpackConfig": { "path": "./projects/elements/elements-webpack.config.js" }, ... 

  3. Erstellen Sie eine benutzerdefinierte Webpack-Konfigurationsdatei:

     module.exports = { output: { jsonpFunction: 'myElements-' + uuidv1(), library: 'elements', }, }; 

    Darin müssen wir auf bequeme Weise eine eindeutige ID für jede Baugruppe generieren. Ich habe uuid ausgenutzt.

Sie können das Build-Skript erneut ausführen - die neuen Komponenten verstehen sich auf derselben Seite gut.

Schönheit bringen


Unsere Komponenten verwenden globale CSS-Variablen, die das Farbthema und die Größe der Komponenten angeben.

In Winkelanwendungen, die unsere Bibliothek verwenden, werden sie in die Stammkomponente des Projekts gelegt. Bei unabhängigen Webkomponenten ist dies nicht möglich. Kompilieren Sie einfach die Stile und stellen Sie eine Verbindung zu der Seite her, auf der die Webkomponenten verwendet werden.

 // compileHelpers.js compileMainTheme(); function compileMainTheme() { const pathFrom = `../../main-project/styles/themes`; const pathTo = `dist/helpers`; execSync( `lessc ${pathFrom}/theme-default-vars.less ${pathTo}/main-theme.css`,; ); } 

Wir verwenden weniger, kompilieren Sie einfach unsere lessc-Variablen und legen Sie die resultierende Datei im Ordner "helers" ab.

Mit diesem Ansatz können Sie das Design aller Webkomponenten der Seite steuern, ohne sie neu kompilieren zu müssen.

Endgültiges Skript


Tatsächlich kann der gesamte Prozess des Zusammenbaus der oben beschriebenen Elemente auf eine Reihe von Aktionen reduziert werden:

 #!/bin/sh rm -r -f dist/ && mkdir -p dist/components && node compileElements.js && node compileHelpers.js && rm -r -f dist/tmp 

Es bleibt nur, dieses Skript aus dem Hauptpaket.json aufzurufen, um den gesamten Prozess des Kompilierens der tatsächlichen Winkelkomponenten auf den Start eines einzelnen Befehls zu reduzieren.

Alle oben beschriebenen Skripte sowie Demos zur Verwendung von eckigen und nativen Komponenten finden Sie auf github .

Insgesamt


Wir haben einen Prozess organisiert, bei dem das Hinzufügen einer neuen Webkomponente nur wenige Minuten dauert, während die Struktur der wichtigsten Winkelprojekte beibehalten wird, aus denen sie stammen.

Jeder Entwickler kann einer Reihe von Elementen eine Komponente hinzufügen und diese zu einer Reihe separater JS-Bündel von Webkomponenten zusammenfügen, ohne auf die Besonderheiten der Arbeit mit Angular Elements eingehen zu müssen.

Source: https://habr.com/ru/post/de456462/


All Articles