Server Side Mustard Cut

Avatar of Chris Coyier
Chris Coyier on

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Der Begriff „Mustard Cutting“ im Webdesign stammt von Entwicklern bei der BBC, die unterschiedliche Erlebnisse ihrer Website für verschiedene Browser auf verschiedenen Geräten bereitstellen wollten. Sie versuchten insbesondere, die User Agent-Erkennung zu vermeiden.

Aber heutzutage, da immer mehr User Agents auf die Website zugreifen, wird dies zu einer fruchtlosen Übung.

Stattdessen nutzten sie Feature-Erkennung. Modernizr ist das klassische (tolle) Beispiel dafür, aber auch einzeln zusammengestellte Feature-Tests können für sich allein ziemlich klein und einfach sein. Dies ist die Logik, die die BBC verwendet hat, um zu bestimmen, ob ein Browser den Test bestanden hat oder nicht.

if('querySelector' in document
     && 'localStorage' in window
     && 'addEventListener' in window) {
     // bootstrap the javascript application
     }

Wenn diese Logik fehlschlug, lud die Website weiterhin das, was sie als Kern-Erlebnis bezeichneten. Wenn die Logik bestand, wurden zusätzliche Ressourcen für ein verbessertes Erlebnis geladen.

Ziemlich cool.

Das Laden zusätzlicher CSS und JavaScript ist ziemlich einfach

Es gibt verschiedene Möglichkeiten, dies zu tun, normalerweise über XHR für die Ressource. Filament Group bietet einige sehr kleine, fokussierte Skripte nur dafür: loadCSS und loadJS.

Das Laden von etwas zusätzlichem HTML über XHR ist ähnlich einfach. Aber…

Es ist zu schwer, ein komplett anderes Dokument clientseitig zu laden

Nehmen wir an, Sie brauchen nicht nur ein bisschen zusätzliches CSS, Skripte oder ein bisschen HTML. Was Sie wollen, ist ein komplett anderes Dokument.

Ihr „Kern-Erlebnis“ und „verbessertes Erlebnis“ sind komplett unterschiedliche Sätze von HTML, CSS und JavaScript. Der Versuch, dies clientseitig zu tun, würde bedeuten, ein sehr einfaches Dokument zu laden und dann zu versuchen, im Wesentlichen nachzubilden, wie der Browser-Parser funktioniert. Zuerst machen Sie den Mustard Cut, dann XHR für die richtige Menge an HTML, die Sie benötigen, und fügen sie dann entweder in den DOM ein oder warten vielleicht, bis Sie das CSS per XHR geladen haben, damit Sie keinen „Flash of Unstyled Document“ bekommen. Dann XHR für alle Skripte und stellen Sie sicher, dass Sie sie in der richtigen Reihenfolge ausführen (schwierig).

Schwer umzusetzen.

Dem Server erlauben, das richtige Dokument basierend auf Mustard-Cutting-Informationen zu servieren

Wenn der Server die Ergebnisse Ihres Mustard-Cuts kennen würde, könnten Sie all diesen Ärger vermeiden und das richtige Dokument sofort bereitstellen.

Das ist der Sinn des clientseitigen Mustard-Cuts: Er kann nur auf dem Client durchgeführt werden. Aber… man könnte diese Daten in einem Cookie speichern, und Cookies können vom Server gelesen werden.

Wenn Sie einen Cookie hätten, auf den Sie zählen könnten, könnten Sie etwas wie das hier im Routing Ihrer Website tun.

<?php
  // This is just a fake routing/controller kinda setup.
  if (isset($_COOKIE["mustard"])) {
    // Enhanced experience
    if ($_COOKIE["mustard"] == true) {
      include_once("enhanced.php");
    // Core experience
    } else {
      include_once("core.php");
    }
  // No cookie = core experience
  } else {
    include_once("core.php");
  }
?>

Was passiert aber beim ersten Seitenaufruf?

Das ist der knifflige Teil. Dieser Cookie wird beim ersten Seitenaufruf nicht vorhanden sein. Sie könnten einfach zulassen, dass nachfolgende Seiten das richtige Erlebnis liefern, aber das ist wahrscheinlich nicht akzeptabel.

Hier kommt der kontroverseste Teil: Wenn Sie den Cookie nicht haben, aber feststellen können, dass der Browser ihn unterstützt und er aktiviert ist, laden Sie die Seite neu.

Seite neu laden?! Sie machen Witze?

Völlig berechtigte Fragen: Wie kann ein Neuladen eine gute Benutzererfahrung sein? Sind Neuladungen nicht langsam? Könnte man nicht in einer Neuladeschleife gefangen werden?

Ich denke, all diese Dinge können angesprochen werden.

Ganz **oben** im Dokument, wenn der Cookie **nicht** vorhanden ist und der Browser Cookies **unterstützt**.

  1. Mustard-Cut und speichern Sie die Daten mit JavaScript in einem Cookie
  2. Wenn die Mustard-Cut-Daten Ihnen sagen, dass Sie ein anderes Dokument laden sollten: Stoppen Sie das Laden/Ausführen der Seite (window.stop();) und laden Sie neu (location.reload(true);).

Beim Neuladen ist der Cookie für den Server vorhanden.

Wenn dies das erste ist, was ein Dokument tut, geschieht alles so schnell, dass ich es kaum bemerke. Das tun wir für die Editor-Seite auf CodePen, siehe.

Neuer Seitenaufruf in einem neuen Inkognito-Fenster (keine gespeicherten Cookies). Die Desktop-Ansicht ist eigentlich die Standardansicht, aber das Neuladen erfolgt und die mobile Ansicht wird aufgrund eines Mustard-Cuts geladen.

Der Trick, um eine Neuladeschleife zu vermeiden, besteht darin, diesen Teil des JavaScript nur auszuführen, wenn Sie sicher sind, dass Cookies unterstützt und aktiviert sind.

Das Mustard-Cutting-Skript

Hier ist ein Mustard-Cut, der nur die Bildschirmbreite testet. Bedenken Sie, dass ein Mustard-Cut alles sein kann, was Sie möchten und was Sie clientseitig testen können.

(function() {

  // If the browser supports cookies and they are enabled
  if (navigator.cookieEnabled) {

    // Set the cookie for 3 days
    var date = new Date();
    date.setTime(date.getTime() + (3 * 24 * 60 * 60 * 1000));
    var expires = "; expires=" + date.toGMTString();

    // This is where we're setting the mustard cutting information.
    // In this case we're just setting screen width, but it could
    // be anything. Think http://modernizr.com/
    document.cookie = "screen-width=" + window.outerWidth + expires + "; path=/";

    /*
      Only refresh if the WRONG template loads.

      Since we're defaulting to a small screen,
      and we know if this script is running the
      cookie wasn't present on this page load,
      we should refresh if the screen is wider
      than 700.

      This needs to be kept in sync with the server
      side distinction
    */
    if (window.outerWidth > 700) {

      // Halt the browser from loading/doing anything else.
      window.stop();

      // Reload the page, because the cookie will now be
      // set and the server can use it.
      location.reload(true);

    }

  }

}());

Tatsächlich **müssen wir dieses Skript gar nicht erst laden**, wenn der Cookie bereits vorhanden ist, da wir wissen, dass die richtige Seite bereits geladen wurde.

<?php
    // Run this script as high up the page as you can,
    // but only if the cookie isn't already present.
    if (isset($_COOKIE["screen-width"]) == 0) { ?>
      <script src="mobile-mustard.js"></script>
<?php } ?>

Mögliche Szenarien

  • Der normale Erstbesucher: Kein Cookie vorhanden. Das Mustard-Cut-Skript wird ausgeführt und die Seite wird schnell neu geladen. Er erhält das richtige Dokument basierend auf dem Cut.
  • Der wiederkehrende Besucher: Cookie ist bereits vorhanden. Er erhält das richtige Dokument basierend auf dem Cut.
  • Besucher mit falschem Cookie: Möglicherweise hat er einen Desktop-Browser, der jedoch beim ersten Laden der Seite sehr schmal war, und er hat ihn seitdem verbreitert. Dies kann mit einer CSS @media-Abfrage erkannt werden und es wird ein Link angeboten, um das Problem zu beheben (Demo ansehen).
  • Besucher mit ausgeschalteten Cookies: Wir liefern unsere Wahl von Dokumenten. Könnte potenziell falsch sein. Liefern Sie den besten wahrscheinlichen Fall basierend auf den Daten.
  • Besucher, bei denen JavaScript nicht ausgeführt wird: Wir liefern unsere Wahl von Dokumenten. Könnte potenziell falsch sein. Liefern Sie den besten wahrscheinlichen Fall basierend auf den Daten.

Mögliche Probleme

Ich sage nicht, dass dies die beste Lösung für dieses Problem ist. Tatsächlich ist es wahrscheinlich besser, wenn Sie alles rein clientseitig erledigen können.

Hier sind einige potenzielle Probleme mit dieser Lösung

  • Vielleicht ist das Neuladen langsamer, als ich denke. Ich habe keine Tests mit super alten/super langsamen Geräten durchgeführt, wie ich es wahrscheinlich hätte tun sollen.
  • HTML-Caching könnte ein Problem darstellen. Ich habe dies aus erster Hand erlebt, als ich die Demo auf einer Website erstellte, die diese Methode verwendete. Der Server liefert ein gecachtes Dokument, das dann als falsch bestimmt und neu geladen wird, was zu der gefürchteten Neuladeschleife führt. Lösung: Dieses HTML-Dokument nicht cachen oder auf Unterdomänen umleiten basierend auf dem Cut.
  • Sicherheitseinstellungen, die den serverseitigen Zugriff auf von JavaScript erstellte Cookies verhindern. Wenn Sie das nicht kontrollieren können, wäre das ein Problem.

Ich verwende diese Technik jedoch in der Produktion und hatte seit Monaten keine Probleme mehr, daher bin ich damit ziemlich zufrieden.

Demo und Repository

Hier ist eine Demo und der Code ist auf GitHub verfügbar, wenn Sie Fehler finden.

Außerdem dachte ich, Client Hints seien hier die Rettung, aber ich bin mir nicht mehr sicher, wo sie in diese Situation passen.