JavaScript: Wie bindet man eine JavaScript-Datei in einer anderen ein?

JavaScript hat kein #include. Es hat Module, und im Browser hat es eine Skript-Reihenfolge — und aus Letzterer kommen die meisten Fehler.

import

// helper.mjs
export const greet = n => `hello ${n}`;
// main.mjs
import { greet } from "./helper.mjs";
[helper.mjs body ran]
greet('world') : hello world | version: esm

import ist die Antwort für alles, was heute geschrieben wird. Im Browser braucht es <script type="module">, in Node die Endung .mjs oder "type": "module" in der package.json.

Der Rumpf läuft einmal

-> imported three times in total, body printed once. Modules are cached.

[helper.mjs body ran] erscheint einmal, obwohl drei verschiedene Stellen das Modul importieren. Ein Modul wird beim ersten Anfordern ausgewertet und das Ergebnis gecacht — deshalb darf ein Modul gefahrlos Zustand halten, und deshalb kann man mit einem Import eine Datei nicht “noch einmal ausführen”.

require

[helper.cjs body ran]
require('./helper.cjs') : hello world | version: cjs

require ist die ältere CommonJS-Form. Nur in Node, nie ein Browser-Feature, und es cacht genauso. In bestehendem Code begegnet es dir weiterhin.

import kann nicht bedingt sein, import() schon

a conditional static import is a SyntaxError
dynamic import() is a function call, so it CAN be conditional:
  loaded on demand: esm

Ein statisches import wird gehoistet und aufgelöst, bevor irgendetwas aus der Datei läuft, es kann also nicht in einem if stehen. Das ist keine willkürliche Einschränkung, sondern genau das, was einem Bundler den Abhängigkeitsgraphen verrät, ohne etwas auszuführen.

Wenn du wirklich bedingt oder verzögert laden willst, nimm import(): Das ist ein Ausdruck, gibt ein Promise zurück und darf überall stehen.

Im Browser entscheidet die Reihenfolge

execution order : inline-1 -> inline-2

Einfache <script>-Tags laufen in Dokumentreihenfolge und blockieren dabei jeweils den Parser. Also passiert Folgendes:

first script saw laterVar as : undefined

Das erste Skript sieht nicht, was das zweite definiert. Das ist die eigentliche Ursache der meisten “X is not defined”-Fehler — keine fehlende Datei, sondern eine Datei, die nach dem Code geladen wird, der sie benutzt.

defer und async

a.js   defer=false async=false
b.js   defer=true async=false
c.js   defer=false async=true
  • einfach: blockiert das Parsen, läuft sofort
  • defer: läuft nach dem Parsen, in Dokumentreihenfolge
  • async: läuft, sobald es da ist, in keiner garantierten Reihenfolge

async ist das, was einen erwischt. Zwei async-Skripte, von denen eines das andere braucht, funktionieren auf deiner Maschine und scheitern auf einer langsameren Leitung, weil die Reihenfolge von der Ladezeit abhängt.

type : module | the spec defers module scripts even without the attribute

type="module" ist verzögert, ob du defer schreibst oder nicht.

Was man nicht tun sollte

Die alte Antwort — ein <script>-Element erzeugen und ins DOM hängen — ist weiterhin im Umlauf. Sie funktioniert, aber nichts sagt dir, wann sie fertig ist, die nächste Zeile deines Codes läuft also, bevor die Datei geladen ist. Brauchst du diese Form, gibt dir import() ein Promise, auf das du warten kannst.

Hinweis zu Netcup (Werbung)

Der deutsche Hoster Netcup bietet unter anderem günstige und zugleich leistungsstarke Webhosting Pakete, KVM-basierte Root Server und dezidierte Server an. Mit unseren Gutscheincodes kannst du noch mehr Geld sparen (6€ bei deiner ersten Bestellung, 30% Rabatt auf alle KVM-basierten Root Server, ...).