Adobe Launch Extensions als Retter in der Not: Warum ich meine eigene Custom Code Action geschrieben habe

on 02.06.2021 by Hannes Schmieding

Als Antwort auf einige Probleme, die ich mit der Custom Code Rule Action (CCA) von Adobes Core Extension hatte, habe ich vor zwei Jahren meine eigene Adobe Launch Extension geschrieben. Angeregt durch eine  aktuelle Diskussion auf Twitter, habe ich mich nun entschlossen, diesen Beitrag zu verfassen.

Zudem veröffentliche ich meine neue Extension Friendly Helpers, die eine Synchronous Code Rule Action als Lösung für die Schwierigkeiten mit der CCA beinhaltet. Zusätzliche Funktionen sind bereits in Planung. Der Code ist Open Source. Weitere Details finden Sie am Ende dieses Beitrags.

Einleitung

Ich möchte vorwegschicken, dass ich im Herzen Entwickler bin. Um das Setup für weniger technisch versierte Nutzer nicht unnötig kompliziert zu machen, sollte Custom Code in Adobe Launch meiner Meinung nach möglichst zurückhaltend eingesetzt werden. In vielen Fällen ist benutzerdefinierter Code jedoch unvermeidlich, sobald man ein umfangreiches Setup aufbauen möchte – und genau das tun wir hier bei FELD M. Ein gut durchdachtes Data Layer Konzept kann zwar helfen, Regeln einfach zu halten. Letztendlich werden fortlaufende Ergänzungen aber immer mehr Lösungen auf Basis von benutzerdefiniertem Code erforderlich machen. Das beste Beispiel dafür sind in meinen Augen die technischen Implikationen der DSGVO.

Vor Einführung der DSGVO schickten die meisten Webseiten ihre Analytics Requests am Seitenende ab. Doch das war einmal – heute läuft alles asynchron. Launch muss warten, bis das Consent Management Tool der Wahl geladen wurde und dann auf dessen Antwort reagieren. Außer natürlich, Sie haben die Rechtsabteilung mit (echten) Cookies bestochen und man hat das Tracking standardmäßig erlaubt… nur um es dann zwei Monate später auf einen Aktiven Opt-in anzupassen. Ja, so kann das laufen. Ich kann gar nicht sagen, wie viele verschiedene Datenschutzimplementierungen und -interpretationen mir in den letzten Jahren bei meinen Kunden untergekommen sind. Ohne den regelmäßigen Einsatz von Custom Code wäre dies nicht möglich gewesen wäre.

Der etwas unsanfte Start von Launch

Mit dem offiziellen Ende vom DTM starteten im Jahr 2018 eine Vielzahl von Migrationsprojekten – die meist nicht ganz so glatt liefen, wie erhofft. Adobe hat zwar umfangreiche Informationen über die Migration von einem System zum anderen bereitgestellt (siehe hier), aber der Teufel steckte wie so oft im Detail. Die CCA und die Frage, wie Launch mit der von Adobe selbst eingeführten asynchronen Ausführung umgeht – oder besser gesagt nicht umgeht – führte regelmäßig zu Schwierigkeiten.

Tatsächlich war das größte Problem gar nicht die CCA selbst, sondern die Adobe Analytics Extension. Im DTM erfolgte die Ausspielung des AppMeasurement Objekts (auch bekannt als „s“-Object) synchron mit dem auslösenden Event. Nicht so in Launch. Hier sind Adobe Analytics Aktionen, wie beispielsweise „Set Variables“, in ein sogenanntes „Promise“ verpackt. Das bedeutet, dass sie unabhängig vom auslösenden Event erst zu einem späteren Zeitpunkt ausgeführt werden, beispielsweise wenn die Analytics Extension all ihre Abhängigkeiten, wie z.B. das AppMeasurement, geladen hat. Hat sich der Data Layer in der Zwischenzeit geändert? Pech gehabt; falsche Daten wurden an Adobe Analytics gesendet.

Dieses Problem veranlasste mich, meine erste Launch-Erweiterung für einen event-driven data layer (EDDL) mit kontextbezogenen Daten zu programmieren, ähnlich dem Adobe Client Data Layer – nur etwa 2 Jahre früher und nur 5KB groß…
Sie wird derzeit von DER SPIEGEL, den Schweizerischen Bundesbahnen (SBB) und anderen Kund*innen in der DACH-Region verwendet. Die CCA, die ich eigentlich mehr als kleines Goodie zu der Erweiterung hinzugefügt hatte, hat sich im Laufe der Zeit zu einem geschätzten Asset entwickelt.

Wie die CCA funktioniert

Bevor ich auf die technischen Gründe eingehe, warum ich die Synchronous Code Action erstellt habe, werfen wir einen kurzen Blick darauf, wie die CCA der Adobe Core Extension für JavaScript funktioniert. Aaron Hardy von Adobe hat die Details in diesem Community-Post beschrieben.

Kurz gesagt: Custom JavaScript Code wird in Launch auf zwei Arten behandelt:

Regeln basieren auf dem Event „page bottom“ oder „page top“ (library loaded)
Der Code wird als String in die Launch library Datei aufgenommen. Auf der Client-Seite wird er dann in einen Skript-Tag verpackt und mit Hilfe von document.write zum Dokument hinzugefügt.

Regeln basieren auf einem anderen Event
Der Code wird nicht in der Launch library gebündelt. Stattdessen wird er in eine separate Datei gespeichert, die geladen werden muss. Der Code selbst ist nur Text, der mit Hilfe von postscribe zum Dokument hinzugefügt wird.

Grund 1: Verwirrend

Im DTM konnten Sie aktiv zwischen „sequentiellem“ und „nicht sequentiellem“ benutzerdefiniertem JavaScript wählen.

Auch Launch bietet zwei verschiedene Implementierungen von benutzerdefiniertem Code an. Allerdings stellt es nur eine CCA zur Verfügung, deren Verhalten – wie oben beschrieben – von den event-Typen der Regel abhängig ist. Welche Methode verwendet wird, ist in der Benutzeroberfläche nicht ohne Weiteres ersichtlich. Entweder Sie wissen es – oder sie wissen es eben es nicht. Aufgrund der weitreichenden technischen Implikationen, die damit einhergehen, stellt dies ein großes Problem dar. Wir sollten in der Lage sein, aktiv zu wählen, welche Methode verwendet werden soll.

Grund 2: Keine sofortige Ausführung

Zeitkritische Events wie Click-Listener und das Unload-/Page Hide Event sind regelmäßig vorkommende Anwendungsfälle, bei denen eine sofortige Codeausführung erforderlich ist. Leider funktioniert die Verwendung der CCA in einer Regel, die auf einem einfachen Click-Listener basiert, so nicht. Wenn ein Benutzer auf einen Link klickt, muss der Code erst heruntergeladen werden. In dieser Zeit wurde der Seitenkontext mit hoher Wahrscheinlichkeit bereits aktualisiert (Single Page Application => falsche URL/Seitenname) oder er geht sogar ganz verloren, wenn der Browser navigiert (=> kein Analytics-Hit wird gesendet).

Eine mögliche Lösung wäre es, den Code entweder in Rule Conditions oder Events einzubinden. Diese sind immer direkt in der Launch library gebündelt. Damit sind zwar meine technischen Probleme gelöst, aber die Regeln werden dadurch weniger übersichtlich. Würde nur ich selbst mit dem Launch Setup arbeiten, könnte ich darüber noch hinwegsehen.

Eine andere mögliche Lösung, bei der nur die Core Extension verwendet wird, besteht darin, die Code-Bündelung zu erzwingen, indem man der Rule ein „page bottom“- oder „page top“-Event hinzufügt und es in einer Bedingung aufhebt.  Urs Boller hat dazu einen hervorragenden Blog-Beitrag verfasst. Jedoch bläht dieser Ansatz die Regel ein wenig auf. Zudem löst er auch nicht die anderen technischen Probleme.

Grund 3.1: Performance – Gehostete Dateien und Dateigröße

Besonders aufmerksam haben die Entwickler*innen bei meinen Kund*innen die Performance von Launch beobachtet, da wir ihnen im Zusammenhang mit dem Wechsel von DTM zu Launch natürlich ein deutliche Performance-Verbesserung versprochen hatten. Sowohl die Größe der gebündelten Dateien als auch die Größe der gesamten Assets wurden kritisch betrachtet. Hier hat Adobe das Versprechen eingehalten: Launch ist schneller, die Bündelung ist intelligenter. Es lädt z.B. nur die Teile von Erweiterungen, die Sie tatsächlich brauchen und benutzen.

Abgesehen von den Extensions fehlt jedoch die Möglichkeit zur Feinjustierung von Custom Code. Es steht nur der Custom Code von Adobes Core Extension zur Verfügung. Außerdem führen, wie bereits erwähnt, die technischen Implikationen der DSGVO und andere asynchrone Abhängigkeiten dazu, dass sowohl „Page Bottom“ als auch „Page Top“ (library loaded) Events seltener verwendet werden. Dadurch steigt die Anzahl der separaten Code-Dateien, die auf jeder Seite für die Ausführung des Analytics-Setups benötigt werden. Nur nutzen diese eben nicht die Standard event trigger.

Es gibt also Anwendungsfälle, in denen man unbedingt möchte, dass der Code direkt in der Launch-Bibliothek gebündelt wird und nicht erst von einem Server geladen werden muss. Es kann vorteilhaft sein, eine einzelne, größere Datei zu laden, anstatt vieler kleiner Dateien. Das ist einer der Gründe, warum JS-Code-Bundler existieren.

Außerdem wird CCA-Code nicht minimiert.

Zu guter Letzt fügt das einfache Einbinden von CCA in Ihre Library, selbst wenn es nur um das Hinzufügen von console.log('yay it works') geht, Ihrer Launch Library-Datei etwa 6kb hinzu. Launch muss nun die entsprechende Logik einbinden, einschließlich Postscribe, die das CCA-Verhalten ermöglicht.

Grund 3.2: Performance – document.write

Eine zweite Implikation im Zusammenhang mit der Performance ist die fortlaufende Verwendung von document.write, das nach wie vor von der CCA verwendet wird, um Code zum Dokument hinzuzufügen. Ich weiß nicht wie oft sich schon Entwickler*innen bei mir gemeldet haben, um sich genau darüber zu beschweren. Das war ein großes Problem für meine Kund*innen. „Ihr habt hohe Performance versprochen, aber ihr verwendet hier document.write“.

Fürs Protokoll: Ich habe dieses Verhalten bei Launch in letzter Zeit seltener beobachtet. Trotzdem, document.write ist im Quellcode enthalten und das wird von Entwickler:innen in Reviews auch weiterhin kritisch angemerkt.

Grund 4: XHTML-Fehler

Letztes Jahr meldete sich ein Kunde bei mir: Man hatte festgestellt, dass für bestimmte Webseiten Daten fehlten. Ich sah nach. Alles war in Launch korrekt aufgesetzt. Ein Schlüsselelement des benutzerdefinierten Codes, der eine direkt in die Library eingebundene JavaScript CCA verwendete, wurde jedoch nicht richtig auf der Website geladen.

Es stellte sich heraus, dass alle betroffenen Seiten XHTML-Seiten waren.

Failed to set the 'innerHTML' property on 'Element': The provided markup is invalid XML, and therefore cannot be inserted into an XML document.

Leider kann ich den Fehler derzeit nicht reproduzieren. Die betroffenen Webseiten wurden seitdem angepasst. Aus der „Known Errors“-Dokumentation meines Kunden geht hervor, dass die Lösung darin bestand, von der CCA zur Verwendung der Synchronous Code Action zu wechseln.

Dies könnte damit zusammenhängen, dass Launch den benutzerdefinierten Code als String speichert, wenn er in die Library eingebunden wird. Der String muss dann zur Laufzeit in eine Funktion zur Ausführung konvertiert werden. Das Einfügen des geparsten Codes in das Dokument schlug fehl.

Lösung: Synchronous Code Action

Die oben genannten Gründe haben mich dazu veranlasst, eine Synchronous Code Action zu erstellen, bei der benutzerdefinierter JavaScript Code direkt in Form einer Funktionsdefinition in die Adobe Launch library eingebunden wird. Dadurch werden alle oben genannten Probleme behoben.

Der folgende Code zeigt, wie die Synchronous Code Action und die CCA (unter Verwendung eines Page-Top-Events) in die launch-library.js eingebunden werden.

{
    // [...]
    "actions": [
        {
            "modulePath": "friendly-helpers-feldm/src/lib/actions/synchronousCode.js",
            "settings": {
                "callback": function (event, target, Promise) {
                    console.log('Friendly Helper', arguments, 'this:', this);
                }
            }
        },
        {
            "modulePath": "core/src/lib/actions/customCode.js",
            "settings": {
                "source": "console.log('Custom Code',arguments,'this:', this);",
                "language": "javascript"
            }
        }
    ]
}

Beachten Sie, dass der CCA-Benutzercode ein String ist, der auf der Client-Seite erst umgewandelt werden muss.

Die Entwicklung der Extension selbst und die technischen Details würden den Rahmen dieses Blogs sprengen. Daher sei bis zu diesem Punkt auf  die offizielle Dokumentation als guten Ausgangspunkt verwiesen.

Technische Details für Entwickler:innen von Extensions

Das Kernstück der Synchronous Code Action ist ein „transform“ Feature im Extension Manifest. Hier die relevante Dokumentation: extension manifest documentation > function transforms:

Using the function transform tells Platform Launch to wrap the user’s code in a executable function when it is emitted in the Platform Launch runtime library

Der benutzerdefinierte Code für Events und Bedingungen in Adobes Core Extension verwendet ebenfalls diese Funktionstransformation. Die Synchronous Code Action ist somit einfach die CCA, die anstelle des derzeit undokumentierten customCodeTransformation die Funktionstransformation verwendet. Wir können dies im Extension Manifest im Git-Repository von Adobes Core Extension sehen. Zum jetzigen Zeitpunkt (April 2021) sind nur file, function undremove Transformationen dokumentiert.

Ein weiteres wichtiges Puzzlestück – die Bereitstellung einer GUI, in der Benutzer ihren eigenen Code eingeben können – müssen wir glücklicherweise nicht selbst schreiben. Launch bietet über Extension Bridge API Zugriff auf den eingebauten Code-Editor.

Die „Friendly Helpers“-Erweiterung

Ich habe eine neue Extension entwickelt, „Friendly Helpers“, um Adobe Launch um eine Reihe nützlicher Funktionen zu erweitern, die nicht von Haus aus mitgeliefert werden. Den Anfang macht dabei die Synchronous Code Action.

Der Code ist Open Source und hier verfügbar: https://github.com/feld-m/Friendly-Helpers

Ich beabsichtige, die Erweiterung über Adobe öffentlich zugänglich zu machen. Damit können Sie die Extension ganz einfach zu Ihrer Launch Property hinzufügen. Bis dahin ist sie leider nur als private Erweiterung verfügbar, die Sie für Ihre Organisation installieren müssen. Eine Anleitung dazu finden Sie in der Readme-Datei im Repository.

Folgende weitere Rule Actions sollen der Erweiterung hinzugefügt werden.

Wenn Sie Vorschläge für weitere Funktionen haben, melden Sie sich bitte und ich werde sehen, was ich tun kann! E-Mail: analytics@feld-m.de

Die Erweiterung verwendet die Adobe Coral/Spectrum UI, um ihr das gleiche Aussehen und Nutzer:innenerlebnis wie der Launch GUI zu geben. Der relevante Code befindet sich im src/views Ordner.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert