The post Web-Crawling mit JavaScript leicht gemacht appeared first on JavaScript & Angular Days.
]]>Auf den ersten Blick scheint eine der populären, gehosteten GUIs mit einem extrem leistungsfähigen LLM im Hintergrund die Lösung für das Problem zu sein. Beispiele hierfür sind ChatGPT, Gemini oder Claude. Ein Vorteil dieser Plattformen ist, dass die Modelle standardmäßig auf das Internet zugreifen können und somit selbstständig recherchieren können. Doch das ernüchternde Ergebnis liegt schnell vor. Weder ChatGPT noch Claude oder Gemini beantworten die Frage korrekt. Vielmehr ist das Ergebnis entweder nur ein Bruchteil (bei ChatGPT) oder schlichtweg falsch (bei Gemini), wobei Gemini ungefragt ein fotorealistisches Bild der Konferenzatmosphäre generiert.
Möglicherweise können die Modelle die Informationen der Webseite nicht richtig abrufen. Dieses Problem ließe sich jedoch lösen, indem man dem Modell die entsprechende Seite zur Verfügung stellt. Doch wenn der Code der Seite einfach in das Eingabefeld des Prompts kopiert wird, quittieren die Web-UIs der Modelle das mit einer Fehlermeldung, da die Eingabe zu lang ist. Also bleibt nur, den HTML-Code in Form einer Datei hochzuladen. Dann werden die Ergebnisse schon deutlich besser.
Von Nachteil ist dabei nur, dass wir den Quellcode der Seite jedes Mal lokal zwischenspeichern und dem Modell zur Verfügung stellen müssten. Mit ein wenig Code lässt sich dieser Prozess jedoch deutlich vereinfachen.
Die Basis unserer eleganteren Lösung bildet Node.js. Als Schnittstelle soll die Kommandozeile des Systems dienen. Die Applikation benötigt zwei Informationen: die Webseite, für die wir uns interessieren, und unseren Prompt, also die Fragestellung. Die Applikation besteht aus drei Teilen: einer CLI-Schnittstelle für die Ein- und Ausgabe, einer Funktion zum Herunterladen des Webseiteninhalts und einer weiteren Funktion zum Senden der Informationen an ein LLM.
Eine solche Applikation kann auf verschiedene Weise aufgebaut werden. Die einfachste Methode besteht darin, die drei Kernelemente jeweils in einer Funktion zu kapseln und die Funktionen nacheinander aufzurufen. Eine etwas elegantere Variante ist der Einsatz von LangChain als LLM-Framework. LangChain wurde genau für diese Art von Applikation entwickelt. Die einzelnen Teile bilden dabei eine Kette, an deren Ende die Antwort des LLMs steht.
Als Erstes wird die Applikation mit npm init -y initialisiert und in der erzeugten package.json-Datei der type auf module umgestellt, um das ECMAScript-Modulsystem zu aktivieren. Anschließend können die erforderlichen LangChain-Module initialisiert werden:
npm install @langchain/core @langchain/ollama
Für die Interaktion auf der Kommandozeile stellt Node.js das Readline-Modul zur Verfügung. Die createInterface-Funktion erzeugt eine neue Schnittstelle für zwei Streams. Normalerweise sind das die Standardeingabe und die Standardausgabe. Über dieses Schnittstellenobjekt können wir dann mit der question-Methode Eingaben auf der Kommandozeile abfragen. Node.js bietet für dieses Modul mittlerweile auch eine Promise-basierte Variante, die in Kombination mit async/await zu gut lesbarem und kompaktem Code führt. Die beiden Eingaben kapseln wir in ein RunnableLambda, eine Abstraktion von LangChain für Funktionen, die es ermöglicht, diese Funktionen in eine LangChain-Kette zu integrieren.
Der Rückgabewert der Funktion ist ein Objekt, das den eingegebenen URL und die Frage zur Webseite, also den Prompt, enthält. Dieses Objekt stellt LangChain dem nächsten Element in der Kette zur Verfügung. Testen können wir diese Funktion, indem wir die invoke-Methode des Objekts aufrufen. In Listing 1 ist das mit dem Code des ersten Kettenglieds zu sehen.
Listing 1: Eingaben über die Kommandozeile
import readline from 'node:readline/promises';
import { RunnableLambda } from '@langchain/core/runnables';
export const cliInput = new RunnableLambda({
async func() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const url = await rl.question('Gib die URL ein: ');
const question = await rl.question('Welche Frage möchtest du stellen? ');
rl.close();
return { url, question };
},
});
// test:
// const result = await cliInput.invoke();
// console.log(result);
Für den Download einer einfachen Webseite kann die fetch-Funktion von Node.js verwendet werden. Alternativ gibt es Pakete wie crawler, die über den einfachen Download von HTML-Dokumenten hinaus noch zusätzliche Komfortfeatures bieten, beispielsweise die Verarbeitung mehrerer Seiten oder die Unterstützung von Rate Limits, Timeouts und Retries. Für unser Beispiel, die Programmseite der JavaScript Days, hilft uns jedoch weder die fetch-Funktion noch das crawler-Paket, da die Webseite die wichtigsten Inhaltselemente asynchron mit JavaScript nachlädt. Das Problem der meisten Web-Crawler ist, dass sie kein JavaScript unterstützen und nur die initiale HTML-Struktur zur Verfügung stellen. Diese hilft jedoch nicht bei der Beantwortung unserer Frage. Hier müssen wir also etwas tiefer in die Trickkiste greifen.
Eine mögliche Lösung heißt Puppeteer. Im Kern ist dies ein Headless-Browser, also ein Browser ohne grafische Oberfläche. Er ist in der Lage, JavaScript auszuführen, sodass auch dynamische Inhalte konsumiert werden können.
Puppeteer wird mit dem Kommando npm install puppeteer installiert. Da mit dem Paket auch ein vollwertiger Chrome-Browser heruntergeladen wird, kann der Installationsprozess je nach System und Internetanbindung etwas länger dauern.
Die Verarbeitung von Webseiten mit Puppeteer erfolgt in mehreren Schritten: Zunächst starten wir den Browser im Headless-Modus mit der launch-Methode von Puppeteer. Anschließend erzeugen wir mit der newPage-Methode ein Objekt, das eine Seite repräsentiert, und wechseln mit der goto-Methode zum gewünschten URL. Im letzten Schritt erhalten wir den HTML-Code der Seite mit der evaluate-Methode des Page-Objekts.
Der Workflow mit Puppeteer wird für die Ausführung in der LangChain-Kette in ein RunnableLambda gekapselt. Der Rückgabewert enthält den Inhalt der Seite sowie die ursprünglich übergebene Frage, die hier nur durchgereicht wird. Den Code können wir wiederum mit einem Aufruf der invoke-Methode des RunnableLambda-Objekts testen (Listing 2).
Listing 2: Download einer dynamischen Webseite mit Puppeteer
import { RunnableLambda } from '@langchain/core/runnables';
import puppeteer from 'puppeteer';
export const fetchPage = new RunnableLambda({
async func({ url, question }) {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2' });
const html = await page.evaluate(() => document.body.innerText);
await browser.close();
return { content: html, question };
},
});
// test:
// const result = await fetchPage.invoke({
// url: 'https://de.wikipedia.org/wiki/Kuh',
// question: 'Nenne mir eine interessante Tatsache über Kühe.',
// });
// console.log(result);
Im letzten Schritt fehlt nun noch die Interaktion mit dem Modell sowie das Zusammenfügen der einzelnen Teile zu einer vollwertigen Kette. Diese besteht aus mehreren Elementen:
Konsoleneingabe: Am Anfang der Interaktion steht die Konsoleneingabe, die wir in der cliInput-Funktion gekapselt haben. Als erstes Element in der Kette benötigt diese Funktion keine Argumente und liefert als Ausgaben den URL und die ursprüngliche Frage des Users.
Download der Webseite: Der URL aus der Eingabe ist der wichtigste Input für den Download mit Puppeteer. Die entsprechende Logik haben wir in der fetchPage-Funktion gekapselt und in eine eigene Datei ausgelagert. Die Userfrage wird an das nächste Element der Kette weitergereicht.
Prompt-Template: Mit dem Prompt-Template können wir die Informationen, die wir an das Modell übergeben, strukturieren. Das verbessert die Ergebnisse deutlich. Neben dem verwendeten Modell ist dieses Template eine der wichtigsten Stellschrauben. Experimentieren Sie mit der Formulierung und der Struktur, um die Ergebnisse zu beeinflussen.
Modellinteraktion: Das Herzstück der Applikation ist die Kommunikation mit dem Modell. Die Wahl des passenden Modells ist entscheidend für die Qualität des Ergebnisses. Im Beispiel in Listing 3 kommt mit Llama 3.2:1b ein vergleichsweise kleines Modell zum Einsatz. Der Vorteil ist, dass dieses Modell auf einem einigermaßen gut ausgestatteten Rechner problemlos ausgeführt werden kann. Das Ergebnis ist jedoch in der Regel nicht optimal. Wenn wir ein größeres Modell wie das Granite4-Small-Modell mit seinen 32 Milliarden Parametern nutzen, werden die Ergebnisse deutlich besser. Eine weitere Optimierung besteht in der Verwendung eines kommerziellen Modells, wie beispielsweise GPT-5 von OpenAI. Um dieses zu nutzen, ersetzen Sie das Paket @langchain/ollama durch @langchain/openai, wählen das entsprechende Modell aus und hinterlegen Ihren API-Key. Dabei ist jedoch zu beachten, dass durch die Kommunikation Kosten auf Basis der Input- und Output-Tokens entstehen. Zu den Inputtokens zählt in diesem Fall der gesamte Quellcode, den unser Skript im Zuge der Kette heruntergeladen hat. Zwar sprechen wir hier zunächst über verschwindend geringe Centbeträge. Wird das Skript jedoch sehr häufig ausgeführt, können durchaus nennenswerte Kosten entstehen.
Parsen der Antwort: Der StringOutputParser von LangChain stellt sicher, dass die Antwort des Modells ohne zusätzliche Metainformationen zur Verfügung steht, sodass sie direkt ausgegeben werden kann.
Listing 3 zeigt den Quellcode der Applikation.
Listing 3: Integration der einzelnen Bestandteile der LangChain-Kette
import { RunnableSequence, RunnableLambda } from '@langchain/core/runnables';
import { PromptTemplate } from '@langchain/core/prompts';
import { Ollama } from '@langchain/ollama';
import { StringOutputParser } from '@langchain/core/output_parsers';
import { fetchPage } from './fetchPage.js';
import { cliInput } from './cliInput.js';
const prompt = new PromptTemplate({
template: `
You are a helpful assistant.
Here is the content of a web page:
---
{content}
---
Answer this question based on the page content:
{question}
`,
inputVariables: ['content', 'question'],
});
const model = new Ollama({
model: 'llama3.2:1b',
});
const parser = new StringOutputParser();
const chain = RunnableSequence.from([
cliInput,
fetchPage,
prompt,
model,
parser,
]);
const answer = await chain.invoke();
console.log(answer);
Die Applikation können wir nun mit Node.js auf der Kommandozeile ausführen und erhalten dann die Antworten auf unsere Frage zu einer Webseite.
Brancheninsights im Newsletter erhalten:
In diesem Beispiel stellen wir eine konkrete Frage zu einer Webseite und servieren dem LLM die Antwort praktisch auf einem Silbertablett. Doch es bereitet gerade kleineren Modellen erhebliche Schwierigkeiten, die gewünschte Information zu extrahieren und zu präsentieren.
Allerdings gibt es in diesem Prozess eine Reihe von Stellschrauben, an denen wir drehen können. Zunächst wäre da das Prompt-Template. Je nachdem, wie es formuliert ist, kann es das Ergebnis erheblich beeinflussen. Auch die verwendete Sprache kann Bedeutung haben. Die meisten Modelle arbeiten beispielsweise mit Englisch deutlich besser als mit Deutsch. Zudem können wir, wie schon erwähnt, das Modell gegen ein leistungsfähigeres ersetzen und damit die Ergebnisqualität nochmals verbessern.
Aktuell müssen wir bei sehr spezifischen Problemstellungen also noch immer kreativ sein, um eine gute Lösung zu finden. Die technischen Möglichkeiten entwickeln sich in diesem Bereich jedoch rasant weiter.
JavaScript Web-Crawling beschreibt das automatische Laden und Auslesen von Webseiten mit Node.js. Im Artikel liegt der Fokus auf dynamischen Webseiten, die Inhalte per JavaScript nachladen, um diese Inhalte anschließend an Large Language Models (LLMs) weiterzugeben.
LLMs können Webseiteninhalte oft nicht zuverlässig selbst abrufen oder verstehen. Durch das lokale Herunterladen und Vorverarbeiten der Inhalte kann das Modell die relevanten Informationen direkt erhalten, was präzisere und vollständigere Antworten ermöglicht.
Verwendet werden Node.js als Laufzeitumgebung, Puppeteer als Headless-Browser für die Ausführung von JavaScript auf Webseiten und LangChain, um die Interaktion zwischen Eingaben, Webseiteninhalt und LLM zu strukturieren.
Puppeteer führt JavaScript in einem Headless-Browser aus. Dadurch können dynamische Inhalte, die erst clientseitig geladen werden, abgerufen werden – etwas, das klassische Web-Crawler, die nur statisches HTML herunterladen, nicht leisten können.
LangChain verbindet mehrere Schritte – CLI-Eingabe, Seiten-Download, Prompt-Erstellung, Modellinteraktion und Ausgabe – zu einer strukturierten Kette. Das macht die Anwendung modular, gut lesbar und leicht erweiterbar.
Die Methode ist besonders nützlich für JavaScript-Entwickler, Data Engineers und alle, die Webseiteninhalte mit KI analysieren oder abfragen möchten. Besonders relevant ist sie bei dynamischen Webseiten und automatisierten LLM-Prozessen.
Die Lösung dient dazu, konkrete Fragen zu Webseiten zu beantworten, strukturierte Informationen zu extrahieren, Forschungsaufgaben zu automatisieren oder Webseiteninhalte für KI-Analysen mit LLMs vorzubereiten.
The post Web-Crawling mit JavaScript leicht gemacht appeared first on JavaScript & Angular Days.
]]>The post LangChain Framework: So integrieren Sie LLMs in Ihre Anwendungen appeared first on JavaScript & Angular Days.
]]>Ein entscheidender Vorteil des AI-Booms für uns Softwareentwickler ist, dass sich Probleme herauskristallisieren, die in der gleichen oder ähnlichen Form überall auftreten. Die Integration eines KI-Modells ist ein typisches Beispiel. Die meisten Plattformen wie das OpenAI API, Google Gemini oder auch das lokale Ollama bieten HTTP-Schnittstellen, über die Sie mit einem LLM interagieren können. Zusätzlich dazu stellt Ihnen jeder Anbieter ein eigenes Paket zur Integration in Ihre Applikation zur Verfügung.
Das Problem an dieser Stelle ist jedoch, dass sich die Anbieter in den Details ihrer APIs unterscheiden. Das macht einen leichtgewichtigen Austausch der Modelle schwierig. Was für die Modelle gilt, gilt auch für andere Komponenten einer KI-Applikation. Wäre es da nicht schön, wenn es ein Framework gäbe, das Ihnen die Arbeit mit dem unübersichtlichen und schnell wachsenden KI-Ökosystem erleichtert? Das Framework, nach dem Sie suchen, heißt LangChain.
LangChain ist modular aufgebaut und adressiert die wichtigsten Aspekte einer AI-Applikation von der Anbindung von Modellen über den Umgang mit Prompts und Templates und der Anbindung von Vektordatenbanken bis hin zur Verarbeitung der Ausgabe. Dabei arbeitet das Framework, wie der Name schon andeutet, mit Ketten. Diese sind vergleichbar mit dem Konzept von Pipes, die Sie beispielsweise aus Unix-Shell oder der Rx-Bibliothek kennen. Die einzelnen Glieder der Kette haben jeweils eine Ein- und Ausgabe und können zu beliebig langen Ketten zusammengefügt werden.
Das LangChain-Framework existiert in zwei verschiedenen Varianten: Python und JavaScript bzw. TypeScript. Die grundlegende Architektur und die Konzepte sind in beiden Versionen die gleichen. Dieser Artikel verwendet für die konkreten Umsetzungsbeispiele die JavaScript-Variante.
Das wichtigste Paket des LangChain-Frameworks ist das Core-Paket. Es enthält die grundlegenden Strukturen für alle weiteren Komponenten. In der JavaScript-Variante trägt dieses Paket den Namen @langchain/core. Es enthält alle Basisklassen, wie beispielsweise BaseLLM, von der die konkreten Model-Klassen für die LLM-Integration ableiten. Außerdem definiert dieses Paket die LangChain Expression Language (LCEL), eine Schnittstelle, mit der Sie auf einfache Art die Ketten für Ihre Applikation definieren können. Das zweite Paket ist das langchain-Paket, das die Strukturen für Ketten, Agents und Retrieval Strategien beisteuert.
Zu diesen beiden Paketen kommt noch eine Vielzahl zusätzlicher Integrationspakete für die verschiedenen Bestandteile der Applikation. So können Sie das @langchain/openai-Paket nutzen, um das OpenAI API in Ihre Applikation einzubinden, oder Sie nutzen @langchain/pinecore, um die Pinecore-Vektordatenbank zu integrieren.
Für beide Pakete gilt, dass Sie sich problemlos sowohl für JavaScript als auch für TypeScript entscheiden können. LangChain liefert immer die aktuellen Typdefinitionen selbst aus, da das gesamte Framework in TypeScript umgesetzt ist. Die wichtigsten Bestandteile des Frameworks lernen Sie im Folgenden am konkreten Beispiel kennen und sehen dabei, wie die einzelnen Komponenten ineinandergreifen.
Wenn Sie von KI-Applikationen hören, kommt Ihnen wahrscheinlich sehr schnell die typische Chatbot-Anwendung in den Sinn. Und genau mit einer solchen wollen wir den Einstieg in LangChain beginnen. Das Beispiel ist vollständig lokal lauffähig und setzt dafür auf Ollama als Plattform, die ein Sprachmodell lokal ausführt. Als konkretes Modell kommt Llama in der Version 3.2 zum Einsatz. Neben den bereits vorgestellten Paketen benötigen Sie dafür noch das @langchain/ollama-Paket, um mit der Plattform zu kommunizieren.
Egal, ob Sie Ollama oder eine andere Plattform wie OpenAI nutzen, die HTTP-Schnittstellen sind in der Regel zustandslos. Das bedeutet, dass das Modell kein Gedächtnis hat, die Konversation nach der Antwort also sofort wieder vergisst. Dieses Problem können Sie lösen, indem Sie eine Nachrichtenhistorie integrieren. Auch für diesen Fall bietet Ihnen LangChain eine passende Struktur. Doch bevor es an die konkrete Umsetzung geht, werfen wir einen Blick auf die Komponenten, die Sie für die Umsetzung eines solchen Chatbots benötigen:
Prompt: Der Prompt ist die Nachricht, die Sie an das Sprachmodell senden, um eine Antwort zu erhalten.
Prompt-Template: Mit dem Prompt-Template können Sie zum Prompt noch weitere Strukturen hinzufügen, um dem Model zusätzlichen Kontext zu bieten.
Model: Das Model steht für das Sprachmodell, mit dem Ihre Applikation interagiert. Dieser Klasse übergeben Sie im Fall von Ollama das konkrete Modell, das Sie verwenden möchten, oder bei OpenAI zusätzlich noch Ihren API-Key.
OutputParser: Mit dem OutputParser können Sie die Antwort des Modells manipulieren und den Inhalt der Nachricht extrahieren.
Der Prompt ist die Eingabe eines Menschen. Sie kann in ein Prompt-Template eingefügt werden. Diese Eingabe wird an das Modell weitergegeben, das dann die Antwort erzeugt. Der OutputParser liefert Ihnen schließlich die Antwort des Modells. In Listing 1 sehen Sie den Code des Beispiels.
Listing 1: Chatbot mit Historie in LangChain
import { InMemoryChatMessageHistory } from '@langchain/core/chat_history';
import { AIMessage, HumanMessage } from '@langchain/core/messages';
import { StringOutputParser } from '@langchain/core/output_parsers';
import { ChatPromptTemplate } from '@langchain/core/prompts';
import {
RunnableSequence,
RunnableWithMessageHistory,
} from '@langchain/core/runnables';
import { ChatOllama } from '@langchain/ollama';
const messageHistories: Record<string, InMemoryChatMessageHistory> = {};
const prompt = ChatPromptTemplate.fromMessages([
[
'system',
'You are a helpful assistant who remembers all details the user shares with you.',
],
['placeholder', '{chat_history}'],
['human', '{input}'],
]);
const model = new ChatOllama({
model: 'llama3.2',
});
const parser = new StringOutputParser();
const chain = RunnableSequence.from([prompt, model, parser]);
const withMessageHistory = new RunnableWithMessageHistory({
runnable: chain,
getMessageHistory: async (sessionId) => {
if (messageHistories[sessionId] === undefined) {
messageHistories[sessionId] = new InMemoryChatMessageHistory();
}
return messageHistories[sessionId];
},
inputMessagesKey: 'input',
historyMessagesKey: 'chat_history',
});
const config = {
configurable: {
sessionId: 'theSession',
},
};
const messages = [
new HumanMessage('Hey AI, I need help with my JavaScript project.'),
new AIMessage("Sure! What's the project about, and what's the issue?"),
];
const stream = await withMessageHistory.stream(
{
chat_history: messages,
input:
"I'm building a task list app, but the date filtering isn't working right.",
},
config
);
for await (const chunk of stream) {
console.log(chunk);
}
Die erste Komponente der Implementierung ist das Prompt-Template. Dieses erstellen Sie mit der fromMessages-Methode der ChatPromptTemplate-Klasse. Ihr übergeben Sie ein Array aus Nachrichten, wobei jede Nachricht aus einem Tupel aus Nachrichtentyp und Nachricht gebildet wird. Es gibt drei verschiedene Typen von Nachrichten: system steht für den System-Prompt, mit dem Sie das Verhalten des Systems beeinflussen können. Den Typ ai nutzen Sie, um eine Antwort des Modells zu speichern und human steht schließlich für eine menschliche Eingabe. Eine Sonderrolle nimmt der Typ placeholder ein. Hier platzieren Sie später die Einträge der Chat-Historie.
Als Modell nutzt das Beispiel eine Instanz der ChatOllama. Hier wird nur der Wert für das zu verwendende Modell überschrieben, sodass das Llama-3.2-Modell verwendet wird. Diese Konfiguration sorgt dafür, dass die Applikation mit dem Ollama API auf http://localhost:11434 kommuniziert. Als Output Parser kommt im Beispiel der StringOutputParser zum Einsatz. Dieser nimmt ein Objekt vom Typ AIMessage entgegen und extrahiert den Textinhalt, da das ursprüngliche Objekt neben diesem Inhalt noch eine ganze Reihe von Metainformationen enthält, die für das Beispiel nicht relevant sind.
Prompt-Template, Modell und Output Parser fasst die from-Methode der RunnableSequence-Klasse zu einer Kette zusammen. Diese können Sie bereits verwenden, indem Sie die invoke– oder stream-Methode des zurückgegebenen Objekts verwenden. In diesem Fall hat Ihre Applikation jedoch noch kein Gedächtnis. Dieses erhält sie erst mit dem withMessageHistory-Objekt. Dafür instanziieren Sie die RunnableWithMessageHistory-Klasse. Dem Konstruktor übergeben Sie ein Objekt, das die Kette mit Prompt, Modell und Parser in der runnable-Eigenschaft enthält. Außerdem definieren Sie die getMessageHistory-Methode. Sie arbeitet mit einer SessionId, um verschiedene User-Sessions verwalten zu können. Jede Session erhält ihr eigenes InMemoryChatMessageHistory-Objekt, das im globalen messageHistories-Objekt festgehalten wird. Außerdem definieren Sie mit inputMessagesKey und historyMessagesKey zwei Platzhalter, die für den User-Prompt und die Chat-Historie stehen und auf die Sie im Prompt-Template zugreifen können.
Mit dem withMessageHistory-Objekt können Sie mit dem Modell interagieren. Im Beispielcode sehen Sie zwei verschiedene Arten: die stream– und die invoke-Methode. Die stream-Methode stellt Ihnen die Antworten als Stream aus Tokens zur Verfügung. Das hat den Vorteil, dass Sie mit der Anzeige der Ergebnisse schon beginnen können, bevor die Antwort vollständig vorliegt. Die einfachste Variante, die einzelnen Tokens zu konsumieren, ist die Verwendung der asynchronen for-of-Schleife. Im Gegensatz dazu erhalten Sie das Ergebnis bei der invoke-Methode erst, wenn das Modell die Antwort vollständig generiert hat. Egal, welche der beiden Methoden Sie nutzen, in jedem Fall übergeben Sie ein Objekt mit dem Prompt und optional können Sie zusätzlich noch eine Chat-Historie angeben. Im zweiten Argument geben Sie die Session-ID für die korrekte Zuordnung der Chat-Session an. Diese Implementierung können Sie im nächsten Schritt in ein beliebiges Web Application Framework, wie beispielsweise Express oder Nest, integrieren und eine Clientapplikation anbinden.
Einen Aspekt der Chat-Historie müssen Sie jedoch noch beachten: Die Historie darf nicht beliebig groß werden. Die LLMs, mit denen Sie hier arbeiten, verfügen über einen Kontext. Das ist Text, den Sie dem Modell für eine Interaktion übergeben können, um passende Antworten zu erzeugen. Dadurch kann beispielsweise der bisherige Gesprächsverlauf zur Verfügung gestellt werden. Die Größe des Kontexts ist jedoch limitiert. Sie liegt bei Llama 3.2 beispielsweise bei 128 000 Tokens. Sie sollten darauf achten, dass Ihre Konversation diese Größe nicht überschreitet, und wenn dies der Fall ist, entweder die Historie entsprechend limitieren und Ihre User darüber in Kenntnis setzen oder ältere Nachrichten einfach kontrolliert abschneiden. In diesem Fall kommt es zu Informationsverlust.
Neben dem typischen Chatbot können Sie mit LangChain noch viele weitere spannende Probleme lösen. In den folgenden Abschnitten erfahren Sie, wie Sie LangChain nutzen können, um sich Texte zusammenfassen zu lassen.
Grundsätzlich können Sie einem LLM einen beliebigen Text übergeben und es mit einem Prompt auffordern, ihn für sich zusammenfassen zu lassen. Die einzige Limitierung ist hier das Kontextfenster. Bei modernen LLMs liegt seine Größe normalerweise bei 128 000 Tokens. Hier fangen die Probleme aber schon an: Ist das viel oder wenig? Gehen wir von einer normalen 12-Punkt-Schriftart und einem einfachen Zeilenabstand und etwas Seitenrändern aus, passen etwa 500 Wörter auf eine DIN-A4-Seite. Allerdings können Sie Wörter nicht einfach in Tokens umrechnen, sondern sollten eher von 0,75 Wörtern pro Token ausgehen. Damit ergeben die 128 000 Tokens etwa 190 bis 200 Seiten, also schon eine ganze Menge.
Irgendwann stoßen Sie jedoch selbst mit einem großen Kontext an eine Grenze, und auch sonst können Sie es einem LLM mit einem Trick leichter machen, große Mengen von Text zu verarbeiten. Dieser besteht darin, dass Sie den Text in kleinere Einheiten herunterbrechen und diese verarbeiten lassen. LangChain kann Sie dabei mit der SummarizationChain unterstützten. Der grundlegende Aufbau sieht dabei folgendermaßen aus:
Das gesamte Dokument laden
Unterteilen des Dokuments in kleinere Bestandteile
Erzeugen der SummarizationChain
Ausführen der Summarization Chain
Wie die Implementierung konkret im Code aussieht, sehen Sie in Listing 2.
Listing 2: Textzusammenfassung mit Langchain
import { Ollama } from '@langchain/ollama';
import { loadSummarizationChain } from 'langchain/chains';
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
import * as fs from 'fs';
import { PDFLoader } from '@langchain/community/document_loaders/fs/pdf';
const filePath = './docs/Ch2.pdf';
const loader = new PDFLoader(filePath);
const pdf = await loader.load();
const text = pdf.map((doc) => doc.pageContent).join('\n\n');
const model = new Ollama({ model: 'llama3.2' });
const textSplitter = new RecursiveCharacterTextSplitter({ chunkSize: 1000 });
const docs = await textSplitter.createDocuments([text]);
const chain = loadSummarizationChain(model);
const res = await chain.invoke({
input_documents: docs,
});
console.log({ res });
Für das Beispiel gehen wir von einer PDF-Datei als Eingabe aus. Diese müssen Sie zunächst in Text umwandeln. Da es sich hierbei um ein Problem handelt, das sehr häufig auftritt, gibt es in der LangChain-Community ein Paket, das eine Lösung für das Lesen von PDF-Dateien bietet. Sie finden es unter dem Namen PDFLoader im @langchain/community-Paket. Zusätzlich zu diesem Paket müssen Sie noch das pdf-parse-Paket installieren. Anschließend können Sie den PDFLoader mit dem Pfad zur PDF-Datei instanziieren und das Dokument mit der load-Methode des Loader laden.
Die wichtigsten Komponenten neben der SummarizationChain sind in diesem Beispiel das Modell, das auch in diesem Fall wieder Llama 3.2 ist, und der TextSplitter. Der TextSplitter zerlegt, wie der Name vermuten lässt, den Text in annähernd gleichgroße Teile. Der RecursiveCharacterTextSplitter ist am besten für generische Texte geeignet und zerlegt den Text anhand einer bestimmten Zeichensequenz. Bei der Instanziierung geben Sie die gewünschte Blockgröße, hier 1000 Zeichen, an. Als Trennzeichen für die Zerlegung nutzt dieser TextSplitter die Zeichenfolgen „\n\n“, „\n“ und „ “. Zuerst versucht das Werkzeug einen 1000-Zeichen-Block anhand der zwei Zeilenumbrüche abzutrennen. Ist das nicht möglich, wird das Gleiche mit einem Zeilenumbruch versucht und schließlich beim nächstgelegenen Leerzeichen. Damit soll der TextSplitter sicherstellen, dass möglichst zusammengehörige Textblöcke erzeugt werden.
Der createDocuments-Methode können Sie einen, aber auch mehrere Texte in Form eines String-Arrays übergeben. Die daraus resultierenden Dokumente nutzen Sie beim invoke-Aufruf der SummarizationChain als Eingabe. Um diese Kette zu erstellen, nutzen Sie die loadSummarizationChain-Funktion. Dieser müssen Sie beim Aufruf lediglich das Modell übergeben. Führen Sie den Code aus, erhalten Sie die Zusammenfassung des übergebenen Texts.
Sie können noch weiter Einfluss auf die Zusammenfassung nehmen, indem Sie beim Aufruf der loadSummarizationChain-Funktion ein Konfigurationsobjekt übergeben. Hier können Sie über die type-Eigenschaft den Typ der Zusammenfassung festlegen. Die SummarizationChain unterstützt hier drei verschiedene Werte:
map_reduce: Dieser Typ arbeitet nach dem Map-Reduce-Muster und erzeugt für jeden Textblock eine Zusammenfassung. Diese werden dann zu einer Gesamtzusammenfassung reduziert. Der map_reduce-Typ ist der Standard von LangChain, wenn Sie keinen Typ angeben.
refine: Mit diesem Typ bildet die SummarizationChain eine initiale Zusammenfassung über den ersten Block und verfeinert sie mit allen folgenden Blöcken, bis am Ende eine Gesamtzusammenfassung entstanden ist. Diese Strategie eignet sich besonders für sehr lange Texte.
stuff: Der stuff-Typ ist die einfachste Art der Zusammenfassung. Alle Textblöcke werden zu einem großen Block zusammengefasst und eine Zusammenfassung darüber gebildet. Diese Strategie ist nicht für Texte geeignet, die länger als das Kontextfenster sind.
Je nachdem, welchen Typ Sie auswählen, können Sie zusätzlich noch Prompt-Templates für die Zusammenfassung angeben und so die Zusammenfassung anpassen. Die Qualität des Ergebnisses hängt sowohl vom verwendeten Modell als auch vom gewählten Typ und zu einem kleinen Teil von den Prompt-Templates ab. Sie sollten mit verschiedenen Kombinationen experimentieren, um ein optimales Ergebnis für Ihre Anforderungen zu erreichen.
Die bisherigen beiden Beispiele waren auf Ihr lokales System beschränkt. LangChain ist jedoch nicht nur auf die Arbeit mit lokalen Interaktionen und Datenquellen beschränkt, wie Sie im nächsten Beispiel sehen werden.
Sie können LangChain auch dazu verwenden, Webseiten einzulesen und Fragen dazu zu stellen. Sie benötigen dazu ein Modell, Embeddings und das WebBrowser-Werkzeug. Mit dem WebBrowser ist natürlich nicht der Browser auf Ihrem System gemeint, sondern eine Klasse aus dem tools-Modul von LangChain, das die gleiche Rolle wie ein Browser spielt. In Listing 3 sehen Sie, wie Sie mit Hilfe von LangChain und Wikipedia herausfinden können, wann JavaScript erfunden wurde.
Listing 3: Webseiten mit LangChain auswerten
import { WebBrowser } from 'langchain/tools/webbrowser';
import { Ollama, OllamaEmbeddings } from '@langchain/ollama';
const model = new Ollama({
model: 'llama3.2',
});
const embeddings = new OllamaEmbeddings({
model: 'llama3.2',
});
const browser = new WebBrowser({ model, embeddings });
const result = await browser.invoke(
`"https://en.wikipedia.org/wiki/JavaScript", "Wann wurde JavaScript erfunden?"`
);
console.log(result);
Die Grundlage des Beispiels bildet wieder ein Modell wie das Llama 3.2. Außerdem benötigen Sie ein Embeddings-Modell. Dieses erzeugt eine Vektorrepräsentation aus einem Stück Text, die Sie mit LangChain weiterverarbeiten können. LangChain bietet Ihnen eine ganze Reihe von Embeddings wie OpenAI, MistralAI, Nomic oder die im Beispiel verwendeten Ollama Embeddings. Geben Sie nichts weiter an, nutzt die OllamaEmbeddings-Klasse das mxbai-embed-large-Modell. Das Problem mit diesem Modell ist, dass es einen verhältnismäßig kleinen Kontext hat, was schon bei einfachen Webseiten dazu führen kann, dass Sie die Fehlermeldung „Input length exceeds maximum context length“ erhalten. Die einfachste Lösung für dieses Problem ist, dass Sie ein anderes Modell angeben. Mit dem Modell und den Embeddings können Sie schließlich eine Instanz der WebBrowser-Klasse erzeugen. Diese stellt Ihnen die invoke-Methode zur Verfügung, die Sie mit einer Zeichenkette aufrufen. Zuerst geben Sie den URL der Webseite an, die Sie analysieren möchten, in unserem Beispiel ist das die Wikipedia-Seite über JavaScript. Anschließend formulieren Sie einen Prompt. Das WebBrowser-Werkzeug nutzt Axios, um eine Anfrage an den URL zu stellen und wertet die Antwort mit Hilfe des HTML-Parsers cheerio aus. Diese Daten wandelt das Embeddings-Modell in eine Vektorrepräsentation um und stellt sie dem Modell als Kontext zur Verfügung. Führen Sie diesen Code aus, erhalten Sie die Antwort, dass Brendan Eich JavaScript 1995 erfunden hat und zusätzlich dazu noch eine Reihe weiterführender Links.
Brancheninsights im Newsletter erhalten:
Neben dem WebBrowser gibt es noch einige weitere Tools für Langchain. Eine Kategorie, die für Applikationen besonders spannend sein können, sind Suchmaschinenschnittstellen, mit denen Sie Ihrer Applikation das Tor zum Internet noch weiter öffnen können. Beim WebBrowser müssen Sie einen einzelnen URL angeben. Mit den Suchmaschinenintegrationen können Sie eine Suchmaschine befragen und erhalten aktuelle Informationen aus dem Internet, die Sie in Ihrer LangChain-Applikation weiterverwenden können. Beispiele für solche Suchmaschinenintegrationen sind die DuckDuckGoSearch oder die TavilySearch. Im folgenden Beispiel sehen Sie, wie Sie die TavilySearch nutzen können. Im einfachsten Fall erzeugen Sie eine Instanz des Werkzeugs und rufen die invoke-Methode mit Ihrer Suchanfrage auf. In Listing 4 sehen Sie jedoch, wie Sie es in eine Kette einbinden können.
Listing 4: Integration der Web-Suche in eine LangChain-Applikation
import { HumanMessage } from '@langchain/core/messages';
import { ChatPromptTemplate } from '@langchain/core/prompts';
import { RunnableLambda } from '@langchain/core/runnables';
import { TavilySearchResults } from '@langchain/community/tools/tavily_search';
import { ChatOllama } from '@langchain/ollama';
const tool = new TavilySearchResults({
maxResults: 2,
});
const model = new ChatOllama({
model: 'llama3.2',
});
const llmWithTools = model.bindTools([tool]);
const prompt = ChatPromptTemplate.fromMessages([
['system', 'You are a helpful assistant.'],
[
'system',
`You have access to the following tool:\n\n${tool.description}\n\nUse this tool whenever you need additional information to answer the user's question.`,
],
['placeholder', '{messages}'],
]);
const chain = prompt.pipe(llmWithTools);
const toolChain = RunnableLambda.from(async (userInput, config) => {
const humanMessage = new HumanMessage(userInput);
const aiMsg = await chain.invoke(
{
messages: [humanMessage],
},
config
);
if (aiMsg.tool_calls && aiMsg.tool_calls.length > 0) {
const toolMsgs = await tool.batch(aiMsg.tool_calls, config);
return chain.invoke(
{
messages: [humanMessage, aiMsg, ...toolMsgs],
},
config
);
} else {
return aiMsg;
}
});
const toolChainResult = await toolChain.invoke(
'What is the current weather in Seattle?'
);
console.log(toolChainResult.content);
Für die Verwendung der Tavily-Suchmaschine benötigen Sie einen API-Key. Die Plattform bietet Ihnen verschiedene Verträge an. Mit dem kostenlosen Einstieg können Sie 1000 Suchanfragen pro Monat stellen. Den API-Key geben Sie über die Umgebungsvariable TAVILY_API_KEY an. Mit der TavilySearchResults-Klasse können Sie eine Instanz erzeugen, die Sie als Werkzeug an das LLM binden können. Dies erfolgt, wie Sie im Code sehen, über die bindTools-Methode der Modell-Klasse. Im Prompt-Template teilen Sie dem LLM mit, dass es auf die Suchmaschinenintegration zugreifen kann. Die Werkzeugintegration bedeutet jedoch nicht, dass das LLM von selbst auf das Such-API zugreifen kann. Es erzeugt lediglich die Anfragen für die Suche, die Sie dann selbst ausführen müssen. Wie das funktioniert, sehen Sie im Aufruf der from-Methode der RunnableLambda-Klasse. Hier kommunizieren Sie zunächst mit dem LLM. Enthält dessen Antwort ein Element in der tool_calls-Eigenschaft, bedeutet das, dass Sie mit dem Tavily API kommunizieren sollten. Dies erfolgt mit der batch-Methode. Das Resultat geben Sie in einem invoke-Aufruf wiederum an das LLM, das die finale Antwort erzeugt. Mit dieser Konfiguration können Sie über das toolChain-Objekt und dessen invoke-Methode so über einen kleinen Umweg Ihr LLM an eine Suchmaschine anschließen.
Mit den Bausteinen, die Sie bis jetzt kennengelernt haben, können Sie noch viele weitere Anwendungsfälle umsetzen. Eines der wohl populärsten Beispiele einer KI-Anwendung ist nach dem Chatbot aktuell wohl RAG. Mit LangChain können Sie eine solche Applikation mit nur wenigen Zeilen umsetzen.
RAG steht für Retrieval Augmented Generation und ist eine Technik, mit deren Hilfe Sie die Möglichkeiten eines LLMs noch deutlich erweitern können. Sie können der Applikation aktuelle und spezialisierte Informationen zur Verfügung stellen, ohne dass Sie das LLM modifizieren müssen. RAG besteht aus zwei Teilen:
Retriever: Der Retriever sammelt die passenden Informationen, meist liegen diese in Form von Vektoren vor.
Generator: Die Informationen des Retrievers nutzt der Generator, um die Antwort auf einen Prompt zu erzeugen.
Das folgende Beispiel soll eine PDF-Datei zum Thema Python einlesen, verarbeiten und es Ihnen ermöglichen, Fragen zu stellen. Den Code für die RAG-Kette finden Sie in Listing 5.
Listing 5: Implementierung einer RAG-Applikation mit LangChain
import { Ollama, OllamaEmbeddings } from '@langchain/ollama';
import { PDFLoader } from '@langchain/community/document_loaders/fs/pdf';
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
import { MemoryVectorStore } from 'langchain/vectorstores/memory';
import { ChatPromptTemplate } from '@langchain/core/prompts';
import {
RunnablePassthrough,
RunnableSequence,
} from '@langchain/core/runnables';
import { formatDocumentsAsString } from 'langchain/util/document';
import { StringOutputParser } from '@langchain/core/output_parsers';
const filePath = './python.pdf';
const loader = new PDFLoader(filePath);
const docs = await loader.load();
const textSplitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000,
chunkOverlap: 200,
});
const splits = await textSplitter.splitDocuments(docs);
const embeddings = new OllamaEmbeddings();
const vectorStore = await MemoryVectorStore.fromDocuments(splits, embeddings);
const retriever = vectorStore.asRetriever();
const prompt = ChatPromptTemplate.fromMessages([
`You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question.
If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.
Question: {question}
Context: {context}
Answer:`,
]);
const model = new Ollama({
model: 'llama3.2',
});
const ragChain = RunnableSequence.from([
{
context: (...args) => retriever.pipe(formatDocumentsAsString),
question: new RunnablePassthrough(),
},
prompt,
model,
new StringOutputParser(),
]);
const result = await ragChain.invoke(
'Was sind die Unterschiede zwischen list und tuple in Python'
);
console.log(result);
Der Code gliedert sich in zwei Teile. Im ersten Teil verarbeiten Sie die Informationen aus der PDF-Datei und im zweiten Teil arbeiten Sie mit dieser Datenquelle. Zunächst nutzen Sie den PDFLoader, um die Datei zu laden. Anschließend nutzen Sie den RecursiveCharacterTextSplitter, um Textblöcke mit 1000 Zeichen zu erzeugen. Die Blöcke überlappen sich dabei um 200 Zeichen. Die Überlappung erhöht die Genauigkeit und Relevanz der Antworten und stellt sicher, dass keine Informationen verloren gehen, wenn der Text beliebig unterteilt wird.
Die Textblöcke werden im nächsten Schritt in einen Vektorspeicher eingefügt. Für die Umwandlung des Texts in Vektoren kommen wieder die OllamaEmbeddings zum Einsatz. In dieser Variante können Sie das Standard-Embeddings-Modell mxbai-mebed-large verwenden, da die Textblöcke problemlos in dessen Kontext passen. Als Vektorspeicher nutzt das Beispiel den MemoryVectorStore von LangChain. Sie können an dieser Stelle aber auch eine persistente Vektordatenbank, wie beispielsweise FAISS oder ChromaDB, verwenden. Der größte Vorteil dieser Implementierung mit dem MemoryVectorStore ist, dass die Vektoren bei jedem Durchlauf erneut aufgebaut werden müssen, was unnötig Zeit in Anspruch nimmt. Arbeiten Sie also an einer Produktivapplikation, sollten Sie in jedem Fall auf eine Vektordatenbank zurückgreifen.
Sobald Sie den Datenspeicher aufgebaut haben, können Sie sich dem eigentlichen Teil der RAG-Applikation widmen und die RAG-Kette aufbauen. Die Kette besteht aus der Eingabe, einem Prompt-Template, dem Modell und einem Output-Parser. Die Besonderheit im Vergleich zu den bisherigen Implementierungen liegt hier im Einstieg in die Kette. Das Prompt-Template arbeitet mit zwei Platzhaltern: context und question. Die question, also die eigentliche Prompt-Eingabe aus dem Aufruf der invoke-Methode, wird unverändert an das Modell weitergereicht, dafür sorgt die RunnablePassthrough-Instanz. Beim Kontext sucht der Retriever nach relevanten Informationen aus dem Vektorspeicher. Die Resultate werden mit der formatDocumentsAsString in einen Stringwert umgewandelt, den das Modell als Kontext verwenden kann.
Die übrige Kette arbeitet dann ganz ähnlich wie die Kette im ersten Beispiel des Chatbots. Rufen Sie die invoke-Methode mit einer beliebigen Frage auf, liefert der Retriever den Kontext, fügt ihn ins Prompt-Template ein und übergibt es ans Modell. Der OutputParser extrahiert die Antwort und liefert sie als Zeichenkette aus.
Die Laufzeit der Applikation hängt von zahlreichen Faktoren ab. Entscheidend sind vor allem die Ressourcen des Systems, auf dem Sie sie ausführen. Steht eine leistungsstarke Grafikkarte zur Verfügung, beschleunigt das den Ablauf erheblich. Auch die Größe der PDF-Datei hat Einfluss auf die Laufzeit, je größer die Datei, desto mehr Textblöcke müssen gebildet, in Vektoren umgewandelt und in der Vektordatenbank gespeichert werden. Damit Sie einen Eindruck über die Verteilung der Laufzeit bekommen, sehen Sie hier eine Messung der Ausführung der einzelnen Applikationsteile:
Laden der PDF-Datei: 1 Sekunde
Aufteilen der Datei in Textblöcke: 0,2 Sekunden
Erzeugen und Speichern der Vektoren: 2,5 Minuten
Erzeugen der RAG-Kette: 0,3 Sekunden
Aufruf der invoke-Methode: 3,5 Sekunden
Wie Sie sehen, nimmt die Umwandlung der Textblöcke in Vektoren und deren Speicherung den mit Abstand größten Teil der gesamten Operation in Anspruch. Da sich aber weder der Inhalt des Dokuments noch die daraus abgeleiteten Vektoren verändern, müssen Sie diese Operation auch nur einmalig durchführen. Der übrige Prozess läuft verhältnismäßig schnell ab und kann durch ein leistungsstärkeres System oder ein kleineres Modell noch beschleunigt werden.
LangChain erfindet das Rad der KI-Applikation nicht neu, sondern stellt Ihnen vielmehr eine Plattform mit einheitlichen Schnittstellen und einem mächtigen Baukastensystem zur Verfügung. Da es die Plattform sowohl für Python als auch für JavaScript bzw. TypeScript gibt, ist die Community auch entsprechend groß und die vorgefertigten und beschriebenen Lösungsansätze sind vielfältig. LangChain schreibt Ihnen nicht vor, welches Sprachmodell Sie für Ihre Applikation nutzen sollen, durch die nahtlose Integration vieler verschiedener Modelle von dem kommerziellen OpenAI API bis hin zu lokal gehosteten LLMs über Plattformen wie Ollama haben Sie die maximale Flexibilität für Ihre Lösung.
Die möglichen Anwendungsfälle reichen von den hier gezeigten typischen Applikationen wie Chatbots oder RAG-Applikationen bis hin zu komplexen Applikationen mit sehr langen Ketten.
LangChain ist so aufgebaut, dass Sie es als Standalone-Framework nutzen können und in jede beliebige Webapplikation integrieren können. Zusätzlich dazu gibt es um LangChain noch ein leistungsstarkes Ökosystem, das aus verschiedenen Open-Source- und kommerziellen Komponenten besteht:
LangChain: Ein Open-Source-Framework, das die Integration von Sprachmodellen, Prompts und Datenbanken erleichtert. Mit ihm können Sie komplexe Ketten für LLM-basierte Applikationen erzeugen.
LangGraph: Mit diesem Framework aus dem LangChain-Ökosystem können Sie Multi-Agenten-Systeme als gerichtete Graphen verwalten. Dabei definieren Sie die Logik der Agenten und können Themen wie Skalierbarkeit und Fehlertoleranz abdecken. LangGraph ist wie LangChain ebenfalls ein Open-Source-Projekt, das Sie ohne zusätzliche Kosten in Ihre Applikation integrieren können.
LangGraph Cloud: Eine kostenpflichtige Cloudumgebung für LangGraph-Applikationen. Durch die Infrastruktur können Sie Ihre Applikation einfacher skalieren und ausfallsicher gestalten.
LangSmith: Diese Entwicklungsplattform unterstützt Sie in allen Schritten des Entwicklungsprozesses einer LLM-Applikation. Der Fokus liegt auf dem Tracing, das Ihnen Einblicke in jede Stufe der Ketten und Agenten einer Applikation gibt.
Hinter diesem Ökosystem steht das Unternehmen LangChain, Inc., das sich aktiv um die Weiterentwicklung der einzelnen Komponenten als auch um die Erbringung der Dienste kümmert. Der Vorteil an diesem Ökosystem ist, dass Sie ohne Einschränkungen mit den Open-Source-Komponenten arbeiten und selbst entscheiden können, ob Sie die kostenpflichtigen Dienste in Anspruch nehmen möchten.
1. Was ist LangChain?
LangChain ist ein Open-Source-Framework, das die Integration von LLMs (Large Language Models) wie OpenAI, Ollama oder Google Gemini erleichtert. Es bietet modulare Bausteine für Prompts, Vektordatenbanken und Ketten.
2. Was kann man mit LangChain machen?
Mit LangChain lassen sich Chatbots, RAG-Anwendungen, Textzusammenfassungen und sogar Websuchen entwickeln. Das Framework eignet sich für viele praxisnahe KI-Szenarien.
3. Wie funktioniert die Integration von LLMs mit LangChain?
LangChain stellt einheitliche Schnittstellen und Tools bereit, um LLMs flexibel anzubinden. Entwickler können Modelle austauschen, Prompts gestalten und Datenquellen wie PDFs oder Webseiten einbinden.
4. Wer nutzt LangChain?
LangChain wird vor allem von Softwareentwicklern und KI-Teams genutzt, die Anwendungen mit JavaScript, TypeScript oder Python entwickeln. Durch seine Community und Dokumentation ist es für Einsteiger und Profis interessant.
5. Welche Vorteile bietet LangChain gegenüber direkter API-Nutzung?
Statt jede API einzeln zu integrieren, liefert LangChain eine einheitliche Architektur. Das spart Zeit, erhöht die Flexibilität und macht komplexe LLM-Workflows einfacher umsetzbar.
6. Welche Rolle spielt RAG in LangChain?
RAG (Retrieval Augmented Generation) ermöglicht es, externe Informationen in die Antworten eines Modells einzubeziehen. LangChain bietet fertige Bausteine, um RAG-Anwendungen mit Vektordatenbanken schnell umzusetzen.
7. In welchen Sprachen kann man LangChain nutzen?
LangChain steht für JavaScript, TypeScript und Python zur Verfügung. Alle Varianten folgen denselben Konzepten und sind mit vielen Integrationen kompatibel.
The post LangChain Framework: So integrieren Sie LLMs in Ihre Anwendungen appeared first on JavaScript & Angular Days.
]]>The post Webentwicklung lernen: Der komplette Weg vom Anfänger zum Profi appeared first on JavaScript & Angular Days.
]]>Bei der Vielfalt der Möglichkeiten ist eine Kombination aus solidem Grundwissen, praktischer Erfahrung und Überblick wichtig. Eine Schritt-für-Schritt Anleitung oder einen perfekten Pfad vom Junior zum Senior gibt es nicht. Die Entwicklung hängt stark vom persönlichen Lerntyp, dem eigenen Umfeld und nicht zuletzt den Ressourcen ab, die Sie investieren können und wollen.
Für den Einstieg in die schnelllebige Welt der Webentwicklung hat sich ein praxisorientierter Ansatz bewährt. Ein großer Vorteil der Webentwicklung ist, dass Sie sehr schnell greifbare Ergebnisse produzieren können und so Ihren Fortschritt auch sehen können. Dazu sollten Sie sich einen möglichst einfachen Technologiestack wählen und sich eine konkrete Aufgabenstellung suchen. Der Klassiker ist hier die To-do-App, bei der Sie Aufgaben erzeugen, anzeigen, modifizieren und wieder löschen können. Eine solche To-do-App ist nur eines von vielen Beispielen, dabei geht es weniger um das konkrete Thema, sondern vielmehr darum, dass Sie die Standardoperationen einer Webapplikation umsetzen. Diese werden typischerweise unter dem Akronym CRUD zusammengefasst. Es steht für Create, Read, Update und Delete.
Um mit der Entwicklung zu beginnen, benötigen Sie ein wenig Grundwissen über HTML, CSS und JavaScript. Für den Start reicht es jedoch, dass Sie wissen, wie Sie eine Webseite mit HTML strukturieren können, grundlegende Styles wie Ausrichtung, Positionierung und Farben sowie den Umgang mit Text-Styles.
Mozilla Developer Network [1]
W3Schools [2]
Eine Applikation sollten Sie jedoch nicht direkt auf diesen grundlegenden Technologien umsetzen, zu diesem Zweck gibt es Frameworks. Die meisten Entwickler machen ihre ersten Schritte in einem Framework und nicht mit den zugrunde liegenden Basistechnologien. Der Vorteil der Frameworks ist, dass Sie deutlich schneller Ergebnisse produzieren können, da diese für die meisten alltäglichen Problemstellungen wie Navigation, Formularbehandlung oder Zustandsverwaltung bereits fertige Lösungen bieten. Bei den Frameworks gibt es aktuell eine Handvoll etablierter Ansätze:
React [3]
Angular [4]
Vue [5]
Svelte [6]
Jedes dieser Frameworks bietet ein offizielles Tutorial an, das den Einstieg in die Arbeit mit dem Framework erleichtert. Zusätzlich dazu finden sich zahlreiche weitere Ressourcen rund um die Frameworks wie Blogartikel, Bücher und Videotrainings. Ein weiterer Vorteil der Frameworks ist, dass sie alle Werkzeuge mitbringen, die Sie für den Entwicklungs- und Releaseprozess benötigen, und diese auch schon mit einer sinnvollen Konfiguration versehen sind. Die wichtigsten dieser Werkzeuge sind die Bundler, allen voran webpack und Vite.
Zu einem Web-Frontend gehört auch immer ein Backend, das sich um das Speichern der Daten und viele weitere Aspekte wie Validierung, Authentifizierung und nicht zuletzt die serverseitige Businesslogik kümmert. Die Entwicklung von Backend-Applikationen ist eine Welt für sich und teilt sich wiederum in ein breites Spektrum auf. Als Webentwickler sollten Sie zumindest einen Einblick in die Komplexität der Backend-Welt haben. Es gibt auch immer mehr Entwickler, die den Pfad der Fullstack-Entwicklung einschlagen und sowohl im Frontend als auch im Backend unterwegs sind.
Eine naheliegende Wahl für den Einstieg in die Backend-Entwicklung ist die Plattform Node.js, auf der Sie mit JavaScript entwickeln können. Zu Beginn geht es hier vor allem um die Schnittstelle zum Frontend auf der einen Seite und die Anbindung einer Datenbank auf der anderen. Für Schnittstellen ist aktuell das REST-Paradigma etabliert. Es ist ein ressourcenbasierter Ansatz, der die Eigenschaften des HTTP-Protokolls nutzt, um Ressourcen über URL-Pfade abzubilden und mit den HTTP-Methoden die Art des Zugriffs zu beschreiben. Als Datenbanken kommen entweder klassische SQL-Datenbanken wie MySQL oder PostgreSQL, dokumentenorientierte Datenbanken wie MongoDB oder Key-Value-Stores wie Redis in Frage.
Der beste Weg, um Erfahrung in der Entwicklung zu sammeln, führt jedoch nicht über einfache CRUD-Applikationen, sondern über echte Projektarbeit. Im besten Fall arbeiten Sie in einem größeren Team an einer Applikation, dann haben Sie die Gelegenheit, die Technologien im praktischen Einsatz in einem größeren Verbund zu erleben. Der Vorteil ist hier, dass Sie mit verschiedensten Problemstellungen konfrontiert werden, die in einer einfachen Applikation nicht existieren. Das können komplexe Anforderungen des Kunden sein, aber auch Herausforderungen bei der Integration neuer Features in eine bestehende Codebasis oder Schnittstellen, die nicht für den geplanten Anwendungsfall gedacht sind.
In einem solchen Team können Sie von den anderen Teammitgliedern lernen und Ihr Wissen erweitern. Die besten Ansätze sind kooperative Formen der Softwareentwicklung in Form von Pair Programming, bei denen Sie direkt mit anderen Entwicklern zusammen an einem Feature arbeiten, oder Codereviews, bei denen Ihr Quellcode auf den Prüfstand gestellt und hinsichtlich Umsetzung, Integration und Architektur geprüft wird. Wertvoll werden solche Codereviews natürlich erst, wenn Sie auf Fehler aufmerksam gemacht und auch Strategien zur Behebung entwickelt werden.
In der Webentwicklung haben Sie Zugriff auf eine weltweite und sehr aktive Community, mit der Sie sich austauschen können. Dieser Austausch funktioniert sowohl asynchron über Diskussionsforen und Chats als auch synchron beispielsweise auf Konferenzen und anderen Veranstaltungen. Hier erfahren Sie nicht nur alles, was Sie über die neusten Entwicklungen wissen müssen, sondern haben auch Gelegenheiten, sich mit Gleichgesinnten zu unterhalten.
Der Übergang vom Einstieg zum Intermediate oder vom Junior zum Professional ist fließend. Es gibt keine Checkliste, die Sie abhaken können. Sie merken dennoch, dass die Schritte, die Sie machen, immer größer werden, die Probleme immer herausfordernder und Sie generell selbstsicherer werden. Fühlen Sie sich komfortabel in Ihrer Umgebung und mit Ihren Aufgaben, ist es an der Zeit, den nächsten Schritt zu gehen und Ihr Wissen weiter zu vertiefen.
Ein Bereich, der Sie während Ihrer gesamten Entwicklung begleiten wird, sind die Grundlagen der Programmiersprachen und Technologien. Haben Sie Ihre ersten Projekte umgesetzt, geht es an die Vertiefung Ihres Grundwissens. So können Sie sich beispielsweise mit dem korrekten semantischen Aufbau eines HTML-Dokuments beschäftigen. Oder Sie steigen tiefer in das Thema Styling von Web-Frontends mit CSS ein und lernen die Tricks und Kniffe von CSS sowie Werkzeuge wie den Präprozessor SCSS, CSS-Frameworks wie Tailwind oder generelle Konventionen wie BEM.
Auch beim Thema Programmierung gibt es viel zu lernen. Gerade der Bereich JavaScript bietet ein enormes Potenzial. Angefangen mit dem Umgang mit Events und asynchronen Programmflüssen, die Ihnen nahezu überall begegnen, sei es bei der User-Interaktion oder bei der Kommunikation mit einem Backend. Des Weiteren sollten Sie sich mit der Modularisierung Ihres Quellcodes beschäftigen und ihn in Klassen, Funktionen und Module aufteilen und dafür das Modulsystem verwenden. Noch besser als JavaScript ist TypeScript, da es Ihnen zusätzlich zum Funktionsumfang von JavaScript noch Typsicherheit bietet. Damit können Sie die Datentypen für Variablen und Funktionssignaturen festlegen. Der TypeScript Compiler kann aufgrund dieser Informationen Ihren Quellcode überprüfen und weist Sie noch vor der Ausführung Ihrer Applikation auf Probleme hin. Einige Frameworks wie Angular setzen standardmäßig auf TypeScript, andere wie React oder Vue unterstützen sowohl JavaScript als auch TypeScript, wobei Sie in jedem Fall auf die typsichere Variante setzen sollten.
Haben Sie ihre ersten Aufgabenstellungen in der Welt der JavaScript-Frameworks erledigt, gilt es, das Ökosystem um die Frameworks zu meistern. Neben den eigentlichen Frameworks gibt es noch zahlreiche zusätzliche Pakete und Erweiterungen, die Ihnen bei der Entwicklung von Applikationen helfen. Typische Beispiele sind Bibliotheken zum State Management oder Komponentenbibliotheken wie:
Redux: zentrales State Management für verschiedene Frameworks [5]
NgRx: ein Angular-Framework, das unter anderem reaktives State Management bietet [6]
Pina: eine flexible Lösung für zentrales State Management in Vue [7]
Material-UI: eine Komponentensammlung für React [8]
PrimeVue: eine moderne Komponentenbibliothek für Vue [9]
Angular Material: Material-Design-Komponenten für Angular [10]
Mit dem Umfang Ihrer Aufgabenstellungen steigt auch ihre Auswirkung auf das Gesamtsystem und Sie müssen sich immer häufiger auf der architektonischen Ebene der Applikation bewegen. Die Architektur einer Applikation betrifft ihren Aufbau und die Struktur einer Applikation, wie Daten verarbeitet werden und wie die einzelnen Teile miteinander interagieren. Die Architektur beeinflusst die Skalierbarkeit, Wartbarkeit, Sicherheit und Performance des Gesamtsystems ganz entscheidend.
Konkrete Ausprägungen von Architekturmustern sind Event-Systeme zur Kommunikation in und zwischen Applikationsteilen. Eine komponentenbasierte Architektur des Frontends, bei der die grafische Oberfläche aus einer Baumstruktur aus vielen kleinen lose gekoppelten Komponenten besteht, zwischen denen Informationen fließen. Oder Fullstack-Applikationen, bei denen ein Teil der Zuständigkeiten des Frontends zum Server verschoben werden und der Server das eigentlich für den Browser gedachte Framework ausführt und serverseitig die Strukturen für den Client vorbereitet. Dieser Ansatz wird als serverseitiges Rendern (SSR) bezeichnet und ist mittlerweile in allen gängigen Frontend-Frameworks als optionale Erweiterung verfügbar.
Je größer eine Applikation wird, desto wichtiger wird auch die Qualität des Quellcodes. Sie sollten Entscheidungen zu Style und Konsistenz Ihres Quellcodes treffen und Regeln für die Formatierung festlegen. Ein unverzichtbares Hilfsmittel hierbei ist ESlint, ein Werkzeug zur statischen Codeanalyse, das dabei hilft, einen festgelegten Codestandard im Bereich von JavaScript und TypeScript durchzusetzen. Damit Sie nicht jeden Aspekt des Codestyles selbst festlegen müssen, gibt es etablierte Standards wie den AirBnB JavaScript Style, für den es auch ESlint-Regelsätze gibt.
Neben der Codequalität gilt es auch, die Features einer Applikation in einer hohen Qualität abzuliefern. Ein Mittel, um das sicherzustellen, sind automatisierte Tests auf verschiedenen Ebenen. Diese reichen von leichtgewichtigen Unit-Tests bis zu umfangreichen Integrations- und Ende-zu-Ende-Tests. In der Webentwicklung gibt es dafür verschiedene Testframeworks. Im Fall von Unit-Tests können Sie beispielsweise auf Vitest oder Jest zurückgreifen, die sowohl JavaScript als auch TypeScript unterstützen und die Sie unabhängig vom Framework Ihrer Applikation einsetzen können. Für Ende-zu-Ende-Tests kommen Werkzeuge wie Cypress oder Playwright zum Einsatz. Sie schlüpfen in die Rolle der Benutzer Ihrer Applikation und überprüfen die verschiedenen Workflows aus der Browserperspektive.
Generell ist die Automatisierung von wiederkehrenden Aufgaben im Projekt ein wichtiger Faktor, um die Effizienz im Entwicklungsprozess zu steigern. Dabei sind automatisierte Tests ein Baustein, eine solide CI/CD-Pipeline ein weiterer. Diese Pipeline bereitet die Applikation für den Test- und Produktivbetrieb vor, führt automatisierte Überprüfungen durch und deployt die Applikation auf das Zielsystem.
Ein weiteres Thema, das mindestens genauso wichtig ist wie die Qualitätssicherung in einer Applikation, ist Sicherheit. In Webapplikationen haben Sie häufig mit sensiblen Daten Ihrer Nutzer zu tun, die Sie schützen müssen. Die Grundlagen bilden die sichere Übertragung von Daten über verschlüsselte Verbindungen, die Handhabung von Passwörtern und Schlüsseln sowie der Schutz vor gängigen Schwachstellen wie Cross-site Scripting, SQL Injections und Cross-site Request Forgery. Eine gute Anlaufstelle, wenn es um Sicherheit geht, ist OWASP (Open Worldwide Application Security Project). Es veröffentlicht regelmäßig die häufigsten Schwachstellen in Form der OWASP Top 10 [13] und stellt Ressourcen zur Identifikation, Vermeidung und Behebung dieser Schwachstellen zur Verfügung.
Die Ressourcen für die Weiterentwicklung für erfahrenere Entwickler sind deutlich weniger als für den Einstieg. Ein Grund hierfür ist, dass sie deutlich tiefer gehen müssen und auch die Qualitätsansprüche steigen. Dennoch können Sie gerade bei professionellen Trainingsplattformen oder auch auf Workshops bei Konferenzen fündig werden.
Eine weitere gute Möglichkeit zur Weiterentwicklung ist die praktische Arbeit an größeren Projekten zusammen mit anderen Entwicklern. Hier können Sie größere Aufgaben und die Verantwortung für Teilprojekte übernehmen. Zum Üben eignen sich außerdem komplexere Aufgabenstellungen, die Sie unabhängig vom Arbeitsalltag lösen können. Hier eignen sich vor allem spezialisierte Problemstellungen, die aufwendigere Lösungsansätze benötigen. Hier sollten Sie sich vor allem auf eine saubere Architektur konzentrieren.
Irgendwann haben Sie dann alles gesehen, was es in der Webentwicklung zu sehen gibt, oder? Dieser Zeitpunkt wird in der Realität niemals eintreten, denn die Problemstellungen und die möglichen Lösungsansätze sind so vielfältig, dass Ihnen garantiert niemals langweilig werden wird. Dennoch haben Sie irgendwann so viel Erfahrung gesammelt, dass Sie für nahezu jedes Problem eine Lösung finden können. Hier wird es dann auch irgendwann schwierig, passende Ressourcen für die Weiterentwicklung zu finden. Normalerweise suchen Sie sich in dieser Karrierestufe Ihre Problemstellungen selbst und entwickeln passende Lösungen dafür.
In Projekten stehen Sie an der Spitze und beteiligen sich maßgeblich an der Gestaltung der Architektur. Sie übernehmen die Verantwortung und sorgen dafür, dass die Qualität des Produkts so hoch wie möglich ist. Meist kümmern Sie sich um die anspruchsvollen Aufgaben und sind die Person, die gefragt wird, wenn man nicht mehr weiterweiß. Das bedeutet, dass Sie mit den wirklich spannenden Problemstellungen im Projekt konfrontiert werden und im Team eine Lösung dafür entwickeln müssen.
Spätestens jetzt ist es an der Zeit, Ihr Wissen an andere weiterzugeben. Das kann in Form eines Chefarchitekten in einem Projekt sein, der weniger erfahrene Kollegen anleitet und ihnen hilft, sich weiterzuentwickeln. Oder man gibt sein Wissen in Form von Schulungen oder anderen, öffentlichen Kanälen weiter. Wenn es darum geht, Wissen zu vermitteln, müssen Sie sich intensiv mit dem jeweiligen Thema auseinandersetzen, um die Zusammenhänge erklären und auf Fragen antworten zu können.
Die Webtechnologien werden stetig weiterentwickelt und fast im Tagesrhythmus tauchen neue Frameworks und Bibliotheken auf oder verschwinden wieder in der Bedeutungslosigkeit. Und als ob das noch nicht genug wäre, haben wir mit der zunehmenden Popularität und Verfügbarkeit von Generative AI ein weiteres, sehr mächtiges Werkzeug erhalten. Doch wie verändert KI den Arbeitsalltag und wie wirkt sie sich auf die Entwicklung von Personen aus?
Werkzeuge wie ChatGPT, Gemini, Claude, GitHub CoPilot und Cursor machen das Entwicklerleben deutlich angenehmer, da Sie damit einfache und sich wiederholende Aufgaben gut auslagern können. Sie können sich Datenbankstrukturen, Codeblöcke und ganze grafische Oberflächen von KI-Werkzeugen generieren lassen. Auch in der Wissensvermittlung spielen diese Werkzeuge eine immer größere Rolle. Dennoch ist bei allen Vorzügen auch Vorsicht geboten. Zum einen sind die KI-Tools noch mehr Werkzeuge, die Sie lernen müssen. Zum anderen beschleunigt die KI die Entwicklung noch weiter und erhöht die Menge an Informationen. Was noch erschwerend hinzu kommt: Sie können den Informationen nicht uneingeschränkt vertrauen. Das gilt sowohl für die Antworten auf Ihre Prompts in einem Chatinterface als auch beim generierten Quellcode.
Brancheninsights im Newsletter erhalten:
Die Webentwicklung ist ein spannendes Betätigungsfeld mit sehr vielen Facetten und Möglichkeiten. Diese reichen vom Backend-Spezialisten über den Full-Stack-Allrounder bis hin zum UI- und UX-Experten.
Es ist auch nicht so, dass Sie einen einmal eingeschlagenen Pfad für Ihre gesamte Karriere beibehalten müssen. Sie können sich jederzeit umorientieren und andere Bereiche der Webtechnologien erkunden. Das Zusammenspiel der einzelnen Systeme und die architektonischen Prinzipien helfen Ihnen dabei.
Allerdings ist es unerlässlich, dass Sie sich in der schnell voranschreitenden IT-Welt kontinuierlich weiterentwickeln und am Ball bleiben, da Sie sonst schnell den Anschluss an die aktuellen Trends und Entwicklungen verlieren.
1. Was ist Webentwicklung?
Webentwicklung bezeichnet den Prozess, Webseiten und Webanwendungen zu erstellen. Dazu gehören Frontend-Technologien (HTML, CSS, JavaScript) für die Nutzeroberfläche, Backend-Logik für Server und Datenbanken sowie Schnittstellen und Protokolle, die die Kommunikation ermöglichen.
2. Wer kann Webentwicklung lernen?
Jeder mit Interesse an Programmierung und digitaler Technik kann Webentwicklung erlernen — vom Quereinsteiger über Studenten bis hin zu Profis aus anderen IT-Bereichen. Einsteiger sollten einfache Projekte starten und Schritt für Schritt Wissen aufbauen.
3. Wofür braucht man HTML, CSS und JavaScript im Web?
4. Was sind Frameworks und warum benutzt man sie?
Frameworks wie React, Angular, Vue oder Svelte bieten vordefinierte Strukturen, Komponenten und Tools, mit denen Entwickler schneller und effizienter komplexe Webanwendungen bauen können. Sie reduzieren Boilerplate-Code und bringen Best Practices mit.
5. Was unterscheidet Backend- von Frontend-Entwicklung?
Ein Full-Stack-Entwickler verbindet beide Bereiche.
6. Warum ist es sinnvoll, mit echten Projekten zu lernen?
Echte Projekte bringen reale Herausforderungen mit: Integration mehrerer Technologien, Fehlerbehandlung, Architektur, Arbeitsaufteilung im Team. Das hilft, theoretisches Wissen in praktisches Können zu verwandeln und Routine aufzubauen.
7. Wie beeinflusst KI das Lernen in der Webentwicklung?
KI-Tools wie ChatGPT, GitHub Copilot oder ähnliche können Code-Snippets liefern, Routinetasks automatisieren und als Lernhilfe dienen. Aber: Sie ersetzen nicht das Verständnis. Kritisches Hinterfragen, eigenes Debugging und Qualitätskontrollen bleiben essenziell.
[1] Mozilla Developer Network: https://developer.mozilla.org/en-US/docs/Learn_web_development
[2] W3Schools: https://www.w3schools.com
[3] React: https://react.dev
[4] Angular: https://angular.dev
[5] Vue: https://vuejs.org
[6] Svelte: https://svelte.dev
[7 ]Redux: https://redux.js.org
[8] NgRx: https://ngrx.io
[9] Pina: https://pinia.vuejs.org
[10] Material-UI: https://mui.com
[11] PrimeVue: https://primevue.org
[12] Angular Material: https://material.angular.io
[13] OWASP Top 10: https://owasp.org/www-project-top-ten
The post Webentwicklung lernen: Der komplette Weg vom Anfänger zum Profi appeared first on JavaScript & Angular Days.
]]>The post JavaScript News & Trends in 2024 sowie Zukunftsprognose für 2025 appeared first on JavaScript & Angular Days.
]]>Karsten Sitterberg: Die Einführung des neuen Buildsystems mit esbuild und Vite für Angular war für mich ein echter Meilenstein. Die Geschwindigkeit und Effizienz dieses Systems bringen deutliche Verbesserungen bei der Produktivität in der Entwicklung. Besonders spannend daran finde ich, dass Angular damit eine Technologie integriert, die ursprünglich aus dem Vue-Universum stammt. Neben der zunehmenden Zusammenarbeit zwischen Angular und dem Google-internen Framework Wiz findet sich so ein weiteres großartiges Beispiel dafür, wie Frameworks voneinander profitieren und sich gegenseitig inspirieren können.
Martina Kraus: Eindeutig die Entwicklung der KIs im Webbrowser. WebLLM bietet beispielsweise die Möglichkeit, Large Language Models (LLMs) direkt im Browser zu operationalisieren, ohne dass eine Verbindung zu externen Servern benötigt wird. Dieses API ermöglicht eine direkte Interaktion mit der GPU des Endgeräts, wodurch KI-Modelle erheblich beschleunigt werden können. Daneben wurde auch der neue Webstandard WebNN etabliert. Damit können Web-Applikationen und Frameworks Deep Neural Networks mit GPUs, CPUs oder speziell erstellten KI-Beschleunigern wie NPUs performanter machen. Ich bin selbst keine KI- oder LLM-Expertin, aber ich bin sehr begeistert von den Demos und vor allem der guten Integration dieser APIs in meine Frontend-Applikation. Es gibt so viele Anwendungsfälle, bei denen diese APIs hilfreich sein können. Etwa wenn es darum geht, ein lästiges Formular in einer Web App intelligent ausfüllen zu lassen, indem man schlichtweg einen Text – in dem theoretisch alle Infos enthalten sind – in ein dafür vorgesehenes Textfeld kopiert. Mit Hilfe der KI im Webbrowser können dann alle wichtige Daten herangezogen werden.
David Müllerchen: Das sich alle Frontend Framework Teams zusammengetan haben, um ein Abstract zu Signals zu schreiben. Das fand ich schon sehr nice. Ich bin ja eh sehr viel in Communities unterwegs und freu mich, wenn die Zusammenarbeit größer und die Abstände kleiner werden.
Sebastian Springer: Für mich, wie für viele andere, ist nach wie vor der Siegeszug der KI in alle Entwicklungsbereiche sehr spannend. Wobei es mir da weniger darum geht, dass man überall „AI powered“ draufschreibt, sondern vielmehr darum, dass z.B. die KI im Entwicklungsprozess einen echten Mehrwert bietet. Das hat natürlich auch einen entscheidenden Nachteil: Wenn man wieder einmal mit der Bahn unterwegs ist und die Verbindung so schlecht ist, dass man alle nervigen und sich wiederholenden Aufgaben plötzlich wieder selbst machen muss.
Der zweite Teil der KI in der Webentwicklung, die Integration in die Applikationen, ist für mich genauso wichtig wie die KI als Entwicklungswerkzeug. Mittlerweile hat wahrscheinlich jeder schon einen Chatbot mit dem OpenAI-API gebaut. Aber es geht noch deutlich mehr. Sowohl die Dienste, die man anbinden kann, als auch die Werkzeuge, Frameworks und Bibliotheken werden immer besser, sodass für die Enduser unserer Applikationen ein echter Mehrwert entsteht.
Aber das Leben besteht nicht nur aus KI. Für mich als alten React-Fanboy war 2024 das Jahr von React 19. Es gibt einige sehr sinnvolle Erweiterungen, die in Richtung Fullstack-Entwicklung (mit z. B. Next.js) gehen. Aber auch rein clientseitige Erweiterungen kommen nicht zu kurz. Und das beste Feature, das streng genommen noch nicht einmal React 19 ist: der Compiler, der die unselige manuelle Memoisierung endlich überflüssig macht und uns ohne weiteres Zutun einen Performancegewinn schenkt.
Manfred Steyer: Es war total spannend zu sehen, wie sich die Signal-Story in Angular entwickelt hat. Gerade jetzt mit dem neuen Resource API haben wir zum ersten Mal die Möglichkeit, in der Signal-Welt einen reaktiven Datenfluss end-to-end abzubilden – also von den Benutzereingaben bis hin zur Ausgabe. Die neuen Linked Signals bieten daneben eine lokale Arbeitskopie und sind ein einfacher, aber auch effektiver Weg, um Signals, die von einem Store kommen und readonly sind, an Formularfelder zu binden.
Nils Hartmann: Für das ganze JavaScript-Universum kann ich das nicht beantworten, denn ich bin eher monothematisch im React-Universum unterwegs. Da finde ich es einerseits spannend, dass die serverseitige- bzw. Fullstack-Entwicklung von React-Anwendungen durch React selbst, aber auch durch Next.js in Form von Vercel massiv vorangetrieben wird. Andererseits erfahre ich aber in Gesprächen und Workshops häufig, dass es nach wie vor eine gewisse Skepsis gegenüber den Fullstack-Ansätzen gibt. Und auch wenn das vielleicht eine subjektive Verzerrung durch meine Bubble ist, hätte ich den Wunsch, dass diese Bedenken ernst(er) genommen werden. Glücklicherweise gibt es mit neuen Versionen populärer Bibliotheken (etwa React Router oder TanStack Query) und auch dem gänzlich neuen TanStack Router zumindest aus der Community weiterhin viel Unterstützung für die Entwicklung von Single-Page-Anwendungen mit React.
Lena Rosenboom: Das ganze Jahr hat mich das Thema Barrierefreiheit beschäftigt. Immer mehr Unternehmen merken, dass sie ihre Websites anpassen müssen. Das künftige Gesetz ist zwar keine Neuigkeit, aber für viele Unternehmen kommt es, wie die DSGVO, doch sehr spontan.
Katja Potensky: In JavaScript direkt: Wahrscheinlich die Aufnahme von Shebangs !# in die offizielle Syntax. !# /usr/bin/env node ist schon cool! Im allgemeinen WebDev-Bereich finde ich das Popover API und die immer breiter werdende Abkehr von schwergewichtigen Frameworks gut.
Karsten Sitterberg: Meiner Meinung nach hat Angular dieses Jahr an manchen Stellen die Bedürfnisse von Enterprise-Kunden aus den Augen verloren. Innovation und dadurch bedingt die Einführung vieler neuer Konzepte ist zwar spannend und teils auch wichtig, aber neue Konzepte bringen immer auch eine erhebliche kognitive Last mit sich. Vor allem, wenn gleichzeitg auch die älteren Konzepte noch stark im Code vertreten sind, erhöht die Menge der möglichen Programmiermuster zunächst einmal die Hürden für den Einstieg von neuen Entwicklern. Gerade für Teams, die eher auf Stabilität und (Abwäts-)Kompatibilität angewiesen sind, kann das zu einer Herausforderung werden.
Sebastian Springer: Was ich dieses Jahr sehr schade fand, waren die Schwierigkeiten, die das neue Major-Release von React mit sich brachte – beziehungsweise das Nicht-Release. Durch eine Änderung im Verhalten der Suspense-Komponente wurde eine massive Diskussion in der Community vom Zaun gebrochen, die schließlich dazu führte, dass sich das Release um mehrere Monate verzögerte. Andererseits ist es aber auch erfreulich, dass das React-Team auf das Feedback der Community reagiert und eine gute Lösung für alle Beteiligten findet.
Nils Hartmann: Obwohl die Version 19 von React schon im April 2024 angekündigt und in einem ersten Release-Kandidaten vorgestellt wurde, hat es Angular noch geschafft, React in der Versionsnummer zu überholen und seine Version 19 vor der von React herauszubringen Spaß bei Seite, es ist natürlich schade, dass es mit dem React-Release so lange dauerte, aber immerhin wurden die Probleme, die es mit dem Release-Kandidaten gab, ernst genommen und behoben.
Lena Rosenboom: Enttäuschend finde ich, dass viele große Unternehmen noch immer nicht verstanden haben, dass sie für das BFSG (Barrierefreiheitsstärkungsgesetz) handeln müssen. Außerdem finde ich es auch sehr schade, dass einige Entwickler sich gegen diese Änderungen aussprechen. Dabei wäre die digitale Welt eine bessere, wenn sie alle nutzen könnten.
Martina Kraus: Ich war lange Zeit eine Anhängerin von WebAssembly und habe auch selbst viel auf Konferenzen über WebAssembly gesprochen. Leider findet WebAssembly nur bei größeren Firmen wie Google Twitch oder Amazon Music Anklang. Mittelständische Firmen sehen oftmals keinen Vorteil und fragen sich, warum sie sich neben JavaScript und einer Backend-Technologie noch eine weitere Technologie ins Haus holen sollten. Die Integration von WebAssembly ist meist noch sehr schwierig und es fehlt eine gute Unterstützung der Tools beispielsweise zum einfachen Debuggen einer WebAssembly-Anwendung. Darüber hinaus sind gewisse Probleme wie die Garbage Collection in WebAssembly immer noch nicht vollständig gelöst. Lustigerweise nutzen Web APIs wie WebLLM und WebNN WebAssembly für die Ausführung der Berechnungen und Models – und genau da ergibt WebAssembly auch total Sinn – aber irgendwie schafft es WebAssembly nicht in die Köpfe der Entwickler.
David Müllerchen: Ich hatte irgendwie gehofft, dass das neue Date API schon kommt. Date Handling ist schon ein wilder Ritt, wenn man es nativ mit JavaScript machen will/muss. Ein Funktionsumfang wie day.js oder date-fns würde hier helfen
Katja Potensky: Ein weiteres Jahr ohne Pipeline Operator
Manfred Steyer: Ich bin derzeit von den Möglichkeiten der forensischen Code-Analyse sehr begeistert. Dabei kombiniert man Code-Metriken mit Informationen aus der Quellcodeverwaltung, z. B. der git history, um versteckte Muster zu entdecken. Mit diesen Mustern lässt sich auch einiges über die Qualität der vorherrschenden Architektur sagen. Und es ist wirklich lustig, Code, an dem man täglich arbeitet, aus dieser neuen Perspektive zu betrachten. Mein aktuelles Open-Source-Projekt Detective [1] bietet ein paar dieser Analysen an.
[1] https://www.npmjs.com/package/@softarc/detective
Karsten Sitterberg: Ich empfehle Testcontainers. Sie ermöglichen es, komplette Integrationstests in isolierten Containern durchzuführen – auch in der JavaScript-Welt perfekt für Datenbanken, APIs und andere Services. Gerade wenn mit komplexen Systemen gearbeitet wird, bieten Testcontainers eine hervorragende Möglichkeit, Tests auf stabile und reproduzierbare Füße zu stellen. Sie ermöglichen es auch, ohne ein komplexes Docker-Compose-Setup die gleiche Umgebung in der Pipeline wie auch lokal aufzubauen. Ein tolles Tool für nachhaltige Qualitätssicherung!
Martina Kraus: Nun, ich glaube es ist kein Geheimtipp mehr, aber ich möchte nochmal auf den GitHub Copiloten aufmerksam machen. Das Geld, das man selbst darin investieren muss, lohnt sich auf jeden Fall. Ich stand dem ganzen anfangs sehr skeptisch gegenüber – vermutlich auch weil der Programm-Code, den ChatGPT generiert hat, oft noch nicht einmal funktionierte, geschweige denn überhaupt kompiliert hat Die Qualität ist mit dem Copiloten deutlich höher, und ich nutze die Erweiterung für VS Code oder auch WebStorm sehr häufig, um mir schonmal ein Grundgerüst bzw. eine Grundstruktur meines Programms zu generieren. Natürlich ist das immer mit Vorsicht zu betrachten, und der generierte Code sollte immer noch einmal gründlich reviewt werden. Nichtsdestotrotz ist dieses Vorgehen für mich weitaus schneller, als wenn ich alles selbst schreiben müsste.
Lena Rosenboom: Jeder Entwickler sollte hin und wieder mal das Tool Accessibility Insights verwenden. Mit diesem Browser AddOn kann man seine Website auf Barrierefreiheit prüfen und bekommt Tipps, wie man sie verbessern kann.
David Müllerchen: Mein absolutes Lieblingstool ist VSCode. Ich habe noch ein paar wenige Extensions installiert, wie Framework-spezifische Tools (Angular Language Service) und UI Tweaks (Material Icon Theme). Aber ein Highlight ist immer Console Ninja. Das Tool zeigt dir die Konsolenausgaben in Code an. Sehr hilfreich!
Sebastian Springer: Mein persönlicher Geheimtipp (ok, so geheim ist der gar nicht) sind die KI-Helferlein, die die tägliche Arbeit enorm erleichtern. Konkret sind das GitHub Copilot, Continue oder Cursor – je nach persönlicher Vorliebe. Sich wiederholende Aufgaben, Vorschläge und auch (mehr oder weniger) gute Tipps sind Dinge, die sich mit diesen Werkzeugen gut erledigen lassen beziehungsweise die man von ihnen erwarten kann.
Nils Hartmann: Ein echter Geheimtipp ist es wohl nicht mehr, aber wenn Ihr React-Anwendungen baut, solltet Ihr euch die TanStack-Bibliotheken ansehen. Da gibt es viele bekannte Bibliotheken wie TanStack Table oder Query, die schon fast zum De-facto-Standard in React-Anwendungen gehören. Aber es finden sich auch neuere wie Start, Router oder Forms, die mit innovativen Ideen und neuen Konzepten aufwarten. Einige der Bibliotheken sind übrigens auch für andere SPA-Frameworks einsetzbar. Also auch Angular-, Vue- oder Svelte-Fans sollten mal einen Blick darauf werfen.
Katja Potensky: Gleich zwei: https://developer.mozilla.org/en-US/plus/docs/features/updates [2] und – unironisch – https://developer.mozilla.org/en-US/docs/Web/HTML/Element [3]. Einfach mal alle HTML-Elemente durchgehen, bevor man das nächste Mal Angular Material reinzieht.
Sebastian Springer: Die guten Vorsätze für das nächste Jahr: ein bisschen mehr Python machen – schließlich ist das jetzt die Programmiersprache Nummer 1 auf GitHub, dicht gefolgt von JavaScript und TypeScript (jeweils als eigene Programmiersprache). Aber Spaß beiseite: Gerade was die Arbeit mit KI und Daten angeht hat Python die mit Abstand aktivste Community, und allein deshalb lohnt es sich schon, sich eingehender damit auseinanderzusetzen.
Der zweite gute Vorsatz für 2025 ist, mehr Automatisierung zu betreiben – und zwar in nahezu allen Bereichen: von Maschinen und Geräten über digitale Produkte bis hin zum Entwicklungsprozess. Denn auch hier tut sich gerade sehr viel, und das nicht nur dank vieler neuer KI-basierter Werkzeuge.
Lena Rosenboom: Alles rund um das BFSG wird mich auf jeden Fall beschäftigen, und ich bin gespannt, welche Unternehmen erst einen Monat vorher merken, dass für sie Handlungsbedarf besteht.
Martina Kraus: Da ich in der Web Security tätig bin, werde ich mich wohl weiterhin stark mit passwortloser Authentifizierung beschäftigen. Der FIDO2-Standard bzw. Passkeys sind nach meiner Meinung die Zukunft der sicheren Authentifizierung in Mobile- oder Web-Applikationen. Tatsächlich schreibe ich sogar gerade an einem Buch über Authentifizierung und Autorisierung, daher werde ich mich vermutlich sogar noch tiefer mit Themen wie OAuth 2.1, OpenID Connect und FIDO auseinandersetzen dürfen, als ich ohnehin schon habe.
David Müllerchen: Ich halte ja nichts von Vorsätzen. Ich lass mich gerne treiben und überraschen. Aber ich möchte mehr mit AI-Tools spielen. Die neusten Dinge teste ich erstmal bei mir um Livestream. (Immer montags 20:00 https://webdave.tv [4]).
Manfred Steyer: Ich werde die Neuerungen in Sachen Angular verfolgen und zeigen, wie man damit auch die Architektur einer Anwendung besser mit Leben erfüllen kann. Daneben sind ein paar Neuerungen für Native Federation, das die Umsetzung von Micro Frontends auf Basis von Web Standards erlaubt, geplant.
Karsten Sitterberg: In den letzten zwei Jahren habe ich mich intensiv mit MQTT im Kontext von Angular beschäftigt, vor allem durch verschiedene Kundenprojekte. Für 2025 habe ich mir vorgenommen, dieses Wissen zum einen noch zu vertiefen. Zum anderen möchte ich dieses Wissen auf den Bereich Kollaboration und Schulungen übertragen. Ich plane, das von mir bereits erfolgreich eingesetzte Schulungssystem um innovative Funktionen zu erweitern, um so noch praxisnähere und interaktivere Lernformate anbieten zu können.
Katja Potensky: CSS. Diese Sprache wurde viel mächtiger in letzter Zeit und kann mittlerweile extrem viel JS-Code sparen.
Nils Hartmann: Ich bin gespannt, ob und wie sich das “Fullstack-Thema” auf JavaScript- bzw. React-Basis weiter durchsetzt und auch in der Masse Verbreitung findet. Mit Remix und dem noch ganz neuen TanStack Start stehen zwei Frameworks parat, die etwas leichtgewichtiger als Next.js sind und den Umstieg von einer klassischen Single-Page-Anwendung etwas einfacher machen.
Manfred Steyer: Ich denke, wir werden immer häufiger über reaktive Programmierung nachdenken, und das wird unsere Sicht auf den Code, den wir schreiben, verändern. Das Thema ist nicht neu. Gerade mit RxJS haben wir schon länger die Möglichkeit, eine Anwendung reaktiv aufzubauen. Da Signals nun aber direkt in viele Frameworks wie Angular einziehen, bekommt das Thema mehr Schwung, und somit wird reaktive Programmierung nicht nur eine Option sondern auch das Standardvorgehen werden.
Martina Kraus: Eindeutig weiterhin das Thema KI im Webbrowser. Die bereits erwähnten Webstandards WebNN und WebLLM benötigen noch etwas Feinschliff, und ich bin überzeugt, dass wir noch ganz am Anfang sind in Sachen KI-Unterstützung im Webbrowser. Mit Googles Gemini kann ich direkt in DevTools mit Gemini chatten und erhalte Informationen darüber, warum gewisse Elemente aus dem DOM-Baum auf eine bestimme Weise dargestellt werden und kann Netzwerkanfragen und deren Performance intelligenter und schneller analysieren, als ich es selbst anhand der gegebene Daten tun könnte. Viele sind natürlich jetzt schon genervt von dem ganze KI-Hype, was nach meiner Meinung allerdings oftmals mit einer falschen Erwartungshaltung zu tun hat, was KI für uns tun kann. Als Security Engineer sehe großes Potential in der Unterstützung Security Scans, der Auswertung von Monitoring-Daten oder auch bei der Detektion von Angriffen auf meine Systeme.
Sebastian Springer: Der Trend, den ich am liebsten an Fahrt aufnehmen sehen würde, ist das Immersive Web – also die Arbeit mit 3D im Web in Kombination mit AR und VR. Apple hat hier dank der Vision Pro sehr gute Arbeit geleistet, und auch Meta geht mit ihrem Horizon OS in eine spannende Richtung. Bleibt abzuwarten, ob sich AR- und VR-Geräte in großer Stückzahl durchsetzen werden.
Etwas realistischer sieht es dann schon wieder bei der KI aus. Hier flaut der erste Hype endlich ab und weicht einem etwas vernünftigeren Mindset. Spannend werden die immer leistungsfähigeren SLMs wie Qwen 2.5 oder Llama 3.2, die nicht die teuren H100-Karten benötigen, um mit halbwegs akzeptabler Geschwindigkeit ausgeführt zu werden. Damit ergibt sich für uns die Möglichkeit, mit kleineren lokalen Modellen zu arbeiten, spezialisierte Applikationen zu erstellen und mit AI-Agenten ganz neue Anwendungsfälle umzusetzen.
Auch der SEO-Bereich könnte in nächster Zeit gehörig umgekrempelt werden, wenn die LLMs von OpenAI & Co plötzlich in der Lage sind, das (aktuelle) Internet zu durchsuchen und somit in Konkurrenz mit etablierten Suchmaschinen treten.
Karsten Sitterberg: Ich sehe einen klaren Trend, dass Unternehmen verstärkt in die Qualifikation ihrer Mitarbeiter investieren. Das zeigt sich auch in der steigenden Nachfrage nach Schulungen, die ich in den letzten Jahren erlebt habe. Der Fokus liegt zunehmend darauf, den Fachkräftemangel durch gut ausgebildete Mitarbeiter zu kompensieren. Qualifizierte Teams können mit weniger Personal effizienter arbeiten – ein Ansatz, der meiner Meinung nach 2025 noch an Bedeutung gewinnen wird.
Lena Rosenboom: Das Thema Inklusion ist für unsere digitale Gesellschaft sehr wichtig und wird, auch durch das BFSG, immer wichtiger werden. Entwickler müssen lernen, dass barrierefreier Code genauso dazu gehört wie ein sauberer Code. Wir müssen das Wissen in unseren Alltag integrieren, sodass es irgendwann nichts Besonderes mehr ist, barrierefreie Lösungen zu bauen.
Katja Potensky: Die weitere Abkehr von schwergewichtigen Frameworks wie Angular. HTMX hat hier eine Re-(De?-)volution losgetreten, und viele Entwickler fragen sich, warum sie für denselben Effekt zehn Stunden aufwenden sollen, wenn sie es auch in einer Stunde genauso gut tun könnten. Natürlich wird auch hier das Pendel zu weit ausschlagen, aber grundlegend ist weniger Komplexität schon eine gute Sache. In Verbindung mit dem Trend zu A11y und etablierten Patterns wird WebDev vielleicht sogar so einfach, dass man nicht mehr ständig vor technischen Herausforderungen steht, sondern sich auf die User Needs konzentrieren kann.
Vielen Dank für eure Antworten!
The post JavaScript News & Trends in 2024 sowie Zukunftsprognose für 2025 appeared first on JavaScript & Angular Days.
]]>The post TypeScripts Metaprogrammiersprache appeared first on JavaScript & Angular Days.
]]>Dieses Problem adressiert TypeScript, das ein statisches Typsystem für JavaScript zur Verfügung stellt. Um der Komplexität, die sich aus dem dynamischen Typsystem von JavaScript ergibt, zu begegnen, kann man in TypeScript nicht nur normale Typannotationen hinschreiben, sondern mit einer Art Metaprogrammiersprache auch programmatisch Typen beschreiben. Diese Metasprache wird im Folgenden anhand einer aus technischer Sicht typischen JavaScript-Funktion erläutert, für die die korrekten Typen entwickelt werden sollen. Der Quellcode zu diesem Artikel ist auch online in einem „TypeScript Playground“ unter [1] zu finden.
Listing 1 zeigt die Funktion, deren fachliche Idee es ist, ein Objekt mit Werten zu überprüfen. Sie soll damit exemplarisch für Funktionen stehen, die mehr oder weniger beliebige Daten entgegennehmen und in veränderter Form zurückliefern. Die validate-Funktion erwartet zwei JavaScript-Objekte, einerseits ein Objekt values, das die Menge der zu prüfenden Werte enthält. Der zweite Parameter ist ebenfalls ein JavaScript-Objekt, rules, das Callback-Funktionen enthält, mit denen die validate-Funktion die einzelnen Werte des values-Objekts überprüfen kann. Die Implementierung der validate-Funktion würde für jeden Eintrag im values-Objekt die validate-Funktion für die entsprechende Regelfunktion aus dem rules-Objekt aufrufen. Diese liefert dann jeweils das Prüfergebnis für einen Eintrag zurück (true oder false, je nachdem, ob der Wert gültig ist oder nicht). Wenn unter einem Key im values-Objekt eine Funktion abgelegt ist, soll deren Rückgabewert an die rules-Funktion übergeben werden.
Damit die validate-Funktion weiß, welche Funktion im rules-Objekt zu welchem Feld im values-Objekt gehört, sollen diese Funktionen per Konvention unter einem Key abgelegt sein, der ähnlich heißt wie der zugehörige Key im values-Objekt: Der Name soll dem Muster validateFeldname folgen, also beginnend mit dem String validate und gefolgt von dem Key-Namen mit großem Anfangsbuchstaben. Wenn es also im rules-Objekt einen Eintrag firstname gibt, der auf den Typ string gesetzt ist (bzw. dessen Wert vom Typ string ist), erwartet die validate-Funktion im rules-Objekt unter dem Key validateFirstname eine Funktion, die einen Parameter vom Typ string entgegennehmen kann und die ihrerseits ein boolean zurückliefert. Gibt es diese Funktion im rules-Objekt nicht, soll der Wert ohne Prüfung als gültig gelten.
Die validate-Funktion liefert dann ein Objekt, in dem die Ergebnisse der Prüfungen enthalten sind. Darin entsprechen die Namen der Keys den Keys im values-Objekt und die Werte sind jeweils das Ergebnis der Validierung (true oder false).
Listing 1
function validate(values, rules) {
// Implementierung ausgelassen
}
const person = {
firstname: "Klaus",
age: 32,
sallary() { return 60000 }
}
const personRules = {
validateFirstname(s) {
return s.length > 3
},
validateSallary(n) {
return n > 1000;
}
}
const result = validate(person, personRules);
if (result.firstname === false) {
// firstname ungültig
}
if (result.sallary === true) {
// salary gültig
}
In TypeScript können die oben beschriebenen Regeln auf Typebene mit einem Mapped Type beschrieben werden. Damit können Regeln ausgedrückt werden, wie: „Welche Keys auch immer das Objekt enthält, das der validate-Funktion als ersten Parameter übergeben wird, der Rückgabetyp sieht so aus, dass darin alle Keys des übergebenen Objekts vorhanden sind, die Werte darin aber vom Typ boolean sind.“ Oder: „Der zweite Parameter soll ein Objekt sein, in dem Keys enthalten sind, die der genannten Namenskonvention entsprechen – und keine darüber hinausgehenden Keys.“
Dadurch weiß TypeScript dann, und zwar ohne dass dafür für jedes zu validierende Objekt Typen manuell geschrieben werden müssten, wie Funktionsargumente und Rückgabetyp aussehen. Mit dieser Information kann TypeScript dann (wie bei einem explizit definierten Typ) die korrekte Verwendung überprüfen und bei der Entwicklung mit Code Completion etc. assistieren. Das Konzept der Mapped Types ist sehr mächtig und wird im Folgenden Schritt für Schritt an eigenen Beispielen vorgestellt. Zum Schluss kann dann die validate-Funktion komplett typsicher beschrieben werden.
Um allgemeine Typen oder allgemeine Funktionssignaturen zu beschreiben, werden in TypeScript wie auch in anderen Programmiersprachen Generics verwendet. Damit können an einer Funktion Typvariablen deklariert werden, die beispielsweise den Typ eines Funktionsarguments annehmen und an anderen Stellen in der Funktionssignatur wiederverwendet werden können. Dazu ein Beispiel: Eine fiktive Funktion saveToDatabase soll einen beliebigen Wert in eine Datenbank schreiben. Möglicherweise verändert sie dabei diesen Wert auch (zum Beispiel durch Hochsetzen eines Versionszählers) und liefert den aktualisierten Wert an den Aufrufer zurück. Grundsätzlich könnte die Methodensignatur in TypeScript den any-Typ verwenden, an den alle Werte zugewiesen werden können (Listing 2). Dieser Typ kann sowohl für das Funktionsargument als auch für den Rückgabetyp verwendet werden.
Das Problem: TypeScript weiß dann beim Rückgabetyp nicht, um welchen genauen Typ es sich handelt. Präziser ist es, auszudrücken, dass der Funktionsparameter zwar ein beliebiger Typ sein darf, aber der Rückgabetyp ebenfalls genau diesem Typ entspricht. Dazu kann mit einer Typvariable eine generische Funktion beschrieben werden (Listing 3). Die Typvariablen werden in spitzen Klammern deklariert und können dann in der Funktionssignatur überall verwendet werden, wo TypeScript eine Typangabe erwartet. Hier also im Funktionsparameter und als Rückgabetyp.
Listing 2
function saveToDatabase(value: any): any { /* Implementierung ausgelassen */ }
const result = saveToDatabase({firstname: "Klaus"});
// result ist hier any, sodass keine weitere Typprüfung stattfindet
result.firstname; // OK
result.lastname; // Kein Compile-Fehler, obwohl lastname nicht am Objekt definiert ist
function saveToDatabase<O>(value: O): O { /* Implementierung ausgelassen */ };
const result = saveToDatabase({ firstname: "Klaus" });
result.firstname; // OK
result.lastname; // ERR: Property 'lastname' does not exist
Die saveToDatabase-Funktion kann nun mit beliebigen Typen aufgerufen werden, also mit Objekten, primitiven Typen wie string oder number, aber auch mit null oder undefined. Nicht immer ist dieses Verhalten gewünscht. In vielen Fällen gibt es Restriktionen, welche Typen verwendet werden dürfen. Im Fall der saveToDatabase-Funktion wäre es denkbar, dass diese nur mit Objekten arbeiten kann. Dazu können bei einer Typvariable mit dem Schlüsselwort extends Restriktionen beschrieben werden, die bei der Verwendung der Funktion bzw. der Typvariable eingehalten werden müssen. Listing 4 zeigt zwei weitere Beispiele möglicher Signaturen für die saveToDatabase-Funktion. Die erste Signatur gibt an, dass der Funktion ein beliebiges Objekt übergeben werden darf (aber eben kein string, number, null etc.). Die zweite Signatur schränkt die Vorgabe noch weiter ein und drückt aus, dass das übergebene Objekt mindestens eine Eigenschaft id haben muss, die vom Typ string sein muss.
Listing 4
function saveToDatabase<O extends object>(value: O): O { /* ... */ };
saveToDatabase("Klaus"); // Fehler: "Klaus" ist kein object
function saveToDatabase<O extends { id: string }>(value: O): O { /* ... */ };
saveToDatabase({
// Fehler: id-Property fehlt
firstname: "Klaus"
})
Ein gängiger Anwendungsfall ist, dass an eine Funktion nicht nur ein Objekt, sondern ein String mit einem gültigen Key aus dem Objekt übergeben werden soll. Auch diese Einschränkung lässt sich mit TypeScript abbilden, sodass es zu einem Compile-Fehler kommt, wenn der Funktion ein String übergeben wird, der nicht dem Namen eines Keys entspricht.
Listing 5 zeigt eine addListener-Funktion, die in der Lage sein soll, eine Listener-Funktion (z. B. einen Event Handler) für einen angegebenen Eintrag in einem beliebigen Objekt zu registrieren. Dazu werden ihr drei Parameter übergeben: das Objekt, der Name eines Keys aus dem Objekt und die eigentliche Listener-Funktion, die für dieses Feld registriert werden soll.
Listing 5
function addListener<O extends object>(
object: O,
key: string,
listenerFn: Function) { }
const person = {
firstname: "Klaus",
age: 32
}
addListener(person, "firstname", (newValue) => { /*... */ });
addListener(person, "age", (newValue) => { /*... */ });
// Dieser Aufruf sollte einen Compile-Fehler ergeben:
addListener(person, "lastname", (newValue) => { /*... */ });
Listing 6
type Red = "red";
type Black = "black";
// Variablen, Argumente etc. vom Typ Red können jetzt nur den String// "red" annehmen, alles andere führt zu Compile-Fehlern
const redColor: Red = "red"; // OK
const blackColor: Red = "black" // ERR
type Colors = Red | Black;
const r: Colors = "red"; // OK
const b: Colors = "black"; // OK
const g: Colors = "grey"; // ERROR
Listing 7
const person = {
firstname: "Klaus",
age: 32
}
type KeysOfKlaus = keyof typeof person;
// "firstname" | "age"
Listing 8
function addListener<O extends object, K extends keyof O>(
obj: O,
name: K,
listener: Function
) {
// ...
}
const person = {
firstname: "Klaus", age: 32
};
addListener(person, "firstname", v => { /* ... */ }); // OK
addListener(person, "lastname", v => { /* ... */ }); // ERR:
// Argument of type "lastname" is not // assignable to parameter of type "firstname" // | "age"
Die an addListener übergebene Callback-Funktion (dritter Parameter) wird aufgerufen, sobald sich der Wert im Objekt, für den sie registriert wurde, verändert. Der neue Wert soll ihr dann von addListener übergeben werden. Aus diesem Grund muss die Callback-Funktion über genau ein Argument verfügen. Und dieses Argument muss vom selben Typ sein, den auch der zugehörige Eintrag im überwachten Objekt hat. Für den Key “firstname”, der vom Typ string ist, muss eine Listener-Funktion übergeben werden, die ein Argument vom Typ string hat. Für das Feld “age” müsste die Callback-Funktion ein Argument vom Typ number haben.
In der Signatur der gezeigten addListener-Funktion in Listing 8 wird für den Parameter, mit dem die Listener-Funktion übergeben wird, zurzeit noch der Typ Function verwendet, der eine beliebige Funktion beschreibt und keine Aussage über Funktionsargumente oder -rückgabewerte macht. Funktionen können mit TypeScript aber auch präzise beschrieben werden. Die Callback-Funktion für den firstname-Listener, die genau ein Argument vom Typ string erwartet und nichts zurückliefert, kann wie folgt beschrieben werden: (newValue: string) => void.
Um das Argument für eine Callback-Funktion für addListener korrekt zu typisieren, benötigen wir den Typ, den der zugehörige Eintrag im übergebenen Objekt hat. Typen von Einträgen in einem Objekt können mit einem Index Accessed Type ermittelt werden. Die Notation ist dabei identisch mit dem Indexoperator in JavaScript, nur dass man kein Objekt angibt, sondern einen Typ (Listing 9). Da wir in der Signatur der addListener-Funktion sowohl den Typ des Objekts (Typvariable O) als auch den Namen des Keys (Typvariable K) daraus kennen, können wir folglich den Typ aus dem übergebenen Objekt für den übergebenen Key abfragen und diesen für die Beschreibung der Signatur der erwarteten Listener-Funktion verwenden (Listing 10). Damit ist die addListener-Funktion vollständig beschrieben. Der Aufrufer dieser Funktion hat volle Typsicherheit, ohne beim Verwenden selbst einen einzigen Typ angeben oder gar beschreiben zu müssen!
Listing 9
const person = {
firstname: "Klaus",
age: 32,
address: { city: "Hamburg" }
};
type Person = typeof person;
type Age = Person["age"]; // number
type Address = Person["address"]; // { city: string }
function addListener<O extends object, K extends keyof O>(
obj: O,
name: K,
listener: (newValue: O[K]) => void
) {
// ...
}
// Beim Verwenden der addListener-Funktion ist keine Angabe eines Typs erforderlich:
const person = {
firstname: "Klaus",
age: 32,
};
addListener(person, "firstname", (newValue) => { /* ... */ }); // OK
// Dieser Aufruf geht nicht:
// ERR: Argument of type "lastname" is not assignable to parameter of type "firstname" // | "age"
addListener(person, "lastname", (newValue) => { /* ... */ });
function ageChangeListener(newAge: number) { /* ... */ }
addListener(person, "firstname", ageChangeListener);
// ERR: Argument of type '(newAge: number) => void' is not assignable to parameter of // type '(newValue: string) => void'.
// Types of parameters 'newAge' and 'newValue' are incompatible.
Zurück zur eingangs beschriebenen validate-Funktion. Diese soll ein Objekt zurückliefern, das dieselben Keys wie das übergebene values-Objekt enthält. Die Werte darin sollen allerdings jeweils vom Typ boolean sein. Eine solche Regel lässt sich in TypeScript mit Mapped Types ausdrücken. Ein Mapped Type nimmt einen bestehenden Typ und bildet ihn auf einen neuen Typ ab. Der bestehende Typ kann entweder ein festkodierter oder, wie im Fall der validate-Funktion notwendig, ein generischer Typ sein. Unabhängig davon kann in der Beschreibung des Mapped Type dann über die Keys des Ausgangstyps mit keyof iteriert werden und jedem Eintrag dabei ein neuer Typ zugewiesen werden.
Eine erste Variante für die Typbeschreibung der validate-Funktion mit einem Mapped Type findet sich in Listing 11. Hier wird sichergestellt, dass das übergebene rules-Objekt eine Untermenge des übergebenen values-Objekts ist (durch das Hinzufügen des Fragezeichens im Mapped Type werden die Einträge optional gemacht). Die Einträge, die gesetzt sind, müssen dann aber eine Funktion sein, die als Parameter einen Typ mit demselben Wert wie im values-Objekt entgegennimmt (ähnlich wie im addListener-Beispiel). Um die Anforderung, dass die Keys auch umbenannt werden sollen (validateFeldname), kümmern wir uns später. Der Rückgabetyp ist sogar schon vollständig gemäß den Anforderungen beschrieben, nach denen das zurückgegebene Objekt alle Keys aus dem values-Objekt enthält, deren Werte aber jeweils vom Typ boolean sind.
Hier kann TypeScript die korrekte Angabe des rules-Objekts sowie die korrekte Verwendung des Rückgabetyps sicherstellen, ohne dass dafür vom Aufrufer der validate-Funktion eine Typangabe erfolgen müsste.
Listing 11
type Rules<O extends object> = {
[K in keyof O]?: (value: O[K]) => boolean;
};
type ValidatedObject<O extends object> = {
[K in keyof O]: boolean;
};
function validate<O extends object>(
values: O, rules: Rules<O>
): ValidatedObject<O> {
return /* ... */;
}
const person = {
firstname: "Klaus", age: 32
}
const result = validate(person, {
firstname(v) {
// v ist hier korrekt als string
return v.length > 3
},
age(a) {
// a ist hier korrekt als number
return a > 0;
}
})
result.age; // OK, boolean
result.lastname; // ERR: lastname gibt‘s nicht // an result
// Auch OK: keine rule-Funktion für 'age'
validate(person, {
firstname(v) {
return v.length > 3
},
});
// Der Typ des Rule-Objekts für ein konkretes // Objekt lässt sich auch ermitteln, um diesen// bei Bedarf explizit angeben zu können:
type PersonRules = Rules<typeof person>;
const rules: PersonRules = {
firstname(n) { return n.length > 3; }, // OK
lastname(l) { return true } // ERR: lastname // nicht in 'person'
}
Wenn in einem übergebenen values-Objekt unter einem Key eine Funktion abgelegt ist, soll die validate-Funktion beim Validieren diese Funktion aufrufen und ihren Rückgabewert an die zugehörige rules-Funktion übergeben. In diesem Fall muss also der Typ im rules-Objekt dem Rückgabetyp der Funktion im values-Objekt entsprechen (Listing 12)
Listing 12
const person = {
firstname: "Klaus",
salutation: function () {
"Hello, " + this.firstname;
}
};
// Erwarteter Typ des zugehörigen rules-Objekt:
type RulesForPerson = {
firstname?: (newValue: string) => boolean;
// person.salutation gibt einen string zurück, // folglich muss hier newValue auch string sein:
salutation?: (newValue: string) => boolean;
};
Listing 13
function getLength<O extends string | null>(s: O)
: O extends string ? number : null
{ /* ... */ }
const l = getLength("Moin!"); // l ist // "number"
const n = getLength(null); // n ist "null"
const x = getLength(7); // ERR: 7 ist // kein string
Listing 14
type Rules<O extends object> = {
[K in keyof O]?: O[K] extends Function
? (value: string) => boolean
: (value: O[K]) => boolean;
};
// ValidatedObject und validate-Function // unverändert
const person = {
firstname: "Klaus",
salutation() {
return "Hello, " + this.firstname;
}
}
validate(person, {
firstname(f) { return f.length > 3 },
salutation(s) {
// s ist hier 'string' (nicht 'Function' // wie in 'person')
return s.length > 7;
}
});
Listing 15
type GetReturnType<O> = O extends (...args: any) => infer R ? R : O
function sayHello() { return "Hello" };
type SayHelloReturn = GetReturnType<typeof sayHello>; // string
function sayNothing() { };
type SayNothingReturn = GetReturnType<typeof sayNothing>; // void
type NotAFunction = GetReturnType<string>; // string
Listing 16
type RuleFunction<O extends object, K extends keyof O> =
O[K] extends (...args: any) => infer R
? (value: R) => boolean
: (value: O[K]) => boolean;
type Rules<O extends object> = {
[K in keyof O]?: RuleFunction<O, K>
};
// ...ValidatedObject und validate-// Function unverändert...
const person = {
firstname: "Klaus",
salutation() { // liefert string zurück
return "Hello, " + this.firstname;
},
sallary() { // liefert number zurück
return 123456;
}
}
validate(person, {
salutation(s) {
// s ist hier ‘string’
return s.length > 7;
},
sallary(n) {
// n ist hier korrekt number
return n > 1000;
}
});
Es sind jetzt fast alle Anforderungen an die Typen der validate-Funktion umgesetzt. Allerdings sind die erwarteten Key-Namen im rules-Objekt noch nicht korrekt. Diese sollen nicht feldname, sondern validateFeldname heißen (also validate und dann der Name des Keys aus dem values-Objekt mit führendem Großbuchstaben).
Zur Erinnerung: die keyof-Funktion liefert eine Menge konkreter Ausprägungen von Strings zurück (first-name, age etc.). Dabei handelt es sich aber nicht um Werte, sondern um Typen. (String-)Werte können in JavaScript mit einem Templatestring verändert werden. Dazu kann in einem Templatestring ein Platzhalter definiert werden. Ein vergleichbares Konzept gibt es in TypeScript auch auf Typebene. Ein sehr triviales Beispiel, das nur dazu dient, diese Syntax zu illustrieren, ist in Listing 17 zu sehen. Der sayHello-Funktion wird ein beliebiger String übergeben. Aus diesem String wird ein „Hallo“-Gruß erzeugt und zurückgeliefert. Der Rückgabetyp drückt das ebenfalls aus. Zurück liefert die Funktion nämlich nicht den allgemeinen Typ string, sondern den ganz konkreten String, der auch dem zurückgelieferten Wert entspricht.
Neben der sayHello-Funktion ist ein fachlich sinnvolleres Beispiel zu sehen. Die setSpacing-Funktion soll ein Spacing und eine Größenangabe entgegennehmen. Damit sollen, wie in CSS üblich, Abstände ausgedrückt werden (padding oder margin), die sich auf eine Seite (top, right etc.) beziehen. Außerdem soll der Abstand mit einer Einheit übergeben werden. Beide Argumente können mit einem Template Literal Type beschrieben werden.
Für das Spacing sollen zwei Listen mit konkreten Strings (Abstand und Seite) kombiniert werden. Wenn in einem Template Literal Type ein oder mehrere Union Types verwendet werden, besteht der erzeugte Typ aus einer Kombination aller Ausprägungen aller Union Types (margin-top, padding-top, margin-left, padding-left und so weiter). Für die Größe soll eine beliebige Zahl mit den Abständen angegeben werden. Dazu wird für die Einheit ebenfalls ein Union Type mit den erlaubten Abständen (px, em, rem) definiert, der hier mit dem allgemeinen number-Typ kombiniert wird, sodass alle möglichen Zahlen verwendet werden können. Die setSpacing-Funktion kann mit diesen Typangaben nun nur noch mit korrekten Werten aufgerufen werden, auch wenn es sich bei den Parametern aus JavaScript-Sicht um „normale“ Strings handelt.
Listing 17
function sayHello<S extends string>(s: S): `Hello, ${S}!` {
return `Hello, ${s}!`;
}
const s = sayHello("Susi"); // Typ von S: "Hello, Susi!"
type Spacing = "margin" | "padding";
type Direction = "top" | "right" | "bottom" | "left";
type Unit = "px" | "em" | "rem"
function setSpacing(s: `${Spacing}-${Direction}`, size: `${number}${Unit}`) {
/* ... */
}
setSpacing("margin-right", "2rem"); // OK
setSpacing("padding-center", "2rem"); // ERROR: "padding-center" ungültig
setSpacing("padding-left", "2pt"); // ERROR: "2pt" ungültig
// (Unit 'pt' gibt es nicht)
Um einen Key in einem Mapped Type umzubenennen, wird das Schlüsselwort as verwendet, mit dem in TypeScript ein Type Cast durchgeführt wird. In diesem Fall soll der bestehende Typ (Name des Keys) auf einen neuen Typ (umbenannter Key) gecastet werden: [K in keyof O as RuleFnName <K>]?: RuleFunction<O[K]>.
Leider ist der Aufruf der ValidateFnName-Funktion hier allerdings so nicht möglich. Dieser Typ erwartet nämlich einen String für die Typvariable (S extends string). Der Union Type, der von keyof zurückgeliefert wird, kann allerdings auch number– oder symbol-Typen enthalten. Um Keys dieser Typen auszuschließen, kann man einen Trick anwenden. Dazu wird der keyof-Ausdruck in einen Intersection Type umgeschrieben, der den Union Type mit den einzelnen Keys (keyof O) und dem allgemeinen Typ string zusammenführt. Ein Intersection Type enthält nur die (kompatiblen) Schnittmengen von zwei Typen. In unserem Fall werden dadurch alle Nichtstrings herausgefiltert. Die nun vollständige Typdefinition für die validate-Funktion ist in Listing 18 zu sehen.
Listing 18
type RuleFnName<S extends string> = `validate${Capitalize<S>}`
type RuleFunction<T> = T extends (
...args: any
) => infer R
? (value: R) => boolean
: (value: T) => boolean;
type Rules<O extends object> = {
[K in keyof O & string as RuleFn-Name<K>]?: RuleFunction<O[K]>;
};
// ValidatedObject und validate // unverändert
const person = {
firstname: "Klaus",
salutation() {
return "Hello, " + this.firstname;
},
sallary() {
return 123456;
},
}
const rules: Rules<typeof person> = {
validateFirstname(a) {
return a.length > 3;
},
validateSallary(n) {
return n > 100;
}
}
const result = validate(person, rules);
const firstNameValid: boolean = result.firstname; // OK
const sallaryValid: string = result.sallary; // ERR: Type ‘boolean’ is not assignable // to type 'string'
const addressValid = result.address; // ERR: Property 'address' does not exist // on type 'ValidatedObject<...>'
TypeScript ist mehr als ein „normales“ statisches Typsystem. Mit seinen spezialisierten Typen und Konzepten wie Mapped Types, Conditional Types, Template Literal Types und Generics lassen sich dynamisch Typen erzeugen, mit denen auch komplexe Codestrukturen typsicher beschrieben werden können, die ihrerseits von den Möglichkeiten des dynamischen Typsystems von JavaScript Gebrauch machen.
Der Einsatz dieser „Metasprache“ lohnt sich insbesondere bei Code, der ein API darstellt und von vielen Verwendern genutzt wird. Diese profitieren dann nämlich von typsicherem Code, ohne selbst Typdefinition schreiben zu müssen. Für einige gängige Anforderungen stehen bereits fertige Utility-Typen zur Verfügung, die in der TypeScript-Dokumentation beschrieben sind.
[1] https://react.schule/entwickler-ts
The post TypeScripts Metaprogrammiersprache appeared first on JavaScript & Angular Days.
]]>The post Aftermovie JavaScript Days März 2024 appeared first on JavaScript & Angular Days.
]]>The post Aftermovie JavaScript Days März 2024 appeared first on JavaScript & Angular Days.
]]>The post JavaScript- und .NET-Trends: Was hat sich 2023 getan? Was kommt 2024? appeared first on JavaScript & Angular Days.
]]>
Christian Liebel: Ganz eindeutig Generative AI. Die Veröffentlichung von ChatGPT Ende 2022 war ein wahrer iPhone-Moment. 2023 kam dann enorm viel Bewegung in dieses Thema: Open-Source-Modelle wurden im Wochentakt kleiner, besser und schneller. Urplötzlich reichen unsere Bandbreiten, Rechen- und Speicherkapazitäten nicht mehr aus. All das gab es in der IT schon lange nicht mehr.
Manfred Steyer: 2023 stand für mich im Zeichen der Angular-Renaissance: Das Framework erneuert sich von innen heraus und wird moderner. Dabei nimmt es Trends, die sich in anderen kleineren Frameworks bewährt haben, auf: Partial Hydration, verzögertes Laden von Seitenbereichen, ein neuer Control Flow und Signals.
Das Angular-Team achtet bei diesen Neuerungen sehr stark auf Abwärtskompatibilität. Bestehender Code wird nicht gebrochen, sondern das Framework bekommt nach und nach neue Features. Das ist ein wichtiges Zeichen, da gerade viele langlebige Unternehmensanwendungen auf Angular setzen. Hier braucht es also Nachhaltigkeit. Trotzdem muss das Framework relevant bleiben. Dazu braucht es auch Neuerungen. Wir haben es also mit einem Spagat zwischen Nachhaltigkeit und Neuerungen zu tun und den scheint das Angular-Team echt gut hinzubekommen.
Außerdem ist der auf Signals basierende NgRx Signal Store echt cool: Er ist leichtgewichtig und super erweiterbar. Wiederkehrende Anforderungen lassen sich damit sehr elegant zentral umsetzen. Als Beispiel nenne ich gerne meine Erweiterungen, die es mir erlauben, mit 7 Zeilen Code einen Store für einen CRUD Use Case inkl. Undo/Redo zu implementieren.
Nils Hartmann: Für jemanden, der sich viel mit React beschäftigt, war natürlich die „Fullstack-Empfehlung“ der Moment des Jahres 2023. Im März fand sich plötzlich die Empfehlung auf der React-Homepage, React-Anwendungen nur noch mit einem Fullstack-Framework wie Next.js oder Remix zu bauen. Das hat für einigen Wirbel in der React-Szene (und auch außerhalb) gesorgt. Leider finde ich die Begründung des React-Teams für diesen Schritt bzw. diese Empfehlung zu undifferenziert und in Teilen sogar falsch, auch wenn die Fullstack-Frameworks viele gute Ideen und nützliche Features mitbringen.
Auch wenn man natürlich weiterhin mit React Single-Page-Anwendungen bauen kann, hätte ich es begrüßt, wenn man differenzierter argumentiert hätte (das gilt auch für Teile der meinungsstarken Community), und zum Beispiel den Framework-Weg als zusätzliche Option für React angeboten hätte. So empfinde ich es fast schon als Schlag ins Gesicht derer, die seit Jahren React als Basis für ihre Single-Page-Anwendungen einsetzen und – aus welchen Gründen auch immer – nicht auf ein Framework umstellen wollen oder können.
Tam Hanna: Wir sehen in vielerlei Hinsicht ein Streamlining im Bereich der angekündigten Technologien. AI hat sich gut etabliert, viele angekündigte Produkte – zum Beispiel der Bluetooth-SoC auf RISC/V-Basis von GigaDevice – wurden zumindest in Musterstückzahlen verfügbar.
Veikko Krypczyk: 2023 war geprägt von bedeutenden Fortschritten in der künstlichen Intelligenz, insbesondere in Bereichen wie Sprachverarbeitung und Bildgenerierung. Tools wie GPT-4 und DALL-E haben die Interaktion mit KI und die Erzeugung kreativer Inhalte revolutioniert. Ebenso interessant war ein zunehmender Fokus auf umweltfreundliche Technologien und nachhaltige Entwicklung, vor allem im Bereich der erneuerbaren Energien und der Green IT.
Maria Haubner: Mein Team und ich hatten dieses Jahr besonders viel Spaß dabei uns neue Technologien anzueignen. Beispielsweise haben wir uns intensiv mit Flutter und mobiler Entwicklung beschäftigt. Dabei waren wir auch begeistert von den Entwicklungen, die Flutter im Bereich Web und Desktop geliefert hat. Wir waren positiv überrascht davon, in welcher Geschwindigkeit sich Apps damit Multi-Plattform entwickeln lassen.
Liebel: Das Web entwickelt sich stetig weiter, so auch in diesem Jahr. Interessant war, dass Apple, Google und Microsoft sich auf die Installationskriterien für Progressive Web Apps geeinigt haben: Nämlich, dass es gar keine geben soll. Gerade rechtzeitig zum GenAI-Hype erschien das WebGPU-API in den Chromium-basierten Browsern, das die lokale Ausführung von KI-Modellen direkt im Browser erheblich beschleunigt. Nächstes Jahr werden Third-Party-Cookies in Chrome abgeschaltet. Das ist an sich richtig, aber ich bin gespannt, wo das überall noch zu Problemen führen wird.
Steyer: Man hat wieder einmal gesehen, dass die großen JavaScript-Frameworks – React, Angular und Vue – fest im Sattel sitzen. Das gibt der Community Stabilität. Trotzdem herrscht kein Stillstand: Viele kleinere Frameworks zeigen Mittel ohne Wege auf, bestimmte Sachen besser zu machen. Diese Konkurrenz ist wichtig, weil es die großen Drei dazu bewegt, jene Konzepte, die sich bewähren, aufzunehmen. Genau das ist in diesem Jahr wieder einmal passiert.
Hartmann: Die Möglichkeiten der Webentwicklung sind in den vergangenen Monaten weiter gewachsen. Es gibt für viele Anforderungen und Geschmäcker mittlerweile passende Konzepte, Tools und Bibliotheken – und das betrifft nicht nur die JavaScript-Entwicklung. Wenn man keine Single-Page-Anwendung bauen möchte, hilft einem vielleicht HTMX weiter. Bin ich auf eine sehr schnelle erste Darstellung und eine sehr kurze Zeit bis zur ersten Interaktion angewiesen, können mir vielleicht neue Frameworks wie Astro oder Qwik weiterhelfen. Konkret in React gefallen mir die neuen Möglichkeiten, wie asynchrone Serverkomponenten oder auch die Suspense-Features, die man jetzt auch auf dem Client nutzen kann (z.B. mit der TanStack Query-Bibliothek).
Hanna: Der Fokus liegt meines Erachtens in vielerlei Hinsicht auf Produktivitätssteigerungen. Das ist meiner Ansicht nach erfreulich, auch in Anbetracht des nach wie vor akuten Personalmangels.
Krypczyk: Positiv beurteile ich die Weiterentwicklung von KI-Technologien, die eine breite Anwendung in verschiedenen Industrien ermöglichen. Ich habe einige Bedenken hinsichtlich Datenschutz und ethischen Aspekten im Zusammenhang mit KI und Überwachungstechnologien. Die Einführung von fortschrittlichen KI-Modellen und die zunehmende Integration von KI in den Alltag ist ein echter Höhepunkt.
Haubner: Es gab viele spannende Entwicklungen im React-Umfeld, insbesondere bei Server-Side Rendering, Suspense und State-Management. Was ich in der Javascript-Welt als negativ empfinde ist, dass es ein sehr diverser Teppich aus Tools und Anwendungen ist. Das macht es schwierig immer richtig einzuschätzen, welche Lib einen Reifegrad erreicht hat, die sie Production-Ready macht. Gleichzeitig habe ich dieses Jahr den Eindruck gewonnen, dass sich mehr größere Firmen in diesen Open-Source-Bereich hereinwagen und damit mehr Substanz hinter Libraries kommt und sich die Stabilität und der Reifegrad von einigen Tools erhöht.
Highlight meines Team-Kollegen ist, dass die Dev-Toolchain scheinbar seit diesem Jahr auf Rust umzieht. Das erhöht die Geschwindigkeit und Stabilität.
Liebel: Abgesehen von GitHub Copilot bzw. Jetbrains AI, die als neue Tools Einzug in meinen Workflow erhalten haben, finde ich Tauri als Alternative zu Electron enorm spannend. Es ist Secure by Default, nutzt Rust im Backend und die entstehenden Programmpakete sind dank Verwendung der plattformspezifischen Webview signifikant kleiner als ihre Gegenstücke in Electron.
Steyer: Ich habe mich ein wenig mit Solid.js und seiner Store-Implementierung beschäftigt. Beides nutzt schon seit längerer Zeit das Signals-Konzept. Ryan Carniato, der hinter Solid.js steht, gilt als Vordenker in Sachen Signals für JavaScript-Frameworks. Von ihm kann man also einiges lernen. Insofern verwundert es auch nicht, dass das Angular-Team auf Ryans Erfahrungen zurückgegriffen hat. Ein Blick auf Solid.js hat mir geholfen, besser zu verstehen, wie Signals die Zukunft von Angular prägen können.
Ähnlich war es mit Qwik und Astro. Beide sind auf ihre Weise sehr gut, wenn es um verschiedene Hydration-Szenarien geht. Das ist auch ein Thema, das 2023 in Angular Einzug gehalten hat und in das das Angular-Team auch weiterhin investieren möchte. Wie bei Solid.js und Signals gilt für mich auch hier, dass ein Blick auf solche Vorreiter ein gutes Gefühl für die Zukunft von Angular gibt.
Marc Teufel: Bei uns im Unternehmen wird Go neben Java in Zukunft eine wichtige Rolle spielen. In letzter Zeit liest man immer häufiger von Go und HTMX als neues Heilmittel, mit dem man Webanwendungen gänzlich ohne JavaScript oder TypeScript entwickeln kann. Ich bin aktuell noch hin- und hergerissen von dieser Entwicklung. Auf der einen Seite wirkt der Ansatz wie von gestern, denn dahinter verbirgt sich nichts anderes als AJAX.
Auf der anderen Seite ermöglicht dieser Techstack eine schnelle, effiziente und zeitgemäße Entwicklung von Webanwendungen, die gerade für viele Unternehmensanwendungen mehr als ausreichend sein dürfte. Auf mich wirkt es, als wäre hier viel Komplexität herausgenommen worden, weil alles sehr schlicht und einfach im Aufbau ist. Ich könnte mir vorstellen, dass damit Software besser zu warten ist. Sicher bin ich mir allerdings noch nicht, ob Go und HTMX wirklich eine goldene Zukunft haben werden. Mein Bauchgefühl sagt mir aber, dass ich mir das Gespann näher ansehen werde, denn auf mein Bauchgefühl konnte ich mich bisher immer gut verlassen.
Hanna: MAUI. Definitiv MAUI. Wir haben die Technologie dieses Jahr in einem Bluetooth-Projekt deployt und könnten nicht zufriedener sein. Die Produktivitätssteigerung war immens.
Krypczyk: Ich „liebe“ die Entwicklung von modernen User Interfaces mit XAML-Technologien (MAUI, WinUI).
Holger Schwichtenberg: Ich habe ein Single-Page-Webprojekt mit Svelte implementiert und bin sehr begeistert von der syntaktischen Prägnanz, der Kompaktheit des Kompilats, sowie der Ausführungsgeschwindigkeit. Leider gibt es für Svelte aber noch nicht so viele Komponenten wie für anderen Webfrontend-Frameworks.
Haubner: Flutter und alle Dinge, die mit der Dart-Language einhergehen. Ich kann nur sehr empfehlen, sich damit auseinander zu setzen, wenn man mobile Entwicklung in Betracht zieht. In dem Ökosystem gibt es viele spannende und ausgereifte Tools, wie zum Beispiel Riverpod, die eine gute Dev-Experience liefern.
Hanna: Die Wartung von komplexen Softwaresystemen wird gern mit dem Schütteln von Würfeln aus ballistischem Gel verglichen. Microsoft schaffte es diesmal keine großen breaking changes zu machen – sehr erfreulich!
Krypczyk: Wichtig finde ich, dass an der Stabilität der Frameworks gearbeitet wird, welche das .NET-Framework nutzen, also MAUI und WinUI.
Schwichtenberg: Die wichtigsten Neuerungen in .NET 8.0 gibt es definitiv in Blazor! Mit Blazor Static-Server-Rendering (SSR) steht nun für servergerenderte Multi-Page-Apps das gleiche Komponentenmodell wie für Single-Page-Apps zur Verfügung. Damit gibt es eine bessere Alternative zu ASP.NET Core MVC und Razor Pages! Der neue Auto-Rendering-Modus mit zweifacher Hydrierung vermeidet die Qual der Wahl zwischen Pest oder Cholera (Blazor Server und Blazor WebAssembly). Eine Webanwendung kann nun zunächst auf dem Server vorrendern, dann zu Blazor Server mit Websockets übergehen und im Hintergrund die Dateien für Blazor WebAssembly laden, um schließlich rein im Webbrowser zu laufen. Blazor war bisher stark auf Intranet- und Extranet Anwendungen beschränkt. Mit dem Auto-Rendering-Modus könnte man auch Internetanwendungen mit Blazor denken.
Bei C# 12.0 liebe ich
a) die Primärkonstruktoren für Klassen
public class Person (Guid id, string name, DateTime geb) {
…
}
b) die neue Syntax für die Initialisierung von Arrays und Listen mit eckigen Klammern (wie in JavaScript/TypeScript) inklusive Verwendung des Spread-Operators
List d1 = [1, 2, 3];
List d2 = [.. d2, 4, 5];
c) sowie die Typaliase
global using Author = (int ID, string Name, string Website);
…
Author hs = (42, “Dr. Holger Schwichtenberg”, “www.IT-Visions.de”);
Die Primärkonstruktoren erzeugen leider nicht, wie in TypeScript, direkt öffentliche Klassenmitglieder. Die Parameter des Primärkonstruktors kann man stattdessen nur innerhalb der Klasse für Zuweisungen oder als private Fields verwenden. Ich hätte gerne gesehen, dass die Primärkonstruktorparameter mit den Zusätzen public und private versehen werden können. Bei Verwendung von public bzw. ohne Angabe entsteht ein öffentliches Property; bei Angabe private ein privates Property. So hätte ich das gemacht! Der in .NET 7.0 für Konsolenanwendungen eingeführte „Native AOT“-Compiler wurde in .NET 8.0 auf ASP.NET Core Minimal APIs, gRPC-Dienste und Worker Services erweitert. Sehr gewünscht hätte ich mir, dass man auch Windows Forms- und WPF-Anwendungen mit dem Ahead-of-Time-Compiler kompilieren kann, denn viele Desktop-Anwendungen würden von dem schnelleren Anwendungsstart, der stark verringerten Anwendungsgröße und dem niedrigeren RAM-Bedarf profitieren. Zudem funktioniert Native AOT in .NET 8.0 leider auch immer noch nicht mit Entity Framework Core und Dapper. Möglich sind nur Command, DataReader und DataSet aus ADO.NET sowie NanORM.
Hanna: Kein Kommentar
Krypczyk: Diese Partnerschaft hat Microsoft wahrscheinlich einen strategischen Vorteil im Bereich der KI verschafft, insbesondere durch die Integration von KI-Technologien in Microsoft-Produkte. Microsoft könnte sich durch diese Zusammenarbeit als führender Akteur im Bereich der KI-basierten Produkte und Dienstleistungen etabliert haben.
Schwichtenberg: Endlich ist Microsoft bei einem der großen Entwicklungen in der Geschichte der IT mal wirklich ganz vorne dabei. Es ist sehr gut, dass man die OpenAI-Dienste auch via Microsoft Azure nutzen kann, so brauchen Firmen nicht noch einen Cloud-Anbieter-Vertrag bzw. können bestehenden Azure-Guthaben nutzen.
Liebel: Wenn immer eine Technologie Effizienzgewinne gebracht hat, ist sie geblieben, siehe Taschenrechner oder Computer. Davon ist auch bei künstlicher Intelligenz auszugehen: In Visual Studio Code findet GitHub Copilot Bugs und schreibt die Commit Message, in Photoshop übernimmt Firefly die Retusche und in Outlook wird bald der Microsoft 365 Copilot E-Mails schreiben. Das dürfte in 2024 und darüber hinaus relevant bleiben.
Steyer: Das Thema bleibt relevant und präsent. Eventuell kommt irgendwann der Zeitpunkt, wo man nicht mehr so viel darüber redet, weil es eh normal ist, aber das sehe ich für 2024 noch nicht. Man wird uns das Leben weiterhin mit KI-unterstützten Entwicklungswerkzeugen leichter machen und wir werden immer mehr darüber nachdenken, wie man KI in die eigenen Anwendungen integrieren kann, um die Benutzerfreundlichkeit zu erhöhen und Prozesse besser zu automatisieren.
Dabei spielt es uns in die Hände, dass gerade sehr viel Forschung in diesem Bereich stattfindet und Forscher sowie einige große Firmen um bessere Modelle wetteifern. Diese Modelle werden auch multimodal, d. h. sie können mit mehreren Arten von Daten umgehen: Bild, Text, Audio. Genau das macht diese Modelle für einige Use Cases im Bereich der Anwendungsentwicklung noch spannender.
Teufel: Ich beobachte die Entwicklungen rund um KI im Allgemeinen und in Bezug auf Softwareentwicklung im Besonderen. Bei der Softwareentwicklung bin ich jedoch ziemlich gelassen. Ich habe keine Angst, dass KI uns Entwicklerinnen und Entwicklern bald den Rang ablaufen könnte. Genauso wie Low Code wird es auch KI aus meiner Sicht in absehbarer Zeit nicht schaffen, den Menschen zu ersetzen. Damit das passieren kann, müssen Unternehmen ihr gesamtes Domänenwissen öffentlich preisgeben (wer will das schon?) oder zumindest innerhalb des Unternehmens ein eigenes Datennetzwerk aufbauen, auf dem die KI dann operieren kann (wer macht das schon?). Künstliche Intelligenz ist sicher kein Thema, dem man sich verwehren sollte, aber speziell in der Softwareentwicklung sehe ich in KI aktuell allenfalls ein zusätzliches Helferlein, das mich in der Entwicklung unterstützen kann, mehr aber auch nicht.
Hartmann: Prognosen sind ja immer schwierig, insbesondere, wenn sie die Zukunft betreffen. Aber hier lege ich mich fest, das Thema wird uns auch 2024 weiter beschäftigen. Die Frage ist dann vielleicht weniger, ob sich KI weiterentwickelt und weiter in unseren (Entwickler-)Alltag Einzug erhält, sondern wie sie das tut, wie sie uns in unseren bestehenden Tools und Workflows helfen wird und wie wir KI auch im Zusammenspiel mit vertraulichen bzw. privaten Daten verwenden können.
Haubner: Mein Team und ich glauben, dass bestimmte KI-Themen bleiben werden. Beispielsweise wird es vermutlich in Zukunft nicht mehr sinnvoll sein ohne Tools wie CoPilot oder ähnlichem zu entwickeln. Auch Bibliotheken wie LangChain ermöglichen viel Innovation in der Anwendungsentwicklung. Wie sich das Thema OpenAI und ChatGPT weiterentwickelt, darüber möchte ich keine Aussage treffen. Das hängt zu sehr von der Firmen-Politik der aktuellen Marktführer in dem Segment ab. Spannend wird es hier zu beobachten, welche Konkurrenz-Situation im Markt entstehen wird.
Liebel: Für das kommende Jahr wünsche ich mir, das weitere Project-Fugu-Schnittstellen auch in andere Browser Einzug erhalten, allen voran das File System Access API. Das wird 2024 schon fünf Jahre alt – vielleicht gibt es ja ein Geburtstagsgeschenk von Apple oder Mozilla.
Steyer: Meine Kollegen und ich beschäftigen uns ja sehr stark mit Architektur im Frontend-Bereich. Da sehen wir zwei Herausforderungen: Overengineering und Underengineering. Zum Glück gibt es in der Angular-Welt einige Neuerungen, die helfen, den Sweet Spot zwischen beidem besser zu erreichen: Standalone Components oder auch moderne und leichtgewichtige Stores wie der neue NgRx Signal Store helfen dabei enorm, weil sie unnötiges Gewicht herausnehmen. Ich denke, diese Bestrebungen sollten wir weiterverfolgen.
Dasselbe gilt für Bestrebungen, die auf eine bessere Developer Experience abziehen. Das Angular-Team plant z. B. einige Verbesserungen an den Angular Dev Tools, wie etwa eine Visualisierung von Signals und deren Abhängigkeiten. Das ist wichtig, denn gerade in diesem Bereich sehe ich noch ein wenig Luft nach oben.
Teufel: Ich wünsche mir, dass wir auch im kommenden Jahr noch weiter verinnerlichen, dass Softwareentwicklung kein Selbstzweck ist. Es geht nicht darum, immer den neuesten Scheiß zu verwenden. Es geht nicht darum, jede neue Technologie oder Idee zu adaptieren und einzubauen. Vielmehr geht es darum, Probleme zu lösen. Das sollte unser Fokus sein! Nicht die Technik, sondern die Probleme, um die es geht. Probleme so lösen, dass ein Unternehmen die Lösung in absehbarer Zeit auch nutzen kann, dass ein jeder die Lösung verstehen, nachvollziehen und weiter pflegen kann. Und ich wünsche mir weiterhin viel Leidenschaft von Entwicklern, denn ohne Leidenschaft wird Leiden geschaffen!
Hartmann: Ich bin sicher, dass es auch im Jahr 2024 nicht an technischen Neuerungen und Innovationen mangeln wird. Wünschen würde ich mir aber, dass Debatten darüber, was gut und schlecht, richtig und falsch ist, differenziert geführt werden. Das bedeutet zum Beispiel, Frameworks oder Tools anhand von konkreten Problemen oder konkreter Anforderungen zu beurteilen und nicht pauschal als Allheilmittel zu feiern oder zu verteufeln.
Was React angeht, bin ich sehr gespannt, was nächstes Jahr passiert. Vielleicht gibt es ja (seit Juni 2022!) mal wieder ein neues Release. In diesem könnte unter anderem der use-Hook veröffentlicht werden, der das Arbeiten mit Promises in Client-Komponenten vereinfachen soll, sowie der useEvent-Hook, der die Notwendigkeit von useEffect einschränken soll. Mit React Forget steht ein Compiler vor der Tür, der die Arbeit mit useMemo und useCallback überflüssig machen soll. Clientseitig wird also wohl einiges passieren und serverseitig sicherlich auch.
Next.js hat bereits eine ganze Reihe weiterer Features angekündigt und auch deren Build-Tool TurboPack, könnte in einer ersten Version erscheinen und möglicherweise Verbreitung auch außerhalb von Next.js finden. Remix hat angekündigt, React Server Components zu unterstützen und will wohl auch einen Migrationspfad von React SPAs anbieten. Es gibt also eine ganze Reihe von möglichen Neuerungen, auf die wir uns 2024 freuen, oder vor denen wir uns fürchten können.
Hanna: Eigentlich bin ich technisch sehr zufrieden. Was mir fehlt – der 48-Stunden-Tag!
Krypczyk: Ich finde eine stärkere Fokussierung auf ethische KI und die Entwicklung von Richtlinien, die sicherstellen, dass KI zum Wohl der Gesellschaft eingesetzt wird, sind notwendig.
Haubner: Was ich mir für das nächste Jahr wünsche ist etwas weniger Hype und mehr Pragmatismus im Umgang mit neuen Technologien. Wenn ich nie wieder einen Glaubenskrieg ohne echte Argumente auf Twitter (X) lesen muss, bin ich glücklich
Brancheninsights im Newsletter erhalten:
The post JavaScript- und .NET-Trends: Was hat sich 2023 getan? Was kommt 2024? appeared first on JavaScript & Angular Days.
]]>The post JavaScript-Paketmanager: Yarn appeared first on JavaScript & Angular Days.
]]>Yarn ist der Versuch von Facebook beziehungsweise Meta, die Welt etwas besser, genauer gesagt, die Paketverwaltung von JavaScript sicherer, stabiler, reproduzierbarer und schneller zu machen. Und das hat gute Gründe. Zu dem Zeitpunkt, als Yarn entwickelt und veröffentlicht wurde, galt all das nicht für npm. Es war weder schnell noch zuverlässig noch sicher. Yarn hatte genau für diese Probleme eine Antwort und führte eine Datei mit dem Namen yarn.lock ein, in der nicht nur die direkt installierten Abhängigkeiten, sondern sämtliche Abhängigkeiten, also der gesamte Baum mit genauer Versionsnummer, der Paketquelle sowie einer Prüfsumme festgehalten wurden. Dieses Feature sorgte dafür, dass bei jeder Installation der Applikation immer die gleichen Pakete installiert wurden und somit sichergestellt war, dass die Applikation funktioniert. Dieses Konzept war so gut, dass es npm einige Zeit später selbst übernahm – ein klassisches Beispiel von den Vorteilen der Konkurrenz von Open-Source-Projekten, die zu einer allgemeinen Verbesserung der Lage führt.
Mit der Stabilisierung der Version 1 von Yarn wanderten immer mehr Nutzer:innen von npm ab und hin zu Yarn. Mit der Einführung der zweiten Version des Paketmanagers erwies das Team hinter Yarn kein glückliches Händchen. In den ersten Versionen gab es immer wieder kleinere Probleme und auch die Installation war wenig komfortabel, sodass das Projekt etwas an Schwung verlor und die Akzeptanz in der Community deutlich zurückging. Viele Entwickler:innen kehrten Yarn den Rücken und wanderten zurück zu npm, das zu dieser Zeit kontinuierlich weiterentwickelt wurde und immer wieder neue hilfreiche Features erhielt. Mittlerweile hat sich die Situation wieder beruhigt und Yarn hat eine stabile und treue Nutzerschaft um sich versammelt.
Yarn ist der erste Paketmanager, der vollständig auf Corepack setzt, den internen Paketmanager für Paketmanager von Node.js. Für die Installation benötigen Sie also lediglich eine aktuelle Version von Node.js. Sollten Sie Node vor der Version 16.10 nutzen, müssten Sie theoretisch Corepack als externes Paket installieren. Bevor Sie jedoch damit beginnen, aktualisieren Sie lieber schleunigst Ihre Node-Version, da Version 16 am 11.09.2023 ihr Lebensende erreicht. Haben Sie Corepack und eine Node-Version, die Sie guten Gewissens nutzen können, können Sie mit dem folgenden Kommandos Yarn auf Ihrem System aktivieren:
corepack prepare yarn@stable --activateAuf die neueste Version aktualisieren Sie mit yarn set version stable. Und damit ist eigentlich auch schon alles gesagt, was Sie über das Set-up von Yarn wissen müssen, und Sie können mit der Arbeit beginnen.
Ihr Projekt können Sie auf zwei Arten initialisieren: Entweder Sie nutzen yarn init oder yarn init -2. Was genau der Unterschied ist und wie sich das auf Ihr Projekt auswirkt, erfahren Sie später. Wichtig ist nur, dass Sie wissen, dass Sie in beiden Fällen mindestens eine package.json und eine yarn.lock erhalten. Die package.json-Datei ist die zentrale Beschreibungsdatei Ihrer Applikation, hier können Sie alle Metainformationen ablegen, die mit Ihrem Projekt zu tun haben. Das reicht von der aktuellen Versionsnummer über die Angabe der Lizenz und der Autor:innen bis hin zu den installierten Abhängigkeiten und Skripten für die Erledigung verschiedener Aufgaben. Während Sie die package.json von Hand modifizieren dürfen, sollten Sie tunlichst Ihre Finger von der yarn.lock lassen. Diese Datei ist maschinell generiert und enthält, wie bereits erwähnt, wichtige Informationen über die installierten Pakete. Generell sollten Sie sich bei der Paketverwaltung auf Ihren Paketmanager verlassen und nicht anfangen, seine Arbeit erledigen zu wollen. Das gilt vor allem für das Entfernen von Paketen. Zwar können Sie ein Paket aus der package.json und vielleicht sogar aus dem Dateisystem löschen, die durch dieses Paket installierten Abhängigkeiten und die Verweise in der yarn.lock bleiben erhalten und führen damit zu Inkonsistenzen. Für die Verwaltung von Paketen stellt Ihnen Yarn auf der Konsole eine Reihe von Kommandos zur Verfügung. Die wichtigsten davon finden Sie in der folgenden Liste:
yarn init: Das init-Kommando steht am Anfang der Entwicklung einer Applikation. Es hilft Ihnen bei der Erstellung der Basisstruktur für die Paketverwaltung in Ihrer Applikation.
yarn add: Mit yarn add fügen Sie neue Pakete zu Ihrer Applikation hinzu. Yarn installiert das Paket und alle seine Abhängigkeiten, sodass das Paket nach einer erfolgreichen Installation sofort einsatzfähig ist. Yarn erlaubt die Installation aus verschiedenen Quellen wie dem zentralen Paket-Repository, aber auch aus privaten Repositorys oder vom Dateisystem aus. Außerdem können Sie die Versionsnummer des Pakets angeben, das Sie installieren möchten.
yarn remove: Der einzig richtige Weg, ein einmal installiertes Paket wieder loszuwerden, führt über das remove-Kommando. Es stellt sicher, dass sowohl das Paket als auch alle seine Abhängigkeiten korrekt entfernt werden. Benötigt ein weiteres Paket eine installierte Abhängigkeit, wird diese selbstverständlich nicht gelöscht. Auch die yarn.lock und die package.json werden von Yarn konsistent gehalten und spiegeln zu jedem Zeitpunkt den Stand der installierten Pakete wider. Kümmern Sie sich selbst um die Deinstallation, müssten Sie sich um diese Aspekte ebenfalls kümmern.
yarn upgrade: Zur Verwaltung von Paketen gehört nicht nur das Installieren und Entfernen, sondern auch die Aktualisierung. In der package.json ist standardmäßig eine Spanne von erlaubten Versionsupdates festgehalten. Mehr Informationen über Versionsnummern finden Sie unter [1]. Die Limitierung der Versionsnummern soll dem Prinzip des Semantic Versioning Rechnung tragen. Es sagt aus, dass bei einem Sprung in der Major-Version Breaking Changes zu erwarten sind. Hier müssen Sie also sehr vorsichtig vorgehen. Ein leichtfertiges yarn upgrade könnte hier fatale Folgen haben. Steht eine solche Aktualisierung an, müssen Sie sich gesondert darum kümmern.
Sollten Sie Erfahrung mit npm haben, fragen Sie sich bestimmt, was denn mit dem install ist. Bei npm nutzen Sie diesen Befehl sowohl zur Installation von Paketen als auch zur Installation aller Pakete in einer bestehenden Applikation. Der Hintergrund ist hier, dass npm seine Abhängigkeiten in einem Verzeichnis mit dem Namen node_modules ablegt. Dieses Verzeichnis fügen Sie üblicherweise nicht zur Versionskontrolle hinzu, sodass Sie die Abhängigkeiten installieren müssen, wenn Sie sich den Quellcode aus dem Repository holen. Yarn verfügt ebenfalls über das install-Kommando, versucht jedoch über mehrere Features das node_modules-Verzeichnis loszuwerden. Von Beginn an hat Yarn zwischen dem Hinzufügen von Paketen und der Installation der Abhängigkeiten unterschieden. Aus diesem Grund gibt es für diese Zwecke mit add und install zwei verschiedene Kommandos. Diese Idee hat npm mittlerweile aufgegriffen und unterstützt mittlerweile auch das add-Kommando zum Hinzufügen eines neuen Pakets.
Yarn weist einige Unterschiede zu npm auf, so können Sie beispielsweise auf das run-Kommando verzichten, wenn Sie ein Skript aus Ihrer package.json-Datei ausführen möchten. npm unterscheidet hier zwischen Standardskripten wie start, stop oder test. Diese können Sie direkt aufrufen, also mit npm start. Falls Sie jedoch ein Skript mit einem anderen Namen definieren, wie beispielsweise build, müssen Sie das run-Kommando nutzen, also npm run build. Yarn vereinfacht Ihr Leben hier etwas, indem es auf run verzichtet und sich mit yarn build das Skript direkt ausführen lässt.
Ein npm-Feature, das viele Freunde hat, ist npx, ein Kommandozeilenwerkzeug, mit dem Sie ausführbare Pakete aufrufen können. Ein typisches Beispiel hierfür ist Create React App, ein Paket, mit dem Sie eine React-Applikation initialisieren können. Hier geht es weniger um die Initialisierung einer React-Applikation, sondern um die Tatsache, dass Sie das Create-React-App-Paket lediglich ein Mal im ganzen Lebenszyklus Ihrer Applikation benötigen. Sie könnten es zwar lokal auf Ihrem System installieren und dann gleich wieder löschen, sobald Sie Ihre Applikation initialisiert haben. Mit npx stellt sich dieses Problem nicht, da das Werkzeug zunächst nach dem Paket sucht. Falls es nicht gefunden wird, lädt npx es herunter, führt es aus und verwirft es wieder. In Yarn gibt es ein solch praktisches Feature nicht, was jedoch nicht weiter tragisch ist, da Sie die Fälle, in denen Sie npx tatsächlich brauchen, an einer Hand abzählen können. Falls Sie mit Yarn eine neue React-Applikation generieren möchten, erreichen Sie dies beispielsweise mit dem Kommando yarn create react-app my-app, was intern auch nur auf Create React App weiterleitet. Das Ganze ist nicht auf React beschränkt. Mit yarn create vite my-app können Sie mit Hilfe von Vite ein beliebiges Projekt starten. Das einzige Framework, das etwas aus der Reihe tanzt, ist Angular. Es nutzt seine eigene Kommandozeilenapplikation, die Sie mit dem Paketmanager Ihrer Wahl (also auch gerne mit Yarn) global installieren und dann die Entwicklung Ihres Projekts starten.
Wie schon erwähnt, steht Yarn mit dem node_modules-Verzeichnis auf Kriegsfuß. Und tatsächlich suchen Sie ein Verzeichnis mit diesem Namen in einer aktuellen mit Yarn initialisierten Applikation vergeblich. Das hat vor allem Performancegründe. Bei der Installation eines Pakets muss der Paketmanager das Paket herunterladen. Das liegt in der Regel als komprimierte Archivdatei vor, die entpackt wird. Im letzten Schritt kopiert npm den Code in das node_modules-Verzeichnis. Das Kopieren von Dateien kostet jedoch Zeit und genau die versucht Yarn hier einzusparen.
2018 hat Yarn ein neues Feature mit dem Namen Plug’n’Play eingeführt und sich damit vom node_modules-Verzeichnis verabschiedet. Stattdessen enthält Ihr Projekt eine Datei mit dem Namen .pnp.cjs, das Verweise auf Pakete und deren Versionen im Dateisystem enthält. Außerdem enthält es die Liste von Abhängigkeiten eines Pakets. Diese Informationen stellt Yarn dem Node Resolution Algorithm, also dem Algorithmus, mit dem Node die Pakete findet, zur Verfügung. Doch wie funktioniert das konkret? Angenommen, Sie möchten ein einfaches Node.js Backend mit Express implementieren. Im ersten Schritt initialisieren Sie Ihre Applikation mit yarn init. Anschließend fügen Sie Express mit yarn add express als Abhängigkeit hinzu. Dann müssen Sie nur noch Ihre Applikation implementieren. In Listing 1 sehen Sie die Minimalvariante einer solchen Umsetzung.
Listing 1: Minimale Express-Applikation
import { express } from 'express';
const app = express();
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(8080);
Versuchen Sie, die Applikation aus alter Gewohnheit mit node index.js zu starten, quittiert Node das mit der Fehlermeldung „Cannot find module ‚express‘“. Die Modulauflösung scheint nicht zu funktionieren. Das liegt daran, dass Node nichts von der .pnp.cjs-Datei weiß. Nun haben Sie zwei Möglichkeiten: Sie können Ihre Applikation entweder mit yarn node index.js starten oder Sie fügen mit node index.js ein Startskript in Ihre package.json ein und starten die Applikation mit yarn start. In beiden Fällen sorgt Yarn nun dafür, dass Plug’n’Play reibungslos funktioniert.
Ganz wegdiskutieren lässt sich der Quellcode der Pakete jedoch nicht und so legt Yarn die Pakete in einem Verzeichnis mit dem Namen .yarn/cache in Form von ZIP-Dateien ab. Dieses Verzeichnis können Sie, ähnlich wie das node_modules-Verzeichnis, in Ihre .gitignore-Datei aufnehmen und so Ihr Repository klein und übersichtlich halten. Holen Sie sich den Quellcode einer solchen Yarn-Plug’n’Play-Applikation, müssen Sie yarn install ausführen, um die Pakete zu installieren.
Committen Sie das .yarn-Verzeichnis mit seinem lokalen Cache in Ihr Repository, verfügt Ihre Applikation plötzlich über ein Feature mit dem Namen Zero-Installs. Der Aufruf von yarn install ist nicht weiter erforderlich, da die Abhängigkeiten bereits vorhanden sind. Warum sollte man nach all den Jahren mit den ignorierten node_modules plötzlich seine Abhängigkeiten doch im Repository haben wollen? Ganz einfach: Die Funktionsfähigkeit des Quellcodes ist sichergestellt – sobald Sie den Code haben, können Sie ihn ausführen. Sie müssen sich nicht mehr auf eine bestehende Internetverbindung, Yarn als Paketmanager und das Paketrepository verlassen. Außerdem liegen die Pakete in komprimierter Form im Cache, was weiter Speicherplatz spart und das Repository so nicht unnötig aufbläht.
Yarn ist kein Newcomer in der JavaScript-Welt. Der Paketmanager hat mittlerweile einige Jahre auf dem Buckel und hat sich vielfach im produktiven Einsatz bewährt. Bei den Features kann Yarn mindestens mit npm mithalten, in manchen Aspekten ist Yarn sogar besser, andere Funktionalitäten gibt es in Yarn (noch) nicht. Der Überblick, den ich Ihnen hier gegeben habe, deckt natürlich nicht den gesamten Funktionsumfang von Yarn ab. Interessant ist beispielsweise das Workspaces-Feature, mit dem Sie umfangreiche Applikationen in einem Monorepo hervorragend verwalten können.
Falls Sie Yarn also noch nicht ausprobiert haben, ist jetzt ein hervorragender Zeitpunkt, das nachzuholen.
The post JavaScript-Paketmanager: Yarn appeared first on JavaScript & Angular Days.
]]>The post PDFs mit Node.js erstellen appeared first on JavaScript & Angular Days.
]]>Gehen Sie jetzt davon aus, dass es genau eine Bibliothek für das Erstellen von PDFs gibt, muss ich Sie leider enttäuschen. Hier gilt, wie so oft in JavaScript, dass es eine Vielzahl von Lösungen gibt, aus denen Sie sich die passende herauspicken müssen. Ein Blick in die Suchmaschine der Wahl und eine anschließende Prüfung auf www.xpmtrends.com liefert das in Abbildung 1 Gezeigte.
Abb. 1: Auswahl der PDF-Bibliotheken [1]In der engeren Auswahl stehen die Bibliotheken jspdf, pdfkit und pdfmake. Der Langzeittrend zeigt, dass bis Ende 2021 das mittlerweile zwölf Jahre alte pdfkit das Mittel der Wahl war, zumindest wenn es um die wöchentlichen Downloads geht. Zum Jahreswechsel 2022 haben dann jspdf und pdfmake die Führung übernommen. Das letzte Update von jspdf liegt aber schon einige Zeit zurück, also fällt unsere Wahl auf pdfmake. Für unser Beispiel erzeugen wir das PDF serverseitig, was beide Bibliotheken, sowohl jspdf als auch pdfmake unterstützen.
Die Installation von pdfmake läuft ab wie bei nahezu jedem anderen JavaScript-Paket. Mit npm install pdfmake installieren Sie das Paket in Ihrer Applikation. Nutzen Sie TypeScript, können Sie die zugehörigen Typdefinitionen mit npm install @types/pdfmake installieren. Aktuell installieren Sie mit diesem Kommando die Version 0.2.7. Die Version 0.3 steht allerdings schon in den Startlöchern und kann mit npm install [email protected] installiert werden.
Für das Erzeugen eines sehr einfachen PDFs müssen Sie nicht viel tun: Sie definieren, welche Schriftarten und Stile Sie verwenden möchten, beschreiben Ihr Dokument, erzeugen aus dieser Definition das PDF und schreiben es ins Dateisystem (Listing 1).
Listing 1: Erstellen eines PDF
import pdfmake from ‚pdfmake‘;
const fonts = {
Helvetica: {
normal: 'Helvetica',
bold: 'Helvetica-Bold',
italics: 'Helvetica-Oblique',
bolditalics: 'Helvetica-BoldOblique'
},
};
pdfmake.addFonts(fonts);
const docDefinition = {
content: [
'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor',
'invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam'
], defaultStyle: {
font: 'Helvetica'
}
};
const pdf = pdfmake.createPdf(docDefinition);
pdf.write('document.pdf').then(() => {
console.log('pdf generated')
}, err => {
console.error(err);
});
Um die Definition der Schriften kommen Sie nicht herum. Sie können hier entweder eine der Standardschriftarten wie Times, Helvetica oder Courier nutzen oder zusätzliche Schriftarten in Form von .ttf-Dateien einbinden. Diese können Sie übrigens auch über npm-Pakete installieren. Die Roboto-Schriftart, die pdf-make als Standard verwenden möchte, installieren Sie mit npm install @fontsource/roboto. Im Beispiel nutzen wir Helvetica, was wir pdfmake mit der defaultStyle-Eigenschaft auch mitteilen. Das eigentlich Spannende am PDF, den Inhalt, definieren Sie mit der content-Eigenschaft. Im einfachsten Fall übergeben Sie hier eine Reihe von Zeichenketten, die für die Absätze in Ihrem Dokument stehen. Unser generiertes PDF besteht also aus zwei Absätzen formschönen Lorem-ipsum-Texts. Ist der Text zu breit für eine Seite, wird er umgebrochen und in der nächsten Zeile fortgesetzt. Mit diesen Definitionen können Sie mit Hilfe der createPdf-Methode, der Sie die Dokumentendefinition übergeben, Ihr PDF erzeugen. Die write-Funktion schreibt das Ergebnis unter dem angegebenen Namen im aktuellen Verzeichnis ins Dateisystem und liefert Ihnen ein Promise-Objekt zurück.
Das Ausgeben von unformatiertem Text eignet sich vielleicht gerade noch für einfachen Fließtext. Optisch ansprechend ist das Ergebnis aber nicht. Doch auch hierfür hat pdfmake eine Lösung. Sie können innerhalb der content-Eigenschaft nicht nur einfache Zeichenketten angeben, sondern auch komplexere Objekte. Möchten wir z. B. eine Rechnung schreiben, beginnen wir also mit dem Kopf des Dokuments: Er besteht aus unserer Adresse, der Adresse des Rechnungsempfängers und dem Titel Rechnung. Natürlich können Sie all diese Informationen direkt in den Content schreiben. Nachdem Sie den Inhalt des PDF aber als ganz gewöhnliche Objektstruktur beschreiben, können Sie auch Hilfsfunktionen definieren, die bestimmte Teile des Dokuments für Sie erzeugen. Das führt dazu, dass der Code sprechender wird und Sie das Dokument deutlich besser aufteilen können. In Listing 2 sehen Sie, wie Sie diese Elemente in Ihrem PDF definieren können.
Lisiting 2: Weiteren Inhalt definieren
function createDocumentHead(recipient) {
return [
{
text: [recipient.name, recipient.street, recipient.city].join('\n'),
absolutePosition: { x: 100, y: 200 }
},
{
text: [
'ACME',
'Fairfield, New Jersey'
].join('\n'),
absolutePosition: { x: 400, y: 80 }
},
{
text: 'Rechnung', fontSize: 22, bold: true, absolutePosition: { x: 100, y: 300 }
}
];
}
const docDefinition = {
content: [
...createDocumentHead({ name: 'Glover Inc.', street: '1853 Stefanie Bridge', city: 'Carrolton, GA 30118' })
], defaultStyle: {
font: ‚Helvetica‘
}
};
Doch damit ist noch nicht genug. Was unserer Rechnung jetzt noch fehlt, ist eine Auflistung der bestellten Produkte, und was eignet sich dafür besser als eine Tabelle? Aber auch dafür hat unsere PDF-Bibliothek eine Lösung parat. Zusätzlich zur Texteigenschaft wie zuvor können Sie mit table eine Tabelle definieren. Hier legen Sie fest, wie viele der definierten Zeilen der Kopf der Tabelle sein sollen. Ein nettes Zusatzfeature von pdfmake ist, dass dieser Tabellenheader bei einem Seitenumbruch automatisch wiederholt wird. Sie können außerdem die gewünschte Breite der Spalten angeben und schließlich mit der body-Eigenschaft in Form eines Arrays mit Objekten den eigentlich Inhalt der Tabelle definieren. In Listing 3 sehen Sie die Implementierung der Funktion createTable, der Sie einen Warenkorb übergeben können und die daraus eine Tabellendarstellung macht. Wie Sie sehen, lassen sich mit pdfmake die verschiedenen Features miteinander kombinieren, z. B. die Tabelle absolut oder relativ positionieren und innerhalb der Tabelle die Formatierung der einzelnen Zellen anpassen.
Listing 3: Tabelle erstellen
function createDocumentHead(recipient) {... }
function createTable(cart) {
const tableRows = cart.map((item, index) => {
return [index + 1, item.title, item.amount, item.price, item.amount * item.price]
});
return {
layout: 'lightHorizontalLines',
table: {
headerRows: 1,
widths: [30, 200, 30, 30, 50],
body: [
['Pos', 'Titel', 'Stück', 'Preis', 'Gesamt'],
...tableRows,
['', { text: 'Gesamtsumme:', bold: true }, '', '', tableRows.reduce((prev, curr) => { return prev + curr[4] }, 0)]]
},
absolutePosition: { x: 100, y: 350 }
}
}
const docDefinition = {
content: [
...createDocumentHead({ name: 'Glover Inc.', street: '1853 Stefanie Bridge', city: 'Carrolton, GA 30118' }),
createTable(items)
], defaultStyle: {
font: 'Helvetica'
}
};
Doch pdfmake kann noch deutlich mehr, als ich Ihnen bisher gezeigt habe. So können Sie Ihre eigenen Styles definieren und auf Texte anwenden. Dieses Feature erinnert in seinen Grundzügen im weitesten Sinn an CSS. Sie definieren einen Style z. B. bestehend aus einer Schriftgröße und schräggestellter Schrift einmal und wenden ihn dann an verschiedenen Stellen in Ihrer Dokumentdefintion an. Weitere Funktionalität, die Ihnen pdfmake darüber hinaus bietet, ist beispielsweise das Einbinden von Bildern, die Definition von Kopf- und Fußbereichen eines Dokuments oder die Erzeugung eines QR-Codes.
Bei all ihrem Funktionsumfang kann die Bibliothek aber auch nicht alles, was man sich bei der PDF-Erstellung vorstellen kann. So ist es nicht möglich, interaktive Formulare zu implementieren. Jedoch gibt es schon einen Feature-Request im GitHub-Repo des Projekts, in dem diese Funktionalität gewünscht wird.
Das Erstellen von PDFs macht nicht unbedingt jedem Spaß; das liegt aber weniger an den verfügbaren Bibliotheken, als eher daran, wie das Dateiformat und die Positionierung von Elementen generell funktioniert. Projekte wie pdfmake machen es uns Entwickler:innen verhältnismäßig angenehm, solche Dateien zu erzeugen. Auch wenn sich die Bibliothek noch in der Version 0.2 befindet, können Sie sie bedenkenlos nutzen. Bei der Beta der Version 0.3 wäre ich noch etwas zurückhaltend, was den Produktiveinsatz angeht. Diese Version verfügt jedoch über einige Verbesserungen und zeigt, wohin die Reise der Bibliothek gehen wird. Das pdfmake-Team hat vor allem die Schnittstellen vereinheitlicht und arbeitet nun generell mit Promises.
Falls Sie also das Vergnügen haben, ein PDF erzeugen zu dürfen, geben Sie vielleicht pdfmake eine Chance.
1. Was bedeutet „PDFs mit Node.js erstellen“?
Damit ist gemeint, wie man in einer Node.js-Anwendung Dokumente im PDF-Format generiert – also Text, Tabellen, Bilder und Styles in ein PDF gepackt – oft mit Bibliotheken wie pdfmake.
2. Wie funktioniert die Bibliothek pdfmake für PDF-Erstellung?
Mit pdfmake erstellt man ein sogenanntes docDefinition-Objekt, das Inhalte und Layout beschreibt (z. B. Texte, Tabellen, Positionierung). Der Befehl createPdf(docDefinition) erzeugt das PDF und über Methoden wie write(…) schreibt man es auf die Festplatte.
3. Wofür braucht man Schriftdefinitionen in pdfmake?
PDFs benötigen eingebundene oder Standard-Schriften (z. B. Helvetica, Times). Ohne gültige Schriftdefinition kann pdfmake Inhalte nicht korrekt rendern. Auch eigene .ttf-Schriften lassen sich einbinden.
4. Welche Layoutmöglichkeiten bietet pdfmake?
Neben einfachem Fließtext kann man in pdfmake komplexe Strukturierungen definieren: Tabellen mit Kopfzeilen, Spaltenbreiten, absolute oder relative Positionierung und Wiederholen von Tabellenköpfen bei Seitenumbrüchen.
5. Wer nutzt das Erzeugen von PDFs mit Node.js?
Typische Nutzer sind Backend-Entwickler, die automatisiert Rechnungen, Berichte oder Dokumente generieren wollen; z. B. in Webshops, Abrechnungssystemen oder Reporting-Tools.
6. Welche Alternativen zu pdfmake gibt es?
Es gibt diverse Bibliotheken zur PDF-Erzeugung in Node.js, darunter Puppeteer, PDFKit oder jsPDF. Jede eignet sich für unterschiedliche Use-Cases (z. B. HTML → PDF, programmatische Gestaltung).
7. Was sind typische Herausforderungen beim PDF-Generieren mit Node.js?
Einige Herausforderungen sind:
The post PDFs mit Node.js erstellen appeared first on JavaScript & Angular Days.
]]>The post Aftermovie JavaScript, Angular, React und HTML & CSS Days März 2023 appeared first on JavaScript & Angular Days.
]]>The post Aftermovie JavaScript, Angular, React und HTML & CSS Days März 2023 appeared first on JavaScript & Angular Days.
]]>