Aus Linux-Magazin 04/2020

Framework-Komponenten mit Bit extrahieren

© Mikhail Strogalev, 123RF

Das Open-Source-Projekt Bit löst Komponenten aus einer Angular-, React- oder Vue-App heraus, um sie bei Bedarf in anderen Apps wiederzuverwenden.

Auf die Idee von Bit [1] sind die Produzenten der drei großen Frameworks Angular [2], React [3] und Vue [4] offenbar nicht gekommen: Komponenten aus den Frameworks zu ziehen und sie neu zu arrangieren. Dazu speichert der Anbieter Bit.dev den extrahierten Code in der eigenen Cloud zwischen. Ein Anwendungsentwickler kann anschließend eine Komponente entweder wie jedes andere Paket über Nodes Paketmanager einbinden oder den Quellcode mithilfe des Bit-Werkzeugs direkt in sein Projekt importieren. Dort kann er ihn im Team und nach dem Vorbild von Git [5] weiterentwickeln.

Letztendlich erspart Bit also allen Beteiligten einige Arbeit und ebnet den Weg für eine breite Palette von alleinstehenden Open-Source-Komponenten. Um sich zu finanzieren, bietet der Dienst neben einem freien auch kostenpflichtige Accounts für Teams und Firmen an [6]. Die Arbeitsweise von Bit führt der vorliegende Artikel anhand einer Login-Komponente vor. Die borgt sich der Artikel aus der vorliegenden Beispielanwendung in Abbildung 1 unter Angular und PHP.

<a href="#artRef-f1">Abbildung 1</a>: Die Beispielanwendung zeigt links die Session-ID an, welche die Login-Komponente rechts im Bild zuvor aus dem Login-Service ausgelesen hat.

Abbildung 1: Die Beispielanwendung zeigt links die Session-ID an, welche die Login-Komponente rechts im Bild zuvor aus dem Login-Service ausgelesen hat.

Installation

Die Apps moderner Javascript-Frameworks bestehen aus mehreren Bausteinen, den sogenannten Components oder Komponenten. Sie kapseln ein Stück Funktionalität im Stil eines Microservice [7], wie beispielsweise die Login-Maske, die üblicherweise oben rechts auf Webseiten auftaucht. Zu einer Komponente gehört nicht nur die deklarative Beschreibung der Eingabemaske unter HTML wie in Listing 4, sondern auch der Javascript-Code, der die Komponente in die Beispielanwendung einbettet und ihr Verhalten bei einem Klick auf den Login-Button bestimmt.

Beim Klick auf den Anmelden-Knopf aus Abbildung 1 stellt der Code aus Listing 6 eine HTTP-Anfrage an den Login-Dienst (Listing 10). Benutzername und Passwort kommen dabei aus den gleichnamigen Formularfeldern (Listing 4). Im Erfolgsfall liefert der Dienst ein JSON-Objekt mit einer gültigen Session-ID zurück (Abbildung 1), andernfalls erscheint eine Fehlermeldung (Abbildung 2).

<a href="#artRef-f2">Abbildung 2</a>: Schl&auml;gt die Anmeldung fehl, erscheint eine entsprechende Meldung.

Abbildung 2: Schlägt die Anmeldung fehl, erscheint eine entsprechende Meldung.

Die Idee besteht nun darin, diese Funktionalität auch in weiteren Apps wiederzuverwenden. Den Code fügt der Entwickler jedoch nicht durch Copy & Paste ins Projektverzeichnis der jeweiligen App ein, sondern macht ihn über Bit im Stil eines Javascript-Pakets zugänglich.

Bevor die Komponente ihren Dienst in der Beispielanwendung unter Beweis stellen darf, bereitet Listing 1 Elementary OS 5.0, das auf Ubuntu 18.04 LTS basiert, auf den Einsatz von Angular 8 und PHP 7 vor. Zeile 2 installiert zunächst Curl, das in Zeile 3 das Quell-Repository der aktuellen Node-Version 13 an Elementary OS respektive Ubuntu weiterreicht. Zeile 4 installiert Node über den Paketmanager.

Zeile 5 verwendet den mit Node zusammen installierten Javascript-Paketmanager Npm, um die neueste Version von Angular global im System zu installieren. Zeile 6 erzeugt über das zuvor installierte Kommandozeilen-Tool Ng von Angular eine zunächst leere Beispiel-App mit dem Namen »login-master«.

Der Anwender wechselt in Zeile 7 in das Projektverzeichnis der App und spielt in Zeile 8 das fehlende Node-Package rxjs-compat [8] ein. In der letzten Zeile startet Ng schließlich den Watch-Prozess der Angular-App. Die lässt sich dann als noch leere Beispielanwendung über die URL »http://localhost:4200« im lokalen Browser aufrufen (Abbildung 3).

<a href="#artRef-f3">Abbildung 3</a>: Die Befehle aus <a href="#artRef-l1">Listing&nbsp;1</a> installieren die Login-Komponente von Angular. Sie dient als Vorlage f&uuml;r die f&uuml;r Bit verwendete Login-Komponente.

Abbildung 3: Die Befehle aus Listing 1 installieren die Login-Komponente von Angular. Sie dient als Vorlage für die für Bit verwendete Login-Komponente.

Listing 1

Angulars Login-Komponente installieren

$ sudo apt update
$ sudo apt install curl
$ curl -sL https://deb.nodesource.com/setup_13.x | sudo -E bash -
$ sudo apt install nodejs
$ sudo npm install -g @angular/cli
$ ng new login-master
$ cd login-master
$ npm install --save rxjs-compat
$ ng serve

Beispiel-App

Die auf Bit basierende Beispielanwendung mit der extrahierten Login-Komponente entsteht nun, indem der Entwickler die Listings 2 bis**9 unter den jeweils angegebenen Pfaden im Projektverzeichnis ablegt. Das baut die zwar leere, aber bereits funktionstüchtige Angular-App in eine Beispielanwendung um.

Den Auftakt macht die Modulbeschreibung aus Listing 2 unter »src/app/app.module.ts«. Das Listing verwendet wie alle folgenden die Javascript-Variante Typescript. Mit den Codezeilen ersetzt der Entwickler den Inhalt der unter dem Pfad vorgefundenen Datei.

Die zunächst auskommentierte Zeile 5 importiert später das Login-Modul, um die Login-Komponente mit Bit zu verwenden. Bis dahin importiert Zeile 6 die lokal vorhandene Version des Moduls, indem sie den Pfad »./login/login.module« übergibt. Zeile 7 importiert die »ConsumerComponent«, die Listing 9 spezifiziert. Sie zeigt, wie sich Daten aus dem Login-Modul übernehmen lassen.

Der Code ab Zeile 9 bindet die Komponente in die App ein, indem sie den Klassennamen im Feld »declarations« speichert (Zeile 10 bis 13). Ähnlich aktiviert der Code in Zeile 16 das Modul »LoginModule«.

Beide Angaben erfolgen innerhalb des Dekorators »@ngModule«. Dessen Aufgabe besteht darin, Instanzen der Klasse »AppModule« um die Eigenschaften eines Angular-Moduls zu erweitern. Der Vorteil gegenüber abgeleiteten Klassen besteht darin, dass der Entwickler Dekoratoren mit Parametern versehen darf.

Startet die Angular-App, lädt sie zuerst das Modul aus Listing 2 und rendert dann die Komponente »AppComponent« (via »bootstrap«) als Wurzel-Komponente im Viewport des Browsers.

Listing 2

src/app/app.module.ts

// (Fast) alle beteiligten Komponenten laden
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
// import { LoginModule } from '@bit/pamoller.login.login';
import { LoginModule } from './login/login.module';
import { ConsumerComponent } from './consumer/consumer.component';
@NgModule({
  declarations: [
    AppComponent,
    ConsumerComponent
  ],
  imports: [
    BrowserModule,
    LoginModule
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Den Quellcode der Komponente unter »src/app/app.component.ts« muss der Entwickler nicht weiter anpassen. Nachbesserungen benötigt hingegen das Template der Komponente in der Datei »src/app/app.component.html«. Templates mischen bekannte HTML-Elemente mit angepassten Elementen, die als Platzhalter für Components dienen. Der Entwickler löscht den vorhandenen Code und fügt dann den aus Listing 3 ein. Darin rendert Zeile 3 innerhalb des »<div>«-Elements die Login-Komponente (die die Listings 4 bis**8 entwerfen). In Zeile 5 rendert Angular die »ConsumerComponent« aus Listing 9.

Listing 3

src/app/app.component.html

<!-- Login- und Ausgabebereich über Custom-Tags einbinden -->
<div class="login-box">
<login></login>
</div>
<consumer></consumer>

Listing 4 zeigt bereits das Template der Login-Komponente. Der Code gehört in die Datei »src/app/login/login.component.html«, der Entwickler muss also noch den Unterordner »login« anlegen.

Das Element »label« kümmert sich in Zeile 3 darum, das erste Formularfeld aus Zeile 4 zu beschriften. Der Ausdruck »[(ngModel)]=”username”« innerhalb des »input«-Elements in Zeile 4 bindet den Wert des »id«-Attributs an das gleichnamige Feld aus der Klassendefinition der Komponente (Zeile 12 in Listing 5). Die eckigen Klammern sorgen dafür, dass Angular die Werte aus dem gleichnamigen Feld der Klasse im »input«-Element visualisiert. Die runden Klammern bewirken, dass die App die Texteingabe aus dem Formularfeld im Feld speichert.

Diese als Double Binding bekannte Schlüsseltechnologie von Angular löst einen wichtigen Nebeneffekt im Template sowie im Component-Objekt aus: Das Framework berechnet Programmteile, die eine Referenz auf das Feld verwenden, noch einmal neu, wenn sich der Wert ändert – ganz im Stil von Reactive Programming [9]. Dasselbe gilt für das zweite Formularfeld in Zeile 6 und das korrespondierende Feld in Zeile 13 von Listing 5.

Im Übrigen sorgen die runden Klammern im Ausdruck »(click)=”login()”« (Listing 4) für den Aufruf der Klassenmethode »login()« in den Zeilen 19 bis 21 von Listing 5, sobald der Nutzer auf das Button-Element klickt.

Listing 4

src/app/login/login.component.html

<!-- Das Template der Login-Maske verwendet Angulars Binding-Mechanismus -->
<div id="login">
  <label for="username">Benutzername</label>
  <input [(ngModel)]="username" #ctlr="ngModel" id="username">
  <label for="password">Passwort</label>
  <input [(ngModel)]="password" #ctlr="ngModel" id="password" type="password">
  <br/>
  <button (click)="login()">Anmelden</button>
</div>

Listing 5 zeigt den Typescript-Code der Login-Komponente in Form einer Klasse. Die Datei »src/app/login/login.component.ts« legt der Entwickler im Verzeichnis »app/« ebenfalls im neuen Unterordner »login/« ab. Zeile 2 importiert den Dekorator »Component«, den die Zeilen 6 bis 10 entwerfen. Die erste Zeile darin speichert den Namen des angepassten Elements, die zweite die Pfadangabe zum korrespondierenden Template. Das Attribut »styleUrls« in der dritten Zeile speichert ein Feld mit den Pfaden zu den zugehörigen CSS-Stylesheet-Angaben.

Die Klassendefinition der Komponente folgt dem Dekorator in den Zeilen 11 bis 22. Das Schlüsselwort »export« veröffentlicht in Zeile 11 die Klasse für den Import außerhalb der Datei. Die Zeilen 12 und 13 speichern die bereits bekannten Felder »username« und »password«.

Zeile 4 bindet Angulars Injection-Mechanismus ein. Er injiziert eine Instanz der Dienstklasse »LoginService« (auch Listing 6) in einen Klassen-Konstruktor (Zeilen 15 bis 17). Die Instanz übergibt er dabei im Funktionskopf der Konstruktor-Methode von Zeile 16 an die Variable »loginService«. Die Methode »login()« (Zeile 19 bis 21) ruft die gleichnamige Methode aus der Dienstklasse auf und übergibt Benutzernamen und Passwort.

Listing 5

src/app/login/login.component.ts

// Die Login-Komponente ist in Typescript verfasst
import { Component } from '@angular/core';
import { Injectable } from '@angular/core';
import { LoginService } from './login.service'
@Component({
  selector: 'login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent {
  private username;
  private password;
  constructor(
    private loginService: LoginService
  ) {}
  login() {
    this.loginService.login(this.username, this.password);
  }
}

Das Teilen von Daten übernehmen unter Angular Dienste. Der Code aus Listing 6 gehört in die Datei »src/app/login/login.service.ts« und erzeugt den Dienst »LoginService«. Er verwendet eine XHTTP-Anfrage, um über einen gültigen Account Benutzerdaten vom Server der Beispielanwendung auszulesen. Da der Netzzugriff eine Latenz verursacht, verteilt der Dienst das Ergebnis asynchron über einen Benachrichtigungsmechanismus an alle registrierten Beobachter. Dazu verwendet er Angulars Observable-Mechanismus, der in einer App den Austausch von Nachrichten zwischen Publisher und Subscriber regelt [10]. Die Arbeitsweise fasst Abbildung 4 in einem Blockdiagramm zusammen.

<a href="#artRef-f4">Abbildung 4</a>: Der Dienst verteilt asynchron eintreffende Daten aus dem Backend &uuml;ber ein Observable an die Komponenten Consumer und Login.

Abbildung 4: Der Dienst verteilt asynchron eintreffende Daten aus dem Backend über ein Observable an die Komponenten Consumer und Login.

Zeile 2 von Listing 6 importiert dazu zunächst den Dekorator »Injectable«, auf den die Zeilen 8 bis 10 verweisen. Die Angabe »providedIn: ‘root’« sorgt dafür, dass Angular genau eine Instanz der Klasse erzeugt. Dieses Singelton injiziert es dann an allen referenzierenden Stellen, etwa in Zeile 16 von Listing 5 oder in Listing 9.

Zeile 3 von Listing 6 importiert die Klasse »HTTPClient«, um in Zeile 16 eine Instanz davon in die Variable »http« zu injizieren. Zeile 4 importiert die Klasse »BehaviorSubject«. Sie bildet den Kern des bereits angesprochenen Benachrichtigungsmechanismus und verteilt Nachrichten an alle registrierten Beobachter. Das Besondere an der Klasse: Beim Einschreiben in den Mechanismus gibt sie zuerst den aktuellen Zustand zurück. Das ist von Vorteil, wenn sich Teilnehmer erst später melden.

Zeile 5 importiert die Klasse »Observable«, Zeile 6 »MySession«. Beide Klassen kommen als Typangabe in den Deklarationen der Zeilen 12 und 14 zum Einsatz. Die Zeile 12 innerhalb der Klassendefinition »LoginService« erzeugt den Benachrichtigungsmechanismus. Er verteilt Nachrichten in Form von Objekten des Typs »MySession«. Dieser Typ speichert die Antwort des Login-Diensts sowohl im Erfolgs- als auch im Fehlerfall auf dem Server in den Variablen »sessionid« und »error« (Listing 7, Zeile 3 und 4). Der Konstruktor in den Zeilen 6 bis 9 von Listing 7 initialisiert die Felder schließlich.

Zurück in Listing 6 formt Zeile 13 aus dem »BehaviorSubject« ein Objekt, das sich formal wie ein »Observable« verhält. Die Methode »login()« (Zeile 18 bis 23) nutzt die Instanz der Klasse »HttpClient«, um eine HTTP-Anfrage im »POST«-Format an den Login-Server zu senden. Als Parameter übergibt die Anfrage »username« und »password«.

Kommt auf die gesendete XHTTP-Anfrage eine Antwort zurück, aktiviert dies das von der Methode »post()« zurückgegebene Promise-Objekt. Das übernimmt die Callback-Funktion, die sich als Argument in der »subscribe()«-Methode (Zeile 22) verbirgt. Die Callback-Funktion wiederum versendet die Server-Antwort im Format »MySession« an alle eingeschriebenen Beobachter des Beobachtungsmechanismus.

Listing 6

src/app/login/login.service.ts

// Der Dienst verwendet den Injection-Mechanismus, um mit dem HTTP-Modul zu arbeiten
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject } from 'rxjs';
import { Observable } from 'rxjs';
import { MySession } from './mysession';
@Injectable({
  providedIn: 'root'
})
export class LoginService {
  private _session: BehaviorSubject<MySession> = new BehaviorSubject(new MySession('', ''));
  public readonly session: Observable<MySession> = this._session.asObservable();
  constructor(private http: HttpClient) {}
  login(username, password) {
    this.http.post(
      '/login',
      {"username": username, "password": password}
    ).subscribe(data => this._session.next(new MySession(data['sessionid'], data['error'])));
  }
}

Listing 7

src/app/login/mysession.ts

// Typescript verlangt nach Datentypen, wie hier der Klassendefinition von <C>MySession<C>
export class MySession {
  sessionid: string;
  error: string;
  constructor(sessionid: string, error: string) {
    this.sessionid = sessionid;
    this.error = error;
  }
}

Listing 8 zeigt die Modulbeschreibung für die Komponente »LoginModule«. Sie bündelt alle Ressourcen aus dem Projektverzeichnis »src/app/login/« zu einem Modul. Lokal wäre die Komponente auch ohne Modul lauffähig. Dennoch eines anzulegen, folgt einer Empfehlung des Bit-Tutorials [11] für Angular.

Die Zeilen 2 bis 5 von Listing 8 importieren benötigte Module wie »CommonModule«, »FormsModule« und »HttpClientModule« sowie den Dekorator »NgModule«.

Erst Zeile 6 importiert die eigentliche Komponente. Die benötigten Klassen registriert der Code innerhalb des Dekorators »NgModule« (Zeilen 10 bis 22). Die Module speichert der Abschnitt »imports«, die Komponente die Abschnitte »declarations« und »exports«. Um »LoginService« und »MySession« später in der Package-Variante in Zeile 4 und 5 von Listing 9 zu verwenden, importiert und exportiert Listing 8 beide am Ende des Headers.

Listing 8

src/app/login/login.module.ts

// Ein Angular-Modul bündelt Ressourcen auf elegante Weise
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { LoginComponent } from './login.component';
export * from './login.service';
export * from './mysession';
@NgModule({
  declarations: [
    LoginComponent,
  ],
  imports: [
    CommonModule,
    FormsModule,
    HttpClientModule
  ],
  exports: [
    LoginComponent,
  ]
})
export class LoginModule { }

Die Consumer Component aus Listing 9 demonstriert die Funktionstüchtigkeit von Bit, indem sie die Session-Daten aus der Login-Komponente ausliest und im Webbrowser darstellt. Dazu importiert die Komponente zunächst »LoginService« und »MySession« über den relativen Pfad im Projektverzeichnis. Später wechselt der Entwickler beide (wie in Listing 2) gegen die Zeilen 4 und 6 aus.

Der Dekorator »Component« (Zeile 9 bis 11) speichert »consumer« als Namen des Custom-Elements. Anstelle der Referenz auf die Template-Datei merkt sich Zeile 11 den Template-Code zum Schlüsselwert »template« einfach direkt. Der Ausdruck »{{ session.sessionid }}« innerhalb der ersten »<h3>«-Überschrift liest den Wert des Feldes »sessionid« aus der Komponente »session«. Analog liest der Code in der zweiten Hälfte der Zeile die Fehlermeldung aus dem Feld »error« aus.

<a href="#artRef-f5">Abbildung 5</a>: Der Watch-Prozess unter Angular nach dem erfolgreichen Kompilieren der Beispiel-App.

Abbildung 5: Der Watch-Prozess unter Angular nach dem erfolgreichen Kompilieren der Beispiel-App.

In der Klassendefinition zu »ConsumerComponent« (Zeile 13 bis 19) initialisiert die Zeile 14 »session« zunächst mit einem leeren Objekt vom Typ »MySession«. Ohne Initialisierung von »session« würde Zeile 11 einen Fehler verursachen, da sie weder »sessionid« noch »error« lesen könnte.

Listing 9

src/app/consumer/consumer.component.ts

// Die Component demonstriert die Funktionstüchtigkeit des Login-Mechanismus durch Ausgabe ihrer Daten
import { Component } from '@angular/core';
import { Injectable } from '@angular/core';
//import { LoginService } from '@bit/pamoller.login.login';
import { LoginService } from '../login/login.service';
//import { MySession } from '@bit/pamoller.login.login';
import { MySession } from '../login/mysession';
@Component({
  selector: 'consumer',
  template: '<h3>Sessionid: {{ session.sessionid }} </h3> <h3>Error: {{ session.error }}</h3>'
})
export class
 ConsumerComponent {
  private session: MySession = new MySession('', '');
  constructor(private login: LoginService) {
    this.login.session.subscribe(data => this.session = data)
  }
}

Der Klassenkonstruktor (Zeile 16 bis 18) injiziert das Singelton-Objekt der Klasse »LoginService« aus Listing 6. In Zeile 16 registriert »ConsumerComponent« die Callback-Funktion »data => this.session = data«. Sie speichert unter »session« neue Werte aus dem Benachrichtigungsmechanismus vom Typ »MySession«. Abbildung 5 zeigt die App nach dem Speichern von Listing 9 in »consumer.component.ts« und dem Start der Laufzeitumgebung in der Shell.

Emulator

Um den Login-Prozess für das Beispielprogramm zumindest zu emulieren, speichert die Datei »router.php« (Listing 10) den Code des Emulators in PHP im aktuellen Arbeitsverzeichnis.

Zunächst aber installiert »sudo apt install php7.2-cli« PHP zur Verwendung auf der Kommandozeile unter Elementary OS und Ubuntu 18.04 in der Shell. Der Befehl »php7.2 -S localhost:8000 router.php« ruft PHP im Stil eines Webservers auf. Bei jeder HTTP-Anfrage an Port 8000 startet das Skript aus Listing 10.

Darin übernimmt die Funktion »send()« eine Fehlermeldung im Parameter »$error«. Die folgende Zeile setzt den HTTP-Header Content Type auf das Format JSON. Zeile 5 nutzt die PHP-Core-Funktion »json_encode()«, um das übergebene Feld JSON-artig formatiert auszugeben.

Liegt keine Fehlermeldung vor, errechnet der Ausdruck »md5(uniqid(rand()))« eine virtuelle Session-ID zum gleichnamigen Schlüsselwert. Andernfalls bleibt der Wert leer, und »error« gibt die Fehlerursache an.

Die Zeilen 9 bis 17 machen den Hauptteil des Programms aus. So entpackt und speichert Zeile 9 das Feld mit den Eingabeparametern aus dem Körper der HTTP-Anfrage. Als Speicherformat dient das JSON-Format in der Variablen »input«.

Den Körper der Anfrage liest der File-Designator »php://input« aus. Fehlen in der Anfrage der Parameter »username« oder »password«, beendet Zeile 17 die Anfrage mit der Fehlermeldung »Bad Request«. Weist einer der beiden Parameter in Zeile 11 einen falschen Wert auf, quittiert Zeile 14 die Anfrage mit der Fehlermeldung »Bad Credentials« für eine falsche Benutzereingabe.

Klappt alles wie gewünscht, initialisiert Zeile 12 den Pseudonummerngenerator von PHP mithilfe der Server-Zeit, bevor Zeile 13 über die Funktion »send()« eine Session-ID ausliefert.

Listing 10

src/app/router.php

/* Der Login-Service wird emuliert und lässt sich später gegen einen beliebigen Dienst austauschen */
<?php
function send($error=null) {
  header('Content-Type: application/json; charset=utf-9');
  echo json_encode(['sessionid' => $error?null:md5(uniqid(rand())), 'error' => $error]);
  exit(0);
}
$input = json_decode(file_get_contents('php://input'), TRUE);
if (array_key_exists('username', $input) && array_key_exists('password', $input)) {
  if ($input['username'] == 'silva' && $input['password'] == 'secret') {
    srand((double)microtime()*1000000);
    send();
  }
  send('Bad Credentials');
}
send('Bad Request');

Um den PHP-Emulator mit der Angular-Anwendung zu verheiraten, ohne mit dem CORS-Problem [12] aneinanderzugeraten, leitet Listing 11 XHTTP-Anfragen unter Angular an das Backend unter Port 8000 weiter. Das gelingt, indem der Entwickler den Code aus Listing 11 in der Datei »proxy.conf.json« im Projektverzeichnis (»login-master/src/app/«) speichert.

Listing 11

src/app/proxy.conf.json

{
  "/login": {
    "target": "http://localhost:8000",
    "secure": false
  }
}

Es fehlen noch die beiden CSS-Dateien, auf die der Artikel nicht im Detail eingeht. Sie heißen »app.component.css« und »login.component.css«, warten auf dem Listing-Server des Linux-Magazins [13] und gehören in die Verzeichnisse »src/app/« respektive »src/app/login/«.

Um die fertige Login-Seite zu starten, ruft der Entwickler nun zunächst wie oben beschrieben die PHP-Datei im Projektverzeichnis auf, um dann am selben Ort die Entwicklungsumgebung mit »ng serve –proxy-config proxy.conf.json« zu starten.

Bit

Um die Component »LoginComponent« mit anderen Apps beziehungsweise der Community zu teilen, kommt abschließend Bit zum Einsatz. Will der Entwickler den Cloudspeicher von Bit nutzen, benötigt er zunächst einen Account unter »https://bit.dev«.

Bevor er dann die Component hochlädt, erzeugt er eine eigene Collection. Sie speichert Komponenten themenspezifisch. Zum Anlegen wählt er aus dem Hauptmenü der Webseite rechts oben neben seinem Profil den Unterpunkt Collection aus dem Menü New. Hat er einen Namen eingegeben, generiert ein Klick auf den Button Create die zunächst leere Collection. Die Option Public ist praktischerweise voreingestellt.

Ab jetzt geht es in der Shell direkt unter dem Projektverzeichnis weiter. Zunächst installiert Nodes Package Manager Npm das Bit-Tool mit folgendem Befehl:

$ sudo npm install bit-bin -g

Anschließend meldet sich der Nutzer über »bit login« bei dem Werkzeug an. Praktischerweise erkennt Bit die soeben im Browser begonnene Session und leitet die Anmeldung über Bit ohne weitere Dateneingabe daraus ab. Die Befehle »bit config« und »bit init« bereiten Bit auf das Projekt vor.

Als nächstes markiert – oder besser gesagt trackt – der Anwender die Dateien, die zur Komponente gehören, mit folgendem Befehl:

$ bit add src/app/login --main src/app/login/login.module.ts

Die Angabe des Pfads nach »main« folgt dabei der Empfehlung von Bits Angular-Tutorial [11]. Intern analysiert der im Folgenden verwendete Compiler, den Bit höchstselbst mit dem Befehl »bit import bit.envs/compilers/angular –compiler« importiert, alle bestehenden Abhängigkeiten anhand der »Main«-Datei. »bit compile« erstellt letztendlich aus allen getrackten Dateien ein Node-Paket. Dabei konvertiert es die Typescript-Dateien nach Javascript. Die Ausgabe des Compilers begutachtet der Benutzer im Unterordner »main/« des Projektverzeichnisses.

Der Befehl »git tag –all 1.0.0« weist dem Kompilat die Versionsnummer 1.0.0 zu. Das Hochladen in die Collection auf den Server von Bit erledigt der Exportbefehl »bit export Account.login«. Anstelle von Account trägt der Entwickler den Namen des eigenen Zielaccounts ein; »login« bezieht sich auf die zu verwendende Collection als Speicherort.

Im bordeigenen Browser von Elementary OS sieht dann die Komponente unter Bit wie in Abbildung 6 gezeigt aus. Dabei ist bereits Version 1.0.5 der Komponente »login« zu sehen.

<a href="#artRef-f6">Abbildung 6</a>: Die Login-Komponente in der Ansicht von Bit.

Abbildung 6: Die Login-Komponente in der Ansicht von Bit.

Npm als Manager

Um das Bits-Repository einzubinden und seine Funktionstüchtigkeit zu testen, fügt der Entwickler es dem Quellverzeichnis von Npm und oder Yarn hinzu:

$ npm config set '@bit:registry' https://node.bit.dev

Der im Verzeichnis »src/app/« ausgeführte Befehl »npm install @bit/Account.login.login« installiert die Komponente am Ende wie ein gewöhnliches Node-Paket. Die Pfadangabe »@bit/Account.login.login« kopiert der Entwickler bei Bedarf von der Webseite der Komponente (Abbildung 6). Um das Ganze am Ende zu testen, wechselt er in Listing 2 und Listing 9 die auskommentierten Zeilen gegen ihre nicht auskommentierten Gegenstücke aus.

Bit bietet nicht nur die Möglichkeit, Pakete aus einer App zu extrahieren und über Npm zu verteilen. Es eignet sich auch zum verteilten Entwickeln von Components (Abbildung 7). Dazu importiert der Befehl »bit import Account.login/login« zunächst den Quellcode des Pakets in den lokalen Scope des Projektverzeichnisses – im Fall der Beispielanwendung also Typescript. Anfallende Änderungen auf der Seite von Bit reimportiert der Entwickler gegebenenfalls oder integriert sie ähnlich wie unter Git.

<a href="#artRef-f7">Abbildung 7</a>: Bits Import-Mechanismus verwendet intern einen lokalen Scope als Zwischenspeicher, um einen Merge-Vorgang umzusetzen. Quelle: Bit.dev

Abbildung 7: Bits Import-Mechanismus verwendet intern einen lokalen Scope als Zwischenspeicher, um einen Merge-Vorgang umzusetzen. Quelle: Bit.dev

Fazit

Mussten Entwickler Components bislang per Copy & Paste, via Git oder über Subversion in den lokalen Entwicklungskontext einbinden, schraubt Bit diesen Aufwand auf die Installation eines Node-Pakets herunter. Zugleich vereinfacht Bit es, die Funktionalität dezentral weiterzuentwickeln.

Von den dabei anfallenden Komponenten profitieren nicht nur die drei großen Frameworks, sondern auch Microservices. Ob Bit den Stellenwert eines Quasi-Standards erreicht, über den sich auch Components wie Bootstrap laden lassen, wird aber erst die Zukunft zeigen. (kki/jlu)

Infos

  1. Bit-Webseite: https://bit.dev

  2. Angular: https://angular.io

  3. React: https://reactjs.org

  4. Vue: https://vuejs.org

  5. Git: https://git-scm.com

  6. Preismodell von Bit: https://bit.dev/pricing

  7. Webentwicklung: Andreas Möller, “Teile fürs Web”, LM 01/2019, S. 92, https://www.linux-magazin.de/41880

  8. Rxjs: https://www.npmjs.com/package/rxjs-compat

  9. Meteor: Andreas Möller, “Flotte Schnuppe”, LM 03/2014, S. 102, https://www.linux-magazin.de/31620

  10. Observables in Angular: https://angular.io/guide/observables

  11. Tutorial zu Angular und Bit: https://docs.bit.dev/docs/tutorials/bit-angular-tutorial

  12. CORS: https://enable-cors.org/index.html

  13. Alle Dateien zum Artikel: http://www.linux-magazin.de/static/listings/magazin/2020/04/bit

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 7 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben