Nekromantie.
IMHO lassen die vorhandenen Antworten viel zu wünschen übrig.
Am Anfang ist das sehr verwirrend.
Sie haben eine (nirgends definierte) Funktion "require", die dazu dient, Module zu erhalten.
Und in diesen (CommonJS-)Modulen können Sie require, exports and module
,
OHNE DASS SIE JEMALS DEFINIERT WURDEN.
Nicht, dass es neu wäre, dass man undefinierte Variablen in JS verwenden könnte, aber man könnte keine undefinierte Funktion verwenden.
Es sieht also zunächst ein wenig nach Zauberei aus.
Aber alle Magie basiert auf Täuschung.
Wenn man etwas tiefer gräbt, stellt sich heraus, dass es eigentlich ganz einfach ist:
Require ist einfach eine (nicht standardisierte) Funktion auf globaler Ebene definiert .
(globaler Bereich = Fenster-Objekt im Browser, globales Objekt in NodeJS ).
Beachten Sie, dass die "require-Funktion" standardmäßig nur in NodeJS und nicht im Browser implementiert ist.
Um die Verwirrung noch weiter zu vergrößern, gibt es für den Browser RequireJS die, obwohl der Name die Zeichen "require" enthält, RequireJS absolut NICHT require/CommonJS implementiert - stattdessen implementiert RequireJS AMD was etwas Ähnliches, aber nicht dasselbe ist (auch bekannt als inkompatibel).
Letzteres ist nur ein wichtiger Punkt, den Sie auf Ihrem Weg zum Verständnis von require erkennen müssen.
Um die Frage "Was wird benötigt?" zu beantworten, müssen wir also "nur" wissen, was diese Funktion tut.
Dies lässt sich vielleicht am besten mit einem Code erklären.
Hier ist ein einfache Umsetzung durch Michele Nasti finden Sie den Code auf seiner Github-Seite .
Nennen wir unsere minimalistische Implementierung der require-Funktion "myRequire":
function myRequire(name)
{
console.log(`Evaluating file ${name}`);
if (!(name in myRequire.cache)) {
console.log(`${name} is not in cache; reading from disk`);
let code = fs.readFileSync(name, 'utf8');
let module = { exports: {} };
myRequire.cache[name] = module;
let wrapper = Function("require, exports, module", code);
wrapper(myRequire, module.exports, module);
}
console.log(`${name} is in cache. Returning it...`);
return myRequire.cache[name].exports;
}
myRequire.cache = Object.create(null);
window.require = myRequire;
const stuff = window.require('./main.js');
console.log(stuff);
Wie Sie sehen, wird hier das Objekt "fs" verwendet.
Der Einfachheit halber hat Michele nur das NodeJS-Modul fs importiert:
const fs = require('fs');
Das wäre nicht nötig.
Im Browser könnten Sie also eine einfache Implementierung von require mit einem SYNCHRONOUS XmlHttpRequest vornehmen:
const fs = {
file: `
// module.exports = \"Hello World\";
module.exports = function(){ return 5*3;};
`
, getFile(fileName: string, encoding: string): string
{
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Synchronous_and_Asynchronous_Requests
let client = new XMLHttpRequest();
// client.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
// open(method, url, async)
client.open("GET", fileName, false);
client.send();
if (client.status === 200)
return client.responseText;
return null;
}
, readFileSync: function (fileName: string, encoding: string): string
{
// this.getFile(fileName, encoding);
return this.file; // Example, getFile would fetch this file
}
};
Im Grunde lädt require also eine JavaScript-Datei herunter, evaluliert sie in einem anonymen Namespace (aka Function) mit den Parametern "require", "exports" und "module" und gibt die Exporte zurück, also die öffentlichen Funktionen und Eigenschaften eines Objekts.
Beachten Sie, dass diese Auswertung rekursiv ist: Sie benötigen Dateien, die ihrerseits wiederum Dateien benötigen können.
Auf diese Weise sind alle "globalen" Variablen, die in Ihrem Modul verwendet werden, Variablen im require-wrapper-function-Namensraum und verschmutzen den globalen Bereich nicht mit unerwünschten Variablen.
Außerdem können Sie auf diese Weise Code wiederverwenden, ohne auf Namespaces angewiesen zu sein, so dass Sie "Modularität" in JavaScript erhalten. "Modularität" in Anführungszeichen, denn das stimmt nicht ganz, denn man kann immer noch window.bla/global.bla schreiben und damit den globalen Bereich verschmutzen... Außerdem wird dadurch eine Trennung zwischen privaten und öffentlichen Aufgaben vorgenommen, wobei die öffentlichen Aufgaben die Ausfuhren sind.
Anstatt nun zu sagen
module.exports = function(){ return 5*3;};
Sie können auch sagen:
function privateSomething()
{
return 42:
}
function privateSomething2()
{
return 21:
}
module.exports = {
getRandomNumber: privateSomething
,getHalfRandomNumber: privateSomething2
};
und geben ein Objekt zurück.
Da Ihre Module in einer Funktion mit Parametern ausgewertet werden "require", "exports" und "module" ausgewertet werden, können Ihre Module die nicht deklarierten Variablen "require", "exports" und "module" verwenden, was auf den ersten Blick verwundern mag. Der require-Parameter ist natürlich ein Zeiger auf die require-Funktion, der in einer Variablen gespeichert ist.
Cool, nicht wahr?
So gesehen verliert der Bedarf seinen Zauber und wird einfach.
Die eigentliche require-Funktion führt natürlich noch ein paar weitere Prüfungen durch, aber das ist die Essenz dessen, worauf das hinausläuft.
Außerdem sollten Sie im Jahr 2020 die ECMA-Implementierungen anstelle von require verwenden:
import defaultExport from "module-name";
import * as name from "module-name";
import { export1 } from "module-name";
import { export1 as alias1 } from "module-name";
import { export1 , export2 } from "module-name";
import { foo , bar } from "module-name/path/to/specific/un-exported/file";
import { export1 , export2 as alias2 , [...] } from "module-name";
import defaultExport, { export1 [ , [...] ] } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name";
Und wenn Sie einen dynamischen, nicht statischen Import benötigen (z. B. um ein Polyfill auf der Grundlage des Browsertyps zu laden), gibt es die ECMA-Import-Funktion bzw. das ECMA-Schlüsselwort:
var promise = import("module-name");
Beachten Sie, dass import nicht synchron ist wie require.
Stattdessen ist der Import ein Versprechen, also
var something = require("something");
wird
var something = await import("something");
weil import ein Versprechen zurückgibt (asynchron).
Im Gegensatz zu require ersetzt import also fs.readFileSync durch fs.readFileAsync.
async readFileAsync(fileName, encoding)
{
const textDecoder = new TextDecoder(encoding);
// textDecoder.ignoreBOM = true;
const response = await fetch(fileName);
console.log(response.ok);
console.log(response.status);
console.log(response.statusText);
// let json = await response.json();
// let txt = await response.text();
// let blo:Blob = response.blob();
// let ab:ArrayBuffer = await response.arrayBuffer();
// let fd = await response.formData()
// Read file almost by line
// https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/read#Example_2_-_handling_text_line_by_line
let buffer = await response.arrayBuffer();
let file = textDecoder.decode(buffer);
return file;
} // End Function readFileAsync
Dies erfordert natürlich, dass die Importfunktion ebenfalls asynchron ist .
"use strict";
async function myRequireAsync(name) {
console.log(`Evaluating file ${name}`);
if (!(name in myRequireAsync.cache)) {
console.log(`${name} is not in cache; reading from disk`);
let code = await fs.readFileAsync(name, 'utf8');
let module = { exports: {} };
myRequireAsync.cache[name] = module;
let wrapper = Function("asyncRequire, exports, module", code);
await wrapper(myRequireAsync, module.exports, module);
}
console.log(`${name} is in cache. Returning it...`);
return myRequireAsync.cache[name].exports;
}
myRequireAsync.cache = Object.create(null);
window.asyncRequire = myRequireAsync;
async () => {
const asyncStuff = await window.asyncRequire('./main.js');
console.log(asyncStuff);
};
Noch besser, oder?
Nun ja, außer, dass es keine ECMA-Möglichkeit gibt, dynamisch synchron zu importieren (ohne Versprechen).
Um die Auswirkungen zu verstehen, sollten Sie unbedingt Lesen Sie hier mehr über promises/async-await falls Sie nicht wissen, was das ist.
Aber ganz einfach gesagt, wenn eine Funktion ein Versprechen zurückgibt, kann sie "abgewartet" werden:
"use strict";
function sleep(interval)
{
return new Promise(
function (resolve, reject)
{
let wait = setTimeout(function () {
clearTimeout(wait);
//reject(new Error(`Promise timed out ! (timeout = ${timeout})`));
resolve();
}, interval);
});
}
Das Versprechen würde dann normalerweise wie folgt verwendet werden:
function testSleep()
{
sleep(3000).then(function ()
{
console.log("Waited for 3 seconds");
});
}
Aber wenn Sie ein Versprechen zurückgeben, können Sie auch await verwenden, was bedeutet, dass wir den Callback loswerden (sozusagen - tatsächlich wird er im Compiler/Interpreter durch eine State-Machine ersetzt).
Auf diese Weise fühlt sich asynchroner Code wie synchroner Code an, so dass wir jetzt try-catch für die Fehlerbehandlung verwenden können.
Beachten Sie, dass, wenn Sie await in einer Funktion verwenden wollen, diese Funktion als async deklariert werden muss (daher async-await).
async function testSleep()
{
await sleep(5000);
console.log("i waited 5 seconds");
}
Und beachten Sie bitte auch, dass es in JavaScript keine Möglichkeit gibt, eine asynchrone Funktion (blockierend) von einer synchronen Funktion (die Sie kennen) aufzurufen. Wenn Sie also await (auch bekannt als ECMA-Import) verwenden möchten, muss Ihr gesamter Code asynchron sein, was höchstwahrscheinlich ein Problem darstellt, wenn nicht bereits alles asynchron ist...
Ein Beispiel dafür, wo diese vereinfachte Implementierung von require versagt, ist, wenn Sie eine Datei benötigen, die kein gültiges JavaScript ist, z.B. wenn Sie css, html, txt, svg und Bilder oder andere binäre Dateien benötigen.
Und es ist leicht zu erkennen, warum:
Wenn Sie z.B. HTML in einen JavaScript-Funktionskörper einfügen, erhalten Sie natürlich zu Recht
SyntaxError: Unexpected token '<'
wegen Function("bla", "<doctype...")
Wenn Sie dies nun erweitern wollten, um beispielsweise Nicht-Module einzuschließen, könnten Sie einfach die heruntergeladenen Datei-Inhalte auf code.indexOf("module.exports") == -1
und dann z.B. eval("jquery content") anstelle von Func (was gut funktioniert, solange man im Browser ist). Da Downloads mit Fetch/XmlHttpRequests der Same-Origin-Policy unterliegen und die Integrität durch SSL/TLS sichergestellt wird, ist die Verwendung von eval hier eher harmlos, vorausgesetzt, Sie haben die JS-Dateien überprüft, bevor Sie sie Ihrer Website hinzugefügt haben, aber das sollte eigentlich zum Standardbetrieb gehören.
Beachten Sie, dass es mehrere Implementierungen der require-ähnlichen Funktionalität gibt:
-
das CommonJS-Format (CJS) , verwendet in Node.js verwendet eine require-Funktion und module.exports, um Abhängigkeiten und Module zu definieren. Das npm-Ökosystem ist auf diesem Format aufgebaut. (dies wurde oben bereits umgesetzt)
-
das Format Asynchronous Module Definition (AMD) die in Browsern verwendet wird, verwendet eine define-Funktion, um Module zu definieren. (Im Grunde ist dies überkomplizierter archaischer Scheiß die Sie niemals verwenden möchten). Außerdem ist AMD das Format, das von RequireJS (Beachten Sie, dass AMD trotz des Namens, der die Zeichen "require" enthält, absolut NICHT CommonJS ist).
-
das ES-Modul-Format (ESM) . Seit ES6 (ES2015) unterstützt JavaScript ein natives Modulformat. Es verwendet ein export-Schlüsselwort, um die öffentliche API eines Moduls zu exportieren, und ein import-Schlüsselwort, um sie zu importieren. Dies ist das Schlüsselwort, das Sie verwenden sollten wenn Ihnen archaische Browser wie Safari und IE/EdgeHTML völlig egal sind .
-
das System.register-Format entwickelt, um ES6-Module innerhalb von ES5 zu unterstützen. ( derjenige, den Sie verwenden sollten, wenn Sie Unterstützung für ältere Browser benötigen (Safari & IE & alte Versionen von Chrome auf Mobiltelefonen/Tablets), weil es alle Formate laden kann [für einige werden Plugins benötigt], mit zyklischen Abhängigkeiten umgehen kann und CSS und HTML - Definieren Sie Ihre Module jedoch nicht als system.register - das Format ist ziemlich kompliziert, und denken Sie daran, dass es auch die anderen einfacheren Formate lesen kann )
-
das Format Universal Module Definition (UMD) Das Programm ist kompatibel zu allen oben genannten Formaten (außer ECMA) und wird sowohl im Browser als auch in Node.js verwendet. Es ist besonders nützlich wenn Sie Module schreiben, die sowohl in NodeJS als auch im Browser verwendet werden können . Es ist etwas mangelhaft, da es die neuesten ECMA-Module nicht unterstützt (vielleicht wird dies behoben) - verwenden Sie stattdessen System.register.
Wichtige Nebenbemerkung zum Funktionsargument "Exporte":
JavaScript verwendet Call-by-Value-Sharing - was bedeutet, dass Objekte als Zeiger übergeben werden, aber der Zeiger-Wert selbst wird durch Wert übergeben, nicht durch Verweis. Sie können also Exporte nicht überschreiben, indem Sie ihnen ein neues Objekt zuweisen. Wenn Sie exports überschreiben möchten, müssen Sie stattdessen das neue Objekt module.exports zuweisen - denn module ist der Zeiger, der als Wert übergeben wird, exports in module.exports ist jedoch der Verweis auf den ursprünglichen exports-Zeiger.
Wichtige Nebenbemerkung zu Modul-Scope:
Module werden bewertet ONCE und dann zwischengespeichert zu verlangen.
Das bedeutet, dass alle Ihre Module eine Singleton Umfang.
Wenn Sie einen nicht-singletonalen Geltungsbereich wünschen, müssen Sie etwas tun wie:
var x = require("foo.js").createInstance();
oder einfach
var x = require("foo.js")();
mit dem entsprechenden Code, der von Ihrem Modul zurückgegeben wird.