Jsdom: Prise en charge de l'API WebComponents

Créé le 17 févr. 2015  ·  89Commentaires  ·  Source: jsdom/jsdom

Nous essayons d'utiliser jsdom pour nos tests unitaires pour React core (http://facebook.github.io/react/).

Malheureusement, la spécification des composants Web n'est pas prise en charge de manière native par jsdom, et le polyfill webcomponents.js ne s'exécute pas sur jsdom. Ce problème nécessite l'ajout de la prise en charge de WebComponent (éléments personnalisés, shadow dom, importations html, etc.).

feature

Commentaire le plus utile

TL;DR

J'ai travaillé au cours des 2 dernières semaines sur l'évaluation de la faisabilité d'ajouter la prise en charge d'un élément personnalisé dans jsdom. Voici le résultat de l'enquête.

Vous pouvez trouver une implémentation conforme aux spécifications de l'élément personnalisé ici : https://github.com/jsdom/jsdom/compare/master...pmdartus :custom-elements?expand=1. Il y a encore quelques aspérités ici et là, mais au moins la plupart des tests WPT réussissent . Les tests restants qui échouent sont soit des problèmes connus de JSDOM, soit des problèmes mineurs qui peuvent être résolus lorsque nous aborderons l'implémentation réelle dans jsdom.

Maintenant, voici la bonne nouvelle, maintenant que Shadow DOM est supporté nativement, avec à la fois la branche d'éléments personnalisés et l'observateur de mutation, j'ai pu charger et rendre la dernière version de l'exemple d' application Polymer 3 hello world dans jsdom 🎉. Dans son état actuel, la branche n'est pas en mesure de charger une application Stencil (le mode de développement Stencil nécessite certaines fonctionnalités non prises en charge comme module , et le mode prod est lancé pour une raison inconnue).

Plan d'action

Voici une liste des modifications qui doivent être apportées avant de commencer à s'attaquer à l'implémentation réelle de la spécification d'élément personnalisé. Chaque élément de la liste est indépendant et peut être traité en parallèle.

Prise en charge des attributs étendus IDL [CEReactions]

L'un des noyaux fonctionnels manquants dans jsdom pour ajouter la prise en charge des éléments personnalisés est les attributs [CEReactions] . J'ai été en partie capable de contourner ce problème en corrigeant les bonnes propriétés du prototype. Cette approche fonctionne tant que la pile de réaction d'éléments personnalisés est globale et non par unité de contextes de navigation d'origine similaire liés puisque tous les prototypes d'interfaces sont partagés.

Cette approche présente quelques lacunes puisque certaines interfaces ont les attributs [CEReactions] associés aux propriétés indexées ( HTMLOptionsCollection , DOMStringMap ). En interne, jsdom utilise des proxys pour suivre la mutation de ces propriétés. Le patch prototype de l'interface ne fonctionne pas dans ce cas. Une autre approche, pour contourner ce problème, serait de patcher l'implémentation au lieu de l'interface (non implémentée).

Je ne suis pas assez familier avec le webidl2js interne, mais nous devrions explorer l'ajout d'un crochet global pour [CEReactions] ?

Changements:

Prise en charge des attributs étendus IDL [HTMLConstructor]

Comme @domenic l'a expliqué ci- dessus , l'ajout de la prise en charge de [HTMLConstructor] est l'un des principaux bloqueurs ici.

J'ai pu contourner ce problème ici en corrigeant le constructeur d'interface pour chaque contexte de navigation. Le constructeur de l'interface serait en mesure d'accéder à la bonne fenêtre et à l'objet document tout en conservant le prototype partagé. Cette approche évite également la surcharge de performances liée à la réévaluation du prototype d'interface pour chaque nouveau contexte de navigation.

Je ne sais pas si c'est la meilleure approche ici, mais elle répond aux exigences sans introduire de surcharge de performances supplémentaire.

Changements:

Rendre l'algorithme de make fragment parsing conforme à la spécification (#2522)

Comme indiqué ici , l'implémentation de l'algo d'analyse de fragment HTML utilisée dans Element.innerHTML et Element.outerHTML est incorrecte. L'algo d'analyse doit être refactorisé, pour que les rappels de réactions d'éléments personnalisés soient invoqués correctement.

Changements:

Améliorer la recherche d'interface pour créer un algorithme d'élément

L'un des problèmes sur lesquels je suis rapidement tombé était l'introduction de nouvelles dépendances circulaires lors de l'ajout de la prise en charge de la création d'éléments personnalisés. CustomElementRegistry et l'algorithme de création d'élément nécessitent tous deux l'accès aux interfaces Element, ce qui crée un cauchemar de dépendances circulaires.

L'approche adoptée dans la branche était de créer un InterfaceCache , qui permettrait la recherche d'interface par espace de noms et nom d'élément mais aussi par nom d'interface. Les modules d'interface sont évalués paresseusement et mis en cache une fois évalués. Cette approche supprime les dépendances circulaires car les interfaces ne sont plus nécessaires au niveau supérieur.

C'est une approche pour résoudre ce problème de longue date dans jsdom, l'un de ces problèmes avec cette approche est qu'elle casserait peut-être la version webpacked/navigatrice de jsdom (non testée).

Changements:

~ Correction Element.isConnected pour prendre en charge Shadow DOM (https://github.com/jsdom/jsdom/pull/2424)~

C'est un problème qui s'est glissé avec l'introduction du DOM fantôme . isConnected renvoie false si l'élément fait partie d'un arbre fantôme. Un nouveau test WPT doit être ajouté ici, car aucun test ne vérifie ce comportement.

Changements:

Correction du document de nœud HTMLTemplateElement.templateContents (#2426)

Le contenu du modèle tel que défini dans la spécification a un document de nœud différent de celui de HTMLTemplateElement lui-même. jsdom n'implémente pas ce comportement aujourd'hui et HTMLTemplateElement et son contenu de modèle partagent le même
nœud de documents.

Changements:

  • HTMLTemplateElement-impl.js
  • htmltodom.js . Ce changement a également un effet en aval sur l'analyseur. Si l'élément de contexte est un HTMLTemplateElement, l'algorithme d'analyse de fragment HTML doit récupérer le nœud du document à partir du contenu du modèle et non à partir de l'élément lui-même.

Ajoutez les étapes d'adoption manquantes au HTMLTemplateElement (#2426)

Le HTMLTemplateElement doit exécuter certaines étapes spécifiques lorsqu'il est adopté dans un autre document. Autant que je sache, c'est l'interface du sol qui a une étape d'adoption spéciale. L'implémentation de l'algorithme de nœud d'adoption devrait également être mise à jour pour invoquer cette étape d'adoption.

Changements:

Ajout de la prise en charge de la recherche isValue dans le sérialiseur parse5

L'algorithme de sérialisation des fragments HTML , lors de la sérialisation d'un élément, recherche la valeur associée à l'élément et la reflète en tant qu'attribut dans le contenu sérialisé. Il serait intéressant d'ajouter un autre crochet dans l'adaptateur d'arbre parse5, qui rechercherait la valeur associée à un élément getIsValue(element: Element): void | string .

Une approche alternative (non implémentée) consisterait à modifier le comportement du crochet getAttrList actuel pour renvoyer la valeur is à la liste d'attributs, si l'élément a une valeur is associée.

Performance

Avant de faire toute optimisation de performance, je voulais également vérifier la performance des changements dans la branche. L'ajout d'éléments personnalisés ajoute une surcharge de performances de 10 % par rapport au résultat actuel sur le maître pour les benchmarks de mutation d'arbre. Cependant, la création d'un nouvel environnement JSDOM est maintenant 3x à 6x plus lente par rapport au maître, cela nécessiterait une enquête plus approfondie pour identifier la cause première.

Plus de détails : ici

Tous les 89 commentaires

Il serait intéressant d'examiner quelles API webcomponents.js utilise que jsdom ne prend pas en charge. Si je devais deviner, ce sera beaucoup plus facile à mettre en œuvre que la spécification complète des composants Web.

Cela dit, ce serait plutôt cool d'implémenter des composants Web. Probablement pas aussi difficile qu'on pourrait le penser --- les spécifications sont relativement petites.

J'ai juste eu le temps de creuser un peu :

Tout d'abord, nous n'avons pas défini Window dans la portée de la fenêtre. Je viens de corriger cela avec this.Window = this.prototype dans le constructeur Window .
Deuxièmement, webcomponentsjs s'attend à ce que Window ait un autre prototype, c'est-à-dire le prototype EventTarget , que nous n'implémentons pas en tant qu'entité séparée.

Juste une petite info, car j'avais un peu de temps.

Agréable. Devrait pouvoir exposer Window assez facilement. Le prototype EventTarget est un peu plus délicat mais semble faisable compte tenu de la façon dont nous implémentons actuellement ce matériel ; ça a été un de mes TODO.

D'accord, les correctifs jusqu'à présent sont plutôt faciles :

  • [x] this.Window = Window; dans le constructeur de fenêtre
  • [x] inherits(dom.EventTarget, Window, dom.EventTarget.prototype); après la définition de Window

Le prochain plantage de webcomponents.js se produit parce que nous n'avons pas implémenté HTMLUnknownElement (#1068), après avoir corrigé le fait que nous devons implémenter le SVGUseElement . C'est ce sur quoi je suis actuellement bloqué, car webcomponents.js n'aime apparemment pas le SVGUseElement calé par un HTMLDivElement et lance une assertion.

D'accord, j'ai vérifié dans le Polyfill un peu plus, nous devons implémenter/vous devez caler ce qui suit :

  • [x] HTMLUnknownElement #1068
  • [ ] SVGUseElement
  • [ ] window.CanvasRenderingContext2D
  • [ ] API de plage (y compris : document.getRange() , window.getSelection() , window.Range , window.Selection ; #804 pourrait être un début)
  • [ ] npm i canvas

(liste non exhaustive pour l'instant)

Un début ressemble à ceci :

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']
  }
});

Cela fait, il y a un bogue dans notre constructeur HTMLDocument , ce qui conduit à une erreur maximale de pile d'appels. Le constructeur est pour le moment uniquement à usage interne, mais il est valide qu'un script sur le site l'appelle, nous devons donc rendre ce constructeur disponible pour une consommation publique.

+1 J'adorerais voir WebComponents sur jsdom, d'autant plus que Polymer gagne en popularité, ce serait formidable de pouvoir tester des éléments personnalisés sur un système sans tête.

À l'heure actuelle, il n'y a pas de définition inter-navigateurs des composants Web, il serait donc prématuré de les mettre en œuvre. (Nous n'allons pas simplement copier Chrome.) En attendant, vous pouvez essayer d'utiliser Polymer avec jsdom.

@domenic assez juste. Eh bien, c'est plus le support du polyfill WebComponents.js que je recherche, car c'est de cela que Polymer dépend - ou webcomponents-lite (polyfills tous sauf Shadow DOM) pour le moment. J'ai fait quelques tentatives pour que Polymer fonctionne sur jsdom, mais pas de chance jusqu'à présent - je suppose que les tâches de @Sebmaster dans le commentaire ci-dessus devront au moins être corrigées en premier.

Je crois comprendre qu'il y a trois polyfills distincts en question. Celui de l'OP est distinct de Polymer. Ensuite, il y a les polyfills webcomponents.org, qui étaient utilisés dans l'ancien Polymer. Ensuite, dans Polymer 1.0, ils ont leurs propres polyfills, je pense, qui ne sont pas vraiment des polyfills, mais plutôt des bibliothèques alternatives qui font des choses un peu comme des composants Web. Peut-être que c'est webcomponents-lite cependant.

Sur le référentiel WebComponentsJS, il est indiqué que le webcomponentsjs-lite est une variante , fournissant des polyfills pour tous les _but_ Shadow DOM, que Polymer tente ensuite indépendamment de caler à l'aide de leur système Shady DOM. Donc, à partir de là, je suis à peu près sûr que Polymer s'appuie autant que possible sur WebComponents, le polyfill WebComponentsJS faisant le gros du travail. La version allégée est censée avoir beaucoup moins de poids (assez drôle ..) donc je vais voir si je peux identifier ce dont jsdom a besoin pour la version allégée. Selon vous, quelles sont les chances de faire fonctionner le polyfill (lite ou full) dans jsdom?

C'est vraiment difficile à dire sans enquête... j'attends avec impatience ce que vous découvrirez.

Oui, je pense que ma liste de tâches est toujours applicable et nécessaire pour utiliser les cales. La fusion de # 1227 pourrait nous rendre beaucoup plus rapides avec la mise en œuvre d'interfaces conformes aux normes afin que nous puissions corriger plus rapidement celles qui manquent.

J'ai (probablement naïvement) commencé à travailler sur l'ajout de CustomElementsRegistry comme moyen de comprendre comment jsdom est structuré. J'ai ajouté "custom-elements/custom-elements-registry/define.html" à la liste des tests de la plate-forme Web et il passe quand il ne devrait pas (je n'en ai pas encore assez implémenté). Je suis presque sûr que le test ne fonctionne pas vraiment car même l'ajout d'un throw en haut du test ne l'empêchera pas de passer. Donc j'ai évidemment raté quelque chose; à part ajouter le test dans test/web-platform-tests/index.js , y a-t-il autre chose que je dois faire ?

On dirait que cela est dû au fait que nous échouons dans la ligne initiale const testWindow = iframe.contentDocument.defaultView; parce que contentDocument n'est pas défini pour une raison quelconque. Peut-être un problème avec notre ordre de chargement par rapport à l'exécution du script, mais nous n'avons pas creusé cela. J'espère que cela vous aidera à contourner ce problème. Nous devrons peut-être simplifier le test pour nos besoins (pour l'instant).

Cela aide beaucoup, merci ! Je vais voir si je peux comprendre ce qui se passe là-bas, et sinon je vais créer un test simplifié comme vous l'avez recommandé.

@Sebmaster Juste au cas où cela vous intéresserait, j'ai fait quelques recherches sur ce qui se passe avec ce test et les résultats me surprennent.

Le test utilise la fonctionnalité d'accès nommé de html . Cela signifie que vous pouvez faire des choses comme :

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

_Cependant_, si l'élément a un contexte de navigation imbriqué, le global devrait pointer vers celui-ci à la place (voir la spécification liée). Pour les iframes, c'est le contentWindow . jsdom obtient ce droit, il y a même un test . Safari le fait bien aussi.

Ce qui est fou, c'est que Chrome et Firefox se trompent ; le global pointe vers l'iframe, pas vers contentWindow. Voyant cela, j'ai supposé qu'il s'agissait d'un bogue jsdom et j'ai fait quelques recherches, trouvant finalement ce test, ce qui m'a conduit à la spécification.

tldr ; travailler sur jsdom est très instructif et vous faites un travail incroyable.

Aller au fichier bogues dans les navigateurs respectifs. Enverra également un PR aux tests de la plate-forme Web, j'ai également trouvé d'autres erreurs dans le test.

C'est encore plus motivant pour les tests en amont comme https://github.com/tmpvar/jsdom/blob/master/test/living-html/named-properties-window.js vers WPT. Merci d'avoir posté! Cela me fait me sentir vraiment bien à propos de jsdom ^ _ ^

Salut!

J'ai réussi à faire fonctionner le polyfill Custom Elements avec jsdom en combinant

Remarque : le dépôt utilise jsdom 8.5.0. La raison en est que je n'ai eu de succès qu'avec un polyfill MutationObserver, qui utilise les événements de mutation en interne. Les événements de mutation ont été supprimés après la version 8.5.0 en raison de mauvaises performances. Si Mutation Observer natif sort, je supprimerai le polyfill et mettrai à jour le dernier jsdom.

J'ai le dernier jsdom, et https://github.com/WebReflection/document-register-element fonctionne pour moi ! J'ai expérimenté les polyfills les plus officiels et j'ai des problèmes pour une raison quelconque. Mon objectif est de faire fonctionner au moins des éléments personnalisés et des importations html ... ce serait génial si nous pouvions également faire fonctionner Polymer.

Je peux faire fonctionner les scripts Polymer sans erreur. Je peux même créer un composant et le transmettre au constructeur Polymer. Après cela, il échoue silencieusement. Je pense que le DOM fantôme est le problème.

J'ai essayé de faire fonctionner le polyfill des importations HTML de webcomponentsjs. Je peux exécuter le script et je pense que mes importations HTML exécutent une requête xmlhttp, mais il ne semble pas que les scripts de mes importations soient exécutés.

Voulez-vous partager un exemple @lastmjs ? Je suis actuellement moi-même profondément impliqué dans les composants Web. Si je peux être utile, je serais ravi de contribuer avec vous.

@snuggs Merci ! Donnez-moi un jour ou deux, je suis au milieu de choses urgentes en ce moment.

@snuggs Si nous pouvons faire fonctionner le polyfill webcomponents-lite , nous devrions pouvoir utiliser Polymer. Shadow DOM semble être le polyfill le plus difficile à faire fonctionner jusqu'à présent, et si nous utilisons webcomponents-lite nous n'aurons pas à nous en soucier pour le moment, car nous aurons accès à template , custom elements et HTML imports .

Je peux faire fonctionner les importations HTML avec le polyfill webcomponents-lite . Je rencontrais un comportement étrange, puis je suis tombé sur ceci: https://github.com/Polymer/polymer/issues/1535 Il semble que les importations HTML ne puissent être chargées que via un protocole non-fichier compatible cors. J'ai donc créé un serveur http rapide dans le répertoire racine de mon projet :

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

Et voici le code de base avec lequel j'ai travaillé :

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)
});

Pour une raison quelconque, je dois envelopper l'instanciation TestApp dans un setTimeout . Il semble que l'importation Polymer HTML ne bloque pas le rendu du reste du HTML, donc sans le setTimeout le constructeur Polymer n'est pas défini. Est-ce un comportement normal pour les importations HTML ?

beforeRegister est appelé, donc le constructeur Polymer fait quelque chose. Alors maintenant, nous avons effectivement des importations HTML, bien sûr des modèles fonctionnant avec le polyfill webcomponents-lite . Je ne sais pas comment se comporte le polyfill des éléments personnalisés.

Lorsque je mets à l'intérieur de la classe TestApp une méthode ready ou created , elles ne sont pas appelées. On dirait que les événements du cycle de vie ne sont pas gérés correctement. La racine de ce problème pourrait être dans la mise en œuvre du polyfill des éléments personnalisés. Je vais continuer à jouer.

Problèmes potentiels à résoudre :

  • [ ] Les importations HTML ne se bloquent pas correctement
  • [ ] le polyfill de l'élément personnalisé fonctionne ou non ?
  • [ ] Les méthodes de cycle de vie des polymères ne sont pas appelées

Plus de bricolage conduit à plus d'idées. Je pense que l'ordre des importations et des enregistrements pourrait nous gâcher la vie. Lorsque j'exécute const testApp = document.createElement('test-app'); après l'appel du constructeur Polymer, les méthodes created et ready sont appelées, mais pas la méthode attached . Peut-être que jsdom ne gère pas correctement les littéraux d'éléments personnalisés ? De plus, même lors de l'appel document.body.appendChild(testApp) , la méthode de cycle de vie attached n'est jamais appelée.

Cela peut aider à comprendre l'ordre de chargement : https://github.com/webcomponents/webcomponentsjs#helper -utilities

@lastmjs J'ai actuellement retourné des pièces entre CustomElementRegistry.define() et document.registerElement() . J'ai vu que Domenic avait donné d'excellentes contributions et fusionné certains travaux relatifs à whatwg (https://github.com/whatwg/html/issues/1329). Il semble qu'une migration d'API soit en cours. Par exemple, je crois que la spécification appelle connectedCallback qui est associée à la fonctionnalité attachedCallback . En supposant également que vous vouliez dire attachedCallback lorsque vous avez dit attached car ce gestionnaire ne fait pas partie de l'API. J'ai expérimenté define() et registerElement() en tirant différents rappels respectifs pour chaque méthode. J'ai compris la stratégie des éléments personnalisés. HTMLImports Domenic a mentionné précédemment une implémentation qui utilise un correctif XMLHTTPRequest. Je crois que peut convertir directement en DocumentFragment directement à partir de la réponse. Des odeurs comme ça pourraient être de l'huile de serpent avec les "importations". Une "fausse" importation peut être là où vit la santé mentale.

Il semble également y avoir du foutage avec super() appelé sur HTMLElement lors de la transpilation depuis ES6 -> ES5, alors gardez un œil sur cela. J'ai vécu cela avec Rollup.js/Babel et j'ai été obligé d'utiliser le shim (léger) du package webcomponents.
https://developers.google.com/web/fundamentals/getting-started/primers/customelements

Enfin, il semble que j'obtienne (plus) de succès lorsque je crée avec une balise prototype.

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

Comme @domenic me l'a mentionné auparavant, nous voulons être prudents pour mettre en œuvre les spécifications du plus petit dénominateur commun et ne pas simplement faire ce que GOOGLE fait. On dirait que les lignes sont floues avec les composants Web. Mais je suis fan.

Avec quelles méthodes avez-vous travaillé ?

Jusqu'à présent, j'ai surtout joué avec les polyfills webcomponents-lite uniquement et Polymer < 2.0. Ainsi, lorsque j'ai mentionné la méthode attached , je voulais dire la méthode du cycle de vie Polymer qu'ils utilisent au lieu de attachedCallback . De plus, pour autant que je sache, les polyfills ne sont pas encore passés à la nouvelle spécification d'éléments personnalisés v1. Donc, tout ce avec quoi je joue est uniquement dans l'espoir de faire fonctionner Polymer avec les polyfills actuels.

@snuggs Utilisez -vous actuellement des polyfills ou travaillez-vous sur une implémentation réelle dans jsdom?

@lastmjs Je n'utilise pas de polyfills car je pense qu'il n'est pas nécessaire d'obtenir 80% du chemin. La plate-forme est suffisamment mature maintenant pour qu'avec un peu de peaufinage initial, vous puissiez simplement utiliser les constructions natives. J'aime utiliser des outils légers (généralement roulés à la main) au lieu de cadres. Cela dit, ce n'est pas la plupart des gens. Il semble que l'intention de Domenic est d'importer des éléments personnalisés 👍 html 👎 mais aucun problème à étendre XMLHTTPRequest pour gérer les fetching du document qui nous y mèneraient. C'était il y a environ 6 mois. Beaucoup de choses ont changé depuis lors de la mise en œuvre. Penser très probablement. Alors, où finissons-nous @lastmjs ?

@snuggs Peut-être que la chose la plus sensée et la plus pérenne à faire est d'implémenter un support de première classe pour les éléments personnalisés et le DOM fantôme dans jsdom. Les deux normes sont à la v1 et il semble probable d'après ce que j'entends que la majorité des principaux navigateurs les implémenteront. Comment devrions-nous procéder? Je dispose d'un temps limité en ce moment, mais peut-être pourrions-nous au moins préciser ce qu'il faut faire. @domenic Avez-vous des suggestions sur la façon d'aller de l'avant avec ces implémentations, ou des raisons pour lesquelles nous ne devrions pas ?

Aucune suggestion concrète de ma part, à part simplement mettre en œuvre la spécification :)

J'ai une succursale où j'ai travaillé dessus il y a quelque temps (les spécifications ont un peu changé depuis). L'implémentation de CustomElementsRegistry était assez facile, où j'ai eu du mal à comprendre comment intégrer des réactions d'éléments personnalisées dans la base de code et quand celles-ci devaient être appelées et d'où. Si je devais reprendre cette sauvegarde (sans promesses), c'est probablement ce sur quoi je me concentrerais.

@matthewp Cela semble utile, où puis-je trouver cette branche ?

@matthewp ouais ce serait bien

https://github.com/matthewp/jsdom/commits/custom-elements comme je l'ai dit, la spécification a changé depuis, elle est donc obsolète. Et c'est la partie la plus facile, mais c'est un point de départ si quelqu'un le veut. @snuggs @lastmjs

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

Personnellement, simplement supporter l'élément personnalisé serait déjà génial.

(Notez que je crois comprendre que phantomJS 2.5 devrait prendre en charge au moins les modèles et peut-être l'élément personnalisé car ils évoluent sur la version la plus récente de Webkit, je ne sais pas laquelle).

En fait, je me moque des customElements, en utilisant le lib document-register-element

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)
}

Génial, avez-vous eu des problèmes?

pour l'instant non :D

mais j'ai besoin d'écrire plus de spécifications, de couvrir plus de choses pour me sentir mieux

C'est génial de voir qu'il y a un moyen. Autant que j'aime le polymère, le setu de test est un enfer et avoir jsdom comme solution de repli est agréable ;) Merci d'avoir mis le travail en place

On dirait qu'il y a un PR qui fait avancer ça ! https://github.com/tmpvar/jsdom/pull/1872

En fait, je me moque des customElements, en utilisant la bibliothèque document-register-element @darlanmendonca

Devrait lire ce lien sur l'attachement des globals jsdom au node global. C'est un anti-modèle.

Bonjour à tous,
Je suis un peu confus quant à l'état de l'exécution de Polymer dans JSDOM (en utilisant Node.js 6.7.0 et JSDOM 11.1.0). J'ai essayé diverses choses, avec des résultats mitigés. Je serais vraiment reconnaissant si quelqu'un pouvait me renseigner ici...

Ce que j'ai fait jusqu'à présent :

1) J'ai lancé un serveur http à partir de mon répertoire racine

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

2) J'ai chargé l'un de mes composants Polymer dans JSDOM :

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);
})

(J'ai également essayé de charger le fichier de composant à partir du système de fichiers, avec les mêmes résultats.)

3) Voici mon code composant :

<!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>

(J'ai ajouté l'en-tête HTML afin de charger le polyfill des composants Web.)

Que puis-je observer ?

Quand je lance ça, je vois

  • que le polyfill des composants Web est chargé à partir du serveur Web
  • le message "javascript est en cours d'exécution" dans la console

Ce que je ne vois pas

  • que le composant polymer.html est chargé depuis le serveur Web
  • le message "les composants web sont prêts" dans la console

Cela m'amène à la conclusion que l'événement WebComponentsReady n'est pas déclenché (probablement parce que l'importation HTML ne fonctionne pas ?). Aussi,
window.WebComponents contient { flags: { log: {} } } -- l'indicateur ready est manquant.

J'ai aussi essayé du mocking et du polyfilling :

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

mais ça n'a pas l'air de changer quoi que ce soit.

Maintenant, je me demande :-) Est-ce censé fonctionner du tout? Si oui, pourquoi ça ne marche pas pour moi ? Si non, que manque-t-il / qu'est-ce qui est nécessaire pour le faire fonctionner ?

Merci pour toute idée !

moin

J'ai même essayé cela avec encore moins de succès et j'ai renoncé à attendre quel sera le résultat de cette discussion ici.

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

pas une solution juste une autre tentative ratée. Même confusion d'ailleurs. Même conclusion aussi

Le polyremplissage d'éléments personnalisés dans jsdom s'est avéré très difficile. Quelqu'un peut-il énumérer les défis liés à l'implémentation de cela dans jsdom ? Essayer d'évaluer le niveau d'effort pour y parvenir.

L'obstacle fondamental est que jsdom partage les constructeurs et leurs prototypes .

Cela rend pratiquement impossible l'implémentation d'un registre d'éléments personnalisés par fenêtre, car le constructeur HTMLElement est partagé entre toutes les fenêtres. Ainsi, lorsque vous effectuez l'appel super() dans votre constructeur d'élément personnalisé, le constructeur HTMLElement en cours d'exécution ne sait pas dans quelle fenêtre rechercher les choses.

Je ne sais pas s'il existe de bonnes solutions intermédiaires. Le gros canon est de déplacer jsdom vers une architecture qui autorise les constructeurs/prototypes non partagés. Nous pourrions le faire de plusieurs façons, toutes avec des compromis différents. Peut-être voudrions-nous ouvrir un sujet dédié pour en discuter avec l'équipe et la communauté, mais pour l'instant, laissez-moi énumérer ceux qui me viennent à l'esprit :

  • Utilisez [WebIDL2JSFactory] pour tout dans jsdom, ou au moins HTMLElement et tous ses descendants. Je ne sais pas si [WebIDL2JSFactory] fonctionne encore bien avec l'héritage, mais cela pourrait fonctionner. Cette alternative oblige tout le monde à payer le coût des constructeurs/prototypes supplémentaires, mais c'est peut-être mieux que de faire des éléments personnalisés une fonctionnalité opt-in.
  • Avoir une option où jsdom exécute tous les modules de définition de classe à l'intérieur du bac à sable vm . Par exemple, avoir une étape de construction qui regroupe toutes les API Web dans jsdom, puis lorsque vous créez une nouvelle fenêtre, nous faisons vm.runScript() avec ce groupe dans le nouveau bac à sable global. Cela nous permettrait probablement de nous débarrasser de [WebIDL2JSFactory] .

Je suppose qu'une autre solution serait d'implémenter des éléments personnalisés avec un avertissement géant indiquant que le registre d'éléments personnalisés est global par processus Node.js ? Cela semble terrible cependant.


Après cet obstacle initial, le reste est relativement simple en termes de respect des spécifications. La partie la plus difficile sera probablement d'implémenter [CEReactions] et de mettre à jour tous nos fichiers IDL pour les avoir aux bons endroits, mais ce n'est pas trop difficile.

J'ai aussi pensé à avoir une version prototype séparée. Voici quelques-unes de mes réflexions.

Je ne sais pas si [WebIDL2JSFactory] fonctionne encore bien avec l'héritage, mais cela pourrait fonctionner.

Non, ce n'est pas le cas, et je ne sais pas exactement comment le faire fonctionner. La deuxième solution est beaucoup plus simple à mon avis.

Avoir une option où jsdom exécute tous les modules de définition de classe à l'intérieur du bac à sable vm .

C'est ce que je préférerais. Le problème principal est de passer les classes impl dans le bac à sable vm lors de l'initialisation, bien que cela puisse être fait en mettant tout le contexte extérieur dans une propriété globale, et delete cette propriété globale après qu'elle soit terminée . Cela permettrait également d'implémenter correctement [NamedConstructor] et quelques autres attributs étendus, et peut-être même de générer un instantané de démarrage V8 pour un environnement jsdom si quelqu'un est assez audacieux.

Toute cette affaire [WebIDL2JSFactory] était un piratage en premier lieu, et j'aimerais m'en débarrasser le plus tôt possible.

Les commentaires +1 ne sont pas utiles pour développer cette fonctionnalité. Je supprime donc au moins un commentaire récent.

Salut, je n'avais pas réalisé que @TimothyGu travaillait là-dessus.
J'ai en fait l'enregistrement et la création d'éléments personnalisés travaillant à
https://github.com/mraerino/jsdom/tree/custom-elements-spec

J'essaie d'être le moins invasif possible et de rester aussi proche que possible des spécifications.
Les tests de la plate-forme Web Custom Element Registry sont en cours.

En piratant hier soir, j'ai trouvé une solution qui fonctionne sans modifier webIdl2JS.
Voir ici : https://github.com/mraerino/jsdom/commit/592ad1236e9ca8f63f789d48e1887003305bc618

@TimothyGu seriez-vous prêt à combiner vos forces sur celui-ci ?

Juste quelques mises à jour ici :
Je suis assez confiant quant à mon implémentation de la spécification, mais je suis actuellement bloqué à cause de l'attribut IDL étendu [HTMLConstructor] . C'est pourquoi j'ai ouvert https://github.com/jsdom/webidl2js/issues/87

En attendant, je vais implémenter l'algorithme [HTMLConstructor] en utilisant un attribut [Constructor] pour pouvoir basculer facilement plus tard. (Je l'ai initialement implémenté en insérant une classe fictive HTMLElement dans window , mais cela ne semblait pas correct.)

Oui, comme indiqué dans https://github.com/tmpvar/jsdom/issues/1030#issuecomment -333994158, la mise en œuvre correcte de HTMLConstructor nécessitera des modifications fondamentales de l'architecture de jsdom.

Avez-vous des informations sur le nombre de tests de plate-forme Web que votre version réussit ?

Juste ceux de customElementRegistry pour le moment, et je pourrais me tromper totalement sur ma progression.

Edit: Ok, après avoir relu votre commentaire, j'ai compris ce que vous vouliez dire. Je vais l'essayer avec mon implémentation, mais @TimothyGu semble également travailler sur la séparation.

J'utilise Polymer donc je suis :+1: sur cette fonctionnalité de demande

@dman777 @mraerino Pareil pour les développeurs slim.js. Slim utilise l'API de composants Web natifs et ne peut pas hériter de HTMLElement sans hacks sur jsdom.

Trois ans se sont écoulés depuis l'ouverture de ce numéro. Quelqu'un peut-il dire quand environ jsdom prendra en charge les éléments personnalisés ?

TL;DR

J'ai travaillé au cours des 2 dernières semaines sur l'évaluation de la faisabilité d'ajouter la prise en charge d'un élément personnalisé dans jsdom. Voici le résultat de l'enquête.

Vous pouvez trouver une implémentation conforme aux spécifications de l'élément personnalisé ici : https://github.com/jsdom/jsdom/compare/master...pmdartus :custom-elements?expand=1. Il y a encore quelques aspérités ici et là, mais au moins la plupart des tests WPT réussissent . Les tests restants qui échouent sont soit des problèmes connus de JSDOM, soit des problèmes mineurs qui peuvent être résolus lorsque nous aborderons l'implémentation réelle dans jsdom.

Maintenant, voici la bonne nouvelle, maintenant que Shadow DOM est supporté nativement, avec à la fois la branche d'éléments personnalisés et l'observateur de mutation, j'ai pu charger et rendre la dernière version de l'exemple d' application Polymer 3 hello world dans jsdom 🎉. Dans son état actuel, la branche n'est pas en mesure de charger une application Stencil (le mode de développement Stencil nécessite certaines fonctionnalités non prises en charge comme module , et le mode prod est lancé pour une raison inconnue).

Plan d'action

Voici une liste des modifications qui doivent être apportées avant de commencer à s'attaquer à l'implémentation réelle de la spécification d'élément personnalisé. Chaque élément de la liste est indépendant et peut être traité en parallèle.

Prise en charge des attributs étendus IDL [CEReactions]

L'un des noyaux fonctionnels manquants dans jsdom pour ajouter la prise en charge des éléments personnalisés est les attributs [CEReactions] . J'ai été en partie capable de contourner ce problème en corrigeant les bonnes propriétés du prototype. Cette approche fonctionne tant que la pile de réaction d'éléments personnalisés est globale et non par unité de contextes de navigation d'origine similaire liés puisque tous les prototypes d'interfaces sont partagés.

Cette approche présente quelques lacunes puisque certaines interfaces ont les attributs [CEReactions] associés aux propriétés indexées ( HTMLOptionsCollection , DOMStringMap ). En interne, jsdom utilise des proxys pour suivre la mutation de ces propriétés. Le patch prototype de l'interface ne fonctionne pas dans ce cas. Une autre approche, pour contourner ce problème, serait de patcher l'implémentation au lieu de l'interface (non implémentée).

Je ne suis pas assez familier avec le webidl2js interne, mais nous devrions explorer l'ajout d'un crochet global pour [CEReactions] ?

Changements:

Prise en charge des attributs étendus IDL [HTMLConstructor]

Comme @domenic l'a expliqué ci- dessus , l'ajout de la prise en charge de [HTMLConstructor] est l'un des principaux bloqueurs ici.

J'ai pu contourner ce problème ici en corrigeant le constructeur d'interface pour chaque contexte de navigation. Le constructeur de l'interface serait en mesure d'accéder à la bonne fenêtre et à l'objet document tout en conservant le prototype partagé. Cette approche évite également la surcharge de performances liée à la réévaluation du prototype d'interface pour chaque nouveau contexte de navigation.

Je ne sais pas si c'est la meilleure approche ici, mais elle répond aux exigences sans introduire de surcharge de performances supplémentaire.

Changements:

Rendre l'algorithme de make fragment parsing conforme à la spécification (#2522)

Comme indiqué ici , l'implémentation de l'algo d'analyse de fragment HTML utilisée dans Element.innerHTML et Element.outerHTML est incorrecte. L'algo d'analyse doit être refactorisé, pour que les rappels de réactions d'éléments personnalisés soient invoqués correctement.

Changements:

Améliorer la recherche d'interface pour créer un algorithme d'élément

L'un des problèmes sur lesquels je suis rapidement tombé était l'introduction de nouvelles dépendances circulaires lors de l'ajout de la prise en charge de la création d'éléments personnalisés. CustomElementRegistry et l'algorithme de création d'élément nécessitent tous deux l'accès aux interfaces Element, ce qui crée un cauchemar de dépendances circulaires.

L'approche adoptée dans la branche était de créer un InterfaceCache , qui permettrait la recherche d'interface par espace de noms et nom d'élément mais aussi par nom d'interface. Les modules d'interface sont évalués paresseusement et mis en cache une fois évalués. Cette approche supprime les dépendances circulaires car les interfaces ne sont plus nécessaires au niveau supérieur.

C'est une approche pour résoudre ce problème de longue date dans jsdom, l'un de ces problèmes avec cette approche est qu'elle casserait peut-être la version webpacked/navigatrice de jsdom (non testée).

Changements:

~ Correction Element.isConnected pour prendre en charge Shadow DOM (https://github.com/jsdom/jsdom/pull/2424)~

C'est un problème qui s'est glissé avec l'introduction du DOM fantôme . isConnected renvoie false si l'élément fait partie d'un arbre fantôme. Un nouveau test WPT doit être ajouté ici, car aucun test ne vérifie ce comportement.

Changements:

Correction du document de nœud HTMLTemplateElement.templateContents (#2426)

Le contenu du modèle tel que défini dans la spécification a un document de nœud différent de celui de HTMLTemplateElement lui-même. jsdom n'implémente pas ce comportement aujourd'hui et HTMLTemplateElement et son contenu de modèle partagent le même
nœud de documents.

Changements:

  • HTMLTemplateElement-impl.js
  • htmltodom.js . Ce changement a également un effet en aval sur l'analyseur. Si l'élément de contexte est un HTMLTemplateElement, l'algorithme d'analyse de fragment HTML doit récupérer le nœud du document à partir du contenu du modèle et non à partir de l'élément lui-même.

Ajoutez les étapes d'adoption manquantes au HTMLTemplateElement (#2426)

Le HTMLTemplateElement doit exécuter certaines étapes spécifiques lorsqu'il est adopté dans un autre document. Autant que je sache, c'est l'interface du sol qui a une étape d'adoption spéciale. L'implémentation de l'algorithme de nœud d'adoption devrait également être mise à jour pour invoquer cette étape d'adoption.

Changements:

Ajout de la prise en charge de la recherche isValue dans le sérialiseur parse5

L'algorithme de sérialisation des fragments HTML , lors de la sérialisation d'un élément, recherche la valeur associée à l'élément et la reflète en tant qu'attribut dans le contenu sérialisé. Il serait intéressant d'ajouter un autre crochet dans l'adaptateur d'arbre parse5, qui rechercherait la valeur associée à un élément getIsValue(element: Element): void | string .

Une approche alternative (non implémentée) consisterait à modifier le comportement du crochet getAttrList actuel pour renvoyer la valeur is à la liste d'attributs, si l'élément a une valeur is associée.

Performance

Avant de faire toute optimisation de performance, je voulais également vérifier la performance des changements dans la branche. L'ajout d'éléments personnalisés ajoute une surcharge de performances de 10 % par rapport au résultat actuel sur le maître pour les benchmarks de mutation d'arbre. Cependant, la création d'un nouvel environnement JSDOM est maintenant 3x à 6x plus lente par rapport au maître, cela nécessiterait une enquête plus approfondie pour identifier la cause première.

Plus de détails : ici

@pmdartus c'est très prometteur, excellent travail ! J'utilise ma branche hack jsdom-wc, faute d'une meilleure option. Je constate un comportement étrange et j'espérais échanger pour votre branche, mais je rencontre des problèmes.

J'enregistre des éléments personnalisés comme :

class Component extends HTMLElement {

}

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

Mais si je fais :

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

Je reçois immédiatement: Error: Uncaught [TypeError: Illegal constructor] .

Des pensées à ce sujet?

L'extrait de code suivant s'exécute correctement sur la branche custom-elements de mon 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" 
});

Le constructeur illégal est probablement lancé par le constructeur HTMLElement d'origine, les modifications apportées à la branche doivent corriger le constructeur pour chaque nouvel objet de fenêtre. @tbranyen Avez-vous un exemple de reproduction complet pour que je puisse l'essayer localement ?

Salut @pmdartus Je ne sais pas encore trop ce qui cause mes problèmes, mais j'ai écrit du code isolé directement dans votre branche qui a parfaitement fonctionné :

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
}

C'est effectivement ce que fait mon système de test, mais il se casse. Donc c'est peut-être quelque chose de mon côté.

Éditer:

Ah d'accord, je pense que j'ai précisé où le problème est le plus susceptible de se produire. Je dois conserver le constructeur initial HTMLElement créé. Si j'ajuste le code ci-dessus pour réutiliser le constructeur :

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

Cela produira ce qui suit :

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

Édition 2 :

Trouvé:

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

Remarquez que ce fil a 4 ans, les composants Web sont-ils pris en charge ou prévus ?

Ce serait bien d'avoir des composants Web dans ce domaine, mais comme alternative, si quelqu'un souhaite savoir .... le chrome sans tête peut maintenant être utilisé dans le nœud pour rendre/construire le fichier html sting.

Remarquez que ce fil a 4 ans, les composants Web sont-ils pris en charge ou prévus ?

C'est un travail en cours car la spécification est mise en œuvre pièce par pièce.

Le polyfill sur : https://github.com/WebReflection/document-register-element Fonctionne comme un charme ! Mes remerciements les plus sincères à l'auteur !

Pour ceux qui ont le même problème, faites simplement :

npm install -D document-register-element

Dans votre configuration jest, définissez un fichier de configuration qui sera exécuté avant tous vos tests :

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

Et enfin, à l'intérieur de ce fichier ('tests/setup.js'):

import 'document-register-element'

Après cela, l'enregistrement et la création d'éléments personnalisés dans jsdom via document.createElement('custom-component') fonctionnent parfaitement ! Cependant, les fragments ne semblent pas fonctionner. (Je n'utilise pas shadow dom, soit dit en passant).

@tebanep comme vous l'avez mentionné, polyfill ne convient pas à la plupart des travaux de composants Web, s'il ne prend pas en charge Shadow DOM, ce n'est pas vraiment une comparaison avec ce que cela accomplit.

@tebanep Merci. Comme je n'ai pas besoin de shadow dom, c'est une excellente information

Un espoir que cela soit mis en place ? En ce moment, nous utilisons jsdom-wc avec beaucoup de bogues, mais nous n'avons pas de meilleure solution. Mon espoir et ma prière sur ce sujet.

@dknight Je sais que jsdom-wc est à peu près un hack pour le faire fonctionner un peu. J'ai publié le module avec une compatibilité nettement meilleure sous mon champ d'application personnel npm. Vous pouvez l'installer avec :

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

Je l'utilise maintenant pour tous mes besoins en composants Web JSDOM jusqu'à ce qu'ils soient stables.

@tbranyen Avez-vous dépublié votre fork ? Je ne le trouve pas sur npm.

@KingHenne dangit, on dirait qu'il s'est retrouvé dans notre registre "entreprise". Je viens de publier au public npm. Désolé pour ça!

Ne me @ pas, mais ne devrions-nous pas simplement tester le code de l'interface utilisateur Web dans un vrai navigateur, par exemple avec marionnettiste. Le problème de prise en charge de Shadow DOM / Custom Elements disparaît alors.

Ne postez pas de commentaire si vous ne voulez pas être @'d @Georgegriff. C'est une stratégie valable, mais c'est lent et bogué à d'autres égards puisque vous faites effectivement de l'IPC, oui même avec marionnettiste. Lorsque le navigateur meurt, il n'est pas évident pourquoi dans de nombreux cas. Essayez simplement de déboguer les problèmes de marionnettiste en plaisantant pour avoir un aperçu de la raison pour laquelle ce n'est pas toujours la meilleure idée.

Personnellement, je préfère continuer à tester de manière synchrone et sur le même fil. Il n'y a aucune raison pour qu'une implémentation isolée de la spécification ne soit pas un temps d'exécution raisonnable pour tester les composants. JSDOM est effectivement un navigateur à ce stade, mais pas aussi stable que les trois grands.

Le polyfill sur : https://github.com/WebReflection/document-register-element Fonctionne comme un charme ! Mes remerciements les plus sincères à l'auteur !

Pour ceux qui ont le même problème, faites simplement :

npm install -D document-register-element

Dans votre configuration jest, définissez un fichier de configuration qui sera exécuté avant tous vos tests :

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

Et enfin, à l'intérieur de ce fichier ('tests/setup.js'):

import 'document-register-element'

Après cela, l'enregistrement et la création d'éléments personnalisés dans jsdom via document.createElement('custom-component') fonctionnent parfaitement ! Cependant, les fragments ne semblent pas fonctionner. (Je n'utilise pas shadow dom, soit dit en passant).

Cela fonctionne bien pour moi mais le connectedCallback n'est jamais appelé, une idée ?

@FaBeyyy pareil pour moi :(

@FaBeyyy @ majo44 vous devez ajouter votre composant à un document, c'est-à-dire. document.body.appendChild(...) pour connectedCallback pour se faire virer. Selon les spécifications, il est appelé lorsque le composant est attaché à un Dom.

JSDOM est effectivement un navigateur à ce stade, mais pas aussi stable que les trois grands.

À ce stade, cela ressemble plus aux deux grands, car Microsoft abandonne le leur, qui est avec eux depuis aussi longtemps que Windows.

@FaBeyyy @ majo44 vous devez ajouter votre composant à un document, c'est-à-dire. document.body.appendChild(...) pour connectedCallback pour se faire virer. Selon les spécifications, il est appelé lorsque le composant est attaché à un Dom.

merci capitaine évident mais ce n'est bien sûr pas le problème ici. Si je ne savais pas comment fonctionne le cycle de vie des composants, je n'essaierais probablement pas d'écrire des tests 😄. Je créerai une vitrine de dépôt plus tard quand je trouverai le temps.

@FaBeyyy
J'ai donc trouvé la configuration qui me convient. J'ai dû ajouter polyfill pour MutationObserver. J'utilise JSDOM pour tester le marsouin, avec Jest, et la configuration de travail est la suivante :

// 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
J'ai donc trouvé la configuration qui me convient. J'ai dû ajouter polyfill pour MutationObserver. J'utilise JSDOM pour tester le marsouin, avec Jest, et la configuration de travail est la suivante :

// 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"}] ] }

Bien, merci !

@ majo44 ce n'est pas nécessaire avec le dernier jsdom. Lorsque vous travaillez avec Jest (qui utilise jsdom v11), vous pouvez simplement utiliser un environnement mis à jour : https://www.npmjs.com/package/jest-environment-jsdom-fourteen

@mgibas merci, avec jest-environment-jsdom-fourteen cela fonctionne aussi bien et le polyfill de l'observateur de mutation n'est pas requis (mais la version est 0.1.0, package de validation unique :) )

Existe-t-il une ventilation des API de composants Web actuellement prises en charge par JSDOM ? On dirait que le shadow DOM est pris en charge, mais pas les éléments personnalisés (au moins dans la branche/le dépôt de la version) ?

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

@tbranyen avez-vous le code source de votre fork disponible quelque part ? Serait curieux de regarder le diff 🙂

J'utilise jest-environment-jsdom-fifteen comme suggéré par @ majo44 , et les éléments babel-polyfill et document-register-element (voir les réponses de @mgibas ). Mais j'obtiens toujours une erreur lorsque j'essaie de récupérer mon dom fantôme de composant Web pour les tests.

el.shadowRoot est nul avec :

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] > *');
})

Une idée d'une solution de contournement? Pour info, il a déjà été testé avec Karma, Moka, Chai & Jasmine.

Rien de particulier dans mon composant :

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

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

Éditer

J'ai fait un débogage avec jsdom 15.1.1 afin de mieux comprendre mon problème.
Pourtant, je ne comprends pas pourquoi c'est nul ici...

Ainsi, Element.shadowRoot est implémenté depuis 88e72ef
https://github.com/jsdom/jsdom/blob/1951a19d8d40bc196cfda62a8dafa76ddda6a0d2/lib/jsdom/living/nodes/Element-impl.js#L388 -L415

Après document.createElement, this._shadowDom est correct sur https://github.com/jsdom/jsdom/blob/15.1.1/lib/jsdom/living/nodes/Element-impl.js#L403. Et chaque élément du shadow dom est créé (constructeur Element appelé avec les bonnes valeurs).

Mais quand j'appelle el.shadowDom immédiatement après document.body.appendChild(el) (https://github.com/jsdom/jsdom/blob/15.1.1/lib/jsdom/living/nodes/Element-impl .js#L408), this. _shadowRoot est nul !

Même chose après

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

Ou même si j'utilise ce qui suit au lieu de document.

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

Pour la reproduction, voir :
https://github.com/noelmace/devcards/tree/jest

@nminhnguyen Je suppose que vous pouvez trouver le code source du fork créé par @tbranyan ici https://github.com/tbranyen/jsdom/tree/initial-custom-elements-impl

J'essaie de tester des composants Web créés avec lit-html et lit-element et j'ai remarqué cette différence lors de la création des éléments.

const myElem = new MyElem();

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

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

et quand j'utilise le document.createElement

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

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

console.log(myElem.shadowRoot) // null

Pour configurer jest, je n'utilise qu'un seul polyfill qui est : setupFiles: ['document-register-element']

Il semble que la méthode de rendu dans myElem ne soit jamais appelée. En déboguant un peu plus loin, j'ai découvert que la méthode initialize qui se trouve dans lit-element n'est jamais appelée.
Donc, le 2ème exemple fonctionnerait si je fais

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

J'ai créé un DOM alternatif qui prend en charge les composants Web. J'ai d'abord essayé de faire un PR, mais la façon dont JSDOM fonctionne m'a rendu difficile la résolution de mes besoins là-bas. Vous êtes libre de l'utiliser ou de regarder le code.

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

Environnement de plaisanterie :
https://www.npmjs.com/package/jest-environment-happy-dom

Ça a l'air génial. Merci.

Le lundi 7 octobre 2019 à 1 h 08, capricorn86 [email protected] a écrit :

J'ai créé un DOM alternatif qui prend en charge les composants Web. Je l'ai fait en premier
essayé de faire un PR, mais la façon dont JSDOM fonctionne m'a rendu difficile la résolution de mon
besoins là-bas. Vous êtes libre de l'utiliser et/ou de regarder le code.

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

Environnement de plaisanterie :
https://www.npmjs.com/package/jest-environment-happy-dom


Vous recevez ceci parce que vous êtes abonné à ce fil.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/jsdom/jsdom/issues/1030?email_source=notifications&email_token=ACQ5ZD5QUEITPND4SXWOHW3QNILSRA5CNFSM4A4G5SF2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEAOO5ZA#issuecomment-577,6076
ou couper le fil
https://github.com/notifications/unsubscribe-auth/ACQ5ZDYU465DXI4KHBQH4KTQNILSRANNCNFSM4A4G5SFQ
.

@ capricorne86
Votre travail simplifie mon environnement de test, merci !
https://github.com/EasyWebApp/WebCell/tree/v2/MobX

@ capricorne86
Votre travail simplifie mon environnement de test, merci !
https://github.com/EasyWebApp/WebCell/tree/v2/MobX

Merci @TechQuery !

Ça a l'air génial. Merci.

Le lun. 7 octobre 2019, 01:08 capricorn86 @ . * > a écrit : J'ai créé un DOM alternatif qui prend en charge les composants Web. J'ai d'abord essayé de faire un PR, mais la façon dont JSDOM fonctionne m'a rendu difficile la résolution de mes besoins là-bas. Vous êtes libre de l'utiliser et/ou de regarder le code. DOM : https://www.npmjs.com/package/happy-dom Jest environment : https://www.npmjs.com/package/jest-environment-happy-dom — Vous recevez ceci parce que vous y êtes abonné fil. Répondre à cet e - mail directement, voir sur GitHub <# 1030? Email_source = notifications & email_token = ACQ5ZD5QUEITPND4SXWOHW3QNILSRA5CNFSM4A4G5SF2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEAOO5ZA # issuecomment-538767076>, ou couper le fil https://github.com/notifications/unsubscribe-auth/ACQ5ZDYU465DXI4KHBQH4KTQNILSRANCNFSM4A4G5SFQ .

Merci @motss!

Existe-t-il une ventilation des API de composants Web actuellement prises en charge par JSDOM ? On dirait que le shadow DOM est pris en charge, mais pas les éléments personnalisés (au moins dans la branche/le dépôt de la version) ?

ça m'intéresserait aussi :)

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

mitar picture mitar  ·  4Commentaires

jacekpl picture jacekpl  ·  4Commentaires

JacksonGariety picture JacksonGariety  ·  4Commentaires

Progyan1997 picture Progyan1997  ·  3Commentaires

lehni picture lehni  ·  4Commentaires