[ Web Proxy ]
URL:
Viewing: https://developer.mozilla.org/de/docs/Web/JavaScript/Guide/Modules#aggregating_modules [Back]  [Original]

JavaScript-Module - JavaScript | MDN

Dieser Inhalt wurde automatisch aus dem Englischen bersetzt, und kann Fehler enthalten. Erfahre mehr ber dieses Experiment.

View in English Always switch to English

JavaScript-Module

Dieser Leitfaden bietet Ihnen alles, was Sie bentigen, um mit der JavaScript-Modulsyntax zu beginnen.

In diesem Artikel

Hintergrund zu Modulen

JavaScript-Programme begannen recht klein in den frhen Tagen wurde es hauptschlich fr isolierte Skriptaufgaben verwendet, die ein wenig Interaktivitt auf Ihren Webseiten boten, wo es ntig war, sodass groe Skripte im Allgemeinen nicht bentigt wurden. Einige Jahre spter haben wir nun vollstndige Anwendungen, die in Browsern mit einer Menge JavaScript ausgefhrt werden, sowie JavaScript, das in anderen Kontexten verwendet wird (zum Beispiel Node.js).

Komplexe Projekte erfordern einen Mechanismus, um JavaScript-Programme in separate Module zu unterteilen, die bei Bedarf importiert werden knnen. Node.js verfgt schon lange ber diese Fhigkeit, und es gibt zahlreiche JavaScript-Bibliotheken und -Frameworks, die die Nutzung von Modulen ermglichen (zum Beispiel andere auf CommonJS und AMD basierende Modulsysteme wie RequireJS, webpack, und Babel).

Alle modernen Browser untersttzen Modulfunktionen nativ ohne Notwendigkeit der Transpilation. Das kann nur vorteilhaft sein Browser knnen das Laden von Modulen optimieren und es effizienter machen, als eine Bibliothek zu verwenden und all diese zustzliche clientseitige Verarbeitung und zustzliche Rundreisen durchzufhren. Es macht jedoch Bundler wie webpack nicht obsolet Bundler leisten nach wie vor gute Arbeit beim Partitionieren von Code in angemessen groe Stcke und knnen andere Optimierungen wie Minifizierung, Entfernung toten Codes und Tree-Shaking durchfhren.

Einfhrung in ein Beispiel

Um die Nutzung von Modulen zu demonstrieren, haben wir ein Set von Beispielen erstellt, die Sie auf GitHub finden knnen. Diese Beispiele demonstrieren eine Reihe von Modulen, die ein <canvas>-Element auf einer Webseite erstellen und dann verschiedene Formen auf der Leinwand zeichnen (und Informationen darber berichten).

Diese sind ziemlich trivial, aber sie wurden absichtlich einfach gehalten, um Module eindeutig zu demonstrieren.

Hinweis: Wenn Sie die Beispiele herunterladen und lokal ausfhren mchten, mssen Sie sie ber einen lokalen Webserver ausfhren.

Grundlegende Beispielstruktur

In unserem ersten Beispiel (siehe basic-modules) haben wir eine Dateistruktur wie folgt:

index.html
main.js
modules/
    canvas.js
    square.js

Hinweis: Alle Beispiele in diesem Leitfaden haben im Grunde die gleiche Struktur; das oben gezeigte sollte ziemlich vertraut werden.

Die Module im Verzeichnis sind wie folgt:

  • canvas.js enthlt Funktionen, die mit der Einrichtung der Leinwand zu tun haben:

    • create() erstellt eine Leinwand mit einer angegebenen width und height innerhalb eines Wrapper-<div> mit einer angegebenen ID, das selbst innerhalb eines angegebenen Elternelements eingefgt wird. Gibt ein Objekt zurck, das den 2D-Kontext der Leinwand und die ID des Wrappers enthlt.
    • createReportList() erstellt eine ungeordnete Liste, die innerhalb eines angegebenen Wrapper-Elements angefgt wird und zur Ausgabe von Berichtsdatendaten verwendet werden kann. Gibt die ID der Liste zurck.
  • square.js enthlt:

    • name eine Konstante, die den String 'square' enthlt.
    • draw() zeichnet ein Quadrat auf einer angegebenen Leinwand mit einer angegebenen Gre, Position und Farbe. Gibt ein Objekt zurck, das die Gre, Position und Farbe des Quadrats enthlt.
    • reportArea() schreibt die Flche eines Quadrats in eine bestimmte Berichtsliste, anhand seiner Lnge.
    • reportPerimeter() schreibt den Umfang eines Quadrats in eine bestimmte Berichtsliste, anhand seiner Lnge.

Anmerkung .mjs vs. .js

In diesem Artikel haben wir .js-Erweiterungen fr unsere Moduldaten verwendet, aber in anderen Ressourcen sehen Sie mglicherweise die .mjs-Erweiterung. V8's Dokumentation empfiehlt dies, zum Beispiel. Die angegebenen Grnde sind:

  • Es ist gut fr die Klarheit, d.h. es wird deutlich, welche Dateien Module sind und welche normale JavaScript-Dateien sind.
  • Es stellt sicher, dass Ihre Moduldaten von Laufzeiten wie Node.js und Tools wie Babel als Modul geparst werden.

Wir haben uns jedoch entschieden, zumindest vorerst bei .js zu bleiben. Um Module korrekt in einem Browser zum Laufen zu bringen, mssen Sie sicherstellen, dass Ihr Server sie mit einem Content-Type-Header bereitstellt, der einen JavaScript-MIME-Typ wie text/javascript enthlt. Andernfalls erhalten Sie einen strikten MIME-Typ-berprfung-Fehler wie "Der Server hat mit einem Nicht-JavaScript-MIME-Typ geantwortet" und der Browser fhrt Ihr JavaScript nicht aus. Die meisten Server setzen bereits den richtigen Typ fr .js-Dateien, aber noch nicht fr .mjs-Dateien. Server, die .mjs-Dateien bereits korrekt bereitstellen, sind GitHub Pages und http-server fr Node.js.

Dies ist in Ordnung, wenn Sie bereits eine solche Umgebung verwenden oder wenn nicht, Sie wissen, was Sie tun und Zugriff haben (d.h. Sie knnen Ihren Server konfigurieren, um den korrekten Content-Type fr .mjs Dateien einzustellen). Es knnte jedoch Verwirrung stiften, wenn Sie nicht den Server kontrollieren, von dem Sie Dateien bereitstellen, oder Dateien zur ffentlichen Nutzung verffentlichen, wie wir es hier tun.

Fr Lern- und Portabilittszwecke haben wir uns entschieden, bei .js zu bleiben.

Wenn Sie wirklich den Wert der Klarheit von .mjs fr Module gegenber .js fr "normale" JavaScript-Dateien schtzen, aber nicht auf das beschriebene Problem stoen mchten, knnten Sie .mjs whrend der Entwicklung verwenden und diese whrend Ihres Build-Schritts in .js umwandeln.

Es ist auch erwhnenswert, dass:

  • Einige Tools mglicherweise niemals .mjs untersttzen.
  • Das <script type="module">-Attribut wird verwendet, um anzugeben, wann auf ein Modul verwiesen wird, wie im Abschnitt Anwenden des Moduls auf Ihr HTML beschrieben.

Exportieren von Modulfunktionen

Der erste Schritt, um Zugriff auf Modulfunktionen zu erhalten, besteht darin, sie zu exportieren. Dies geschieht mit der export-Anweisung.

Der einfachste Weg, es zu nutzen, ist, es vor jedem Element zu platzieren, das Sie aus dem Modul exportieren mchten, zum Beispiel:

js
export const name = "square";

export function draw(ctx, length, x, y, color) {
  ctx.fillStyle = color;
  ctx.fillRect(x, y, length, length);

  return { length, x, y, color };
}

Sie knnen Funktionen, var, let, const und wie wir spter sehen werden Klassen exportieren. Sie mssen Elemente auf oberster Ebene sein: Zum Beispiel knnen Sie export nicht innerhalb einer Funktion verwenden.

Eine bequemere Mglichkeit, alle Elemente, die Sie exportieren mchten, zu exportieren, besteht darin, eine einzelne Exportanweisung am Ende Ihrer Moduldaten zu verwenden, gefolgt von einer kommagetrennten Liste der Funktionen, die Sie exportieren mchten, eingeschlossen in geschweifte Klammern. Zum Beispiel:

js
export { name, draw, reportArea, reportPerimeter };

Importieren von Funktionen in Ihr Skript

Sobald Sie einige Funktionen aus Ihrem Modul exportiert haben, mssen Sie diese in Ihr Skript importieren, um sie verwenden zu knnen. Der einfachste Weg, dies zu tun, ist wie folgt:

js
import { name, draw, reportArea, reportPerimeter } from "./modules/square.js";

Sie verwenden die import-Anweisung, gefolgt von einer kommagetrennten Liste der Funktionen, die Sie importieren mchten, eingeschlossen in geschweifte Klammern, gefolgt vom Schlsselwort from, gefolgt vom Modulspezifizierer.

Der Modulspezifizierer bietet einen String, den die JavaScript-Umgebung in einen Pfad zu den Moduldaten auflsen kann. In einem Browser knnte dies ein Pfad relativ zum Stamm des Standorts sein, was fr unser basic-modules-Beispiel /js-examples/module-examples/basic-modules wre. Hier verwenden wir jedoch stattdessen die Punkt-(.)-Syntax, um den aktuellen Standort zu bedeuten, gefolgt vom relativen Pfad zu den Daten, die wir versuchen zu finden. Dies ist viel besser, als jedes Mal den gesamten absoluten Pfad zu schreiben, da relative Pfade krzer und die URL portabel machen das Beispiel funktioniert weiterhin, wenn Sie es an einen anderen Ort in der Standorthierarchie verschieben.

Also zum Beispiel:

bash
/js-examples/module-examples/basic-modules/modules/square.js

wird zu

bash
./modules/square.js

Sie knnen solche Zeilen in main.js sehen.

Hinweis: In einigen Modulsystemen knnen Sie einen Modulspezifizierer wie modules/square verwenden, der kein relativer oder absoluter Pfad ist und keine Dateierweiterung hat. Diese Art von Spezifizierer kann in einer Browserumgebung verwendet werden, wenn Sie zuerst eine Importkarte definieren.

Sobald Sie die Funktionen in Ihr Skript importiert haben, knnen Sie sie genauso verwenden, als wren sie im gleichen Dateikrper definiert. Das folgende Beispiel ist in main.js unter den Importzeilen zu finden:

js
const myCanvas = create("myCanvas", document.body, 480, 320);
const reportList = createReportList(myCanvas.id);

const square = draw(myCanvas.ctx, 50, 50, 100, "blue");
reportArea(square.length, reportList);
reportPerimeter(square.length, reportList);

Hinweis: Die importierten Werte sind schreibgeschtzte Ansichten der exportierten Funktionen. hnlich wie bei const-Variablen knnen Sie die importierte Variable nicht neu zuweisen, aber Sie knnen immer noch Eigenschaften von Objektwerten ndern. Der Wert kann nur vom Modul, das ihn exportiert, neu zugewiesen werden. Sehen Sie sich den import reference fr ein Beispiel an.

Importieren von Modulen mit Importkarten

Oben haben wir gesehen, wie ein Browser ein Modul mit einem Modulspezifizierer importieren kann, der entweder eine absolute URL oder eine relative URL ist, die mit der Basis-URL des Dokuments aufgelst wird:

js
import { name as circleName } from "https://example.com/shapes/circle.js";
import { name as squareName, draw } from "./shapes/square.js";

Importkarten ermglichen Entwicklern, fast jeden Text, den sie mchten, im Modulspezifizierer anzugeben, wenn sie ein Modul importieren; die Karte liefert einen entsprechenden Wert, der den Text beim Auflsen der Modul-URL ersetzt.

Zum Beispiel definiert der imports-Schlssel in der unten stehenden Importkarte ein JSON-Objekt mit einer Modulspezifiziererkarte, in dem die Eigenschaftenamen als Modulspezifizierer verwendet werden knnen und die entsprechenden Werte bei der Auflsung der Modul-URL ersetzt werden. Die Werte mssen absolute oder relative URLs sein. Relative URLs werden mit der Basis-URL des Dokuments, das die Importkarte enthlt, zu absoluten URL-Adressen aufgelst.

html
<script type="importmap">
  {
    "imports": {
      "shapes": "./shapes/square.js",
      "shapes/square": "./modules/shapes/square.js",
      "https://example.com/shapes/square.js": "./shapes/square.js",
      "https://example.com/shapes/": "/shapes/square/",
      "../shapes/square": "./shapes/square.js"
    }
  }
</script>

Die Importkarte wird mit einem JSON-Objekt in einem <script>-Element mit dem type-Attribut auf importmap gesetzt definiert. Beachten Sie, dass eine Importkarte nur auf das Dokument angewendet wird die Spezifikation beschreibt nicht, wie eine Importkarte im Kontext eines Arbeiters oder Worker angewendet werden kann.

Mit dieser Karte knnen Sie jetzt die oben genannten Eigenschaften als Modulspezifizierer verwenden. Wenn kein nachgestellter Schrgstrich im Modulspezifizierer-Schlssel vorhanden ist, wird der gesamte Modulspezifizierer-Schlssel abgeglichen und ersetzt. Zum Beispiel: Unten vergleichen wir Bare Module-Namen und remap eine URL in einen anderen Pfad.

js
// Bare module names as module specifiers
import { name as squareNameOne } from "shapes";
import { name as squareNameTwo } from "shapes/square";

// Remap a URL to another URL
import { name as squareNameThree } from "https://example.com/shapes/square.js";

Wenn der Modulspezifizierer einen nachgestellten Schrgstrich hat, dann muss der Wert ebenfalls einen haben, und der Schlssel wird als Pfad-Prfix abgeglichen. Dies ermglicht das Remapping ganzer Klassen von URLs.

js
// Remap a URL as a prefix ( https://example.com/shapes/)
import { name as squareNameFour } from "https://example.com/shapes/moduleshapes/square.js";

Es ist mglich, dass mehrere Schlssel in einer Importkarte gltige bereinstimmungen fr einen Modulspezifizierer sind. Zum Beispiel knnte ein Modulspezifizierer von shapes/circle/ sowohl die Modulspezifizierer-Schlssel shapes/ als auch shapes/circle/ abgleichen. In diesem Fall whlt der Browser den spezifischsten (lngsten) bereinstimmenden Modulspezifizierer-Schlssel aus.

Importkarten ermglichen es, Module mit Bare-Namen zu importieren (wie in Node.js), und knnen auch das Importieren von Modulen aus Paketen mit und ohne Dateierweiterungen simulieren. Obwohl oben nicht gezeigt, erlauben sie auch das Importieren bestimmter Versionen einer Bibliothek, basierend auf dem Pfad des Skripts, das das Modul importiert. Im Allgemeinen ermglichen sie es Entwicklern, ergonomischere Import-Codes zu schreiben und erleichtern das Verwalten der verschiedenen Versionen und Abhngigkeiten von Modulen, die von einer Website verwendet werden. Dies kann den Aufwand reduzieren, um die gleichen JavaScript-Bibliotheken sowohl im Browser als auch auf dem Server zu verwenden.

Die folgenden Abschnitte erweitern die oben beschriebenen Funktionen.

Feature-Erkennung

Sie knnen die Untersttzung fr Importkarten mit der statischen Methode HTMLScriptElement.supports() berprfen (die selbst breit untersttzt wird):

js
if (HTMLScriptElement.supports?.("importmap")) {
  console.log("Browser supports import maps.");
}

Importieren von Modulen als Bare-Namen

In einigen JavaScript-Umgebungen wie Node.js knnen Sie Bare-Namen fr den Modulspezifizierer verwenden. Dies funktioniert, weil die Umgebung Modulnamen in einem Standardort im Dateisystem auflsen kann. Zum Beispiel knnten Sie die folgende Syntax verwenden, um das Modul "square" zu importieren.

js
import { name, draw, reportArea, reportPerimeter } from "square";

Um Bare-Namen in einem Browser zu verwenden, bentigen Sie eine Importkarte, die dem Browser die Informationen bietet, die erforderlich sind, um Modulspezifizierer in URLs aufzulsen (JavaScript wirft einen TypeError, wenn es versucht, einen Modulspezifizierer zu importieren, der nicht zu einem Modulstandort aufgelst werden kann).

Unten sehen Sie eine Karte, die einen square-Modulspezifizierer-Schlssel definiert, der in diesem Fall auf einen relativen Adresswert abgebildet wird.

html
<script type="importmap">
  {
    "imports": {
      "square": "./shapes/square.js"
    }
  }
</script>

Mit dieser Karte knnen wir jetzt einen Bare-Namen verwenden, wenn wir das Modul importieren:

js
import { name as squareName, draw } from "square";

Remapping von Modulpfaden

Eintrge in der Modulspezifiziererkarte, bei denen sowohl der Spezifizierer-Schlssel als auch der zugehrige Wert einen nachgestellten Schrgstrich (/) haben, knnen als Pfad-Prfix verwendet werden. Dies erlaubt das Remapping einer ganzen Reihe von Import-URLs von einem Ort zu einem anderen. Es kann auch verwendet werden, um zu emulieren, mit Paketen und Modulen zu arbeiten, wie Sie es vielleicht im Node-kosystem sehen.

Hinweis: Der nachgestellte / zeigt an, dass der Modulspezifizierer-Schlssel als Teil eines Modulspezifizierers ersetzt werden kann. Wenn dies nicht vorhanden ist, wird der Browser nur den gesamten Modulspezifizierer-Schlssel abgleichen (und ersetzen).

Pakete von Modulen

Die folgende JSON-Importkartendefinition ordnet lodash als Bare-Namen zu und das Modulspezifizierer-Prfix lodash/ dem Pfad /node_modules/lodash-es/ (aufgelst zur Basis-URL des Dokuments) zu:

json
{
  "imports": {
    "lodash": "/node_modules/lodash-es/lodash.js",
    "lodash/": "/node_modules/lodash-es/"
  }
}

Mit dieser Zuweisung knnen Sie sowohl das gesamte Paket mit dem Bare-Namen als auch Module darin entlang dem Pfad importieren:

js
import _ from "lodash";
import fp from "lodash/fp.js";

Es ist mglich, fp oben ohne die .js Dateierweiterung zu importieren, aber Sie mssten einen Bare-Modulspezifizierer-Schlssel fr diese Datei erstellen, z.B. lodash/fp, anstatt den Pfad zu verwenden. Dies mag vernnftig fr nur ein Modul sein, skaliert aber schlecht, wenn Sie viele Module importieren mchten.

Generelles URL-Remapping

Ein Modulspezifizierer-Schlssel muss kein Pfad sein er kann auch eine absolute URL (oder ein URL-hnlicher relativer Pfad wie ./, ../, /) sein. Dies kann ntzlich sein, wenn Sie eine Modulressource mit Ihren eigenen lokalen Ressourcen remappen mchten.

json
{
  "imports": {
    "https://www.unpkg.com/moment/": "/node_modules/moment/"
  }
}

Geschichtete Module fr Versionsmanagement

kosysteme wie Node verwenden Paketmanager wie npm, um Module und deren Abhngigkeiten zu verwalten. Der Paketmanager stellt sicher, dass jedes Modul von anderen Modulen und deren Abhngigkeiten getrennt ist. Daher kann eine komplexe Anwendung dasselbe Modul mehrmals mit verschiedenen Versionen in verschiedenen Teilen des Modulgrafen enthalten, ohne dass sich die Benutzer mit dieser Komplexitt auseinandersetzen mssen.

Hinweis: Sie knnen das Versionsmanagement auch mit relativen Pfaden erreichen, aber dies ist suboptimal, weil es unter anderem eine bestimmte Struktur fr Ihr Projekt erzwingt und Sie daran hindert, Bare-Modulnamen zu verwenden.

Importkarten erlauben es Ihnen ebenfalls, mehrere Versionen von Abhngigkeiten in Ihrer Anwendung zu haben und auf sie mit dem gleichen Modulspezifizierer zu verweisen. Dies setzen Sie mit dem scopes-Schlssel um, der es Ihnen ermglicht, Modulspezifizierer-Karten bereitzustellen, die je nach Pfad des Skripts, das den Import ausfhrt, verwendet werden. Das folgende Beispiel zeigt dies.

json
{
  "imports": {
    "cool-module": "/node_modules/cool-module/index.js"
  },
  "scopes": {
    "/node_modules/dependency/": {
      "cool-module": "/node_modules/some/other/location/cool-module/index.js"
    }
  }
}

Mit dieser Zuweisung, wenn ein Skript mit einer URL, die /node_modules/dependency/ enthlt, cool-module importiert, wird die Version in /node_modules/some/other/location/cool-module/index.js verwendet. Die Karte in imports wird als Rckfall verwendet, wenn es keinen passenden Geltungsbereich in der geschichteten Karte gibt oder die passenden Bereiche keinen passenden Spezifizierer enthalten. Wenn zum Beispiel cool-module aus einem Skript mit einem nicht passenden Geltungsbereichspfad importiert wird, wird stattdessen die Modulspezifizierer-Karte in imports verwendet, wobei auf die Version in /node_modules/cool-module/index.js verwiesen wird.

Beachten Sie, dass der Pfad, der verwendet wird, um einen Geltungsbereich auszuwhlen, nicht beeinflusst, wie die Adresse aufgelst wird. Der Wert im zugeordneten Pfad muss nicht mit dem Geltungsbereichspfad bereinstimmen, und relative Pfade werden weiterhin auf die Basis-URL des Skripts, das die Importkarte enthlt, aufgelst.

Genau wie bei Modulspezifizierer-Karten knnen Sie viele Geltungsbereichs-Schlssel haben, und diese knnen sich berschneidende Pfade enthalten. Wenn mehrere Geltungsbereiche mit der Verweis-URL bereinstimmen, dann wird der spezifischste Geltungsbereichspfad (der lngste Geltungsbereichsschlssel) zuerst berprft. Wenn es keinen bereinstimmenden Spezifizierer gibt, fllt der Browser auf den zweit spezifischsten bereinstimmenden Geltungsbereichspfad zurck, und so weiter. Wenn es keinen bereinstimmenden Spezifizierer in einem der bereinstimmenden Geltungsbereiche gibt, berprft der Browser auf eine bereinstimmung in der Modulspezifizierer-Karte im imports-Schlssel.

Verbesserung des Cachings durch Weglassen der hashes in Dateinamen

Skriptdateien, die von Websites verwendet werden, haben oft hashbasierte Dateinamen, um das Caching zu vereinfachen. Der Nachteil dieses Ansatzes ist, dass, wenn sich ein Modul ndert, alle Module, die es mit seinem hashbasierten Dateinamen importieren, ebenfalls aktualisiert bzw. neu generiert werden mssen. Dies fhrt mglicherweise zu einer Kaskade von Aktualisierungen, was verschwenderisch in Bezug auf Netzwerkauslastung ist.

Importkarten bieten eine bequeme Lsung fr dieses Problem. Anstatt von spezifischen hashbasierten Dateinamen abhngig zu sein, knnen Anwendungen und Skripte stattdessen von einer nicht gehashten Version des Modulnamens (Adresse) abhngen. Eine Importkarte wie die folgende liefert dann eine Zuordnung zur tatschlichen Skriptdatei.

json
{
  "imports": {
    "main_script": "/node/srcs/application-fg7744e1b.js",
    "dependency_script": "/node/srcs/dependency-3qn7e4b1q.js"
  }
}

Wenn sich dependency_script ndert, ndert sich auch sein Hash, der im Dateinamen enthalten ist. In diesem Fall mssen wir nur die Importkarte aktualisieren, um den genderten Namen des Moduls zu reflektieren. Wir mssen den Quellcode fr abhngige JavaScript-Codes nicht aktualisieren, weil der Spezifizierer in der Importanweisung sich nicht ndert.

Laden von Nicht-JavaScript-Ressourcen

Eine spannende Funktion, die eine einheitliche Modularchitektur bringt, ist die Mglichkeit, Nicht-JavaScript-Ressourcen als Module zu laden. Zum Beispiel knnen Sie JSON als JavaScript-Objekt oder CSS als CSSStyleSheet-Objekt importieren.

Sie mssen explizit angeben, welche Art von Ressource Sie importieren. Standardmig geht der Browser davon aus, dass die Ressource JavaScript ist, und wird einen Fehler auslsen, wenn die aufgelste Ressource etwas anderes ist. Um JSON, CSS oder andere Ressourcentypen zu importieren, verwenden Sie die Import-Attribut-Syntax:

js
import colors from "./colors.json" with { type: "json" };
import styles from "./styles.css" with { type: "css" };

Browser fhren auch eine Validierung des Modultyps durch und schlagen fehl, wenn zum Beispiel ./data.json nicht als JSON-Datei aufgelst wird. Dies stellt sicher, dass Sie nicht versehentlich Code ausfhren, wenn Sie nur Daten importieren mchten. Sobald erfolgreich importiert, knnen Sie den importierten Wert nun als normales JavaScript-Objekt oder CSSStyleSheet-Objekt verwenden.

js
console.log(colors.map((color) => color.value));
document.adoptedStyleSheets = [styles];

Anwendung des Moduls auf Ihr HTML

Jetzt mssen wir nur noch das main.js-Modul auf unsere HTML-Seite anwenden. Dies hnelt sehr dem, wie wir ein regulres Skript auf eine Seite anwenden, mit einigen bemerkenswerten Unterschieden.

Zuerst mssen Sie type="module" im <script>-Element einschlieen, um dieses Skript als Modul zu deklarieren. Um das main.js-Skript zu importieren, verwenden wir dies:

html
<script type="module" src="main.js"></script>

Sie knnen das Modulskript auch direkt in die HTML-Datei einbetten, indem Sie den JavaScript-Code in den Body des <script>-Elements platzieren:

html
<script type="module">
  /* JavaScript module code here */
</script>

Sie knnen import- und export-Anweisungen nur innerhalb von Modulen verwenden, nicht in regulren Skripten. Ein Fehler wird ausgelst, wenn das <script>-Element nicht das type="module"-Attribut enthlt und versucht, andere Module zu importieren. Zum Beispiel:

html
<script>
  import _ from "lodash"; // SyntaxError: import declarations may only appear at top level of a module
  // 
</script>
<script src="a-module-using-import-statements.js"></script>
<!-- SyntaxError: import declarations may only appear at top level of a module -->

Sie sollten im Allgemeinen alle Ihre Module in separaten Dateien definieren. Inline in HTML deklarierte Module knnen nur andere Module importieren, aber was auch immer sie exportieren, wird fr andere Module nicht zugnglich sein (weil sie keine URL haben).

Hinweis: Module und ihre Abhngigkeiten knnen vorgeladen werden, indem sie in <link>-Elementen mit rel="modulepreload" angegeben werden. Dies kann die Ladezeit erheblich reduzieren, wenn die Module verwendet werden.

Andere Unterschiede zwischen Modulen und klassischen Skripten

  • Sie mssen auf lokale Tests achten wenn Sie versuchen, die HTML-Datei lokal zu laden (d.h. mit einer file://-URL), stoen Sie aufgrund der Sicherheitsanforderungen von JavaScript-Modulen auf CORS-Fehler. Sie mssen Ihre Tests ber einen Server durchfhren.
  • Beachten Sie auch, dass Sie mglicherweise unterschiedliches Verhalten von innerhalb von Modulen definierten Skriptabschnitten im Vergleich zu klassischen Skripten erhalten. Dies liegt daran, dass Module automatisch strikten Modus verwenden.
  • Es besteht keine Notwendigkeit, das defer-Attribut zu verwenden (siehe <script>-Attribute), wenn Sie ein Modulskript laden; Module werden automatisch verzgert geladen.
  • Module werden nur einmal ausgefhrt, auch wenn sie in mehreren <script>-Tags referenziert werden.
  • Zuletzt, um dies deutlich zu machen Modulfunktionen werden in den Bereich eines einzelnen Skripts importiert sie sind im globalen Bereich nicht verfgbar. Daher knnen Sie nur auf importierte Funktionen in dem Skript zugreifen, in das sie importiert werden, und Sie knnen nicht auf sie von der JavaScript-Konsole zugreifen, beispielsweise. Sie erhalten weiterhin Syntaxfehler in den Entwicklertools angezeigt, aber Sie werden nicht in der Lage sein, einige der Debugging-Techniken zu verwenden, die Sie erwartet htten.

Im Modul definierte Variablen sind auf das Modul beschrnkt, es sei denn, sie werden explizit an das globale Objekt gebunden. Auf der anderen Seite sind global definierte Variablen innerhalb des Moduls verfgbar. Zum Beispiel, geben Sie den folgenden Code:

html
<!doctype html>
<html lang="en-US">
  <head>
    <meta charset="UTF-8" />
    <title>Example page</title>
    <link rel="stylesheet" href="" />
  </head>
  <body>
    <div id="main"></div>
    <script>
      // A var statement creates a global variable.
      var text = "Hello";
    </script>
    <script type="module" src="./render.js"></script>
  </body>
</html>
js
/* render.js */
document.getElementById("main").innerText = text;

wrde die Seite trotzdem Hello rendern, weil die globalen Variablen text und document im Modul verfgbar sind. (Beachten Sie auch in diesem Beispiel, dass ein Modul nicht unbedingt eine Import-/Export-Anweisung bentigt das Einzige, was erforderlich ist, ist, dass der Einstiegspunkt type="module" hat.)

Standardexporte vs. benannte Exporte

Die Funktionalitt, die wir bisher exportiert haben, bestand aus benannten Exporten jedes Element (sei es eine Funktion, const, etc.) wurde beim Export namentlich genannt und dieser Name wurde auch beim Import verwendet.

Es gibt auch eine Art Export, genannt Standardexport entwickelt, um es einfach zu machen, eine Standardfunktion von einem Modul bereitzustellen, und es hilft JavaScript-Module, mit vorhandenen CommonJS- und AMD-Modulsystemen zu interagieren (wie es schn im Artikel ES6 In Depth: Modules von Jason Orendorff erlutert wird; suchen Sie nach "Default exports").

Schauen wir uns ein Beispiel an, whrend wir erklren, wie es funktioniert. In unserem square.js im Verzeichnis basic-modules finden Sie eine Funktion namens randomSquare(), die ein Quadrat mit einer zuflligen Farbe, Gre und Position erstellt. Wir mchten dies als unser Standardexport definieren. Am Ende der Datei schreiben wir:

js
export default randomSquare;

Beachten Sie das Fehlen geschweifter Klammern.

Wir knnten stattdessen export default an die Funktion anfgen und sie als anonyme Funktion definieren, wie folgt:

js
export default function (ctx) {
  // 
}

In unserer main.js-Datei importieren wir die Standardfunktion mit dieser Zeile:

js
import randomSquare from "./modules/square.js";

Beachten Sie auch hier das Fehlen geschweifter Klammern. Dies liegt daran, dass pro Modul nur ein Standardexport erlaubt ist und wir wissen, dass es sich um randomSquare handelt. Die obige Zeile ist im Wesentlichen eine Kurzform fr:

js
import { default as randomSquare } from "./modules/square.js";

Hinweis: Die Syntax "as" zum Umbenennen von exportierten Elementen wird im Abschnitt Umbenennen von Importen und Exporten weiter unten erklrt.

Vermeidung von Namenskonflikten

Bisher scheinen unsere Module zum Zeichnen von Leinwandformen gut zu funktionieren. Aber was passiert, wenn wir versuchen, ein Modul hinzuzufgen, das sich mit dem Zeichnen einer anderen Form wie einem Kreis oder Dreieck beschftigt? Diese Formen htten wahrscheinlich hnliche zugeordnete Funktionen wie draw(), reportArea(), etc.; wenn wir versuchen, verschiedene Funktionen mit demselben Namen in die gleiche Top-Level-Moduldatei zu importieren, wrden wir auf Konflikte und Fehler stoen.

Glcklicherweise gibt es mehrere Mglichkeiten, dies zu umgehen. Wir werden diese in den folgenden Abschnitten betrachten.

Umbenennen von Importen und Exporten

Innerhalb Ihrer import- und export-Anweisung geschweifter Klammern knnen Sie das Schlsselwort as zusammen mit einem neuen Funktionsnamen verwenden, um den Bezeichner zu ndern, den Sie innerhalb des Top-Level-Moduls fr eine Funktion verwenden werden.

Zum Beispiel wrden beide der folgenden die gleiche Aufgabe ausfhren, jedoch auf leicht unterschiedliche Weise:

js
// -- module.js --
export { function1 as newFunctionName, function2 as anotherNewFunctionName };

// -- main.js --
import { newFunctionName, anotherNewFunctionName } from "./modules/module.js";
js
// -- module.js --
export { function1, function2 };

// -- main.js --
import {
  function1 as newFunctionName,
  function2 as anotherNewFunctionName,
} from "./modules/module.js";

Schauen wir uns ein reales Beispiel an. In unserem Verzeichnis renaming sehen Sie das gleiche Modulsystem wie im vorherigen Beispiel, auer dass wir zustzlich circle.js und triangle.js Module hinzugefgt haben, um Kreise und Dreiecke zu zeichnen und zu berichten.

Innerhalb jedes dieser Module haben wir gleichnamige Funktionen, die exportiert werden, und daher hat jedes am Ende die gleiche export-Anweisung:

js
export { name, draw, reportArea, reportPerimeter };

Beim Importieren in main.js, wenn wir versuchen,

js
import { name, draw, reportArea, reportPerimeter } from "./modules/square.js";
import { name, draw, reportArea, reportPerimeter } from "./modules/circle.js";
import { name, draw, reportArea, reportPerimeter } from "./modules/triangle.js";

wrde der Browser einen Fehler wie "SyntaxError: redeclaration of import name" (Firefox) werfen.

Stattdessen mssen wir die Importe umbenennen, damit sie einzigartig sind:

js
import {
  name as squareName,
  draw as drawSquare,
  reportArea as reportSquareArea,
  reportPerimeter as reportSquarePerimeter,
} from "./modules/square.js";

import {
  name as circleName,
  draw as drawCircle,
  reportArea as reportCircleArea,
  reportPerimeter as reportCirclePerimeter,
} from "./modules/circle.js";

import {
  name as triangleName,
  draw as drawTriangle,
  reportArea as reportTriangleArea,
  reportPerimeter as reportTrianglePerimeter,
} from "./modules/triangle.js";

Beachten Sie, dass Sie das Problem stattdessen auch in den Moduldaten lsen knnten, z.B.

js
// in square.js
export {
  name as squareName,
  draw as drawSquare,
  reportArea as reportSquareArea,
  reportPerimeter as reportSquarePerimeter,
};
js
// in main.js
import {
  squareName,
  drawSquare,
  reportSquareArea,
  reportSquarePerimeter,
} from "./modules/square.js";

Und es wrde genauso funktionieren. Welche Stilrichtung Sie verwenden, bleibt Ihnen berlassen. Es macht jedoch mglicherweise mehr Sinn, Ihren Modulcode unberhrt zu lassen und die nderungen in den Importen vorzunehmen. Dies macht besonders Sinn, wenn Sie von Drittanbieter-Modulen importieren, ber die Sie keine Kontrolle haben.

Erstellen eines Modulobjekts

Die obige Methode funktioniert gut, ist aber etwas unbersichtlich und langwierig. Eine noch bessere Lsung besteht darin, die Funktionen jedes Moduls in einem Modulobjekt zu importieren. Die folgende Syntaxform tut dies:

js
import * as Module from "./modules/module.js";

Dies erfasst alle im module.js verfgbaren Exporte und macht sie als Mitglieder eines Objekts verfgbar, wodurch es effektiv seinen eigenen Namensraum erhlt. Zum Beispiel:

js
Module.function1();
Module.function2();

Schauen wir uns wieder ein reales Beispiel an. Wenn Sie in unser Verzeichnis module-objects gehen, sehen Sie wieder das gleiche Beispiel, aber diesmal auf diese neue Syntax umgeschrieben. In den Modulen sind die Exporte alle in der folgenden einfachen Form:

js
export { name, draw, reportArea, reportPerimeter };

Die Importe hingegen sehen so aus:

js
import * as Canvas from "./modules/canvas.js";

import * as Square from "./modules/square.js";
import * as Circle from "./modules/circle.js";
import * as Triangle from "./modules/triangle.js";

In jedem Fall knnen Sie jetzt auf die Importe des Moduls unter dem angegebenen Objektnamen zugreifen. Zum Beispiel:

js
const square = Square.draw(myCanvas.ctx, 50, 50, 100, "blue");
Square.reportArea(square.length, reportList);
Square.reportPerimeter(square.length, reportList);

Sie knnen den Code genauso wie zuvor schreiben, solange Sie die Objektnamen dort verwenden, wo ntig, und die Importe sind viel sauberer.

Module und Klassen

Wie wir bereits angedeutet haben, knnen Sie auch Klassen exportieren und importieren; das ist eine weitere Mglichkeit, Konflikte in Ihrem Code zu vermeiden, und ist besonders ntzlich, wenn Sie Ihren Modulcode bereits in einem objektorientierten Stil geschrieben haben.

Sie knnen ein Beispiel fr unser Formenzeichenmodul, umgeschrieben mit ES-Klassen, in unserem Verzeichnis classes sehen. Zum Beispiel enthlt die Datei square.js jetzt ihre gesamte Funktionalitt in einer einzelnen Klasse:

js
class Square {
  constructor(ctx, listId, length, x, y, color) {
    // 
  }

  draw() {
    // 
  }

  // 
}

die wir dann exportieren:

js
export { Square };

ber in main.js importieren wir es so:

js
import { Square } from "./modules/square.js";

Und verwenden dann die Klasse, um unser Quadrat zu zeichnen:

js
const square = new Square(myCanvas.ctx, myCanvas.listId, 50, 50, 100, "blue");
square.draw();
square.reportArea();
square.reportPerimeter();

Aggregieren von Modulen

Es wird Zeiten geben, in denen Sie Module zusammenfassen mchten. Sie knnten mehrere Ebenen von Abhngigkeiten haben, wo Sie die Dinge vereinfachen mchten, indem Sie mehrere Untermodule in ein bergeordnetes Modul kombinieren. Dies ist mit der folgenden Export-Syntax mglich, in der das bergeordnete Modul:

js
export * from "x.js";
export { name } from "x.js";

Fr ein Beispiel, schauen Sie in unser Verzeichnis module-aggregation. In diesem Beispiel (basierend auf unserem vorherigen Klassenbeispiel) haben wir ein zustzliches Modul namens shapes.js, das alle Funktionalitten von circle.js, square.js und triangle.js zusammenfasst. Wir haben auch unsere Untermodule in ein Unterverzeichnis innerhalb des modules Verzeichnisses namens shapes verschoben. So ist die Modulstruktur in diesem Beispiel:

modules/
  canvas.js
  shapes.js
  shapes/
    circle.js
    square.js
    triangle.js

In jedem der Untermodule ist die Exportform gleich, z.B.

js
export { Square };

Als nchstes kommt der Aggregationsteil. Innerhalb von shapes.js beinhalten wir die folgenden Zeilen:

js
export { Square } from "./shapes/square.js";
export { Triangle } from "./shapes/triangle.js";
export { Circle } from "./shapes/circle.js";

Diese erfassen die Exporte von den einzelnen Untermodule und stellen sie effektiv aus dem shapes.js Modul zur Verfgung.

Hinweis: Die Exporte, auf die in shapes.js verwiesen wird, werden im Grunde durch die Datei umgeleitet und existieren dort nicht wirklich, sodass Sie keine ntzlichen verwandten Codes in derselben Datei schreiben knnen.

Jetzt knnen wir in der main.js-Datei auf alle drei Modulklassen zugreifen, indem wir

js
import { Square } from "./modules/square.js";
import { Circle } from "./modules/circle.js";
import { Triangle } from "./modules/triangle.js";

durch die folgende Einzelzeile ersetzen:

js
import { Square, Circle, Triangle } from "./modules/shapes.js";

Dynamisches Modulladen

Eine krzliche Ergnzung zur Funktionalitt von JavaScript-Modulen ist das dynamische Laden von Modulen. Dies erlaubt Ihnen, Module nur dann dynamisch zu laden, wenn sie bentigt werden, anstatt alles im Voraus laden zu mssen. Dies hat einige offensichtliche Leistungsvorteile, lassen Sie uns weiterlesen und sehen, wie es funktioniert.

Diese neue Funktionalitt erlaubt es Ihnen, import() als Funktion aufzurufen und den Pfad zum Modul als Parameter zu bergeben. Sie gibt ein Promise zurck, das sich mit einem Modulobjekt erfllt (siehe Erstellen eines Modulobjekts), das Ihnen Zugriff auf die Exporte dieses Objekts gibt. Zum Beispiel:

js
import("./modules/myModule.js").then((module) => {
  // Do something with the module.
});

Hinweis: Dynamischer Import ist im Hauptthread des Browsers und in gemeinsamen und dedizierten Arbeitern erlaubt. import() wird jedoch einen Fehler werfen, falls es in einem Service Worker oder Worklet aufgerufen wird.

Schauen wir uns ein Beispiel an. Im Verzeichnis dynamic-module-imports haben wir ein weiteres Beispiel basierend auf unserem Klassenbeispiel. Dieses Mal zeichnen wir jedoch nichts auf die Leinwand, wenn das Beispiel ldt. Stattdessen haben wir drei Schaltflchen "Circle", "Square" und "Triangle" die, wenn sie gedrckt werden, das erforderliche Modul dynamisch laden und es dann verwenden, um die zugehrige Form zu zeichnen.

In diesem Beispiel haben wir nur nderungen an unseren index.html und main.js Dateien vorgenommen die Modulexporte bleiben wie zuvor.

ber in main.js haben wir einen Verweis auf jede Schaltflche mit einem document.querySelector() Aufruf erhalten, zum Beispiel:

js
const squareBtn = document.querySelector(".square");

Wir fgen dann jedem Button einen Event-Listener hinzu, sodass beim Drcken das entsprechende Modul dynamisch geladen und verwendet wird, um die Form zu zeichnen:

js
squareBtn.addEventListener("click", () => {
  import("./modules/square.js").then((Module) => {
    const square = new Module.Square(
      myCanvas.ctx,
      myCanvas.listId,
      50,
      50,
      100,
      "blue",
    );
    square.draw();
    square.reportArea();
    square.reportPerimeter();
  });
});

Beachten Sie, dass, da die Promise-Erfllung ein Modulobjekt zurckgibt, die Klasse dann zu einem Unterfunktionsmerkmal des Objekts wird, daher mssen wir nun auf den Konstruktor mit Module. vorangestellt zugreifen, z.B. Module.Square( /* */ ).

Ein weiterer Vorteil von dynamischen Importen besteht darin, dass sie immer verfgbar sind, sogar in Skriptumgebungen. Daher, wenn Sie bereits ein bestehendes <script>-Tag in Ihrem HTML haben, das type="module" nicht hat, knnen Sie dennoch Code, der als Module vertrieben wird, durch dynamisches Importieren wiederverwenden.

html
<script>
  import("./modules/square.js").then((module) => {
    // Do something with the module.
  });
  // Other code that operates on the global scope and is not
  // ready to be refactored into modules yet.
  var btn = document.querySelector(".square");
</script>

Top-Level-Await

Top-Level-Await ist eine in Modulen verfgbare Funktion. Dies bedeutet, dass das await-Schlsselwort verwendet werden kann. Es erlaubt Modulen, als groe asynchrone Funktionen zu funktionieren, was bedeutet, dass Code vor der Verwendung in bergeordneten Modulen ausgewertet werden kann, jedoch ohne das Laden von Geschwistermodulen zu blockieren.

Werfen wir einen Blick auf ein Beispiel. Sie knnen alle in diesem Abschnitt beschriebenen Dateien und Codes im top-level-await Verzeichnis finden, das von den vorherigen Beispielen abgeleitet ist.

Zuerst deklarieren wir unsere Farbpalette in einer separaten colors.json Datei:

json
{
  "yellow": "#F4D03F",
  "green": "#52BE80",
  "blue": "#5499C7",
  "red": "#CD6155",
  "orange": "#F39C12"
}

Dann erstellen wir ein Modul namens getColors.js, das einen Fetch-Request verwendet, um die colors.json-Datei zu laden und die Daten als Objekt zurckzugeben.

js
// fetch request
const colors = fetch("../data/colors.json").then((response) => response.json());

export default await colors;

Beachten Sie die letzte Exportzeile hier.

Wir verwenden das Schlsselwort await, bevor wir die Konstante colors angeben, um zu exportieren. Dies bedeutet, dass alle anderen Module, die dieses eine enthalten, solange warten, bis colors heruntergeladen und geparst wurde, bevor sie es verwenden.

Lassen Sie uns dieses Modul in unserem main.js Datei einbinden:

js
import colors from "./modules/getColors.js";
import { Canvas } from "./modules/canvas.js";

const circleBtn = document.querySelector(".circle");

// 

Wir verwenden colors anstelle der zuvor verwendeten Strings, wenn wir unsere Formfunktionen aufrufen:

js
const square = new Module.Square(
  myCanvas.ctx,
  myCanvas.listId,
  50,
  50,
  100,
  colors.blue,
);

const circle = new Module.Circle(
  myCanvas.ctx,
  myCanvas.listId,
  75,
  200,
  100,
  colors.green,
);

const triangle = new Module.Triangle(
  myCanvas.ctx,
  myCanvas.listId,
  100,
  75,
  190,
  colors.yellow,
);

Dies ist ntzlich, da der Code innerhalb der main.js nicht ausgefhrt wird, bis der Code in getColors.js ausgefhrt wurde. Es wird jedoch nicht andere Module blockieren, die geladen werden. Zum Beispiel wird unser canvas.js Modul whrend des Abrufs von colors weiterhin geladen.

Importdeklarationen sind gehoben

Importdeklarationen sind gehoben. In diesem Fall bedeutet es, dass die importierten Werte im Modulcode verfgbar sind, noch bevor die Stelle, die sie deklariert, erreicht wird und dass die Seiteneffekte des importierten Moduls produziert werden, bevor der Rest des Modulcodes beginnt, zu laufen.

Zum Beispiel, in main.js, wrde das Importieren von Canvas in der Mitte des Codes immer noch funktionieren:

js
// 
const myCanvas = new Canvas("myCanvas", document.body, 480, 320);
myCanvas.create();
import { Canvas } from "./modules/canvas.js";
myCanvas.createReportList();
// 

Dennoch wird es allgemein als gute Praxis angesehen, alle Ihre Importe am Anfang des Codes zu platzieren, was es erleichtert, Abhngigkeiten zu analysieren.

Zyklische Importe

Module knnen andere Module importieren, und diese Module knnen wiederum andere Module importieren, und so weiter. Dies bildet einen gerichteten Graphen namens Abhngigkeitsgraph. In einer idealen Welt ist dieser Graph azyklisch. In diesem Fall kann der Graph durch eine Tiefensuche ausgewertet werden.

Kreise sind jedoch oft unvermeidlich. Zyklischer Import entsteht, wenn Modul a Modul b importiert, b jedoch direkt oder indirekt auf a angewiesen ist. Zum Beispiel:

js
// -- a.js --
import { b } from "./b.js";

// -- b.js --
import { a } from "./a.js";

// Cycle:
// a.js > b.js
//  ^         
//  

Zyklische Importe scheitern nicht immer. Der Wert der importierten Variablen wird nur dann abgerufen, wenn die Variable tatschlich verwendet wird (was so genannte live bindings ermglicht), und nur wenn die Variable zu diesem Zeitpunkt nicht initialisiert ist, wird ein ReferenceError geworfen.

js
// -- a.js --
import { b } from "./b.js";

setTimeout(() => {
  console.log(b); // 1
}, 10);

export const a = 2;

// -- b.js --
import { a } from "./a.js";

setTimeout(() => {
  console.log(a); // 2
}, 10);

export const b = 1;

In diesem Beispiel werden sowohl a als auch b asynchron verwendet. Daher wird zum Zeitpunkt der Modulevaluierung weder b noch a tatschlich gelesen, sodass der Rest des Codes wie gewohnt ausgefhrt wird, und die beiden export-Deklarationen die Werte von a und b produzieren. Dann, nach dem Timeout, sind sowohl a als auch b verfgbar, sodass die beiden console.log-Anweisungen ebenfalls normal ausgefhrt werden.

Wenn Sie den Code ndern, um a synchron zu verwenden, schlgt die Modulevaluierung fehl:

js
// -- a.js (entry module) --
import { b } from "./b.js";

export const a = 2;

// -- b.js --
import { a } from "./a.js";

console.log(a); // ReferenceError: Cannot access 'a' before initialization
export const b = 1;

Dies liegt daran, dass, wenn JavaScript a.js auswertet, muss es zuerst b.js auswerten, die Abhngigkeit von a.js. b.js verwendet jedoch a, was zu diesem Zeitpunkt noch nicht verfgbar ist.

Andererseits, wenn Sie den Code ndern, um b synchron, aber a asynchron zu verwenden, gelingt die Modulevaluierung:

js
// -- a.js (entry module) --
import { b } from "./b.js";

console.log(b); // 1
export const a = 2;

// -- b.js --
import { a } from "./a.js";

setTimeout(() => {
  console.log(a); // 2
}, 10);
export const b = 1;

Dies liegt daran, dass die Auswertung von b.js normal abgeschlossen wird, sodass der Wert von b verfgbar ist, wenn a.js ausgewertet wird.

Generell sollten Sie zyklische Importe in Ihrem Projekt vermeiden, da sie Ihren Code fehleranflliger machen. Einige bliche Techniken zur Beseitigung von Zyklen sind:

  • Die beiden Module zu einem zusammenfhren.
  • Der gemeinsame Code in ein drittes Modul verschieben.
  • Einige Codes von einem Modul in das andere verschieben.

Zyklische Importe knnen jedoch auch auftreten, wenn Bibliotheken voneinander abhngen, was schwerer zu beheben ist.

Verfassen "isomorpher" Module

Die Einfhrung von Modulen ermutigt das JavaScript-kosystem, Code modular zu verteilen und wiederzuverwenden. Das bedeutet jedoch nicht unbedingt, dass ein JavaScript-Code in jeder Umgebung laufen kann. Angenommen, Sie haben ein Modul entdeckt, das SHA-Hashes des Passworts Ihres Benutzers generiert. Knnen Sie es im Frontend-Browser verwenden? Knnen Sie es auf Ihrem Node.js-Server verwenden? Die Antwort ist: Es hngt davon ab.

Module haben weiterhin Zugriff auf globale Variablen, wie bereits demonstriert. Wenn das Modul auf globale Variablen wie window verweist, kann es im Browser ausgefhrt werden, aber auf Ihrem Node.js-Server einen Fehler auslsen, da window dort nicht verfgbar ist. hnlich, wenn der Code Zugriff auf process bentigt, um funktional zu sein, kann es nur in Node.js verwendet werden.

Um die Wiederverwendbarkeit eines Moduls zu maximieren, wird oft empfohlen, den Code isomorph zu machen das bedeutet, dass er sich in jeder Laufzeitumgebung gleich verhlt. Dies wird blicherweise auf drei Arten erreicht:

  • Trennen Sie Ihre Module in Kern und Binding. Fr den Kern konzentrieren Sie sich auf reine JavaScript-Logik wie das Berechnen des Hashs, ohne jeglichen DOM-, Netzwerk-, Dateizugriff, und exportieren Sie Dienstfunktionen. Fr den Binding-Teil knnen Sie aus dem globalen Kontext lesen und schreiben. Zum Beispiel knnte das Browser-Binding whlen, den Wert aus einem Eingabefeld zu lesen, whrend das Node-Binding es aus process.env lesen knnte, aber Werte aus beiden Orten werden zur gleichen Kernfunktion gefhrt und auf die gleiche Weise behandelt. Der Kern kann in jede Umgebung importiert und auf die gleiche Weise verwendet werden, whrend nur das Binding, das in der Regel leichtgewichtig ist, plattformspezifisch sein muss.

  • berprfen Sie, ob eine bestimmte globale Variable existiert, bevor Sie sie verwenden. Zum Beispiel, wenn Sie prfen, dass typeof window === "undefined" ist, wissen Sie, dass Sie wahrscheinlich in einer Node.js-Umgebung sind und den DOM nicht lesen sollten.

    js
    // myModule.js
    let password;
    if (typeof process !== "undefined") {
      // We are running in Node.js; read it from `process.env`
      password = process.env.PASSWORD;
    } else if (typeof window !== "undefined") {
      // We are running in the browser; read it from the input box
      password = document.getElementById("password").value;
    }
    

    Dies ist vorzuziehen, wenn die beiden Zweige tatschlich das gleiche Verhalten ("isomorph") ergeben. Wenn es unmglich ist, die gleiche Funktionalitt bereitzustellen, oder wenn dies bedeutet, dass groe Mengen an Code geladen werden, whrend ein groer Teil ungenutzt bleibt, verwenden Sie besser unterschiedliche Bindings.

  • Verwenden Sie ein Polyfill, um eine Alternative fr fehlende Funktionen bereitzustellen. Zum Beispiel, wenn Sie die fetch-Funktion verwenden mchten, die nur seit v18 in Node.js untersttzt wird, knnen Sie eine hnliche API verwenden, wie die von node-fetch bereitgestellte. Sie knnen dies optional durch dynamische Importe tun:

    js
    // myModule.js
    if (typeof fetch === "undefined") {
      // We are running in Node.js; use node-fetch
      globalThis.fetch = (await import("node-fetch")).default;
    }
    // 
    

    Die globalThis-Variable ist ein globales Objekt, das in jeder Umgebung verfgbar ist und ntzlich ist, wenn Sie globale Variablen innerhalb von Modulen lesen oder erstellen mchten.

Diese Praktiken sind nicht einzigartig fr Module. Dennoch, mit dem Trend der Wiederverwendbarkeit und Modularisierung von Code, werden Sie ermutigt, Ihren Code plattformbergreifend zu gestalten, sodass er von so vielen Menschen wie mglich genutzt werden kann. Laufzeitumgebungen wie Node.js implementieren auch aktiv Web-APIs dort, wo mglich, um die Interoperabilitt mit dem Web zu verbessern.

Problembehebung

Hier sind einige Tipps, die Ihnen helfen knnen, wenn Sie Probleme haben, Ihre Module zum Laufen zu bringen. Fhlen Sie sich frei, die Liste zu erweitern, wenn Sie mehr entdecken!

  • Wir haben es vorher schon erwhnt, aber um es noch einmal zu betonen: .mjs-Dateien mssen mit einem MIME-Typ von text/javascript (oder einem anderen mit JavaScript kompatiblen MIME-Typ, aber text/javascript wird empfohlen) geladen werden, sonst erhalten Sie einen strikten MIME-Type-Prfungsfehler wie "Der Server hat mit einem Nicht-JavaScript-MIME-Typ geantwortet".
  • Wenn Sie versuchen, die HTML-Datei lokal zu laden (d.h. mit einer file://-URL), stoen Sie auf CORS-Fehler aufgrund der Sicherheitsanforderungen von JavaScript-Modulen. Sie mssen Ihre Tests ber einen Server durchfhren. GitHub Pages ist ideal, da es ebenfalls .mjs-Dateien mit dem richtigen MIME-Typ bereitstellt.
  • Da .mjs eine nicht standardisierte Dateierweiterung ist, knnen einige Betriebssysteme sie mglicherweise nicht erkennen oder versuchen, sie durch etwas anderes zu ersetzen. Zum Beispiel haben wir festgestellt, dass macOS stillschweigend .js am Ende von .mjs-Dateien hinzufgte und dann die Dateierweiterung automatisch versteckte. Also alle unsere Dateien waren tatschlich als x.mjs.js herausgekommen. Sobald wir das automatische Verstecken der Dateierweiterungen abgeschaltet und es dazu trainiert hatten, .mjs zu akzeptieren, war es in Ordnung.

Siehe auch


Web Proxy Viewer  |  New URL  |  Original Page