Verhindern Sie, dass RequireJS erforderliche Skripte zwischenspeichert

302

RequireJS scheint intern etwas zu tun, das die erforderlichen Javascript-Dateien zwischenspeichert. Wenn ich eine Änderung an einer der erforderlichen Dateien vornehme, muss ich die Datei umbenennen, damit die Änderungen übernommen werden.

Der übliche Trick, eine Versionsnummer als Querystring-Parameter an das Ende des Dateinamens anzuhängen, funktioniert bei requirejs nicht <script src="jsfile.js?v2"></script>

Was ich suche, ist eine Möglichkeit, dieses interne Cacheing von RequireJS-erforderlichen Skripten zu verhindern, ohne meine Skriptdateien bei jeder Aktualisierung umbenennen zu müssen.

Plattformübergreifende Lösung:

Ich verwende es jetzt urlArgs: "bust=" + (new Date()).getTime()für das automatische Cache-Busting während der Entwicklung und urlArgs: "bust=v2"für die Produktion, bei der ich die fest codierte Versionsnummer nach dem Rollout eines aktualisierten erforderlichen Skripts erhöhe.

Hinweis:

@Dustin Getz erwähnte kürzlich in einer Antwort, dass Chrome Developer Tools beim Debuggen Haltepunkte löschen, wenn Javascript-Dateien auf diese Weise kontinuierlich aktualisiert werden. Eine Problemumgehung besteht darin, debugger;Code zu schreiben , um in den meisten Javascript-Debuggern einen Haltepunkt auszulösen.

Serverspezifische Lösungen:

In den folgenden Antworten finden Sie spezifische Lösungen, die für Ihre Serverumgebung möglicherweise besser funktionieren, z. B. Node oder Apache.

BumbleB2na
quelle
Sie sind sicher, dass dies nicht der Server oder Client ist, der das Caching durchführt? (Ich verwende seit einigen Monaten die erforderlichen JS und habe nichts Ähnliches bemerkt.) Der IE wurde beim Zwischenspeichern der MVC-Aktionsergebnisse erwischt. Chrome hat unsere HTML-Vorlagen zwischengespeichert, aber die JS-Dateien scheinen alle aktualisiert zu werden, wenn der Browser-Cache zurückgesetzt wurde. Ich nehme an, wenn Sie das Caching nutzen möchten, aber nicht das Übliche tun können, weil die Anforderungen von den erforderlichen js die Abfragezeichenfolge entfernt haben, die das Problem verursachen könnte?
PJUK
Ich bin nicht sicher, ob RequireJS angehängte Versionsnummern wie diese entfernt. Es könnte mein Server gewesen sein. Interessant, wie RequireJS über eine Cache-Buster-Einstellung verfügt, sodass Sie möglicherweise Recht haben, wenn Sie meine angehängten Versionsnummern für erforderliche Dateien entfernen.
BumbleB2na
Ich habe meine Antwort mit einer möglichen Caching-Lösung aktualisiert
Dustin Getz
Jetzt kann ich der Litanei, die ich heute Morgen in meinem Blogbeitrag veröffentlicht habe, Folgendes hinzufügen: codrspace.com/dexygen/… Und das heißt, ich muss nicht nur Cache-Busting hinzufügen, sondern require.js ignoriert eine harte Aktualisierung.
Dexygen
Ich bin verwirrt über den Anwendungsfall dafür ... Ist dies für das Hot-Reload von AMD-Modulen in das Front-End oder was?
Alexander Mills

Antworten:

457

RequireJS kann so konfiguriert werden, dass an jede der Skript-URLs ein Wert für das Cache-Busting angehängt wird.

Aus der RequireJS-Dokumentation ( http://requirejs.org/docs/api.html#config ):

urlArgs : Zusätzliche Abfragezeichenfolgenargumente, die an URLs angehängt werden, die RequireJS zum Abrufen von Ressourcen verwendet. Am nützlichsten, um Bust zwischenzuspeichern, wenn der Browser oder Server nicht richtig konfiguriert ist.

Beispiel: Anhängen von "v2" an alle Skripte:

require.config({
    urlArgs: "bust=v2"
});

Zu Entwicklungszwecken können Sie RequireJS zwingen, den Cache zu umgehen, indem Sie einen Zeitstempel anhängen:

require.config({
    urlArgs: "bust=" + (new Date()).getTime()
});
Phil McCullick
quelle
46
Sehr hilfreich, danke. Ich verwende es urlArgs: "bust=" + (new Date()).getTime()für das automatische Cache-Busting während der Entwicklung und urlArgs: "bust=v2"für die Produktion, bei der ich die fest codierte Versionsnummer nach dem Rollout eines aktualisierten erforderlichen Skripts erhöhe.
BumbleB2na
9
... auch als Leistungsoptimierer können Sie Math.random () anstelle von (new Date ()). getTime () verwenden. Es ist mehr Schönheit, kein Objekt zu erstellen und ein bisschen schneller jsperf.com/speedcomparison .
Vlad Tsepelev
2
Sie können es nur ein bisschen kleiner bekommen:urlArgs: "bust=" + (+new Date)
Mrzmyr
11
Ich denke, es ist eine schreckliche Idee, den Cache jedes Mal zu sprengen. Leider bietet RequireJS keine andere Alternative. Wir verwenden urlArgs, verwenden dafür jedoch keinen Zufalls- oder Zeitstempel. Stattdessen verwenden wir unseren aktuellen Git SHA. Auf diese Weise ändert sich dies nur, wenn wir neuen Code bereitstellen.
Ivan Torres
5
Wie kann diese "v2" -Variante in der Produktion funktionieren, wenn die Datei, die die "v2" -String selbst bereitstellt, zwischengespeichert wird? Wenn ich eine neue Anwendung in die Produktion freigebe, wird das Hinzufügen von "v3" nichts bewirken, da die Anwendung weiterhin gerne mit den zwischengespeicherten v2-Dateien arbeitet, einschließlich der alten Konfiguration mit v2 urlArgs.
Benny Bottema
54

Verwenden Sie hierfür keine urlArgs!

Das Laden von Skripten berücksichtigt die HTTP-Caching-Header. (Skripte werden mit einem dynamisch eingefügten Skript geladen <script>, was bedeutet, dass die Anforderung genauso aussieht wie jedes alte Asset, das geladen wird.)

Stellen Sie Ihre Javascript-Assets mit den richtigen HTTP-Headern bereit, um das Caching während der Entwicklung zu deaktivieren.

Wenn Sie die urlArgs von require verwenden, bleiben die von Ihnen festgelegten Haltepunkte bei Aktualisierungen nicht erhalten. Am Ende müssen Sie debuggerAnweisungen überall in Ihren Code einfügen. Schlecht. Ich verwende es urlArgsfür Cache-Busting-Assets während Produktions-Upgrades mit dem Git Sha. Dann kann ich festlegen, dass mein Vermögen für immer zwischengespeichert wird und garantiert niemals veraltetes Vermögen hat.

In der Entwicklung verspotte ich alle Ajax-Anforderungen mit einer komplexen Mockjax- Konfiguration. Anschließend kann ich meine App im Nur-Javascript-Modus mit einem 10-Zeilen-Python-HTTP-Server mit deaktiviertem Caching bereitstellen . Dies hat sich für mich zu einer ziemlich großen "unternehmerischen" Anwendung mit Hunderten von erholsamen Webservice-Endpunkten ausgeweitet. Wir haben sogar einen Vertragsdesigner, der mit unserer realen Produktionscodebasis arbeiten kann, ohne ihm Zugriff auf unseren Backend-Code zu gewähren.

Dustin Getz
quelle
8
@ JamesP.Wright, da (zumindest in Chrome), wenn Sie einen Haltepunkt für etwas festlegen, das beim Laden der Seite passiert, und dann auf Aktualisieren klicken, der Haltepunkt nicht erreicht wird, weil sich die URL geändert hat und Chrome den Haltepunkt gelöscht hat. Ich würde gerne eine Lösung nur für Kunden kennen.
Drew Noakes
1
Danke, Dustin. Wenn Sie einen Weg finden, um dies zu umgehen, posten Sie bitte. In der Zwischenzeit können Sie debugger;Ihren Code überall dort verwenden, wo ein Haltepunkt bestehen bleiben soll.
BumbleB2na
2
Für alle, die http-server on node verwenden (npm installiere http-server). Sie können das Caching auch mit -c-1 (dh http-server -c-1) deaktivieren.
Yourpalal
5
Sie können das Caching in Chrome deaktivieren, um das Debugging-Problem während der Entwicklung zu umgehen
Deepak Joy
5
+1 bis !!! VERWENDEN SIE KEINE URLS IN DER PRODUKTION !!! . Stellen Sie sich den Fall vor, dass Ihre Website 1000 JS-Dateien enthält (ja, möglich!) Und deren Auslastung durch das erforderliche JS gesteuert wird. Jetzt veröffentlichen Sie v2 oder Ihre Site, auf der nur wenige JS-Dateien geändert werden! Durch Hinzufügen von urlArgs = v2 müssen Sie jedoch alle 1000 JS-Dateien neu laden! Sie werden viel Verkehr bezahlen! Es sollten nur geänderte Dateien neu geladen werden, alle anderen sollten mit dem Status 304 (Nicht geändert) beantwortet werden.
Walv
24

Die urlArgs-Lösung weist Probleme auf. Leider können Sie nicht alle Proxyserver steuern, die sich möglicherweise zwischen Ihnen und dem Webbrowser Ihres Benutzers befinden. Einige dieser Proxyserver können leider so konfiguriert werden, dass URL-Parameter beim Zwischenspeichern von Dateien ignoriert werden. In diesem Fall wird Ihrem Benutzer die falsche Version Ihrer JS-Datei zugestellt.

Ich gab schließlich auf und implementierte mein eigenes Update direkt in require.js. Wenn Sie bereit sind, Ihre Version der requirejs-Bibliothek zu ändern, funktioniert diese Lösung möglicherweise für Sie.

Sie können den Patch hier sehen:

https://github.com/jbcpollak/requirejs/commit/589ee0cdfe6f719cd761eee631ce68eee09a5a67

Nach dem Hinzufügen können Sie in Ihrer erforderlichen Konfiguration Folgendes tun:

var require = {
    baseUrl: "/scripts/",
    cacheSuffix: ".buildNumber"
}

Verwenden Sie Ihr Build-System oder Ihre Serverumgebung, um diese buildNumberdurch eine Revisions-ID / Softwareversion / Lieblingsfarbe zu ersetzen .

Verwendung erfordern wie folgt:

require(["myModule"], function() {
    // no-op;
});

Wird erforderlich sein, um diese Datei anzufordern:

http://yourserver.com/scripts/myModule.buildNumber.js

In unserer Serverumgebung verwenden wir Regeln zum Umschreiben von URLs, um die buildNumber zu entfernen und die richtige JS-Datei bereitzustellen. Auf diese Weise müssen wir uns nicht um das Umbenennen aller unserer JS-Dateien kümmern.

Der Patch ignoriert alle Skripte, die ein Protokoll angeben, und wirkt sich nicht auf Nicht-JS-Dateien aus.

Dies funktioniert gut für meine Umgebung, aber mir ist klar, dass einige Benutzer lieber ein Präfix als ein Suffix bevorzugen. Es sollte einfach sein, mein Commit an Ihre Bedürfnisse anzupassen.

Aktualisieren:

In der Pull-Request-Diskussion schlägt der Autor von requirejs vor, dass dies als Lösung für das Präfix der Revisionsnummer dienen könnte:

var require = {
    baseUrl: "/scripts/buildNumber."
};

Ich habe dies nicht versucht, aber die Implikation ist, dass dies die folgende URL anfordern würde:

http://yourserver.com/scripts/buildNumber.myModule.js

Was für viele Leute, die ein Präfix verwenden können, sehr gut funktionieren könnte.

Hier sind einige mögliche doppelte Fragen:

RequireJS und Proxy-Caching

require.js - Wie kann ich eine Version für erforderliche Module als Teil der URL festlegen?

JBCP
quelle
1
Ich würde wirklich gerne sehen, dass Ihr Update in den offiziellen Requirement Builds aufgenommen wird. Der Hauptautor von requirejs könnte ebenfalls interessiert sein (siehe die Antwort von @ Louis oben).
BumbleB2na
@ BumbleB2na - zögern Sie nicht, die PullRequest ( github.com/jrburke/requirejs/pull/1017 ) zu kommentieren , jrburke schien nicht interessiert zu sein. Er schlägt eine Lösung mit einem Dateinamenpräfix vor. Ich werde meine Antwort aktualisieren, um dies einzuschließen.
JBCP
1
Schönes Update. Ich denke, ich mag diesen Vorschlag des Autors, aber das liegt nur daran, dass ich in letzter Zeit diese Namenskonvention verwendet habe : /scripts/myLib/v1.1/. Ich habe versucht, meinen Dateinamen ein Postfix (oder Präfix) hinzuzufügen, wahrscheinlich, weil dies bei jquery der Fall ist, aber nach einer Weile wurde ich faul und begann, eine Versionsnummer im übergeordneten Ordner zu erhöhen. Ich denke, es hat mir die Wartung auf einer großen Website erleichtert, aber jetzt mache ich mir Sorgen über Alpträume beim Umschreiben von URLs.
BumbleB2na
1
Moderne Front-End-Systeme schreiben nur die JS-Dateien und mit einer MD5-Summe im Dateinamen neu und schreiben dann die HTML-Dateien neu, um beim Erstellen die neuen Dateinamen zu verwenden. Bei Legacy-Systemen, bei denen der Front-End-Code von der Serverseite bereitgestellt wird, wird dies jedoch schwierig.
JBCP
funktioniert das, wenn ich ein paar js in der jspx-datei benötige?, so<script data-main="${pageContext.request.contextPath}/resources/scripts/main" src="${pageContext.request.contextPath}/resources/scripts/require.js"> <jsp:text/> </script> <script> require([ 'dev/module' ]); </script>
masT
19

Inspiriert vom Expire-Cache auf require.js data-main haben wir unser Bereitstellungsskript mit der folgenden Ant-Task aktualisiert:

<target name="deployWebsite">
    <untar src="${temp.dir}/website.tar.gz" dest="${website.dir}" compression="gzip" />       
    <!-- fetch latest buildNumber from build agent -->
    <replace file="${website.dir}/js/main.js" token="@Revision@" value="${buildNumber}" />
</target>

Wo der Anfang von main.js aussieht:

require.config({
    baseUrl: '/js',
    urlArgs: 'bust=@Revision@',
    ...
});
dvtoever
quelle
11

In Produktion

urlArgs kann Probleme verursachen!

Der Hauptautor von requirejs zieht es vor,urlArgs Folgendes nicht zu verwenden :

Für bereitgestellte Assets ziehe ich es vor, die Version oder den Hash für den gesamten Build als Build-Verzeichnis zu verwenden und dann einfach das zu ändern baseUrl für das Projekt verwendete Konfiguration , dass dieses versionierte Verzeichnis als verwendet wird baseUrl. Dann ändern sich keine anderen Dateien, und es hilft, einige Proxy-Probleme zu vermeiden, bei denen eine URL mit einer Abfragezeichenfolge möglicherweise nicht zwischengespeichert wird.

[Styling meins.]

Ich folge diesem Rat.

In Entwicklung

Ich bevorzuge die Verwendung eines Servers, der Dateien, die sich häufig ändern können, intelligent zwischenspeichert: einen Server, der bei Bedarf 304 ausgibt Last-Modifiedund darauf antwortet If-Modified-Since. Sogar ein Server, der auf dem Express- Set von Node basiert , um statische Dateien bereitzustellen, erledigt dies sofort. Es erfordert nichts mit meinem Browser zu tun und bringt keine Haltepunkte durcheinander.

Louis
quelle
Gute Punkte, aber Ihre Antwort ist spezifisch für Ihre Serverumgebung. Vielleicht ist die jüngste Empfehlung, dem Dateinamen anstelle eines Querystring-Parameters eine Versionsnummer hinzuzufügen, eine gute Alternative für alle anderen, die darüber stolpern. Hier sind weitere Informationen zu diesem Thema: stevesouders.com/blog/2008/08/23/…
BumbleB2na
Wir stoßen in einem Produktionssystem auf dieses spezielle Problem. Ich empfehle, Ihre Dateinamen zu ändern, anstatt einen Parameter zu verwenden.
JBCP
7

Ich habe dieses Snippet aus AskApache genommen und es in eine separate .conf-Datei meines lokalen Apache-Webservers (in meinem Fall /etc/apache2/others/preventcaching.conf) gestellt:

<FilesMatch "\.(html|htm|js|css)$">
FileETag None
<ifModule mod_headers.c>
Header unset ETag
Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
Header set Expires "Wed, 11 Jan 1984 05:00:00 GMT"
</ifModule>
</FilesMatch>

Für die Entwicklung funktioniert dies einwandfrei, ohne dass der Code geändert werden muss. Für die Produktion könnte ich den Ansatz von @ dvtoever verwenden.

Myrho
quelle
6

Schnelle Lösung für die Entwicklung

Für die Entwicklung können Sie einfach den Cache in Chrome Dev Tools deaktivieren ( Deaktivieren des Chrome-Cache für die Website-Entwicklung ). Das Deaktivieren des Caches erfolgt nur, wenn das Dialogfeld "Entwicklertools" geöffnet ist. Sie müssen sich also nicht jedes Mal umschalten, wenn Sie diese Option durchsuchen.

Hinweis: Die Verwendung von ' urlArgs ' ist die richtige Lösung in der Produktion, damit Benutzer den neuesten Code erhalten. Das Debuggen wird jedoch schwierig, da Chrome bei jeder Aktualisierung Haltepunkte ungültig macht (da jedes Mal eine "neue" Datei bereitgestellt wird).

Deepak Joy
quelle
3

Ich empfehle nicht, ' urlArgs ' zu verwenden ' für Cache mit RequireJS zu verwenden. Da dies das Problem nicht vollständig löst. Wenn Sie eine Versionsnummer aktualisieren, werden alle Ressourcen heruntergeladen, obwohl Sie nur eine einzelne Ressource geändert haben.

Um dieses Problem zu beheben, empfehle ich die Verwendung von Grunt-Modulen wie 'filerev' zum Erstellen der Revisionsnummer. Darüber hinaus habe ich eine benutzerdefinierte Aufgabe in Gruntfile geschrieben, um die Revision zu aktualisieren, sofern dies nicht erforderlich ist.

Bei Bedarf kann ich das Code-Snippet für diese Aufgabe freigeben.

Amit Sagar
quelle
Ich benutze eine Kombination aus grunt-filerev und grunt-cache-buster, um Javascript-Dateien neu zu schreiben.
Ian Jamieson
2

So mache ich es in Django / Flask (kann leicht an andere Sprachen / VCS-Systeme angepasst werden):

In Ihrem config.py(ich verwende dies in Python3, daher müssen Sie möglicherweise die Codierung in Python2 anpassen)

import subprocess
GIT_HASH = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip().decode('utf-8')

Dann in Ihrer Vorlage:

{% if config.DEBUG %}
     require.config({urlArgs: "bust=" + (new Date().getTime())});
{% else %}
    require.config({urlArgs: "bust=" + {{ config.GIT_HASH|tojson }}});
{% endif %}
  • Erfordert keinen manuellen Erstellungsprozess
  • git rev-parse HEADWird beim Start der App nur einmal ausgeführt und im configObjekt gespeichert
Stephen Fuhry
quelle
0

Dynamische Lösung (ohne urlArgs)

Für dieses Problem gibt es eine einfache Lösung, sodass Sie für jedes Modul eine eindeutige Versionsnummer laden können.

Sie können die ursprüngliche Funktion requirejs.load speichern, sie mit Ihrer eigenen Funktion überschreiben und Ihre geänderte URL erneut auf die ursprüngliche Funktion requirejs.load analysieren:

var load = requirejs.load;
requirejs.load = function (context, moduleId, url) {
    url += "?v=" + oRevision[moduleId];
    load(context, moduleId, url);
};

In unserem Erstellungsprozess habe ich "gulp-rev" verwendet, um eine Manifestdatei mit allen Überarbeitungen aller verwendeten Module zu erstellen. Vereinfachte Version meiner Schluckaufgabe:

gulp.task('gulp-revision', function() {
    var sManifestFileName = 'revision.js';

    return gulp.src(aGulpPaths)
        .pipe(rev())
        .pipe(rev.manifest(sManifestFileName, {
        transformer: {
            stringify: function(a) {
                var oAssetHashes = {};

                for(var k in a) {
                    var key = (k.substr(0, k.length - 3));

                    var sHash = a[k].substr(a[k].indexOf(".") - 10, 10);
                    oAssetHashes[key] = sHash;
                }

                return "define([], function() { return " + JSON.stringify(oAssetHashes) + "; });"
            }
        }
    }))
    .pipe(gulp.dest('./'));
});

Dadurch wird ein AMD-Modul mit den Revisionsnummern für moduleNames generiert, das in der Datei main.js als 'oRevision' enthalten ist. Dort überschreiben Sie die Funktion requirejs.load wie zuvor gezeigt.

Matte
quelle
-1

Dies ist zusätzlich zu der von @phil mccull akzeptierten Antwort.

Ich verwende seine Methode, automatisiere aber auch den Prozess, indem ich eine T4-Vorlage erstelle, die vor dem Build ausgeführt werden soll.

Pre-Build-Befehle:

set textTemplatingPath="%CommonProgramFiles(x86)%\Microsoft Shared\TextTemplating\$(VisualStudioVersion)\texttransform.exe"
if %textTemplatingPath%=="\Microsoft Shared\TextTemplating\$(VisualStudioVersion)\texttransform.exe" set textTemplatingPath="%CommonProgramFiles%\Microsoft Shared\TextTemplating\$(VisualStudioVersion)\texttransform.exe"
%textTemplatingPath% "$(ProjectDir)CacheBuster.tt"

Geben Sie hier die Bildbeschreibung ein

T4-Vorlage:

Geben Sie hier die Bildbeschreibung ein

Generierte Datei: Geben Sie hier die Bildbeschreibung ein

In Variable speichern, bevor require.config.js geladen wird: Geben Sie hier die Bildbeschreibung ein

Referenz in require.config.js:

Geben Sie hier die Bildbeschreibung ein

Zach Maler
quelle
2
Wahrscheinlich, weil Ihre Lösung für ein JavaScript-Problem eine Menge C # -Code ist. Dies ist auch eine Menge zusätzlicher Arbeit und Code, um etwas zu tun, das letztendlich genauso ausgeführt wird wie die akzeptierte Antwort.
mAAdhaTTah
@mAAdhaTTah Sie können dies mit jeder serverseitigen Sprache tun ... muss nicht c # sein. Dies automatisiert auch den Prozess und aktualisiert den Cache-Bust, wenn Sie eine neue Version des Projekts erstellen, um sicherzustellen, dass der Kunde immer die neueste Version der Skripte zwischenspeichert. Ich denke nicht, dass dies einen negativen Abschlag verdient. Es erweitert lediglich die Antwort, indem es einen automatisierten Ansatz für die Lösung bietet.
Zach Painter
Ich habe dies im Grunde genommen erstellt, weil ich es beim Verwalten einer Anwendung, die require.js verwendet, ziemlich ärgerlich fand, das "(new Date ()). GetTime ()) manuell auskommentieren und den statischen Cachebuster jedes Mal auskommentieren zu müssen, wenn ich die Anwendung aktualisierte Leicht zu vergessen. Plötzlich überprüft der Kunde Änderungen und sieht zwischengespeicherte Skripte, sodass er denkt, dass sich nichts geändert hat. Alles nur, weil Sie einfach vergessen haben, den Cachebuster zu ändern. Dieser kleine zusätzliche Code löscht die Wahrscheinlichkeit, dass dies geschieht.
Zach Painter
2
Ich habe es nicht notiert, ich weiß nicht, was tatsächlich passiert ist. Ich schlage nur vor, dass der Code, der nicht nur js, sondern eine C # -Vorlagensprache ist, für JS-Entwickler, die versuchen, eine Antwort auf ihr Problem zu erhalten, nicht so hilfreich sein wird.
mAAdhaTTah
-2

In meinem Fall wollte ich bei jedem Klick dasselbe Formular laden. Ich wollte nicht, dass die Änderungen, die ich an der Datei vorgenommen habe, erhalten bleiben. Es ist möglicherweise nicht genau für diesen Beitrag relevant, aber dies könnte eine mögliche Lösung auf der Clientseite sein, ohne die Konfiguration für erforderlich festzulegen. Anstatt den Inhalt direkt zu senden, können Sie eine Kopie der erforderlichen Datei erstellen und die eigentliche Datei intakt halten.

LoadFile(filePath){
    const file = require(filePath);
    const result = angular.copy(file);
    return result;
}
Mahib
quelle