Ein umfassender Leitfaden zu JavaScript Import Maps, mit Schwerpunkt auf der leistungsstarken 'Scopes'-Funktion und der Modulauflösungs-Hierarchie für moderne Webentwicklung.
Erschließen einer neuen Ära der Webentwicklung: Ein Deep Dive in die JavaScript Import Maps Scope-Vererbung
Die Reise der JavaScript-Module war ein langer und verschlungener Weg. Vom globalen Namespace-Chaos des frühen Webs bis hin zu ausgeklügelten Mustern wie CommonJS für Node.js und AMD für Browser haben Entwickler kontinuierlich nach besseren Wegen gesucht, um Code zu organisieren und zu teilen. Die Ankunft der nativen ES-Module (ESM) markierte eine monumentale Verschiebung und standardisierte ein Modulsystem direkt in der JavaScript-Sprache und in Browsern.
Dieser neue Standard brachte jedoch eine erhebliche Hürde für die browserbasierte Entwicklung mit sich. Die einfachen, eleganten Import-Anweisungen, an die wir uns in Node.js gewöhnt hatten, wie z. B. import _ from 'lodash';
, würden im Browser einen Fehler auslösen. Dies liegt daran, dass Browser im Gegensatz zu Node.js mit seinem node_modules
-Algorithmus keinen nativen Mechanismus zur Auflösung dieser "Bare Module Specifiers" in eine gültige URL haben.
Jahrelang war die Lösung ein obligatorischer Build-Schritt. Tools wie Webpack, Rollup und Parcel würden unseren Code bündeln und diese Bare Specifiers in Pfade umwandeln, die der Browser verstehen konnte. Obwohl diese Tools leistungsfähig waren, fügten sie dem Entwicklungsprozess Komplexität, Konfigurationsaufwand und langsamere Feedback-Schleifen hinzu. Was wäre, wenn es einen nativen, Build-Tool-freien Weg gäbe, dies zu lösen? Betreten Sie die JavaScript Import Maps.
Import Maps sind ein W3C-Standard, der einen nativen Mechanismus zur Steuerung des Verhaltens von JavaScript-Imports bereitstellt. Sie fungieren als Nachschlagetabelle, die dem Browser genau sagt, wie Modul-Specifiers in konkrete URLs aufgelöst werden. Aber ihre Leistungsfähigkeit geht weit über das einfache Aliasing hinaus. Der wahre Game-Changer liegt in einem weniger bekannten, aber unglaublich leistungsstarken Feature: `scopes`. Scopes ermöglichen die kontextbezogene Modulauflösung, sodass verschiedene Teile Ihrer Anwendung denselben Specifier importieren, ihn aber in verschiedene Module auflösen können. Dies eröffnet neue architektonische Möglichkeiten für Micro-Frontends, A/B-Tests und komplexes Abhängigkeitsmanagement ohne eine einzige Zeile Bundler-Konfiguration.
Dieser umfassende Leitfaden führt Sie auf einen tiefen Einblick in die Welt der Import Maps, mit besonderem Schwerpunkt auf der Entmystifizierung der Modulauflösungs-Hierarchie, die von `scopes` gesteuert wird. Wir werden untersuchen, wie die Scope-Vererbung (oder, genauer gesagt, der Fallback-Mechanismus) funktioniert, den Auflösungsalgorithmus analysieren und praktische Muster aufdecken, um Ihren modernen Webentwicklungs-Workflow zu revolutionieren.
Was sind JavaScript Import Maps? Ein grundlegender Überblick
Im Kern ist eine Import Map ein JSON-Objekt, das eine Zuordnung zwischen dem Namen eines Moduls, das ein Entwickler importieren möchte, und der URL der entsprechenden Moduldatei bereitstellt. Es ermöglicht Ihnen, saubere, Bare Module Specifiers in Ihrem Code zu verwenden, genau wie in einer Node.js-Umgebung, und lässt den Browser die Auflösung übernehmen.
Die grundlegende Syntax
Sie deklarieren eine Import Map mithilfe eines <script>
-Tags mit dem Attribut type="importmap"
. Dieses Tag muss im HTML-Dokument vor allen <script type="module">
-Tags platziert werden, die die zugeordneten Imports verwenden.
Hier ein einfaches Beispiel:
<!DOCTYPE html>
<html>
<head>
<!-- The Import Map -->
<script type="importmap">
{
"imports": {
"moment": "https://cdn.skypack.dev/moment",
"lodash": "/js/vendor/lodash-4.17.21.min.js",
"app/": "/js/app/"
}
}
</script>
<!-- Your Application Code -->
<script type="module" src="/js/main.js"></script>
</head>
<body>
<h1>Welcome to Import Maps!</h1>
</body>
</html>
In unserer /js/main.js
-Datei können wir jetzt Code wie diesen schreiben:
// This works because "moment" is mapped in the import map.
import moment from 'moment';
// This works because "lodash" is mapped.
import { debounce } from 'lodash';
// This is a package-like import for your own code.
// It resolves to /js/app/utils.js because of the "app/" mapping.
import { helper } from 'app/utils.js';
console.log('Today is:', moment().format('MMMM Do YYYY'));
Lassen Sie uns das imports
-Objekt aufschlüsseln:
"moment": "https://cdn.skypack.dev/moment"
: Dies ist eine direkte Zuordnung. Immer wenn der Browserimport ... from 'moment'
sieht, ruft er das Modul von der angegebenen CDN-URL ab."lodash": "/js/vendor/lodash-4.17.21.min.js"
: Dies ordnet denlodash
-Specifier einer lokal gehosteten Datei zu."app/": "/js/app/"
: Dies ist eine pfadbasierte Zuordnung. Beachten Sie den abschließenden Schrägstrich sowohl beim Schlüssel als auch beim Wert. Dies weist den Browser an, dass alle Import-Specifiers, die mitapp/
beginnen, relativ zu/js/app/
aufgelöst werden sollen. Zum Beispiel würdeimport ... from 'app/auth/user.js'
zu/js/app/auth/user.js
aufgelöst. Dies ist unglaublich nützlich, um Ihren eigenen Anwendungscode zu strukturieren, ohne unordentliche relative Pfade wie../../
zu verwenden.
Die Kernvorteile
Selbst mit dieser einfachen Verwendung sind die Vorteile klar:
- Build-less Development: Sie können modernes, modulares JavaScript schreiben und es direkt im Browser ausführen, ohne einen Bundler. Dies führt zu schnelleren Aktualisierungen und einem einfacheren Entwicklungsumfeld.
- Entkoppelte Abhängigkeiten: Ihr Anwendungscode verweist auf abstrakte Specifiers (
'moment'
) anstelle von fest codierten URLs. Dies macht es trivial, Versionen, CDN-Anbieter auszutauschen oder von einer lokalen Datei zu einem CDN zu wechseln, indem nur das Import Map JSON geändert wird. - Verbessertes Caching: Da Module als einzelne Dateien geladen werden, kann der Browser sie unabhängig voneinander cachen. Eine Änderung an einem kleinen Modul erfordert kein erneutes Herunterladen eines riesigen Bundles.
Über die Grundlagen hinaus: Einführung von scopes
für die granulare Kontrolle
Der Top-Level-Schlüssel imports
bietet eine globale Zuordnung für Ihre gesamte Anwendung. Aber was passiert, wenn Ihre Anwendung an Komplexität zunimmt? Stellen Sie sich ein Szenario vor, in dem Sie eine große Webanwendung erstellen, die ein Chat-Widget eines Drittanbieters integriert. Die Hauptanwendung verwendet Version 5 einer Charting-Bibliothek, aber das Legacy-Chat-Widget ist nur mit Version 4 kompatibel.
Ohne scopes
würden Sie vor einer schwierigen Wahl stehen: Versuchen Sie, das Widget zu refaktorieren, ein anderes Widget zu finden oder akzeptieren Sie, dass Sie die neuere Charting-Bibliothek nicht verwenden können. Genau dieses Problem sollten scopes
lösen.
Der Schlüssel scopes
in einer Import Map ermöglicht es Ihnen, verschiedene Zuordnungen für denselben Specifier basierend darauf zu definieren, wo der Import vorgenommen wird. Es bietet kontextbezogene oder bereichsbezogene Modulauflösung.
Die Struktur von scopes
Der scopes
-Wert ist ein Objekt, bei dem jeder Schlüssel ein URL-Präfix ist, das einen "Scope-Pfad" darstellt. Der Wert für jeden Scope-Pfad ist ein imports
-ähnliches Objekt, das die Zuordnungen definiert, die speziell innerhalb dieses Bereichs gelten.
Lassen Sie uns unser Charting-Bibliothek-Problem mit einem Beispiel lösen:
<script type="importmap">
{
"imports": {
"charting-lib": "/libs/charting-lib/v5/main.js",
"api-client": "/js/api/v2/client.js"
},
"scopes": {
"/widgets/chat/": {
"charting-lib": "/libs/charting-lib/v4/legacy.js"
}
}
}
</script>
<script type="module" src="/js/app.js"></script>
<script type="module" src="/widgets/chat/init.js"></script>
So interpretiert der Browser dies:
- Ein Skript, das sich unter `/js/app.js` befindet, möchte `charting-lib` importieren. Der Browser prüft, ob der Pfad des Skripts (`/js/app.js`) mit einem der Scope-Pfade übereinstimmt. Es stimmt nicht mit `/widgets/chat/` überein. Daher verwendet der Browser die Top-Level-
imports
-Zuordnung, und `charting-lib` wird zu `/libs/charting-lib/v5/main.js` aufgelöst. - Ein Skript, das sich unter `/widgets/chat/init.js` befindet, möchte ebenfalls `charting-lib` importieren. Der Browser sieht, dass der Pfad dieses Skripts (`/widgets/chat/init.js`) unter den `/widgets/chat/`-Scope fällt. Es sucht in diesem Scope nach einer `charting-lib`-Zuordnung und findet eine. Daher wird für dieses Skript und alle Module, die es von innerhalb dieses Pfads importiert, `charting-lib` zu `/libs/charting-lib/v4/legacy.js` aufgelöst.
Mit scopes
haben wir erfolgreich zugelassen, dass zwei Teile unserer Anwendung verschiedene Versionen derselben Abhängigkeit verwenden und friedlich ohne Konflikte nebeneinander existieren. Dies ist ein Maß an Kontrolle, das zuvor nur mit komplexen Bundler-Konfigurationen oder der iframe-basierten Isolation erreichbar war.
Das Kernkonzept: Verstehen der Scope-Vererbung und der Modulauflösungs-Hierarchie
Nun kommen wir zum Kern der Sache. Wie entscheidet der Browser, welchen Scope er verwenden soll, wenn mehrere Scopes möglicherweise mit dem Pfad einer Datei übereinstimmen? Und was passiert mit den Zuordnungen in den Top-Level-imports
? Dies wird durch eine klare und vorhersehbare Hierarchie geregelt.
Die goldene Regel: Der spezifischste Scope gewinnt
Das grundlegende Prinzip der Scope-Auflösung ist die Spezifität. Wenn ein Modul unter einer bestimmten URL ein anderes Modul anfordert, betrachtet der Browser alle Schlüssel im scopes
-Objekt. Es findet den längsten Schlüssel, der ein Präfix der URL des anfordernden Moduls ist. Dieser "spezifischste" übereinstimmende Scope ist der einzige, der zur Auflösung des Imports verwendet wird. Alle anderen Scopes werden für diese spezielle Auflösung ignoriert.
Lassen Sie uns dies mit einer komplexeren Dateistruktur und Import Map veranschaulichen.
Dateistruktur:
- `/index.html` (enthält die Import Map)
- `/js/main.js`
- `/js/feature-a/index.js`
- `/js/feature-a/core/logic.js`
Import Map in `index.html`:
{
"imports": {
"api": "/js/api/v1/api.js",
"ui-kit": "/js/ui/v2/kit.js"
},
"scopes": {
"/js/feature-a/": {
"api": "/js/api/v2-beta/api.js"
},
"/js/feature-a/core/": {
"api": "/js/api/v3-experimental/api.js",
"ui-kit": "/js/ui/v1/legacy-kit.js"
}
}
}
Lassen Sie uns nun die Auflösung von `import api from 'api';` und `import ui from 'ui-kit';` aus verschiedenen Dateien nachverfolgen:
-
In `/js/main.js`:
- Der Pfad `/js/main.js` stimmt nicht mit `/js/feature-a/` oder `/js/feature-a/core/` überein.
- Kein Scope stimmt überein. Die Auflösung fällt auf die Top-Level-
imports
zurück. - `api` wird zu `/js/api/v1/api.js` aufgelöst.
- `ui-kit` wird zu `/js/ui/v2/kit.js` aufgelöst.
-
In `/js/feature-a/index.js`:
- Der Pfad `/js/feature-a/index.js` wird von `/js/feature-a/` vorangestellt. Es wird nicht von `/js/feature-a/core/` vorangestellt.
- Der spezifischste übereinstimmende Scope ist `/js/feature-a/`.
- Dieser Scope enthält eine Zuordnung für `api`. Daher wird `api` zu `/js/api/v2-beta/api.js` aufgelöst.
- Dieser Scope enthält keine Zuordnung für `ui-kit`. Die Auflösung für diesen Specifier fällt auf die Top-Level-
imports
zurück. `ui-kit` wird zu `/js/ui/v2/kit.js` aufgelöst.
-
In `/js/feature-a/core/logic.js`:
- Der Pfad `/js/feature-a/core/logic.js` wird sowohl von `/js/feature-a/` als auch von `/js/feature-a/core/` vorangestellt.
- Da `/js/feature-a/core/` länger und damit spezifischer ist, wird es als der gewinnende Scope ausgewählt. Der Scope `/js/feature-a/` wird für diese Datei vollständig ignoriert.
- Dieser Scope enthält eine Zuordnung für `api`. `api` wird zu `/js/api/v3-experimental/api.js` aufgelöst.
- Dieser Scope enthält auch eine Zuordnung für `ui-kit`. `ui-kit` wird zu `/js/ui/v1/legacy-kit.js` aufgelöst.
Die Wahrheit über "Vererbung": Es ist ein Fallback, kein Merge
Es ist entscheidend, einen häufigen Punkt der Verwirrung zu verstehen. Der Begriff "Scope-Vererbung" kann irreführend sein. Ein spezifischerer Scope erbt nicht von oder verschmilzt mit einem weniger spezifischen (übergeordneten) Scope. Der Auflösungsprozess ist einfacher und direkter:
- Finden Sie den einzig spezifischsten übereinstimmenden Scope für die URL des importierenden Skripts.
- Wenn dieser Scope eine Zuordnung für den angeforderten Specifier enthält, verwenden Sie sie. Der Prozess endet hier.
- Wenn der gewinnende Scope keine Zuordnung für den Specifier enthält, prüft der Browser sofort das Top-Level-
imports
-Objekt auf eine Zuordnung. Es sucht nicht nach anderen, weniger spezifischen Scopes. - Wenn eine Zuordnung in den Top-Level-
imports
gefunden wird, wird sie verwendet. - Wenn keine Zuordnung weder im gewinnenden Scope noch in den Top-Level-
imports
gefunden wird, wird einTypeError
ausgelöst.
Lassen Sie uns unser letztes Beispiel wiederholen, um dies zu festigen. Beim Auflösen von `ui-kit` aus `/js/feature-a/index.js` war der gewinnende Scope `/js/feature-a/`. Dieser Scope definierte `ui-kit` nicht, daher prüfte der Browser nicht den `/`-Scope (der als Schlüssel nicht existiert) oder einen anderen übergeordneten. Er ging direkt zu den globalen imports
und fand dort die Zuordnung. Dies ist ein Fallback-Mechanismus, keine kaskadierende oder verschmelzende Vererbung wie bei CSS.
Praktische Anwendungen und erweiterte Szenarien
Die Leistungsfähigkeit von scoped Import Maps zeigt sich wirklich in komplexen, realen Anwendungen. Hier sind einige Architekturmuster, die sie ermöglichen.
Micro-Frontends
Dies ist wohl der Killer-Anwendungsfall für Import Map Scopes. Stellen Sie sich eine E-Commerce-Site vor, bei der die Produktsuche, der Warenkorb und der Checkout alles separate Anwendungen (Micro-Frontends) sind, die von verschiedenen Teams entwickelt wurden. Sie sind alle in eine einzelne Host-Seite integriert.
- Das Suchteam kann die neueste Version von React verwenden.
- Das Cart-Team befindet sich möglicherweise aufgrund einer Legacy-Abhängigkeit in einer älteren, stabilen Version von React.
- Die Host-Anwendung kann Preact für ihre Shell verwenden, um leichtgewichtig zu sein.
Eine Import Map kann dies nahtlos orchestrieren:
{
"imports": {
"react": "/libs/preact/v10/preact.js",
"react-dom": "/libs/preact/v10/preact-dom.js",
"shared-state": "/js/state-manager.js"
},
"scopes": {
"/apps/search/": {
"react": "/libs/react/v18/react.js",
"react-dom": "/libs/react/v18/react-dom.js"
},
"/apps/cart/": {
"react": "/libs/react/v17/react.js",
"react-dom": "/libs/react/v17/react-dom.js"
}
}
}
Hier erhält jedes Micro-Frontend, identifiziert durch seinen URL-Pfad, seine eigene isolierte Version von React. Sie können weiterhin alle ein shared-state
-Modul aus den Top-Level-imports
importieren, um miteinander zu kommunizieren. Dies bietet eine starke Kapselung und ermöglicht gleichzeitig eine kontrollierte Interoperabilität, alles ohne komplexe Bundler-Federations-Setups.
A/B-Tests und Feature-Flagging
Möchten Sie eine neue Version eines Checkout-Flows für einen Prozentsatz Ihrer Benutzer testen? Sie können der Testgruppe eine leicht abweichende index.html
mit einer modifizierten Import Map bereitstellen.
Import Map der Kontrollgruppe:
{
"imports": {
"checkout-flow": "/js/checkout/v1/flow.js"
}
}
Import Map der Testgruppe:
{
"imports": {
"checkout-flow": "/js/checkout/v2-beta/flow.js"
}
}
Ihr Anwendungscode bleibt identisch: `import start from 'checkout-flow';`. Das Routing, welches Modul geladen wird, wird vollständig auf der Import Map-Ebene gehandhabt, die dynamisch auf dem Server basierend auf Benutzer-Cookies oder anderen Kriterien generiert werden kann.
Verwaltung von Monorepos
In einem großen Monorepo haben Sie möglicherweise viele interne Pakete, die voneinander abhängig sind. Scopes können helfen, diese Abhängigkeiten sauber zu verwalten. Sie können den Namen jedes Pakets während der Entwicklung seinem Quellcode zuordnen.
{
"imports": {
"@my-corp/design-system": "/packages/design-system/src/index.js",
"@my-corp/utils": "/packages/utils/src/index.js"
},
"scopes": {
"/packages/design-system/": {
"@my-corp/utils": "/packages/design-system/src/vendor/utils-shim.js"
}
}
}
In diesem Beispiel erhalten die meisten Pakete die Hauptbibliothek `utils`. Das Paket `design-system` erhält jedoch, möglicherweise aus einem bestimmten Grund, eine abgeschirmte oder andere Version von `utils`, die innerhalb seines eigenen Scopes definiert ist.
Browserunterstützung, Werkzeuge und Bereitstellungsaspekte
Browserunterstützung
Ab Ende 2023 ist die native Unterstützung für Import Maps in allen gängigen modernen Browsern verfügbar, einschließlich Chrome, Edge, Safari und Firefox. Dies bedeutet, dass Sie sie in der Produktion für einen Großteil Ihrer Benutzerbasis ohne Polyfills verwenden können.
Fallbacks für ältere Browser
Für Anwendungen, die ältere Browser unterstützen müssen, denen die native Import Map-Unterstützung fehlt, hat die Community eine robuste Lösung: den `es-module-shims.js`-Polyfill. Dieses einzelne Skript, das vor Ihrer Import Map enthalten ist, portiert die Unterstützung für Import Maps und andere moderne Modulfunktionen (wie dynamisches `import()`) auf ältere Umgebungen zurück. Es ist leichtgewichtig, kampferprobt und der empfohlene Ansatz, um eine breite Kompatibilität sicherzustellen.
<!-- Polyfill for older browsers -->
<script async src="https://ga.jspm.io/npm:es-module-shims@1.8.2/dist/es-module-shims.js"></script>
<!-- Your import map -->
<script type="importmap">
...
</script>
Dynamische, serverseitig generierte Maps
Eines der leistungsstärksten Bereitstellungsmuster ist es, überhaupt keine statische Import Map in Ihrer HTML-Datei zu haben. Stattdessen kann Ihr Server das JSON dynamisch basierend auf der Anfrage generieren. Dies ermöglicht:
- Umgebungsumschaltung: Stellen Sie in einer
development
-Umgebung un-minifizierte, quellenzuordnende Module und inproduction
minifizierte, produktionsbereite Module bereit. - Benutzerrollenbasierte Module: Ein Administratorbenutzer könnte eine Import Map erhalten, die Zuordnungen für Admin-Tools enthält.
- Lokalisierung: Ordnen Sie ein
translations
-Modul basierend auf demAccept-Language
-Header des Benutzers verschiedenen Dateien zu.
Best Practices und potenzielle Fallstricke
Wie bei jedem leistungsstarken Tool gibt es Best Practices zu befolgen und Fallstricke zu vermeiden.
- Lesbarkeit gewährleisten: Obwohl Sie sehr tiefe und komplexe Scope-Hierarchien erstellen können, kann es schwierig werden, sie zu debuggen. Streben Sie nach der einfachsten Scope-Struktur, die Ihren Anforderungen entspricht. Kommentieren Sie Ihr Import Map JSON, wenn es komplex wird.
- Verwenden Sie immer abschließende Schrägstriche für Pfade: Wenn Sie ein Pfadpräfix (wie ein Verzeichnis) zuordnen, stellen Sie sicher, dass sowohl der Schlüssel in der Import Map als auch der URL-Wert mit einem `/` enden. Dies ist entscheidend für das korrekte Funktionieren des Übereinstimmungsalgorithmus für alle Dateien in diesem Verzeichnis. Dies zu vergessen, ist eine häufige Fehlerquelle.
- Fallstrick: Die Nicht-Vererbungsfalle: Denken Sie daran, ein bestimmter Scope erbt nicht von einem weniger spezifischen. Es fällt *nur* auf die globalen
imports
zurück. Wenn Sie ein Auflösungsproblem debuggen, identifizieren Sie immer zuerst den einzigen gewinnenden Scope. - Fallstrick: Caching der Import Map: Ihre Import Map ist der Einstiegspunkt für Ihren gesamten Modulgraphen. Wenn Sie die URL eines Moduls in der Map aktualisieren, müssen Sie sicherstellen, dass die Benutzer die neue Map erhalten. Eine gängige Strategie ist es, die Hauptdatei
index.html
nicht stark zu cachen oder die Import Map dynamisch von einer URL zu laden, die einen Inhalts-Hash enthält, obwohl ersteres üblicher ist. - Debugging ist Ihr Freund: Moderne Browser-Entwicklertools eignen sich hervorragend zum Debuggen von Modulproblemen. Auf der Registerkarte Netzwerk können Sie genau sehen, welche URL für jedes Modul angefordert wurde. In der Konsole geben Auflösungsfehler deutlich an, welcher Specifier von welchem importierenden Skript nicht aufgelöst werden konnte.
Fazit: Die Zukunft der Build-less Webentwicklung
JavaScript Import Maps und insbesondere ihre Funktion scopes
stellen einen Paradigmenwechsel in der Frontend-Entwicklung dar. Sie verschieben einen wesentlichen Teil der Logik – die Modulauflösung – von einem Pre-Compilation-Build-Schritt direkt in einen Browser-nativen Standard. Hier geht es nicht nur um Komfort, sondern darum, flexiblere, dynamischere und widerstandsfähigere Webanwendungen zu erstellen.
Wir haben gesehen, wie die Modulauflösungs-Hierarchie funktioniert: Der spezifischste Scope-Pfad gewinnt immer, und er fällt auf das globale imports
-Objekt zurück, nicht auf übergeordnete Scopes. Diese einfache, aber leistungsstarke Regel ermöglicht die Erstellung anspruchsvoller Anwendungsarchitekturen wie Micro-Frontends und ermöglicht dynamisches Verhalten wie A/B-Tests mit überraschender Leichtigkeit.
Da sich die Webplattform weiterentwickelt, nimmt die Abhängigkeit von komplexen Build-Tools für die Entwicklung ab. Import Maps sind ein Eckpfeiler dieser "Build-less"-Zukunft und bieten eine einfachere, schnellere und standardisiertere Möglichkeit, Abhängigkeiten zu verwalten. Indem Sie die Konzepte von Scopes und der Auflösungs-Hierarchie beherrschen, lernen Sie nicht nur eine neue Browser-API, sondern rüsten sich auch mit den Werkzeugen, um die nächste Generation von Anwendungen für das globale Web zu erstellen.