Jsdom: Unterstützung für die WebComponents-API

Erstellt am 17. Feb. 2015  ·  89Kommentare  ·  Quelle: jsdom/jsdom

Wir versuchen, jsdom für unsere Komponententests für React Core (http://facebook.github.io/react/) zu verwenden.

Leider wird die Spezifikation für Webkomponenten von jsdom nicht nativ unterstützt, und die polyfill-Datei webcomponents.js läuft nicht auf jsdom. Dieses Problem erfordert das Hinzufügen von WebComponent-Unterstützung (benutzerdefinierte Elemente, Shadow-Dom, HTML-Importe usw.).

feature

Hilfreichster Kommentar

TL;DR

Ich habe in den letzten 2 Wochen daran gearbeitet, die Machbarkeit zu bewerten, Unterstützung für benutzerdefinierte Elemente in jsdom hinzuzufügen. Hier das Ergebnis der Untersuchung.

Eine spezifikationskonforme Implementierung des benutzerdefinierten Elements finden Sie hier: https://github.com/jsdom/jsdom/compare/master...pmdartus :custom-elements?expand=1. Hier und da gibt es noch einige Ecken und Kanten, aber zumindest die meisten WPT-Tests bestehen . Die verbleibenden fehlgeschlagenen Tests sind entweder bekannte JSDOM-Probleme oder kleinere Probleme, die angegangen werden können, wenn wir uns mit der eigentlichen Implementierung in jsdom befassen.

Nun, hier ist die gute Nachricht: Jetzt, da Shadow DOM nativ unterstützt wird, konnte ich sowohl mit dem benutzerdefinierten Elementzweig als auch mit dem Mutationsbeobachter die neueste Version des Polymer 3-Hello-World-Anwendungsbeispiels in jsdom laden und rendern 🎉. In seinem aktuellen Zustand ist der Zweig nicht in der Lage, eine Stencil-Anwendung zu laden (der Stencil-Entwicklungsmodus erfordert einige nicht unterstützte Funktionen wie module , und der Prod-Modus wirft aus einem unbekannten Grund).

Aktionsplan

Hier ist eine Liste der Änderungen, die zuerst vorgenommen werden müssen, bevor Sie mit der eigentlichen Implementierung der benutzerdefinierten Elementspezifikation beginnen. Jeder Punkt in der Liste ist unabhängig und kann parallel angegangen werden.

Unterstützung für erweiterte [CEReactions] IDL-Attribute

Einer der Kernfunktionen, die in jsdom fehlen, um Unterstützung für benutzerdefinierte Elemente hinzuzufügen, sind die [CEReactions] -Attribute. Ich konnte dieses Problem teilweise umgehen, indem ich die richtigen Prototyp-Eigenschaften gepatcht habe. Dieser Ansatz funktioniert, solange der benutzerdefinierte Elementreaktionsstapel global ist und nicht pro Einheit verwandter Browsing-Kontexte ähnlichen Ursprungs, da alle Schnittstellenprototypen gemeinsam genutzt werden.

Dieser Ansatz hat einige Mängel, da einige Schnittstellen die [CEReactions] -Attribute haben, die mit indizierten Eigenschaften verknüpft sind ( HTMLOptionsCollection , DOMStringMap ). Intern verwendet jsdom Proxys, um Mutationen zu diesen Eigenschaften zu verfolgen. Das Prototyp-Patching der Schnittstelle funktioniert in diesem Fall nicht. Ein anderer Ansatz, um dieses Problem zu umgehen, wäre, die Implementierung anstelle der Schnittstelle (nicht implementiert) zu patchen.

Ich bin mit dem internen webidl2js nicht vertraut genug, aber wir sollten untersuchen, wie man einen globalen Hook für [CEReactions] hinzufügt.

Änderungen:

Unterstützung für erweiterte [HTMLConstructor] IDL-Attribute

Wie @domenic oben erklärt hat, ist das Hinzufügen von Unterstützung für [HTMLConstructor] hier einer der Hauptblocker.

Ich konnte dieses Problem hier umgehen, indem ich den Schnittstellenkonstruktor für jeden Browsing-Kontext gepatcht habe. Der Schnittstellenkonstruktor wäre in der Lage, auf das rechte Fenster und das Dokumentobjekt zuzugreifen, während er den gemeinsam genutzten Prototyp behält. Dieser Ansatz vermeidet auch den Leistungs-Overhead der Neubewertung des Schnittstellen-Prototyps für jeden neuen Browsing-Kontext.

Ich bin mir nicht sicher, ob es hier der beste Ansatz ist, aber es entspricht den Anforderungen, ohne zusätzlichen Leistungsaufwand einzuführen.

Änderungen:

Machen Sie die Spezifikation des Make-Fragment-Parsing-Algorithmus konform (#2522)

Wie hier besprochen, ist die in Element.innerHTML und Element.outerHTML verwendete HTML-Fragment-Parsing-Algo-Implementierung falsch. Der Parsing-Algorithmus muss umgestaltet werden, damit die Callbacks der benutzerdefinierten Elementreaktionen ordnungsgemäß aufgerufen werden.

Änderungen:

Verbesserte Schnittstellensuche für den Algorithmus zum Erstellen von Elementen

Eines der Probleme, auf das ich schnell gestoßen bin, war die Einführung neuer zirkulärer Abhängigkeiten beim Hinzufügen von Unterstützung für die Erstellung benutzerdefinierter Elemente. Sowohl CustomElementRegistry als auch der Algorithmus zum Erstellen von Elementen erfordern Zugriff auf die Elementschnittstellen, wodurch ein Alptraum von kreisförmigen Abhängigkeiten entsteht.

Der in der Verzweigung verfolgte Ansatz bestand darin, ein InterfaceCache zu erstellen, das eine Schnittstellensuche nach Elementnamensraum und -name, aber auch nach Schnittstellenname ermöglichen würde. Die Schnittstellenmodule werden träge ausgewertet und nach der Auswertung zwischengespeichert. Dieser Ansatz beseitigt die zirkulären Abhängigkeiten, da die Schnittstellen auf der obersten Ebene nicht mehr benötigt werden.

Dies ist ein Ansatz, um dieses seit langem bestehende Problem in jsdom zu lösen. Eines dieser Probleme bei diesem Ansatz besteht darin, dass möglicherweise die webgepackte / browserbasierte Version von jsdom beschädigt wird (nicht getestet).

Änderungen:

~Fix Element.isConnected zur Unterstützung von Shadow DOM (https://github.com/jsdom/jsdom/pull/2424)~

Dies ist ein Problem, das sich mit der Einführung des Schatten -DOMs eingeschlichen hat: isConnected gibt false zurück, wenn das Element Teil eines Schattenbaums ist. Hier muss ein neuer WPT-Test hinzugefügt werden, da kein Test dieses Verhalten überprüft.

Änderungen:

Knotendokumente HTMLTemplateElement.templateContents reparieren (#2426)

Der in der Spezifikation definierte Vorlageninhalt hat ein anderes Knotendokument als das HTMLTemplateElement selbst. jsdom implementiert dieses Verhalten heute nicht und das HTMLTemplateElement und seine Vorlageninhalte haben denselben Inhalt
Dokumentenknoten.

Änderungen:

  • HTMLTemplateElement-impl.js
  • htmltodom.js . Diese Änderung hat auch nachgelagerte Auswirkungen auf den Parser. Wenn das Kontextelement ein HTMLTemplateElement ist, sollte der Analysealgorithmus für HTML-Fragmente den Dokumentknoten aus dem Vorlageninhalt und nicht aus dem Element selbst entnehmen.

Fehlende Adoptionsschritte zu HTMLTemplateElement hinzufügen (#2426)

Das HTMLTemplateElement muss einige spezifische Schritte ausführen, wenn es in ein anderes Dokument übernommen wird. Soweit ich weiß, hat die Bodenschnittstelle einen speziellen Adoptionsschritt. Die Implementierung des Knotenübernahmealgorithmus müsste ebenfalls aktualisiert werden, um diesen Übernahmeschritt aufzurufen.

Änderungen:

Unterstützung für die isValue-Suche im parse5-Serialisierungsprogramm hinzugefügt

Die serialisierenden HTML-Fragmente suchen beim Serialisieren eines Elements den mit dem Element verknüpften Wert und spiegeln ihn als Attribut im serialisierten Inhalt wider. Es wäre interessant, einen weiteren Hook im parse5-Baumadapter hinzuzufügen, der den mit einem Element getIsValue(element: Element): void | string verknüpften is -Wert nachschlagen würde.

Ein alternativer Ansatz (nicht implementiert) wäre, das Verhalten des aktuellen getAttrList -Hooks zu ändern, um den is-Wert an die Attributliste zurückzugeben, wenn das Element einen zugeordneten is-Wert hat.

Leistung

Bevor ich eine Performance-Optimierung durchführe, wollte ich auch die Performance der Änderungen in der Branche überprüfen. Das Hinzufügen von benutzerdefinierten Elementen fügt einen Leistungsmehraufwand von 10 % im Vergleich zum aktuellen Ergebnis auf dem Master für Baummutations-Benchmarks hinzu. Allerdings ist die Erstellung einer neuen JSDOM-Umgebung jetzt 3x bis 6x langsamer im Vergleich zum Master, es würde eine tiefere Untersuchung erfordern, um die Grundursache zu identifizieren.

Näheres: hier

Alle 89 Kommentare

Es wäre interessant zu untersuchen, welche APIs webcomponents.js verwendet, die jsdom nicht unterstützt. Wenn ich raten müsste, wäre das viel einfacher zu implementieren als die vollständige Web-Komponenten-Spezifikation.

Allerdings wäre es ziemlich cool, Webkomponenten zu implementieren. Wahrscheinlich nicht so schwer, wie man denken könnte – die Spezifikationen sind relativ klein.

Hatte gerade Zeit, mich ein bisschen damit zu beschäftigen:

Zunächst einmal haben wir Window nicht im Fensterbereich definiert. Ich habe dies gerade mit this.Window = this.prototype im Window -Konstruktor gepatcht.
Zweitens erwartet webcomponentsjs, dass Window einen weiteren Prototyp hat, dh den Prototyp EventTarget , den wir nicht als separate Entität implementieren.

Nur eine kleine Info, da ich etwas Zeit hatte.

Hübsch. Sollte in der Lage sein, Windows ziemlich einfach freizulegen. Der EventTarget-Prototyp ist etwas kniffliger, scheint aber machbar zu sein, wenn man bedenkt, wie wir das Zeug derzeit implementieren. es war ein TODO von mir.

Okay, Patches sind bisher ziemlich einfach:

  • [x] this.Window = Window; im Window-Konstruktor
  • [x] inherits(dom.EventTarget, Window, dom.EventTarget.prototype); nach der Definition von Window

Der nächste Absturz von webcomponents.js passiert, weil wir HTMLUnknownElement (#1068) nicht implementieren, nachdem wir darauf hingewiesen haben, dass wir SVGUseElement implementieren müssen. Das ist es, was mich derzeit blockiert, weil webcomponents.js anscheinend das SVGUseElement nicht mag, das von einem HTMLDivElement umgeben ist, und eine Behauptung einwirft.

Okay, ich habe noch etwas mehr in das Polyfill eingecheckt, wir müssen Folgendes implementieren / Sie müssen Folgendes shimen:

  • [x] HTMLUnknownElement #1068
  • [ ] SVGUseElement
  • [ ] window.CanvasRenderingContext2D
  • [ ] Bereichs-APIs (einschließlich: document.getRange() , window.getSelection() , window.Range , window.Selection ; #804 könnte ein Anfang sein)
  • [ ] npm i canvas

(vorerst nicht erschöpfende Liste)

Ein Start ist in etwa so:

jsdom.env({
  file: __dirname + '/index.htm', // refers to webcomponent.js
  created: function (err, window) {
    jsdom.getVirtualConsole(window).sendTo(console)

    window.document.createRange = function () { }
    window.getSelection = function () { }
    window.Range = function () { }
    window.Selection = function () { }
    window.CanvasRenderingContext2D = function () { } // Object.getPrototypeOf(require("canvas")(0,0).getContext("2d")) might be better
    window.SVGUseElement = window.HTMLUnknownElement
  },
  done: function (err, window) {
    console.log(err[0].data.error);
    console.log(window.CustomElements)
  },
  features: {
    ProcessExternalResources: ['script']
  }
});

Danach gibt es einen Fehler in unserem HTMLDocument -Konstruktor, der zu einem maximalen Call-Stack-Fehler führt. Der Konstruktor ist im Moment nur für den internen Gebrauch, es ist jedoch gültig, dass ein Skript auf der Website ihn aufruft, also müssen wir diesen Konstruktor für den öffentlichen Gebrauch verfügbar machen.

+1 Würde gerne WebComponents auf jsdom sehen, insbesondere da Polymer immer beliebter wird, wäre es großartig, benutzerdefinierte Elemente auf einem Headless-System testen zu können.

Im Moment gibt es keine Cross-Browser-Definition von Webkomponenten, daher wäre es verfrüht, sie zu implementieren. (Wir werden Chrome nicht einfach kopieren.) In der Zwischenzeit können Sie versuchen, Polymer mit jsdom zu verwenden.

@domenic fair genug. Nun, es ist mehr die Unterstützung für das WebComponents.js-Polyfill, nach dem ich suche, da Polymer davon abhängt - oder webcomponents-lite (polyfills alle mit Ausnahme von Shadow DOM) im Moment. Ich habe ein paar Versuche unternommen, Polymer auf jsdom zum Laufen zu bringen, aber bisher kein Glück - ich gehe davon aus, dass die Aufgaben von @Sebmaster im obigen Kommentar zumindest zuerst gepatcht werden müssen.

Meines Wissens nach handelt es sich um drei separate Polyfills. Der im OP ist von Polymer getrennt. Dann gibt es die Polyfills von webcomponents.org, die früher in Old-Polymer verwendet wurden. Dann haben sie in Polymer 1.0 ihre eigenen Polyfills, denke ich, die nicht wirklich Polyfills sind, sondern alternative Bibliotheken, die Dinge tun, die irgendwie Web-Komponenten-artig sind. Vielleicht ist das aber webcomponents-lite.

Im WebComponentsJS-Repo heißt es, dass webcomponentsjs-lite eine Variante ist, die Polyfills für alle _but_ Shadow-DOM bereitstellt, die Polymer dann unabhängig versucht, mithilfe ihres Shady-DOM-Systems zu shimen. Daher bin ich mir ziemlich sicher, dass sich Polymer so weit wie möglich auf WebComponents verlässt, wobei das WebComponentsJS-Polyfill die Hauptarbeit erledigt. Die Lite-Version soll deutlich weniger Gewicht haben (witzigerweise ...), also werde ich sehen, ob ich feststellen kann, was jsdom für die Lite-Version benötigt. Wie hoch sind Ihrer Meinung nach die Chancen, dass Polyfill (lite oder full) in jsdom funktioniert?

Ohne Nachforschungen ist das wirklich schwer zu sagen... bin gespannt, was du herausfindest.

Ja, ich denke, meine Todo-Liste ist immer noch anwendbar und erforderlich, um die Shims zu verwenden. Das Einbinden von #1227 könnte uns bei der Implementierung standardkonformer Schnittstellen viel schneller machen, sodass wir die fehlenden Schnittstellen schneller beheben können.

Ich habe (wahrscheinlich naiv) damit begonnen, daran zu arbeiten, CustomElementsRegistry hinzuzufügen, um zu verstehen, wie jsdom strukturiert ist. Ich habe "custom-elements/custom-elements-registry/define.html" zur Testliste der Webplattform hinzugefügt und es wird bestanden, wenn es nicht sollte (ich habe noch nicht annähernd genug implementiert). Ich bin mir ziemlich sicher, dass der Test nicht wirklich läuft, da selbst das Hinzufügen eines throw am Anfang des Tests nicht verhindert, dass er bestanden wird. Also habe ich offensichtlich etwas verpasst; Gibt es außer dem Hinzufügen des Tests in test/web-platform-tests/index.js noch etwas, das ich tun muss?

Anscheinend liegt das daran, dass wir in der ersten const testWindow = iframe.contentDocument.defaultView; -Zeile fehlschlagen, weil contentDocument aus irgendeinem Grund nicht definiert ist. Könnte ein Problem mit unserer Ladereihenfolge im Vergleich zur Skriptausführung sein, aber wir haben uns nicht damit befasst. Ich hoffe, das hilft Ihnen, das zu umgehen. Möglicherweise müssen wir den Test für unsere Zwecke (vorerst) vereinfachen.

Das hilft sehr, danke! Ich werde sehen, ob ich herausfinden kann, was dort vor sich geht, und wenn nicht, werde ich einen vereinfachten Test erstellen, wie Sie es empfohlen haben.

@Sebmaster Nur für den Fall, dass Sie interessiert sind, ich habe ein wenig recherchiert, was mit diesem Test los ist, und die Ergebnisse sind für mich überraschend.

Der Test verwendet die benannte Zugriffsfunktion von html . Das bedeutet, dass Sie Dinge tun können wie:

<div id="foo"></div>
<script>
  console.log(window.foo === document.getElementById('foo'));
</script>

_Jedoch_, wenn das Element einen verschachtelten Browsing-Kontext hat, sollte global stattdessen darauf verweisen (siehe die verlinkte Spezifikation). Für Iframes ist das contentWindow . jsdom macht das richtig, es gibt sogar einen Test . Safari macht es auch richtig.

Das Verrückte ist, dass Chrome und Firefox das falsch machen; Das globale zeigt auf den Iframe, nicht auf das contentWindow. Als ich das sah, nahm ich an, dass es sich um einen jsdom-Fehler handelte, und machte mich auf die Suche, bis ich schließlich diesen Test fand, der mich zu der Spezifikation führte.

tldr; Die Arbeit an jsdom ist sehr lehrreich und ihr macht einen tollen Job.

Fehler in den jeweiligen Browsern eintragen. Ich werde auch eine PR an Web-Platform-Tests senden, ich habe auch einige andere Fehler im Test gefunden.

Dies ist noch mehr Motivation, Tests wie https://github.com/tmpvar/jsdom/blob/master/test/living-html/named-properties-window.js zu WPT vorzuladen. Danke fürs Posten! Ich fühle mich wirklich großartig bei jsdom ^_^

Hallo!

Ich habe es geschafft, dass Custom Elements Polyfill durch Kombinieren mit jsdom funktioniert

Hinweis: Das Repo verwendet jsdom 8.5.0. Der Grund ist, dass ich nur mit einem MutationObserver-Polyfill Erfolg hatte, der Mutation Events intern verwendet. Mutationsereignisse wurden nach 8.5.0 aufgrund schlechter Leistung entfernt. Wenn der native Mutation Observer herauskommt, werde ich das Polyfill entfernen und auf die neueste jsdom aktualisieren.

Ich habe das neueste jsdom und https://github.com/WebReflection/document-register-element funktioniert für mich! Ich habe mit den offizielleren Polyfills experimentiert und habe aus irgendeinem Grund Probleme. Mein Ziel ist es, zumindest benutzerdefinierte Elemente und HTML-Importe zum Laufen zu bringen ... es wäre großartig, wenn wir Polymer auch zum Laufen bringen könnten.

Ich kann die Polymer-Skripte fehlerfrei ausführen lassen. Ich kann sogar eine Komponente erstellen und an den Polymer-Konstruktor übergeben. Danach schlägt es stillschweigend fehl. Ich denke, Shadow DOM ist das Problem.

Ich habe versucht, das HTML-Import-Polyfill von webcomponentsjs zum Laufen zu bringen. Ich kann das Skript zum Ausführen bringen, und ich glaube, dass meine HTML-Importe eine xmlhttprequest ausführen, aber es scheint nicht so, als würden die Skripts in meinen Importen ausgeführt.

Möchtest du ein Beispiel @lastmjs teilen? Ich bin derzeit selbst knietief in Webkomponenten. Wenn ich Ihnen helfen kann, würde ich gerne mit Ihnen beitragen.

@snuggs Danke! Geben Sie mir ein oder zwei Tage Zeit, ich bin im Moment mitten in einigen dringenden Dingen.

@snuggs Wenn wir das Polyfill webcomponents-lite zum Laufen bringen können, sollten wir in der Lage sein, Polymer zu verwenden. Shadow DOM scheint das bisher schwierigste Polyfill zum Laufen zu bringen, und wenn wir webcomponents-lite verwenden, müssen wir uns darüber vorerst keine Sorgen machen, da wir Zugriff auf template haben , custom elements und HTML imports .

Ich kann HTML-Importe dazu bringen, mit dem Polyfill webcomponents-lite zu arbeiten. Ich bin auf ein seltsames Verhalten gestoßen, dann bin ich auf Folgendes gestoßen: https://github.com/Polymer/polymer/issues/1535 Es sieht so aus, als könnten HTML-Importe nur über ein cors-fähiges Nicht-Datei-Protokoll geladen werden. Also habe ich einen schnellen HTTP-Server im Stammverzeichnis meines Projekts eingerichtet:

npm install -g http-server
http-server --cors

Und hier ist der grundlegende Code, mit dem ich gearbeitet habe:

const jsdom = require('jsdom');

const doc = jsdom.jsdom(`
    <!DOCTYPE html>

    <html>
        <head>
            <script src="bower_components/webcomponentsjs/webcomponents-lite.js"></script>
            <link rel="import" href="http://localhost:8080/bower_components/polymer/polymer.html">
        </head>

        <body>
            <test-app></test-app>

            <dom-module id="test-app">
                <template>
                </template>

                <script>
                    setTimeout(() => {
                        class TestApp {
                            beforeRegister() {
                                this.is = 'test-app';
                                console.log('before register');
                            }

                            ready() {
                                console.log('ready');
                            }

                            created() {
                                console.log('created');
                            }

                            attached() {
                                console.log('attached');
                            }
                        }

                        Polymer(TestApp);
                    }, 1000);
                </script>
            </dom-module>
        </body>
    </html>
`, {
    virtualConsole: jsdom.createVirtualConsole().sendTo(console)
});

Aus irgendeinem Grund muss ich die Instanziierung TestApp in eine setTimeout . Es scheint, als würde der Polymer-HTML-Import das Rendern des restlichen HTML nicht blockieren, sodass der Polymer-Konstruktor ohne die setTimeout nicht definiert ist. Ist das normales Verhalten bei HTML-Importen?

beforeRegister wird aufgerufen, also tut der Polymer-Konstruktor etwas. Jetzt haben wir effektiv HTML-Importe, natürlich Vorlagen, die mit dem webcomponents-lite Polyfill arbeiten. Ich bin mir nicht sicher, wie die benutzerdefinierte Elemente Polyfill abschneidet.

Wenn ich in die TestApp -Klasse eine ready - oder created -Methode einfüge, werden sie nicht aufgerufen. Scheint, als würden die Lebenszyklusereignisse nicht richtig behandelt. Die Wurzel dieses Problems könnte in der Implementierung der benutzerdefinierten Elemente Polyfill liegen. Ich werde weiter herumspielen.

Mögliche zu lösende Probleme:

  • [ ] HTML-Importe werden nicht richtig blockiert
  • [ ] Benutzerdefiniertes Polyfill-Element funktioniert oder nicht?
  • [ ] Polymerlebenszyklusmethoden werden nicht aufgerufen

Mehr Basteln führt zu mehr Erkenntnissen. Ich denke, die Reihenfolge der Importe und Registrierungen könnte die Dinge für uns durcheinander bringen. Wenn ich const testApp = document.createElement('test-app'); ausführe, nachdem der Polymer-Konstruktor aufgerufen wurde, werden die Methoden created und ready aufgerufen, aber nicht die Methode attached . Vielleicht behandelt jsdom benutzerdefinierte Elementliterale nicht richtig? Auch beim Aufruf document.body.appendChild(testApp) wird die Lebenszyklusmethode attached nie aufgerufen.

Dies kann beim Verständnis der Ladereihenfolge hilfreich sein: https://github.com/webcomponents/webcomponentsjs#helper -utilities

@lastmjs Ich werfe derzeit Münzen zwischen CustomElementRegistry.define() und document.registerElement() . Ich habe gesehen, dass Domenic einige großartige Beiträge geleistet und einige Arbeiten in Bezug auf whatwg (https://github.com/whatwg/html/issues/1329) zusammengeführt hat. Es scheint, als ob eine API-Migration im Gange ist. Ich glaube zum Beispiel, dass die Spezifikation connectedCallback aufruft, was mit der attachedCallback -Funktionalität gepaart ist. Angenommen, Sie meinten attachedCallback , als Sie attached sagten, da dieser Handler nicht Teil der API ist. Ich habe erlebt, dass define() und registerElement() für jede Methode unterschiedliche Rückrufe ausgelöst haben. Ich habe die Strategie der benutzerdefinierten Elemente herausgefunden. HTMLImports Domenic erwähnte zuvor eine Implementierung, die einen XMLHTTPRequest-Patch verwendet. Ich glaube, ich kann direkt aus der Antwort direkt in DocumentFragment umwandeln. Das könnte bei den "Importen" nach Schlangenöl riechen. Ein "Faux"-Import kann sein, wo die Vernunft lebt.

Es scheint auch einige Fockereien zu geben, bei denen super() HTMLElement aufgerufen werden, also halten Sie sich dafür auf dem Laufenden. Ich habe dies mit Rollup.js/Babel erlebt und war gezwungen, das (leichte) Shim aus dem Paket webcomponents zu verwenden.
https://developers.google.com/web/fundamentals/getting-started/primers/customelements

Schließlich scheint es, dass ich (mehr) Erfolg habe, wenn ich mit einem Prototyp-Tag erstelle.

document.createElement('main', 'test-app')

Wie @domenic mir zuvor erwähnt hat, wollen wir vorsichtig sein, die Spezifikationen des kleinsten gemeinsamen Nenners zu implementieren und nicht nur das zu tun, was GOOGLE tut. Scheint, als würden die Grenzen mit Webkomponenten verwischt. Aber ich bin ein Fan.

Mit welchen Methoden haben Sie gearbeitet?

Bisher habe ich hauptsächlich nur mit den webcomponents-lite Polyfills und Polymer < 2.0 gespielt. Als ich also die Methode attached erwähnte, meinte ich die Polymer-Lebenszyklusmethode, die sie anstelle von attachedCallback verwenden. Soweit mir bekannt ist, wurden die Polyfills noch nicht auf die neue v1-Spezifikation für benutzerdefinierte Elemente umgestellt. Alles, womit ich spiele, ist also nur in der Hoffnung, Polymer dazu zu bringen, mit den aktuellen Polyfills zu arbeiten.

@snuggs Verwenden Sie gerade Polyfills oder arbeiten Sie an einer tatsächlichen Implementierung in jsdom?

@lastmjs Ich verwende keine Polyfills, da ich der Meinung bin, dass es nicht notwendig ist, 80% des Weges zu erreichen. Die Plattform ist jetzt ausgereift genug, um mit ein wenig Voraboptimierung einfach die nativen Konstrukte zu verwenden. Ich verwende gerne leichte (normalerweise handgerollte) Werkzeuge anstelle von Rahmen. Das heißt, das ist nicht die meisten Menschen. Anscheinend hat Domenic die Absicht, benutzerdefinierte Elemente 👍 HTML-Importe 👎 zu importieren, aber kein Problem damit, XMLHTTPRequest zu erweitern, um die fetching des Dokuments zu verarbeiten, was uns dorthin bringen würde. Das war vor etwa 6 Monaten. Seitdem hat sich bei der Umsetzung viel geändert. Sehr wahrscheinlich denken. Wo beenden wir also @lastmjs?

@snuggs Vielleicht ist es am vernünftigsten und zukunftssichersten, erstklassige Unterstützung für benutzerdefinierte Elemente und Shadow DOM in jsdom zu implementieren. Beide Standards befinden sich auf v1 und es scheint nach dem, was ich höre, wahrscheinlich zu sein, dass die Mehrheit der großen Browser sie implementieren werden. Wie sollen wir vorgehen? Ich habe im Moment nur begrenzt Zeit, aber vielleicht können wir zumindest darlegen, was getan werden muss. @domenic Haben Sie Vorschläge, wie wir mit diesen Implementierungen vorankommen können, oder Gründe, warum wir dies nicht tun sollten?

Keine konkreten Vorschläge von mir, außer nur die Spezifikation zu implementieren :)

Ich habe einen Zweig, in dem ich vor einiger Zeit daran gearbeitet habe (die Spezifikation hat sich seitdem etwas geändert). Die Implementierung von CustomElementsRegistry war einfach genug, wobei ich Schwierigkeiten hatte, herauszufinden, wie man benutzerdefinierte Elementreaktionen in die Codebasis einwebt und wann diese aufgerufen werden sollten und von wo. Wenn ich das wieder aufnehmen würde (keine Versprechungen), würde ich mich wahrscheinlich darauf konzentrieren.

@matthewp Das klingt hilfreich, wo finde ich diesen Zweig?

@matthewp ja das wäre schön

https://github.com/matthewp/jsdom/commits/custom-elements wie gesagt, die Spezifikation hat sich seitdem geändert, also ist sie veraltet. Und dies ist der einfachste Teil, aber es ist ein Ausgangspunkt, wenn es jemand möchte. @snuggs @lastmjs

(http://jonrimmer.github.io/are-we-componentized-yet/)

Persönlich wäre das einfache Unterstützen von Custom-Elementen schon großartig.

(Beachten Sie, dass meines Wissens nach phantomJS 2.5 mindestens Vorlagen und möglicherweise benutzerdefinierte Elemente unterstützen sollte, da sie die neuere Version von Webkit verwenden, nicht sicher, welche).

Eigentlich verspotte ich die customElements, indem ich das lib document-register-element verwende

const {before} = require('mocha')

before(mockDOM)
before(mockCustomElements)

function mockDOM() {
  const {JSDOM: Dom} = require('jsdom')
  const dom = new Dom('<!doctype html><html><body></body></html>')
  global.document = dom.window.document
  global.window = document.defaultView
  window.Object = Object
  window.Math = Math
}

function mockCustomElements() {
  require('document-register-element/pony')(window)
}

Super, hattest du irgendwelche Probleme?

bis jetzt nein :D

aber ich muss mehr Spezifikationen schreiben, mehr Dinge abdecken, um mich besser zu fühlen

Toll zu sehen, dass es einen Weg gibt. So sehr ich Polymer auch mag, das Test-Setu ist die Hölle und jsdom als Fallback zu haben, ist nett ;) Danke, dass Sie sich die Arbeit gemacht haben

Sieht so aus, als gäbe es eine PR, die dies vorantreibt! https://github.com/tmpvar/jsdom/pull/1872

Eigentlich verspotte ich die customElements, indem ich das lib document-register-element @darlanmendonca verwende

Lesen Sie diesen Link zum Anhängen von jsdom globals an node global. Es ist ein Anti-Pattern.

Hallo alle,
Ich bin etwas verwirrt über den Status der Ausführung von Polymer in JSDOM (mit Node.js 6.7.0 und JSDOM 11.1.0). Ich habe verschiedene Dinge ausprobiert, mit gemischten Ergebnissen. Ich wäre sehr dankbar, wenn mich hier jemand ausfüllen könnte...

Was ich bisher gemacht habe:

1) Ich habe einen HTTP-Server aus meinem Stammverzeichnis gestartet

./node_modules/http-server/bin/http-server --cors

2) Ich habe eine meiner Polymer-Komponenten in JSDOM geladen:

jsdom.JSDOM.fromURL("http://localhost:8080/path/to/my-component.html",
  { runScripts: "dangerously",
    resources: "usable"
  })
.then(function (dom) {
  setTimeout(() => {
    window = dom.window;
    component = window.document.querySelector("my-component");
  }, 10000);
})

(Ich habe auch versucht, die Komponentendatei aus dem Dateisystem zu laden, mit den gleichen Ergebnissen.)

3) Dies ist mein Komponentencode:

<!DOCTYPE html>
<html>
<head>
  <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
</head>

<body>
<link rel="import" href="/bower_components/polymer/polymer.html">
<dom-module id="order-app">
  <template>
    <h1>Hello Polymer</h1>
  </template>

  <script>
    console.log("javascript is being executed");
    addEventListener('WebComponentsReady', function () {
      console.log("web components are ready");
      Polymer({
        is: 'order-app'
      });
    });
  </script>
</dom-module>
</body>
</html>

(Ich habe den HTML-Kopf hinzugefügt, um das Polyfill der Webkomponenten zu laden.)

Was kann ich beobachten?

Wenn ich das starte, sehe ich

  • dass das Polyfill der Webkomponenten vom Webserver geladen wird
  • die Meldung "Javascript wird ausgeführt" in der Konsole

Was ich nicht sehe

  • dass die polymer.html-Komponente vom Webserver geladen wird
  • die Meldung "Webkomponenten sind bereit" in der Konsole

Dies lässt mich zu dem Schluss kommen, dass das WebComponentsReady-Event nicht ausgelöst wird (wahrscheinlich weil der HTML-Import nicht funktioniert?). Ebenfalls,
window.WebComponents enthält { flags: { log: {} } } – der Indikator ready fehlt.

Ich habe auch etwas Mocking und Polyfilling ausprobiert:

  window.Object = Object;
  window.Math = Math;
  require('document-register-element/pony')(window);

aber das schien nichts zu ändern.

Jetzt frage ich mich :-) Soll das überhaupt funktionieren? Wenn ja, warum funktioniert es bei mir nicht? Wenn nicht, was fehlt / ist erforderlich, damit es funktioniert?

Danke für jeden Einblick!

Moin

Das habe ich sogar versucht, mit noch weniger Erfolg, und ich habe es aufgegeben, abzuwarten, was das Ergebnis dieser Diskussion hier sein wird.

https://github.com/sebs/noframework/blob/master/test/configurator.js

keine Lösung, nur ein weiterer Fehlversuch. Übrigens die gleiche Verwirrung. Auch das gleiche Fazit

Das Polyfilling benutzerdefinierter Elemente in jsdom hat sich als sehr herausfordernd erwiesen. Kann jemand die Herausforderungen auflisten, dies in jsdom zu implementieren? Versuchen Sie, den Aufwand einzuschätzen, um das hinzubekommen.

Das grundlegende Hindernis besteht darin, dass jsdom Konstrukteure und ihre Prototypen teilt .

Dies macht es grundsätzlich unmöglich, eine Registrierung für benutzerdefinierte Elemente pro Fenster zu implementieren, da der HTMLElement -Konstruktor von allen Fenstern gemeinsam genutzt wird. Wenn Sie also den super() -Aufruf in Ihrem benutzerdefinierten Elementkonstruktor ausführen, weiß der jetzt laufende HTMLElement -Konstruktor nicht, in welchem ​​Fenster er nachsehen soll. Das ist scheiße.

Ich bin mir nicht sicher, ob es gute Zwischenlösungen gibt. Die große Waffe besteht darin, jsdom auf eine Architektur umzustellen, die nicht gemeinsam genutzte Konstruktoren/Prototypen zulässt. Wir könnten dies auf verschiedene Arten tun, alle mit unterschiedlichen Kompromissen. Vielleicht möchten wir ein spezielles Thema eröffnen, um es mit dem Team und der Community zu diskutieren, aber lassen Sie mich vorerst die auflisten, die mir spontan einfallen:

  • Verwenden Sie [WebIDL2JSFactory] für alles in jsdom oder zumindest HTMLElement und alle seine Nachkommen. Ich bin mir nicht sicher, ob [WebIDL2JSFactory] überhaupt noch gut mit Vererbung funktioniert, aber es könnte zum Laufen gebracht werden. Diese Alternative führt dazu, dass jeder die Kosten für zusätzliche Konstruktoren/Prototypen zahlt, aber vielleicht ist das besser, als benutzerdefinierte Elemente zu einem Opt-in-Feature zu machen.
  • Haben Sie eine Option, bei der jsdom alle Klassendefinitionsmodule innerhalb der vm -Sandbox ausführt. Haben Sie zB einen Build-Schritt, der alle Web-APIs in jsdom bündelt, dann, wenn Sie ein neues Fenster erstellen, machen wir vm.runScript() mit diesem Bündel innerhalb der neuen globalen Sandbox. Dies würde es uns wahrscheinlich ermöglichen, [WebIDL2JSFactory] loszuwerden.

Ich denke, eine andere Lösung wäre, benutzerdefinierte Elemente mit einer riesigen Warnung zu implementieren, dass die benutzerdefinierte Elementregistrierung pro Node.js-Prozess global ist. Das sieht aber schrecklich aus.


Nach dieser anfänglichen Hürde ist der Rest in Bezug auf die Einhaltung der Spezifikation relativ einfach. Der schwierigste Teil wird wahrscheinlich die Implementierung [CEReactions] und die Aktualisierung aller unserer IDL-Dateien sein, damit diese an den richtigen Stellen vorhanden sind, aber es ist nicht allzu schwierig.

Ich habe auch darüber nachgedacht, eine separate Prototyp-Version zu haben. Hier sind einige meiner Gedanken.

Ich bin mir nicht sicher, ob [WebIDL2JSFactory] überhaupt noch gut mit Vererbung funktioniert, aber es könnte zum Laufen gebracht werden.

Nein, das tut es nicht, und ich bin mir nicht sicher, wie genau es funktioniert. Die zweite Lösung ist meiner Meinung nach viel einfacher.

Haben Sie eine Option, bei der jsdom alle Klassendefinitionsmodule innerhalb der vm -Sandbox ausführt.

Das wäre mir lieber. Das Hauptproblem besteht darin, impl-Klassen während der Initialisierung in die vm -Sandbox zu übergeben, obwohl dies erreicht werden kann, indem man alles aus dem äußeren Kontext in eine globale Eigenschaft delete und diese globale Eigenschaft danach einfügt . Es würde auch die ordnungsgemäße Implementierung [NamedConstructor] und ein paar anderer erweiterter Attribute ermöglichen und vielleicht sogar einen V8-Start-Snapshot für eine jsdom-Umgebung generieren, wenn jemand mutig genug ist.

Das ganze [WebIDL2JSFactory] -Geschäft war in erster Linie ein Hack, und ich würde ihn gerne so schnell wie möglich loswerden.

+1-Kommentare sind für die Entwicklung dieser Funktion nicht hilfreich, daher lösche ich mindestens einen der letzten Kommentare.

Hallo, ich wusste nicht, dass @TimothyGu daran arbeitet.
Ich arbeite tatsächlich an der Registrierung und Erstellung benutzerdefinierter Elemente
https://github.com/mraerino/jsdom/tree/custom-elements-spec

Ich versuche, so minimalinvasiv wie möglich zu sein und so nah wie möglich an der Spezifikation zu bleiben.
Die Tests der Webplattform Custom Element Registry bestehen.

Als ich letzte Nacht daran gehackt habe, habe ich eine Lösung gefunden, die funktioniert, ohne webIdl2JS zu ändern.
Siehe hier: https://github.com/mraerino/jsdom/commit/592ad1236e9ca8f63f789d48e1887003305bc618

@TimothyGu , wärst du bereit, hier deine Kräfte zu bündeln?

Nur ein paar Updates hier:
Ich bin ziemlich zuversichtlich, was meine Implementierung der Spezifikation betrifft, stecke aber derzeit wegen des erweiterten IDL-Attributs von [HTMLConstructor] fest. Deshalb habe ich https://github.com/jsdom/webidl2js/issues/87 geöffnet

In der Zwischenzeit werde ich den [HTMLConstructor] -Algorithmus mit einem [Constructor] -Attribut implementieren, um später einfach wechseln zu können. (Ich habe es ursprünglich implementiert, indem ich eine Schein-HTMLElement-Klasse in window eingefügt habe, aber das schien nicht richtig zu sein.)

Ja, wie in https://github.com/tmpvar/jsdom/issues/1030#issuecomment -333994158 erwähnt, erfordert die korrekte Implementierung von HTMLConstructor grundlegende Änderungen an der Architektur von jsdom.

Haben Sie Informationen darüber, wie viele der Webplattformtests Ihre Version besteht?

Vorerst nur die von customElementRegistry, und ich könnte mich in Bezug auf meinen Fortschritt völlig irren.

Edit: Ok, nachdem ich deinen Kommentar noch einmal gelesen habe, habe ich verstanden, was du meinst. Ich werde es mit meiner Implementierung versuchen, aber @TimothyGu scheint auch an der Trennung zu arbeiten.

Ich verwende Polymer, also bin ich :+1: auf dieser Anforderungsfunktion

@dman777 @mraerino Gleiches gilt für slim.js-Entwickler. Slim verwendet die native Webkomponenten-API und kann HTMLElement nicht ohne Hacks auf jsdom erben.

Seit der Eröffnung dieser Ausgabe sind drei Jahre vergangen. Kann jemand sagen, wann ungefähr jsdom benutzerdefinierte Elemente unterstützen wird?

TL;DR

Ich habe in den letzten 2 Wochen daran gearbeitet, die Machbarkeit zu bewerten, Unterstützung für benutzerdefinierte Elemente in jsdom hinzuzufügen. Hier das Ergebnis der Untersuchung.

Eine spezifikationskonforme Implementierung des benutzerdefinierten Elements finden Sie hier: https://github.com/jsdom/jsdom/compare/master...pmdartus :custom-elements?expand=1. Hier und da gibt es noch einige Ecken und Kanten, aber zumindest die meisten WPT-Tests bestehen . Die verbleibenden fehlgeschlagenen Tests sind entweder bekannte JSDOM-Probleme oder kleinere Probleme, die angegangen werden können, wenn wir uns mit der eigentlichen Implementierung in jsdom befassen.

Nun, hier ist die gute Nachricht: Jetzt, da Shadow DOM nativ unterstützt wird, konnte ich sowohl mit dem benutzerdefinierten Elementzweig als auch mit dem Mutationsbeobachter die neueste Version des Polymer 3-Hello-World-Anwendungsbeispiels in jsdom laden und rendern 🎉. In seinem aktuellen Zustand ist der Zweig nicht in der Lage, eine Stencil-Anwendung zu laden (der Stencil-Entwicklungsmodus erfordert einige nicht unterstützte Funktionen wie module , und der Prod-Modus wirft aus einem unbekannten Grund).

Aktionsplan

Hier ist eine Liste der Änderungen, die zuerst vorgenommen werden müssen, bevor Sie mit der eigentlichen Implementierung der benutzerdefinierten Elementspezifikation beginnen. Jeder Punkt in der Liste ist unabhängig und kann parallel angegangen werden.

Unterstützung für erweiterte [CEReactions] IDL-Attribute

Einer der Kernfunktionen, die in jsdom fehlen, um Unterstützung für benutzerdefinierte Elemente hinzuzufügen, sind die [CEReactions] -Attribute. Ich konnte dieses Problem teilweise umgehen, indem ich die richtigen Prototyp-Eigenschaften gepatcht habe. Dieser Ansatz funktioniert, solange der benutzerdefinierte Elementreaktionsstapel global ist und nicht pro Einheit verwandter Browsing-Kontexte ähnlichen Ursprungs, da alle Schnittstellenprototypen gemeinsam genutzt werden.

Dieser Ansatz hat einige Mängel, da einige Schnittstellen die [CEReactions] -Attribute haben, die mit indizierten Eigenschaften verknüpft sind ( HTMLOptionsCollection , DOMStringMap ). Intern verwendet jsdom Proxys, um Mutationen zu diesen Eigenschaften zu verfolgen. Das Prototyp-Patching der Schnittstelle funktioniert in diesem Fall nicht. Ein anderer Ansatz, um dieses Problem zu umgehen, wäre, die Implementierung anstelle der Schnittstelle (nicht implementiert) zu patchen.

Ich bin mit dem internen webidl2js nicht vertraut genug, aber wir sollten untersuchen, wie man einen globalen Hook für [CEReactions] hinzufügt.

Änderungen:

Unterstützung für erweiterte [HTMLConstructor] IDL-Attribute

Wie @domenic oben erklärt hat, ist das Hinzufügen von Unterstützung für [HTMLConstructor] hier einer der Hauptblocker.

Ich konnte dieses Problem hier umgehen, indem ich den Schnittstellenkonstruktor für jeden Browsing-Kontext gepatcht habe. Der Schnittstellenkonstruktor wäre in der Lage, auf das rechte Fenster und das Dokumentobjekt zuzugreifen, während er den gemeinsam genutzten Prototyp behält. Dieser Ansatz vermeidet auch den Leistungs-Overhead der Neubewertung des Schnittstellen-Prototyps für jeden neuen Browsing-Kontext.

Ich bin mir nicht sicher, ob es hier der beste Ansatz ist, aber es entspricht den Anforderungen, ohne zusätzlichen Leistungsaufwand einzuführen.

Änderungen:

Machen Sie die Spezifikation des Make-Fragment-Parsing-Algorithmus konform (#2522)

Wie hier besprochen, ist die in Element.innerHTML und Element.outerHTML verwendete HTML-Fragment-Parsing-Algo-Implementierung falsch. Der Parsing-Algorithmus muss umgestaltet werden, damit die Callbacks der benutzerdefinierten Elementreaktionen ordnungsgemäß aufgerufen werden.

Änderungen:

Verbesserte Schnittstellensuche für den Algorithmus zum Erstellen von Elementen

Eines der Probleme, auf das ich schnell gestoßen bin, war die Einführung neuer zirkulärer Abhängigkeiten beim Hinzufügen von Unterstützung für die Erstellung benutzerdefinierter Elemente. Sowohl CustomElementRegistry als auch der Algorithmus zum Erstellen von Elementen erfordern Zugriff auf die Elementschnittstellen, wodurch ein Alptraum von kreisförmigen Abhängigkeiten entsteht.

Der in der Verzweigung verfolgte Ansatz bestand darin, ein InterfaceCache zu erstellen, das eine Schnittstellensuche nach Elementnamensraum und -name, aber auch nach Schnittstellenname ermöglichen würde. Die Schnittstellenmodule werden träge ausgewertet und nach der Auswertung zwischengespeichert. Dieser Ansatz beseitigt die zirkulären Abhängigkeiten, da die Schnittstellen auf der obersten Ebene nicht mehr benötigt werden.

Dies ist ein Ansatz, um dieses seit langem bestehende Problem in jsdom zu lösen. Eines dieser Probleme bei diesem Ansatz besteht darin, dass möglicherweise die webgepackte / browserbasierte Version von jsdom beschädigt wird (nicht getestet).

Änderungen:

~Fix Element.isConnected zur Unterstützung von Shadow DOM (https://github.com/jsdom/jsdom/pull/2424)~

Dies ist ein Problem, das sich mit der Einführung des Schatten -DOMs eingeschlichen hat: isConnected gibt false zurück, wenn das Element Teil eines Schattenbaums ist. Hier muss ein neuer WPT-Test hinzugefügt werden, da kein Test dieses Verhalten überprüft.

Änderungen:

Knotendokumente HTMLTemplateElement.templateContents reparieren (#2426)

Der in der Spezifikation definierte Vorlageninhalt hat ein anderes Knotendokument als das HTMLTemplateElement selbst. jsdom implementiert dieses Verhalten heute nicht und das HTMLTemplateElement und seine Vorlageninhalte haben denselben Inhalt
Dokumentenknoten.

Änderungen:

  • HTMLTemplateElement-impl.js
  • htmltodom.js . Diese Änderung hat auch nachgelagerte Auswirkungen auf den Parser. Wenn das Kontextelement ein HTMLTemplateElement ist, sollte der Analysealgorithmus für HTML-Fragmente den Dokumentknoten aus dem Vorlageninhalt und nicht aus dem Element selbst entnehmen.

Fehlende Adoptionsschritte zu HTMLTemplateElement hinzufügen (#2426)

Das HTMLTemplateElement muss einige spezifische Schritte ausführen, wenn es in ein anderes Dokument übernommen wird. Soweit ich weiß, hat die Bodenschnittstelle einen speziellen Adoptionsschritt. Die Implementierung des Knotenübernahmealgorithmus müsste ebenfalls aktualisiert werden, um diesen Übernahmeschritt aufzurufen.

Änderungen:

Unterstützung für die isValue-Suche im parse5-Serialisierungsprogramm hinzugefügt

Die serialisierenden HTML-Fragmente suchen beim Serialisieren eines Elements den mit dem Element verknüpften Wert und spiegeln ihn als Attribut im serialisierten Inhalt wider. Es wäre interessant, einen weiteren Hook im parse5-Baumadapter hinzuzufügen, der den mit einem Element getIsValue(element: Element): void | string verknüpften is -Wert nachschlagen würde.

Ein alternativer Ansatz (nicht implementiert) wäre, das Verhalten des aktuellen getAttrList -Hooks zu ändern, um den is-Wert an die Attributliste zurückzugeben, wenn das Element einen zugeordneten is-Wert hat.

Leistung

Bevor ich eine Performance-Optimierung durchführe, wollte ich auch die Performance der Änderungen in der Branche überprüfen. Das Hinzufügen von benutzerdefinierten Elementen fügt einen Leistungsmehraufwand von 10 % im Vergleich zum aktuellen Ergebnis auf dem Master für Baummutations-Benchmarks hinzu. Allerdings ist die Erstellung einer neuen JSDOM-Umgebung jetzt 3x bis 6x langsamer im Vergleich zum Master, es würde eine tiefere Untersuchung erfordern, um die Grundursache zu identifizieren.

Näheres: hier

@pmdartus das ist sehr vielversprechend, hervorragende Arbeit! Ich habe meinen Hack-Zweig jsdom-wc verwendet, da es keine bessere Option gibt. Ich sehe ein seltsames Verhalten und hatte gehofft, gegen Ihren Zweig auszutauschen, aber ich habe Probleme.

Ich registriere benutzerdefinierte Elemente wie:

class Component extends HTMLElement {

}

customElements.define('custom-component', Component);

Aber wenn ich es mache:

const el = assign(this.fixture, {
  innerHTML: `
    <custom-component></custom-component>
  `,
});

Ich bekomme sofort: Error: Uncaught [TypeError: Illegal constructor] .

Irgendwelche Gedanken dazu?

Das folgende Code-Snippet läuft ordnungsgemäß auf dem custom-elements -Zweig auf meinem Fork: https://github.com/pmdartus/jsdom/tree/custom-elements

const { JSDOM } = require("jsdom");

const dom = new JSDOM(`
<body>
  <div id="container"></div>
  <script>
    class Component extends HTMLElement {
        connectedCallback() {
            this.attachShadow({ mode: "open" });
            this.shadowRoot.innerHTML = "<p>Hello world</p>";
        }
    }
    customElements.define('custom-component', Component);

    const container = document.querySelector("#container");

    Object.assign(container, {
        innerHTML: "<custom-component></custom-component>"
    })

    console.log(container.innerHTML); // <custom-component></custom-component>
    console.log(container.firstChild.shadowRoot.innerHTML); // <p>Hello world</p>
  </script>
</body>
`, { 
    runScripts: "dangerously" 
});

Der illegale Konstruktor wird wahrscheinlich vom ursprünglichen HTMLElement-Konstruktor geworfen, die am Zweig vorgenommenen Änderungen sollten den Konstruktor für jedes neue Fensterobjekt patchen. @tbranyen Haben Sie ein vollständiges Reproduktionsbeispiel, damit ich es lokal ausprobieren kann?

Hallo @pmdartus, ich bin mir noch nicht sicher, was meine Probleme verursacht, aber ich habe einen isolierten Code direkt in Ihren Zweig geschrieben, der perfekt funktioniert hat:

const { JSDOM } = require('.');
const window = (new JSDOM()).window;
const { HTMLElement, customElements, document } = window;

class CustomElement extends HTMLElement {
  constructor() {
    super();

    console.log('constructed');
  }

  connectedCallback() {
    console.log('connected');
  }
}

customElements.define('custom-element', CustomElement);
document.body.appendChild(new CustomElement());
//constructed
//connected

{
  const window = (new JSDOM()).window;
  const { HTMLElement, customElements, document } = window;

  class CustomElement extends HTMLElement {
    constructor() {
      super();

      console.log('Constructed');
    }

    connectedCallback() {
      console.log('Connected');
    }
  }

  customElements.define('custom-element', CustomElement);
  document.body.appendChild(new CustomElement());
  //constructed
  //connected
}

Dies ist effektiv das, was mein Testsystem tut, aber bricht. Es kann also etwas an meiner Seite sein.

Bearbeiten:

Ah okay, ich glaube, ich habe eingegrenzt, wo das Problem am wahrscheinlichsten auftritt. Ich muss mich an den ursprünglich erstellten HTMLElement -Konstruktor halten. Wenn ich den obigen Code anpasse, um den Konstruktor wiederzuverwenden:

  // Inside the block, second component, reuse the HTMLElement.
  const { customElements, document } = window;

Dies wird Folgendes erzeugen:

connected
/home/tbranyen/git/pmdartus/jsdom/lib/jsdom/living/helpers/create-element.js:643
        throw new TypeError("Illegal constructor");

Bearbeiten 2:

Fand es:

  // Don't reuse the previously defined Element...
  global.HTMLElement = global.HTMLElement || jsdom.window.HTMLElement;

Beachten Sie, dass dieser Thread 4 Jahre alt ist. Werden Webkomponenten unterstützt oder ist dies geplant?

Es wäre schön, Webkomponenten darin zu haben, aber als Alternative, wenn jemand wissen möchte .... Headless Chrome kann jetzt im Knoten verwendet werden, um die HTML-Sting-Datei zu rendern / zu erstellen.

Beachten Sie, dass dieser Thread 4 Jahre alt ist. Werden Webkomponenten unterstützt oder ist dies geplant?

Es ist noch in Arbeit, da die Spezifikation Stück für Stück implementiert wird.

Das Polyfill unter: https://github.com/WebReflection/document-register-element Funktioniert wie ein Zauber! Mein herzlichster Dank gilt der Autorin!

Für diejenigen, die mit dem gleichen Problem kämpfen, tun Sie einfach:

npm install -D document-register-element

Legen Sie in Ihrer Jest-Konfiguration eine Setup-Datei fest, die vor allen Ihren Tests ausgeführt wird:

{ "setupFilesAfterEnv": [ "./tests/setup.js" ] }

Und schließlich in dieser Datei ('tests/setup.js'):

import 'document-register-element'

Danach funktioniert das Registrieren und Erstellen benutzerdefinierter Elemente in jsdom über document.createElement('custom-component') perfekt! Fragmente scheinen jedoch nicht zu funktionieren. (Ich benutze übrigens kein Schattendom).

@tebanep wie Sie erwähnt haben, dass Polyfill für die meisten Arbeiten mit Webkomponenten ungeeignet ist, wenn es Shadow DOM nicht unterstützt, ist es nicht wirklich ein Vergleich zu dem, was dies erreicht.

@tebanep Danke. Da ich keinen Schattendom brauche, ist dies eine großartige Info

Gibt es Hoffnung, dass dies umgesetzt wird? Im Moment verwenden wir jsdom-wc mit vielen Fehlern, haben aber keine bessere Lösung. Meine Hoffnung und mein Gebet zu diesem Thema.

@dknight Ich weiß, dass jsdom-wc so ziemlich ein Hack ist, um es irgendwie zum Laufen zu bringen. Ich habe das Modul mit deutlich besserer Kompatibilität unter meinem persönlichen npm-Scope veröffentlicht. Sie können es installieren mit:

npm install @tbranyen/[email protected] --save-dev

Ich verwende dies jetzt für alle meine JSDOM-Webkomponenten, bis Stable landet.

@tbranyen Hast du die Veröffentlichung deines Forks aufgehoben? Ich kann es nicht auf npm finden.

@KingHenne dangit, sieht so aus, als wäre es in unserer "Enterprise" -Registrierung gelandet. Ich habe gerade öffentlich npm veröffentlicht. Das tut mir leid!

Don't @ me, aber sollten wir nicht einfach Web-UI-Code in einem echten Browser testen, zB mit Puppeteer. Das Problem mit der Unterstützung von Shadow DOM/benutzerdefinierten Elementen wird dann behoben.

Posten Sie keinen Kommentar, wenn Sie kein @Georgegriff sein möchten. Das ist eine gültige Strategie, aber auf andere Weise ist sie langsam und fehlerhaft, da Sie effektiv IPC machen, ja sogar mit Puppenspieler. Wenn der Browser stirbt, ist es in vielen Fällen nicht offensichtlich, warum. Versuchen Sie einfach, Puppenspieler-Probleme im Scherz zu debuggen, um einen Vorgeschmack darauf zu bekommen, warum dies nicht immer die beste Idee ist.

Persönlich würde ich lieber synchron und im selben Thread testen. Es gibt keinen Grund, warum eine isolierte Implementierung der Spezifikation keine vernünftige Laufzeit zum Testen von Komponenten sein sollte. JSDOM ist zu diesem Zeitpunkt effektiv ein Browser, nur nicht so stabil wie die großen Drei.

Das Polyfill unter: https://github.com/WebReflection/document-register-element Funktioniert wie ein Zauber! Mein herzlichster Dank gilt der Autorin!

Für diejenigen, die mit dem gleichen Problem kämpfen, tun Sie einfach:

npm install -D document-register-element

Legen Sie in Ihrer Jest-Konfiguration eine Setup-Datei fest, die vor allen Ihren Tests ausgeführt wird:

{ "setupFilesAfterEnv": [ "./tests/setup.js" ] }

Und schließlich in dieser Datei ('tests/setup.js'):

import 'document-register-element'

Danach funktioniert das Registrieren und Erstellen benutzerdefinierter Elemente in jsdom über document.createElement('custom-component') perfekt! Fragmente scheinen jedoch nicht zu funktionieren. (Ich benutze übrigens kein Schattendom).

Es funktioniert gut für mich, aber das connectedCallback wird nie aufgerufen, hast du eine Idee?

@FaBeyyy das gleiche für mich :(

@FaBeyyy @majo44 Sie müssen Ihre Komponente an ein Dokument anhängen, dh. document.body.appendChild(...) für connectedCallback gefeuert zu werden. Gemäß den Spezifikationen wird es aufgerufen, wenn eine Komponente an einen Dom angehängt wird.

JSDOM ist zu diesem Zeitpunkt effektiv ein Browser, nur nicht so stabil wie die großen Drei.

An diesem Punkt ist es eher wie die großen Zwei, weil Microsoft ihre fallen lässt, die sie schon so lange wie Windows haben.

@FaBeyyy @majo44 Sie müssen Ihre Komponente an ein Dokument anhängen, dh. document.body.appendChild(...) für connectedCallback gefeuert zu werden. Gemäß den Spezifikationen wird es aufgerufen, wenn eine Komponente an einen Dom angehängt wird.

danke Kapitän offensichtlich, aber das ist hier natürlich nicht das Thema. Wenn ich nicht wüsste, wie der Komponentenlebenszyklus funktioniert, würde ich wahrscheinlich nicht versuchen, Tests zu schreiben 😄. Werde später ein Repo-Showcase erstellen, wenn ich die Zeit finde.

@FaBeyyy
Also habe ich das Setup gefunden, das für mich funktioniert. Ich musste Polyfill für MutationObserver hinzufügen. Ich verwende JSDOM zum Testen von Schweinswalen mit Jest, und das funktionierende Setup ist:

// package.json
{  ...
  "jest": {
    "transform": {
      "^.+\\.(mjs|jsx|js)$": "babel-jest"
    },
    "setupFiles": [
      "<rootDir>/node_modules/babel-polyfill/dist/polyfill.js",
      "<rootDir>/node_modules/mutationobserver-shim/dist/mutationobserver.min.js",
      "<rootDir>/node_modules/document-register-element/build/document-register-element.node.js"
    ]
  }
... 
}
//.bablerc
{
    "presets": [
        ["@babel/preset-env", { "modules": "commonjs"}]
    ]
}

@FaBeyyy
Also habe ich das Setup gefunden, das für mich funktioniert. Ich musste Polyfill für MutationObserver hinzufügen. Ich verwende JSDOM zum Testen von Schweinswalen mit Jest, und das funktionierende Setup ist:

// package.json
{  ...
  "jest": {
    "transform": {
      "^.+\\.(mjs|jsx|js)$": "babel-jest"
    },
    "setupFiles": [
      "<rootDir>/node_modules/babel-polyfill/dist/polyfill.js",
      "<rootDir>/node_modules/mutationobserver-shim/dist/mutationobserver.min.js",
      "<rootDir>/node_modules/document-register-element/build/document-register-element.node.js"
    ]
  }
... 
}

//.bablerc { "presets": [ ["@babel/preset-env", { "modules": "commonjs"}] ] }

Nett, danke!

@ majo44 dies ist mit der neuesten jsdom nicht erforderlich. Wenn Sie mit Jest arbeiten (das jsdom v11 verwendet), können Sie einfach die aktualisierte Umgebung verwenden: https://www.npmjs.com/package/jest-environment-jsdom-fourteen

@mgibas danke, mit jest-environment-jsdom-fourteen funktioniert es auch gut und Mutation Observer Polyfill ist nicht erforderlich (aber Version ist 0.1.0, Single-Commit-Paket :))

Gibt es eine Aufschlüsselung, welche der Webkomponenten-APIs derzeit von JSDOM unterstützt werden? Scheint, als ob Schatten-DOM unterstützt wird, aber keine benutzerdefinierten Elemente (zumindest im Release-Zweig/Repo)?

npm install @tbranyen/[email protected] --save-dev

@tbranyen hast du den Quellcode für deinen Fork irgendwo verfügbar? Wäre gespannt auf das Diff 🙂

Ich verwende jest-environment-jsdom-fifteen wie von @majo44 vorgeschlagen und das babel-polyfill- und document-register-element (siehe @mgibas Antworten). Aber ich erhalte immer noch eine Fehlermeldung, wenn ich versuche, meine Webkomponenten-Schattendom zu Testzwecken abzurufen.

el.shadowRoot ist null mit:

const el;
beforeEach(async() => {
  const tag= 'my-component'
  const myEl = document.createElement(tag);
  document.body.appendChild(myEl);
  await customElements.whenDefined(tag);
  await new Promise(resolve => requestAnimationFrame(() => resolve()));
  el = document.querySelector(tag);
}

it(() => {
  const fooContent = el.shadowRoot.querySelectorAll('slot[name=foo] > *');
})

Irgendeine Idee für einen Workaround? FYI, es wurde bereits mit Karma, Mocha, Chai & Jasmine getestet.

Nichts Besonderes in meiner Komponente:

customElements.define(
  'my-component',
  class extends HTMLElement {
    constructor() {
      super();

      const shadowRoot = this.attachShadow({ mode: 'open' });
      ...
  }
})

Bearbeiten

Ich habe ein Debugging mit jsdom 15.1.1 durchgeführt, um mein Problem besser zu verstehen.
Trotzdem verstehe ich nicht, warum es hier null ist ...

Element.shadowRoot ist also seit 88e72ef implementiert
https://github.com/jsdom/jsdom/blob/1951a19d8d40bc196cfda62a8dafa76ddda6a0d2/lib/jsdom/living/nodes/Element-impl.js#L388 -L415

Nach document.createElement ist this._shadowDom unter https://github.com/jsdom/jsdom/blob/15.1.1/lib/jsdom/living/nodes/Element-impl.js#L403 in Ordnung. Und jedes Element im Schattendom wird erstellt (Elementkonstruktor mit den richtigen Werten aufgerufen).

Aber wenn ich el.shadowDom unmittelbar nach document.body.appendChild(el) (https://github.com/jsdom/jsdom/blob/15.1.1/lib/jsdom/living/nodes/Element-impl .js#L408), this. _shadowRoot ist null!

Danach dasselbe

 await customElements.whenDefined(tag);
 await new Promise(resolve => requestAnimationFrame(() => resolve()));

Oder auch wenn ich statt document folgendes verwende.

document.body.innerHTML = `
  <my-component id="fixture"></my-component>
`:

Zur Wiedergabe siehe:
https://github.com/noelmace/devcards/tree/jest

@nminhnguyen Ich denke, Sie finden den Quellcode des von @tbranyan erstellten Forks hier https://github.com/tbranyen/jsdom/tree/initial-custom-elements-impl

Ich versuche, Webkomponenten zu testen, die mit lit-html und lit-element erstellt wurden, und ich habe diesen Unterschied beim Erstellen der Elemente bemerkt.

const myElem = new MyElem();

document.body.appendChild(myElem);
await myElem.updateComplete;

console.log(myElem.shadowRoot) // exists and has the rendered markup

und wenn ich das document.createElement verwende

const myElem = document.createElement('my-elem');

document.body.appendChild(myElem);
await myElem.updateComplete;

console.log(myElem.shadowRoot) // null

Für die Konfiguration von Scherz verwende ich nur eine Polyfill, nämlich: setupFiles: ['document-register-element']

Scheint, dass die Render-Methode in myElem nie aufgerufen wird. Beim weiteren Debuggen habe ich festgestellt, dass die Methode initialize , die sich in lit-element befindet, nie aufgerufen wird.
Das 2. Beispiel würde also funktionieren, wenn ich es tue

const myElem = document.createElement('my-elem');
myElem.initialize();

document.body.appendChild(myElem);
await myElem.updateComplete;

console.log(myElem.shadowRoot) //  exists and has the rendered markup

Ich habe ein alternatives DOM erstellt, das Webkomponenten unterstützt. Ich habe zuerst versucht, eine PR zu machen, aber die Art und Weise, wie JSDOM funktioniert, hat es mir schwer gemacht, meine Bedürfnisse dort zu lösen. Es steht Ihnen frei, es zu verwenden oder sich den Code anzusehen.

DOM:
https://www.npmjs.com/package/happy-dom

Scherz Umgebung:
https://www.npmjs.com/package/jest-environment-happy-dom

Sieht toll aus. Danke.

Am Mo, 7. Oktober 2019, 1:08 Uhr schrieb capricorn86 [email protected] :

Ich habe ein alternatives DOM erstellt, das Webkomponenten unterstützt. ich zuerst
versucht, eine PR zu erstellen, aber die Art und Weise, wie JSDOM funktioniert, hat es mir schwer gemacht, meine zu lösen
braucht da. Es steht Ihnen frei, es zu verwenden und/oder sich den Code anzusehen.

DOM:
https://www.npmjs.com/package/happy-dom

Scherz Umgebung:
https://www.npmjs.com/package/jest-environment-happy-dom


Sie erhalten dies, weil Sie diesen Thread abonniert haben.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/jsdom/jsdom/issues/1030?email_source=notifications&email_token=ACQ5ZD5QUEITPND4SXWOHW3QNILSRA5CNFSM4A4G5SF2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEAOO5ZA#issuecomment-538,7670
oder den Thread stumm schalten
https://github.com/notifications/unsubscribe-auth/ACQ5ZDYU465DXI4KHBQH4KTQNILSRANCNFSM4A4G5SFQ
.

@ Steinbock86
Ihre Arbeit macht meine Testumgebung einfach, danke!
https://github.com/EasyWebApp/WebCell/tree/v2/MobX

@ Steinbock86
Ihre Arbeit macht meine Testumgebung einfach, danke!
https://github.com/EasyWebApp/WebCell/tree/v2/MobX

Danke @TechQuery!

Sieht toll aus. Danke.

Am Mo, 7. Okt. 2019, 01:08 Uhr capricorn86 @ . * > schrieb: Ich habe ein alternatives DOM erstellt, das Webkomponenten unterstützt. Ich habe zuerst versucht, eine PR zu machen, aber die Art und Weise, wie JSDOM funktioniert, hat es mir schwer gemacht, meine Bedürfnisse dort zu lösen. Es steht Ihnen frei, es zu verwenden und/oder sich den Code anzusehen. DOM: https://www.npmjs.com/package/happy-dom Jest-Umgebung: https://www.npmjs.com/package/jest-environment-happy-dom – Sie erhalten dies, weil Sie es abonniert haben Gewinde. Antworten Sie auf diese E - Mail direkt, sehen sie auf GitHub <# 1030? Email_source = Benachrichtigungen & email_token = ACQ5ZD5QUEITPND4SXWOHW3QNILSRA5CNFSM4A4G5SF2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEAOO5ZA # issuecomment-538767076>, oder schalten Sie den Faden https://github.com/notifications/unsubscribe-auth/ACQ5ZDYU465DXI4KHBQH4KTQNILSRANCNFSM4A4G5SFQ .

Danke @motss!

Gibt es eine Aufschlüsselung, welche der Webkomponenten-APIs derzeit von JSDOM unterstützt werden? Scheint, als ob Schatten-DOM unterstützt wird, aber keine benutzerdefinierten Elemente (zumindest im Release-Zweig/Repo)?

Das würde mich auch interessieren :)

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen