Jsdom: Compatibilidad con la API de componentes web

Creado en 17 feb. 2015  ·  89Comentarios  ·  Fuente: jsdom/jsdom

Estamos tratando de usar jsdom para nuestras pruebas unitarias para React core (http://facebook.github.io/react/).

Desafortunadamente, la especificación de componentes web no es compatible de forma nativa con jsdom, y el polyfill de webcomponents.js no se ejecuta en jsdom. Este problema solicita la adición de compatibilidad con WebComponent (elementos personalizados, shadow dom, importaciones de html, etc.).

feature

Comentario más útil

TL;RD

Trabajé durante las últimas 2 semanas para evaluar la viabilidad de agregar soporte para elementos personalizados en jsdom. Aquí está el resultado de la investigación.

Puede encontrar una implementación de elemento personalizado que cumpla con las especificaciones aquí: https://github.com/jsdom/jsdom/compare/master...pmdartus :custom-elements?expand=1. Todavía hay algunas asperezas aquí y allá, pero al menos la mayoría de las pruebas WPT pasan . Las pruebas fallidas restantes son problemas conocidos de JSDOM o problemas menores que se pueden abordar cuando abordemos la implementación real en jsdom.

Ahora aquí están las buenas noticias, ahora que Shadow DOM es compatible de forma nativa, tanto con la rama de elementos personalizados como con el observador de mutaciones, pude cargar y renderizar la última versión del ejemplo de la aplicación Polymer 3 hello world en jsdom 🎉. En su estado actual, la rama no puede cargar una aplicación de Stencil (el modo de desarrollo de Stencil requiere algunas características no compatibles como module , y el modo de producción se inicia por una razón desconocida).

Plan de ACCION

Aquí hay una lista de los cambios que deben ocurrir primero antes de comenzar a abordar la implementación real de especificaciones de elementos personalizados. Cada elemento de la lista es independiente y se puede abordar en paralelo.

Compatibilidad con atributos extendidos de [CEReactions] IDL

Una de las funciones básicas que faltan en jsdom para agregar soporte para elementos personalizados son los atributos [CEReactions] . Pude solucionar parcialmente este problema parcheando las propiedades correctas del prototipo. Este enfoque funciona siempre que la pila de reacción del elemento personalizado sea global y no por unidad de contextos de navegación de origen similar relacionados, ya que se comparten todos los prototipos de las interfaces.

Este enfoque tiene algunas deficiencias ya que algunas interfaces tienen los atributos [CEReactions] asociados con propiedades indexadas ( HTMLOptionsCollection , DOMStringMap ). Internamente, jsdom usa Proxies para rastrear la mutación a esas propiedades. El prototipo de parcheo de la interfaz no funciona en este caso. Otro enfoque para solucionar este problema sería parchear la implementación en lugar de la interfaz (no implementada).

No estoy lo suficientemente familiarizado con el webidl2js interno, pero deberíamos explorar agregar un gancho global para [CEReactions] .

Cambios:

Compatibilidad con atributos extendidos de [HTMLConstructor] IDL

Como @domenic explicó anteriormente , agregar soporte para [HTMLConstructor] es uno de los principales bloqueadores aquí.

Pude solucionar este problema aquí parcheando el constructor de la interfaz para cada contexto de navegación. El constructor de la interfaz podría acceder a la ventana correcta y al objeto del documento manteniendo el prototipo compartido. Este enfoque también evita la sobrecarga de rendimiento de volver a evaluar el prototipo de interfaz para cada nuevo contexto de navegación.

No estoy seguro de si es el mejor enfoque aquí, pero se ajusta a los requisitos sin introducir una sobrecarga de rendimiento adicional.

Cambios:

Hacer que el algoritmo de análisis de fragmentos sea compatible con las especificaciones (#2522)

Como se discutió aquí , la implementación del algoritmo de análisis de fragmentos HTML utilizada en Element.innerHTML y Element.outerHTML es incorrecta. El algoritmo de análisis debe refactorizarse para que las devoluciones de llamada de reacciones de elementos personalizados se invoquen correctamente.

Cambios:

Mejorar la búsqueda de interfaz para crear algo de elemento.

Uno de los problemas con los que me topé rápidamente fue la introducción de nuevas dependencias circulares al agregar soporte para la creación de elementos personalizados. Tanto CustomElementRegistry como el algoritmo de creación de elementos requieren acceso a las interfaces de Element, lo que crea una pesadilla de dependencias circulares.

El enfoque adoptado en la rama fue crear un InterfaceCache , que permitiría la búsqueda de interfaz por espacio de nombres y nombre del elemento, pero también por nombre de interfaz. Los módulos de interfaz se evalúan de forma perezosa y se almacenan en caché una vez evaluados. Este enfoque elimina las dependencias circulares porque las interfaces ya no son necesarias en el nivel superior.

Este es un enfoque para resolver este problema de larga data en jsdom, uno de los problemas con este enfoque es que tal vez rompería la versión webpacked/browserified de jsdom (no probada).

Cambios:

~Reparar Element.isConnected para admitir Shadow DOM (https://github.com/jsdom/jsdom/pull/2424)~

Este es un problema que surgió con la introducción del shadow DOM , isConnected devuelve false si el elemento es parte de un shadow tree. Es necesario agregar una nueva prueba WPT aquí, ya que ninguna prueba verifica este comportamiento.

Cambios:

Arreglar el documento de nodo HTMLTemplateElement.templateContents (#2426)

El contenido de la plantilla , tal como se define en la especificación, tiene un documento de nodo diferente al del propio HTMLTemplateElement. jsdom no implementa este comportamiento hoy y HTMLTemplateElement y el contenido de la plantilla comparten el mismo
nodo de documento.

Cambios:

  • HTMLTemplateElement-impl.js
  • htmltodom.js . Este cambio también tiene algún efecto posterior en el analizador. Si el elemento de contexto es un HTMLTemplateElement, el algoritmo de análisis de fragmentos de HTML debe seleccionar el nodo del documento del contenido de la plantilla y no del propio elemento.

Agregue los pasos de adopción faltantes a HTMLTemplateElement (#2426)

El HTMLTemplateElement necesita ejecutar algunos pasos específicos cuando se adopta en otro documento. Hasta donde yo sé, es la interfaz del suelo tener un paso de adopción especial. La implementación del algoritmo del nodo de adopción también debería actualizarse para invocar este paso de adopción.

Cambios:

Agregue soporte para la búsqueda de isValue en el serializador parse5

El algoritmo de serialización de fragmentos HTML , al serializar un elemento, busca el valor asociado con el elemento y lo refleja como un atributo en el contenido serializado. Sería interesante agregar otro enlace en el adaptador de árbol parse5, que buscaría el valor asociado con un elemento getIsValue(element: Element): void | string .

Un enfoque alternativo (no implementado) sería agregar un cambio en el comportamiento del gancho getAttrList actual para devolver el valor a la lista de atributos, si el elemento tiene un valor asociado.

Rendimiento

Antes de realizar cualquier optimización de rendimiento, también quería comprobar el rendimiento de los cambios en la rama. La adición de elementos personalizados agrega una sobrecarga de rendimiento del 10% en comparación con el resultado actual en los puntos de referencia maestros para la mutación del árbol. Sin embargo, la creación del nuevo entorno JSDOM ahora es de 3 a 6 veces más lenta en comparación con el maestro, requeriría una investigación más profunda para identificar la causa raíz.

Más detalles: aquí

Todos 89 comentarios

Sería interesante ver qué API usa webcomponents.js que jsdom no admite. Si tuviera que adivinar, sería mucho más fácil de implementar que la especificación completa de componentes web.

Dicho esto, sería genial implementar componentes web. Probablemente no sea tan difícil como uno podría pensar: las especificaciones son relativamente pequeñas.

Solo tuve tiempo de profundizar un poco en esto:

En primer lugar, no tenemos Window definidos en el alcance de la ventana. Acabo de parchear esto con this.Window = this.prototype en el constructor Window .
En segundo lugar, webcomponentsjs espera que Window tenga otro prototipo, es decir, el prototipo EventTarget , que no implementamos como una entidad separada.

Solo un poco de información, porque tenía un poco de tiempo.

Agradable. Debería poder exponer Window con bastante facilidad. El prototipo de EventTarget es un poco más complicado, pero parece factible dada la forma en que implementamos actualmente esas cosas; Ha sido una tarea pendiente mía.

De acuerdo, los parches hasta ahora son bastante fáciles:

  • [x] this.Window = Window; en el constructor de ventanas
  • [x] inherits(dom.EventTarget, Window, dom.EventTarget.prototype); después de la definición de Ventana

El próximo bloqueo de webcomponents.js ocurre debido a que no implementamos HTMLUnknownElement (#1068), después de aclarar que necesitamos implementar SVGUseElement . Eso es en lo que estoy bloqueado actualmente, porque aparentemente a webcomponents.js no le gusta el SVGUseElement compensado por un HTMLDivElement y lanza una afirmación.

De acuerdo, me registré en Polyfill un poco más, necesitamos implementar/necesita corregir lo siguiente:

  • [x] HTMLUnknownElement #1068
  • [ ] SVGUseElement
  • [ ] window.CanvasRenderingContext2D
  • [ ] API de rango (incluyendo: document.getRange() , window.getSelection() , window.Range , window.Selection ; #804 podría ser un comienzo)
  • [ ] npm i canvas

(lista no exhaustiva por ahora)

Un comienzo es algo como lo siguiente:

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

Hecho esto, hay un error en nuestro constructor HTMLDocument , lo que conduce a un error de pila máxima de llamadas. Por el momento, el constructor es solo para uso interno; sin embargo, es válido que algún script en el sitio lo llame, por lo que debemos hacer que ese constructor esté disponible para el consumo público.

+1 Me encantaría ver WebComponents en jsdom, particularmente a medida que Polymer gana popularidad, sería genial poder probar elementos personalizados en un sistema sin cabeza.

En este momento, no existe una definición de componentes web entre navegadores, por lo que sería prematuro implementarla. (No solo vamos a copiar Chrome). Mientras tanto, puede intentar usar Polymer con jsdom.

@domenic bastante justo. Bueno, lo que busco es más el soporte para WebComponents.js polyfill, ya que de eso depende Polymer, o webcomponents-lite (polyfills todos excepto Shadow DOM) en este momento. Hice algunos intentos para que Polymer funcionara en jsdom, pero hasta ahora no tuve suerte. Supongo que las tareas de @Sebmaster en el comentario anterior al menos deberán ser parcheadas primero.

Tengo entendido que hay tres polyfills separados en cuestión. El que está en el OP está separado de Polymer. Luego están los polyfills de webcomponents.org, que solían usarse en el antiguo Polymer. Luego, en Polymer 1.0, tienen sus propios polyfills, creo, que no son realmente polyfills, sino bibliotecas alternativas que hacen cosas como componentes web. Tal vez eso sea webcomponents-lite.

En el repositorio de WebComponentsJS, dice que webcomponentsjs-lite es una variante , que proporciona polyfills para todos _excepto_ Shadow DOM, que luego Polymer intenta corregir de forma independiente usando su sistema Shady DOM. Entonces, estoy bastante seguro de que Polymer se basa en WebComponents tanto como puede, con el polyfill de WebComponentsJS haciendo el trabajo duro. Se supone que la versión lite tiene un peso significativamente menor (curiosamente...) así que veré si puedo identificar qué es lo que jsdom necesita para la versión lite. ¿Cuáles cree que son las posibilidades de que el polyfill (lite o full) funcione en jsdom?

Es muy difícil saberlo sin investigar un poco... espero con ansias lo que descubras.

Sí, creo que mi lista de tareas pendientes sigue siendo aplicable y necesaria para usar las correcciones. Fusionar #1227 podría hacernos mucho más rápidos con la implementación de interfaces compatibles con los estándares para que podamos arreglar las que faltan más rápidamente.

Comencé a trabajar (probablemente de manera ingenua) para agregar CustomElementsRegistry como una forma de comprender cómo se estructura jsdom. Agregué "custom-elements/custom-elements-registry/define.html" a la lista de pruebas de la plataforma web y pasa cuando no debería (todavía no he implementado lo suficiente). Estoy bastante seguro de que la prueba no se está ejecutando realmente, ya que incluso agregar throw en la parte superior de la prueba no evitará que pase. Así que obviamente me perdí algo; Además de agregar la prueba en test/web-platform-tests/index.js , ¿hay algo más que deba hacer?

Parece que eso se debe a que fallamos en la línea inicial const testWindow = iframe.contentDocument.defaultView; porque contentDocument no está definido por alguna razón. Podría ser un problema con nuestro orden de carga frente a la ejecución del script, pero no lo he investigado. Espero que eso te ayude a solucionar eso. Es posible que tengamos que simplificar la prueba para nuestros propósitos (por ahora).

Eso ayuda mucho, gracias! Veré si puedo averiguar qué está pasando allí, y si no, crearé una prueba simplificada como me recomendaste.

@Sebmaster En caso de que esté interesado, investigué un poco sobre lo que está sucediendo con esa prueba y los resultados me sorprenden.

La prueba utiliza la función de acceso con nombre de html . Esto significa que puedes hacer cosas como:

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

_Sin embargo_, si el elemento tiene un contexto de navegación anidado, el global debería señalarlo en su lugar (consulte la especificación vinculada). Para iframe, ese es el contentWindow . jsdom hace esto bien, incluso hay una prueba . Safari también lo hace bien.

Lo loco es que Chrome y Firefox se equivocan; el global apunta al iframe, no a contentWindow. Al ver esto, asumí que era un error de jsdom e investigué un poco, y finalmente encontré esa prueba, lo que me llevó a la especificación.

tldr; trabajar en jsdom es muy educativo y ustedes hacen un trabajo increíble.

Pasando a archivar errores en los respectivos navegadores. También enviaré un PR a web-platform-tests, también encontré algunos otros errores en la prueba.

Esta es una motivación aún mayor para las pruebas ascendentes como https://github.com/tmpvar/jsdom/blob/master/test/living-html/named-properties-window.js para WPT. ¡Gracias por tu publicación! Me hace sentir muy bien acerca de jsdom ^_^

¡Hola!

Me las arreglé para hacer que Custom Elements polyfill funcione con jsdom combinando

Nota: el repositorio usa jsdom 8.5.0. La razón es que solo tuve éxito con un polyfill de MutationObserver, que usa Mutation Events internamente. Los eventos de mutación se eliminaron después de 8.5.0 debido al mal rendimiento. Si aparece Mutation Observer nativo, eliminaré el polyfill y actualizaré a la última versión de jsdom.

¡Tengo el jsdom más reciente y https://github.com/WebReflection/document-register-element me funciona! He estado experimentando con los polyfills más oficiales y tengo problemas por alguna razón. Mi objetivo es hacer que al menos los elementos personalizados y las importaciones html funcionen... sería increíble si pudiéramos hacer que Polymer también funcione.

Puedo hacer que los scripts de Polymer se ejecuten sin errores. Incluso puedo crear un componente y pasarlo al constructor Polymer. Después de eso, falla en silencio. Creo que shadow DOM es el problema.

He estado tratando de hacer que webcomponentsjs HTML imports polyfill funcione. Puedo hacer que se ejecute el script, y creo que mis importaciones HTML ejecutan una solicitud xmlhttp, pero no parece que se ejecuten los scripts en mis importaciones.

¿Te importaría compartir un ejemplo @lastmjs? Actualmente estoy hasta las rodillas en componentes web. Si puedo ser de ayuda, con gusto contribuiré contigo.

@snuggs ¡Gracias! Dame un día o dos, estoy en medio de algunas cosas urgentes en este momento.

@snuggs Si podemos hacer que el polyfill webcomponents-lite funcione, deberíamos poder usar Polymer. Shadow DOM parece ser el polyfill más difícil de hacer funcionar hasta ahora, y si usamos webcomponents-lite no tendremos que preocuparnos por eso por el momento, porque tendremos acceso a template , custom elements y HTML imports .

Puedo hacer que las importaciones de HTML funcionen con el polyfill webcomponents-lite . Me encontré con un comportamiento extraño, luego me encontré con esto: https://github.com/Polymer/polymer/issues/1535 Parece que las importaciones de HTML solo se pueden cargar a través de un protocolo sin archivos habilitado para cors. Así que hice girar un servidor http rápido en el directorio raíz de mi proyecto:

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

Y aquí está el código básico con el que he estado trabajando:

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

Por alguna razón, tengo que envolver la instanciación de TestApp en un setTimeout . Parece que la importación de Polymer HTML no está bloqueando la representación del resto del HTML, por lo que sin setTimeout el constructor de Polymer no está definido. ¿Es ese el comportamiento normal para las importaciones de HTML?

Se llama a beforeRegister , por lo que el constructor Polymer está haciendo algo. Así que ahora tenemos importaciones HTML efectivas, por supuesto, las plantillas funcionan con el polyfill webcomponents-lite . No estoy seguro de cómo le está yendo al polyfill de elementos personalizados.

Cuando pongo dentro de la clase TestApp un método ready o created , no se llaman. Parece que los eventos del ciclo de vida no se manejan correctamente. La raíz de ese problema podría estar en la implementación del polyfill de elementos personalizados. Seguiré jugando.

Posibles problemas a resolver:

  • [ ] Las importaciones de HTML no se bloquean correctamente
  • [ ] ¿Funciona o no el polyfill del elemento personalizado?
  • [ ] No se llama a los métodos del ciclo de vida de los polímeros

Más retoques conducen a más conocimientos. Creo que el orden de las importaciones y las matriculaciones nos puede estar complicando las cosas. Cuando ejecuto const testApp = document.createElement('test-app'); después de llamar al constructor Polymer, se llaman los métodos created y ready , pero no el método attached . ¿Quizás jsdom no está manejando correctamente los literales de elementos personalizados? Además, incluso cuando se llama a document.body.appendChild(testApp) , nunca se llama al método de ciclo de vida attached .

Esto podría ayudar a comprender el orden de carga: https://github.com/webcomponents/webcomponentsjs#helper -utilities

@lastmjs he estado lanzando monedas entre CustomElementRegistry.define() y document.registerElement() . Vi que Domenic hizo una gran contribución y combinó algunos trabajos relacionados con whatwg (https://github.com/whatwg/html/issues/1329). Parece que se está realizando una migración de API. Por ejemplo, creo que la especificación llama a connectedCallback que se combina con la funcionalidad attachedCallback . También suponiendo que quiso decir attachedCallback cuando dijo attached ya que ese controlador no es parte de la API. He experimentado define() y registerElement() disparando diferentes devoluciones de llamada correspondientes a cada método. Descubrí la estrategia de elementos personalizados. HTMLImports Domenic mencionó antes una implementación que utiliza un parche XMLHTTPRequest. Creo que puedo convertir directamente a DocumentFragment directamente desde la respuesta. Huele a que podría ser aceite de serpiente con las "importaciones". Una importación "falsa" puede ser donde vive la cordura.

También parece haber alguna tontería con super() que se llama a HTMLElement al transpilar desde ES6 -> ES5, así que esté atento a eso. He estado experimentando esto con Rollup.js/Babel y me vi obligado a usar la corrección (liviana) del paquete webcomponents.
https://developers.google.com/web/fundamentals/getting-started/primers/customelements

Por último, parece que tengo (más) éxito cuando creo con una etiqueta prototipo.

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

Como @domenic me mencionó antes, queremos ser cautelosos para implementar las especificaciones del mínimo común denominador y no solo hacer lo que hace GOOGLE. Parece que las líneas están borrosas con los componentes web. Pero soy un fan.

¿Con qué métodos has estado trabajando?

Hasta ahora, he estado jugando principalmente con los polyfills webcomponents-lite solamente, y Polymer < 2.0. Entonces, cuando mencioné el método attached , me refería al método de ciclo de vida de Polymer que usan en lugar del attachedCallback . Además, que yo sepa, los polyfills aún no han cambiado a la nueva especificación de elementos personalizados v1. Así que todo lo que estoy jugando es solo con la esperanza de que Polymer funcione con los polyfills actuales.

@snuggs ¿Está utilizando polyfills en este momento o está trabajando en una implementación real en jsdom?

@lastmjs No uso polyfills porque siento que no es necesario obtener el 80% del camino. La plataforma es lo suficientemente madura ahora que con un pequeño ajuste por adelantado puede usar las construcciones nativas. Me gusta usar herramientas livianas (generalmente hechas a mano) en lugar de marcos. Dicho esto, esa no es la mayoría de la gente. Parece que la intención que tiene Domenic es Elementos personalizados 👍 importaciones html 👎 pero no hay problema en extender XMLHTTPRequest para manejar el fetching del documento que nos llevaría allí. Eso fue hace unos 6 meses. Mucho ha cambiado desde la implementación. Muy posiblemente pensando. Entonces, ¿dónde terminamos @lastmjs?

@snuggs Quizás lo más sensato y preparado para el futuro es implementar soporte de primera clase para elementos personalizados y Shadow DOM en jsdom. Ambos estándares están en v1 y parece probable, por lo que escucho, que la mayoría de los principales navegadores los implementarán. ¿Cómo deberíamos abordar esto? Tengo un tiempo limitado en este momento, pero tal vez podamos establecer lo que se debe hacer al menos. @domenic ¿Tiene alguna sugerencia sobre cómo avanzar con estas implementaciones o alguna razón por la que no deberíamos hacerlo?

No tengo sugerencias concretas, además de implementar la especificación :)

Tengo una sucursal donde trabajé en esto hace algún tiempo (las especificaciones han cambiado un poco desde entonces). Implementar CustomElementsRegistry fue bastante fácil, donde tuve problemas fue descubrir cómo entretejer reacciones de elementos personalizados en la base de código y cuándo deberían llamarse y desde dónde. Si tuviera que retomar esto (sin promesas), probablemente sería en eso en lo que me concentraría.

@matthewp Eso suena útil, ¿dónde puedo encontrar esa rama?

@matthewp sí, eso sería bueno

https://github.com/matthewp/jsdom/commits/custom-elements como dije, la especificación cambió desde entonces, por lo que está desactualizada. Y esta es la parte más fácil, pero es un punto de partida si alguien lo quiere. @snuggs @lastmjs

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

Personalmente, simplemente apoyar el elemento personalizado ya sería genial.

(Tenga en cuenta que tengo entendido que phantomJS 2.5 debería admitir al menos Plantillas y tal vez elementos personalizados a medida que avanzan en la versión más reciente de Webkit, no estoy seguro de cuál).

En realidad, me burlo de los elementos personalizados, usando el elemento de registro de documento lib

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

Genial, ¿has tenido algún problema?

hasta ahora no :D

pero necesito escribir más especificaciones, cubrir más cosas para sentirme mejor

Impresionante ver que hay una manera. Por mucho que me guste el polímero, la configuración de prueba es un infierno y tener jsdom como respaldo es agradable;) Gracias por poner el trabajo en

¡Parece que hay relaciones públicas que hacen que esto avance! https://github.com/tmpvar/jsdom/pull/1872

En realidad, me burlo de los elementos personalizados, usando el elemento de registro de documento lib @darlanmendonca

Debería leer este enlace sobre cómo adjuntar jsdom globals al nodo global. Es un antipatrón.

Hola a todos,
Estoy un poco confundido con respecto al estado de ejecución de Polymer dentro de JSDOM (usando Node.js 6.7.0 y JSDOM 11.1.0). He intentado varias cosas, con resultados mixtos. Estaría muy agradecido si alguien pudiera informarme aquí ...

Lo que hice hasta ahora:

1) Encendí un servidor http desde mi directorio raíz

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

2) Cargué uno de mis componentes de polímero en 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);
})

(También intenté cargar el archivo del componente desde el sistema de archivos, con los mismos resultados).

3) Este es mi código de componente:

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

(Agregué el encabezado HTML para cargar el polyfill de los componentes web).

¿Qué puedo observar?

Cuando ejecuto esto, veo

  • que el polyfill de webcomponents se está cargando desde el servidor web
  • el mensaje "javascript se está ejecutando" en la consola

lo que no veo

  • que el componente polímero.html se está cargando desde el servidor web
  • el mensaje "los componentes web están listos" en la consola

Esto me lleva a la conclusión de que el evento WebComponentsReady no se activa (¿probablemente porque la importación de HTML no funciona?). También,
window.WebComponents contiene { flags: { log: {} } } -- falta el indicador ready .

También probé algunas burlas y polirellenos:

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

pero eso no parecía cambiar nada.

Ahora, me pregunto :-) ¿Se supone que esto funciona? Si es así, ¿por qué no me funciona? Si no es así, ¿qué falta / se requiere para que funcione?

¡Gracias por cualquier idea!

Moín

Incluso lo intenté con aún menos éxito y me rendí a esperar cuál será el resultado de esta discusión aquí.

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

no es una solución solo otro intento fallido. La misma confusión por cierto. Misma conclusión también

Se ha demostrado que el polirrelleno de elementos personalizados en jsdom es un gran desafío. ¿Alguien puede enumerar los desafíos de implementar eso en jsdom? Tratando de evaluar el nivel de esfuerzo para lograrlo.

El obstáculo fundamental es que jsdom comparte constructores y sus prototipos .

Esto hace que sea básicamente imposible implementar un registro de elementos personalizados por ventana, porque el constructor HTMLElement se comparte entre todas las ventanas. Entonces, cuando realiza la llamada super() en su constructor de elementos personalizados, el constructor HTMLElement que se está ejecutando ahora no sabe en qué ventana buscar las cosas. Esto apesta.

No estoy seguro de si hay buenas soluciones intermedias. El arma grande es mover jsdom a una arquitectura que permita constructores/prototipos no compartidos. Podríamos hacer esto de varias maneras, todas con diferentes compensaciones. Tal vez querríamos abrir un tema dedicado para discutirlo con el equipo y la comunidad, pero por ahora permítanme enumerar los que me vienen a la mente:

  • Use [WebIDL2JSFactory] para todo en jsdom, o al menos HTMLElement y todos sus descendientes. No estoy seguro de si [WebIDL2JSFactory] aún funciona bien con la herencia, pero podría funcionar. Esta alternativa hace que todos paguen el costo de constructores/prototipos adicionales, pero tal vez eso sea mejor que hacer que los elementos personalizados sean una característica opcional.
  • Tenga una opción en la que jsdom ejecute todos los módulos de definición de clase dentro de la zona de pruebas vm . Por ejemplo, tenga algún paso de compilación que agrupe todas las API web en jsdom, luego, cuando cree una nueva ventana, hacemos vm.runScript() con ese paquete dentro del nuevo sandbox global. Esto probablemente nos permitiría deshacernos de [WebIDL2JSFactory] .

Supongo que otra solución sería implementar elementos personalizados con una advertencia gigante de que el registro de elementos personalizados es global por proceso de Node.js. Eso parece terrible sin embargo.


Después de ese obstáculo inicial, el resto es relativamente sencillo en términos de seguir las especificaciones. La parte más difícil probablemente será implementar [CEReactions] y actualizar todos nuestros archivos IDL para tener eso en los lugares apropiados, pero no es demasiado difícil.

También he estado pensando en tener una versión prototipo separada. Estos son algunos de mis pensamientos.

No estoy seguro de si [WebIDL2JSFactory] aún funciona bien con la herencia, pero podría funcionar.

No, no lo hace, y no estoy seguro de cómo hacer que funcione exactamente. La segunda solución es mucho más sencilla en mi opinión.

Tenga una opción en la que jsdom ejecute todos los módulos de definición de clase dentro del entorno limitado vm .

Esto es lo que preferiría. El principal problema es pasar las clases impl al sandbox vm durante la inicialización, aunque eso se puede hacer colocando todo, desde el contexto externo, en una propiedad global, y delete esa propiedad global después de que se haya hecho. . También permitiría implementar correctamente [NamedConstructor] y un par de otros atributos extendidos, y tal vez incluso generar una instantánea de inicio V8 para un entorno jsdom si alguien es lo suficientemente atrevido.

Todo el asunto [WebIDL2JSFactory] fue un truco en primer lugar, y me encantaría deshacerme de él lo antes posible.

Los comentarios de +1 no son útiles para desarrollar esta función, por lo que eliminaré al menos uno reciente.

Hola, no sabía que @TimothyGu estaba trabajando en esto.
De hecho, tengo registro y creación de elementos personalizados trabajando en
https://github.com/mraerino/jsdom/tree/custom-elements-spec

Estoy tratando de ser lo menos invasivo posible y también de mantenerme lo más cerca posible de las especificaciones.
Las pruebas de la plataforma web Custom Element Registry están pasando.

Mientras pirateaba esto anoche, encontré una solución que funciona sin modificar webIdl2JS.
Consulte aquí: https://github.com/mraerino/jsdom/commit/592ad1236e9ca8f63f789d48e1887003305bc618

@TimothyGu , ¿estaría dispuesto a combinar fuerzas en este caso?

Solo algunas actualizaciones aquí:
Estoy bastante seguro de mi implementación de la especificación, pero actualmente estoy atascado debido al atributo IDL extendido [HTMLConstructor] . Por eso abrí https://github.com/jsdom/webidl2js/issues/87

Mientras tanto, implementaré el algoritmo [HTMLConstructor] usando un atributo [Constructor] para poder cambiar fácilmente más tarde. (Inicialmente lo implementé insertando una clase HTMLElement simulada en window , pero esto no parecía correcto).

Sí, como se indica en https://github.com/tmpvar/jsdom/issues/1030#issuecomment -333994158, la implementación correcta de HTMLConstructor requerirá cambios fundamentales en la arquitectura de jsdom.

¿Tiene alguna información sobre cuántas de las pruebas de la plataforma web está pasando su versión?

Solo los customElementRegistry por ahora, y podría estar totalmente equivocado acerca de mi progreso.

Editar: Ok, después de volver a leer tu comentario, entendí lo que quieres decir. Lo intentaré con mi implementación, pero @TimothyGu también parece estar trabajando en la separación.

Uso Polymer, así que estoy :+1: en esta función de solicitud

@dman777 @mraerino Lo mismo para los desarrolladores de slim.js. Slim utiliza API de componentes web nativos y no puede heredar HTMLElement sin modificaciones en jsdom.

Han pasado tres años desde que se abrió este número. ¿Alguien puede decir cuándo aproximadamente jsdom admitirá elementos personalizados?

TL;RD

Trabajé durante las últimas 2 semanas para evaluar la viabilidad de agregar soporte para elementos personalizados en jsdom. Aquí está el resultado de la investigación.

Puede encontrar una implementación de elemento personalizado que cumpla con las especificaciones aquí: https://github.com/jsdom/jsdom/compare/master...pmdartus :custom-elements?expand=1. Todavía hay algunas asperezas aquí y allá, pero al menos la mayoría de las pruebas WPT pasan . Las pruebas fallidas restantes son problemas conocidos de JSDOM o problemas menores que se pueden abordar cuando abordemos la implementación real en jsdom.

Ahora aquí están las buenas noticias, ahora que Shadow DOM es compatible de forma nativa, tanto con la rama de elementos personalizados como con el observador de mutaciones, pude cargar y renderizar la última versión del ejemplo de la aplicación Polymer 3 hello world en jsdom 🎉. En su estado actual, la rama no puede cargar una aplicación de Stencil (el modo de desarrollo de Stencil requiere algunas características no compatibles como module , y el modo de producción se inicia por una razón desconocida).

Plan de ACCION

Aquí hay una lista de los cambios que deben ocurrir primero antes de comenzar a abordar la implementación real de especificaciones de elementos personalizados. Cada elemento de la lista es independiente y se puede abordar en paralelo.

Compatibilidad con atributos extendidos de [CEReactions] IDL

Una de las funciones básicas que faltan en jsdom para agregar soporte para elementos personalizados son los atributos [CEReactions] . Pude solucionar parcialmente este problema parcheando las propiedades correctas del prototipo. Este enfoque funciona siempre que la pila de reacción del elemento personalizado sea global y no por unidad de contextos de navegación de origen similar relacionados, ya que se comparten todos los prototipos de las interfaces.

Este enfoque tiene algunas deficiencias ya que algunas interfaces tienen los atributos [CEReactions] asociados con propiedades indexadas ( HTMLOptionsCollection , DOMStringMap ). Internamente, jsdom usa Proxies para rastrear la mutación a esas propiedades. El prototipo de parcheo de la interfaz no funciona en este caso. Otro enfoque para solucionar este problema sería parchear la implementación en lugar de la interfaz (no implementada).

No estoy lo suficientemente familiarizado con el webidl2js interno, pero deberíamos explorar agregar un gancho global para [CEReactions] .

Cambios:

Compatibilidad con atributos extendidos de [HTMLConstructor] IDL

Como @domenic explicó anteriormente , agregar soporte para [HTMLConstructor] es uno de los principales bloqueadores aquí.

Pude solucionar este problema aquí parcheando el constructor de la interfaz para cada contexto de navegación. El constructor de la interfaz podría acceder a la ventana correcta y al objeto del documento manteniendo el prototipo compartido. Este enfoque también evita la sobrecarga de rendimiento de volver a evaluar el prototipo de interfaz para cada nuevo contexto de navegación.

No estoy seguro de si es el mejor enfoque aquí, pero se ajusta a los requisitos sin introducir una sobrecarga de rendimiento adicional.

Cambios:

Hacer que el algoritmo de análisis de fragmentos sea compatible con las especificaciones (#2522)

Como se discutió aquí , la implementación del algoritmo de análisis de fragmentos HTML utilizada en Element.innerHTML y Element.outerHTML es incorrecta. El algoritmo de análisis debe refactorizarse para que las devoluciones de llamada de reacciones de elementos personalizados se invoquen correctamente.

Cambios:

Mejorar la búsqueda de interfaz para crear algo de elemento.

Uno de los problemas con los que me topé rápidamente fue la introducción de nuevas dependencias circulares al agregar soporte para la creación de elementos personalizados. Tanto CustomElementRegistry como el algoritmo de creación de elementos requieren acceso a las interfaces de Element, lo que crea una pesadilla de dependencias circulares.

El enfoque adoptado en la rama fue crear un InterfaceCache , que permitiría la búsqueda de interfaz por espacio de nombres y nombre del elemento, pero también por nombre de interfaz. Los módulos de interfaz se evalúan de forma perezosa y se almacenan en caché una vez evaluados. Este enfoque elimina las dependencias circulares porque las interfaces ya no son necesarias en el nivel superior.

Este es un enfoque para resolver este problema de larga data en jsdom, uno de los problemas con este enfoque es que tal vez rompería la versión webpacked/browserified de jsdom (no probada).

Cambios:

~Reparar Element.isConnected para admitir Shadow DOM (https://github.com/jsdom/jsdom/pull/2424)~

Este es un problema que surgió con la introducción del shadow DOM , isConnected devuelve false si el elemento es parte de un shadow tree. Es necesario agregar una nueva prueba WPT aquí, ya que ninguna prueba verifica este comportamiento.

Cambios:

Arreglar el documento de nodo HTMLTemplateElement.templateContents (#2426)

El contenido de la plantilla , tal como se define en la especificación, tiene un documento de nodo diferente al del propio HTMLTemplateElement. jsdom no implementa este comportamiento hoy y HTMLTemplateElement y el contenido de la plantilla comparten el mismo
nodo de documento.

Cambios:

  • HTMLTemplateElement-impl.js
  • htmltodom.js . Este cambio también tiene algún efecto posterior en el analizador. Si el elemento de contexto es un HTMLTemplateElement, el algoritmo de análisis de fragmentos de HTML debe seleccionar el nodo del documento del contenido de la plantilla y no del propio elemento.

Agregue los pasos de adopción faltantes a HTMLTemplateElement (#2426)

El HTMLTemplateElement necesita ejecutar algunos pasos específicos cuando se adopta en otro documento. Hasta donde yo sé, es la interfaz del suelo tener un paso de adopción especial. La implementación del algoritmo del nodo de adopción también debería actualizarse para invocar este paso de adopción.

Cambios:

Agregue soporte para la búsqueda de isValue en el serializador parse5

El algoritmo de serialización de fragmentos HTML , al serializar un elemento, busca el valor asociado con el elemento y lo refleja como un atributo en el contenido serializado. Sería interesante agregar otro enlace en el adaptador de árbol parse5, que buscaría el valor asociado con un elemento getIsValue(element: Element): void | string .

Un enfoque alternativo (no implementado) sería agregar un cambio en el comportamiento del gancho getAttrList actual para devolver el valor a la lista de atributos, si el elemento tiene un valor asociado.

Rendimiento

Antes de realizar cualquier optimización de rendimiento, también quería comprobar el rendimiento de los cambios en la rama. La adición de elementos personalizados agrega una sobrecarga de rendimiento del 10% en comparación con el resultado actual en los puntos de referencia maestros para la mutación del árbol. Sin embargo, la creación del nuevo entorno JSDOM ahora es de 3 a 6 veces más lenta en comparación con el maestro, requeriría una investigación más profunda para identificar la causa raíz.

Más detalles: aquí

@pmdartus esto es muy prometedor, ¡excelente trabajo! He estado usando mi rama hack jsdom-wc, por falta de una mejor opción. Veo un comportamiento extraño y esperaba cambiarme por su rama, pero tengo problemas.

Registro elementos personalizados como:

class Component extends HTMLElement {

}

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

Pero si lo hago:

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

Obtengo un inmediato: Error: Uncaught [TypeError: Illegal constructor] .

Tiene alguna idea sobre esto?

El siguiente fragmento de código se ejecuta correctamente en la rama custom-elements de mi bifurcación: 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" 
});

El constructor ilegal probablemente sea lanzado por el constructor HTMLElement original, los cambios realizados en la rama deberían parchear el constructor para cada nuevo objeto de ventana. @tbranyen ¿Tiene un ejemplo de reproducción completo para que pueda probarlo localmente?

Hola, @pmdartus . Todavía no estoy muy seguro de qué está causando mis problemas, pero escribí un código aislado directamente en tu rama que funcionó perfectamente:

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
}

Esto es efectivamente lo que hace mi sistema de prueba, pero se rompe. Así que puede ser algo de mi parte.

Editar:

Ah, está bien, creo que reduje dónde es más probable que ocurra el problema. Debo conservar el constructor HTMLElement inicial creado. Si ajusto el código anterior para reutilizar el constructor:

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

Esto producirá lo siguiente:

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

Edición 2:

Lo encontré:

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

Al darse cuenta de que este hilo tiene 4 años, ¿los componentes web son compatibles o está previsto que lo sean?

Sería bueno tener componentes web en esto, pero como alternativa, si alguien quisiera saber... ahora se puede usar cromo sin cabeza en el nodo para renderizar/construir el archivo html sting.

Al darse cuenta de que este hilo tiene 4 años, ¿los componentes web son compatibles o está previsto que lo sean?

Es un trabajo en progreso ya que la especificación se implementa pieza por pieza.

El polyfill en: https://github.com/WebReflection/document-register-element ¡Funciona de maravilla! ¡Mi más sincero agradecimiento al autor!

Para aquellos que luchan con el mismo problema, simplemente hagan lo siguiente:

npm install -D document-register-element

En su configuración de broma, establezca un archivo de instalación que se ejecutará antes de todas sus pruebas:

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

Y finalmente, dentro de ese archivo ('tests/setup.js'):

import 'document-register-element'

Después de hacer esto, ¡registrarse y crear elementos personalizados en jsdom a través de document.createElement('custom-component') funciona perfectamente! Sin embargo, los fragmentos no parecen funcionar. (No estoy usando shadow dom, por cierto).

@tebanep como mencionó que polyfill no es adecuado para la mayoría del trabajo de componentes web, si no es compatible con Shadow DOM, entonces no es realmente una comparación de lo que esto está logrando.

@tebanep Gracias. Como no necesito shadow dom, esta es una gran información.

¿Alguna esperanza de que esto se implemente? En este momento estamos usando jsdom-wc con muchos errores, pero no tenemos una solución mejor. Mi esperanza y oración sobre este tema.

@dknight Sé que jsdom-wc es prácticamente un truco para que funcione un poco. Publiqué el módulo con una compatibilidad significativamente mejor bajo mi alcance personal de npm. Puedes instalarlo con:

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

Lo uso ahora para todas mis necesidades de componentes web JSDOM hasta que se estabilice.

@tbranyen ¿Anulaste la publicación de tu fork? No puedo encontrarlo en npm.

@KingHenne dangit, parece que terminó en nuestro registro de "empresa". Acabo de publicar al público npm. ¡Lo siento por eso!

No me @, pero ¿no deberíamos simplemente probar el código web ui en un navegador real, por ejemplo, con titiritero? El problema de soporte de Shadow DOM/Custom Elements desaparece entonces.

No publiques un comentario si no quieres ser @'d @Georgegriff. Esa es una estrategia válida, pero es lenta y con errores en otros sentidos, ya que efectivamente estás haciendo IPC, sí, incluso con titiritero. Cuando el navegador muere, no es obvio por qué en muchos casos. Solo intente y depure los problemas del titiritero en broma para tener una idea de por qué no siempre es la mejor idea.

Personalmente, prefiero seguir probando sincrónicamente y en el mismo hilo. No hay ninguna razón por la que una implementación aislada de la especificación no deba ser un tiempo de ejecución razonable para probar componentes. JSDOM es efectivamente un navegador en este punto, pero no tan estable como los tres grandes.

El polyfill en: https://github.com/WebReflection/document-register-element ¡Funciona de maravilla! ¡Mi más sincero agradecimiento al autor!

Para aquellos que luchan con el mismo problema, simplemente hagan lo siguiente:

npm install -D document-register-element

En su configuración de broma, establezca un archivo de instalación que se ejecutará antes de todas sus pruebas:

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

Y finalmente, dentro de ese archivo ('tests/setup.js'):

import 'document-register-element'

Después de hacer esto, ¡registrarse y crear elementos personalizados en jsdom a través de document.createElement('custom-component') funciona perfectamente! Sin embargo, los fragmentos no parecen funcionar. (No estoy usando shadow dom, por cierto).

Funciona bien para mí, pero el connectedCallback nunca se llama, ¿alguna idea?

@FaBeyyy lo mismo para mí :(

@FaBeyyy @ majo44 , debe agregar su componente a un documento, es decir. document.body.appendChild(...) por connectedCallback para ser despedido. Por especificaciones, se llama cuando el componente se adjunta a un Dom.

JSDOM es efectivamente un navegador en este punto, pero no tan estable como los tres grandes.

En este punto, es más como los dos grandes, porque Microsoft se está deshaciendo del suyo, que ha estado con ellos durante tanto tiempo como Windows.

@FaBeyyy @ majo44 , debe agregar su componente a un documento, es decir. document.body.appendChild(...) por connectedCallback para ser despedido. Por especificaciones, se llama cuando el componente se adjunta a un Dom.

gracias capitán obvio pero ese no es el problema aquí. Si no supiera cómo funciona el ciclo de vida del componente, probablemente no estaría tratando de escribir pruebas 😄. Crearé un escaparate de repositorio más tarde cuando encuentre el tiempo.

@FaBeyyy
Así que encontré la configuración que funciona para mí. Tuve que agregar polyfill para MutationObserver. Estoy usando JSDOM para probar marsopas, con Jest, y la configuración de trabajo es:

// 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
Así que encontré la configuración que funciona para mí. Tuve que agregar polyfill para MutationObserver. Estoy usando JSDOM para probar marsopas, con Jest, y la configuración de trabajo es:

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

¡Genial gracias!

@ majo44 esto no es necesario con el último jsdom. Cuando trabaje con Jest (que usa jsdom v11), puede usar un entorno actualizado: https://www.npmjs.com/package/jest-environment-jsdom-fourteen

@mgibas gracias, con jest-environment-jsdom-fourteen también funciona bien y no se requiere polyfill del observador de mutación (pero la versión es 0.1.0, paquete de confirmación única :))

¿Hay un desglose de cuáles de las API de componentes web son actualmente compatibles con JSDOM? ¿Parece que se admite shadow DOM, pero no elementos personalizados (al menos en la rama de lanzamiento/repositorio)?

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

@tbranyen , ¿tiene el código fuente de su bifurcación disponible en alguna parte? Sería curioso mirar la diferencia 🙂

Estoy usando jest-environment-jsdom-fifteen como sugirió @ majo44 , y babel-polyfill y document-register-element (vea las respuestas de @mgibas ). Pero sigo recibiendo un error cuando intento recuperar mi shadow dom de componente web para las pruebas.

el.shadowRoot es nulo con:

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

¿Alguna idea de una solución? Para su información, ya se probó con Karma, Mocha, Chai y Jasmine.

Nada especial en mi componente:

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

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

Editar

Realicé una depuración con jsdom 15.1.1 para comprender mejor mi problema.
Aún así, no entiendo por qué es nulo aquí ...

Entonces, Element.shadowRoot está implementado desde 88e72ef
https://github.com/jsdom/jsdom/blob/1951a19d8d40bc196cfda62a8dafa76ddda6a0d2/lib/jsdom/living/nodes/Element-impl.js#L388 -L415

Después de document.createElement, this._shadowDom está bien en https://github.com/jsdom/jsdom/blob/15.1.1/lib/jsdom/living/nodes/Element-impl.js#L403. Y se crea cada elemento en el shadow dom (constructor de elementos llamado con los valores correctos).

Pero cuando llamo a el.shadowDom inmediatamente después de document.body.appendChild(el) (https://github.com/jsdom/jsdom/blob/15.1.1/lib/jsdom/living/nodes/Element-impl .js#L408), this. _shadowRoot es nulo.

Lo mismo después

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

O incluso si uso lo siguiente en lugar de document.

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

Para la reproducción, consulte:
https://github.com/noelmace/devcards/tree/jest

@nminhnguyen Supongo que puedes encontrar el código fuente de la bifurcación hecha por @tbranyan aquí https://github.com/tbranyen/jsdom/tree/initial-custom-elements-impl

Estoy tratando de probar componentes web hechos con lit-html y lit-element y noté esta diferencia al crear los elementos.

const myElem = new MyElem();

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

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

y cuando uso el document.createElement

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

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

console.log(myElem.shadowRoot) // null

Para configurar jest, solo uso un polyfill que es: setupFiles: ['document-register-element']

Parece que nunca se llama al método de renderizado en myElem . Al depurar un poco más, descubrí que el método initialize que está en el elemento iluminado nunca se llama.
Así que el segundo ejemplo funcionaría si lo hago

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

Creé un DOM alternativo que admite componentes web. Primero intenté hacer un PR, pero la forma en que funciona JSDOM me dificultó resolver mis necesidades allí. Eres libre de usarlo o mirar el código.

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

Ambiente de broma:
https://www.npmjs.com/package/jest-environment-happy-dom

Parece increíble. Gracias.

El lunes 7 de octubre de 2019 a la 1:08 a. m., capricorn86 [email protected] escribió:

Creé un DOM alternativo que admite componentes web. yo primero
traté de hacer un PR, pero la forma en que funciona JSDOM me dificultó resolver mi
necesidades allí. Eres libre de usarlo y/o mirar el código.

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

Ambiente de broma:
https://www.npmjs.com/package/jest-environment-happy-dom


Estás recibiendo esto porque estás suscrito a este hilo.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/jsdom/jsdom/issues/1030?email_source=notifications&email_token=ACQ5ZD5QUEITPND4SXWOHW3QNILSRA5CNFSM4A4G5SF2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEAOO5ZA#issuecomment-57,6707387
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/ACQ5ZDYU465DXI4KHBQH4KTQNILSRANCNFSM4A4G5SFQ
.

@capricornio86
Su trabajo hace que mi entorno de prueba sea simple, ¡gracias!
https://github.com/EasyWebApp/WebCell/tree/v2/MobX

@capricornio86
Su trabajo hace que mi entorno de prueba sea simple, ¡gracias!
https://github.com/EasyWebApp/WebCell/tree/v2/MobX

¡Gracias @TechQuery!

Parece increíble. Gracias.

El lunes 7 de octubre de 2019 a las 1:08 capricorn86 @ . * > escribió: he creado un DOM alternativo que admite componentes web. Primero intenté hacer un PR, pero la forma en que funciona JSDOM me dificultó resolver mis necesidades allí. Eres libre de usarlo y/o mirar el código. DOM: https://www.npmjs.com/package/happy-dom Entorno Jest: https://www.npmjs.com/package/jest-environment-happy-dom — Estás recibiendo esto porque estás suscrito a este hilo. Responder a este correo electrónico directamente, visualizarla en GitHub <# 1030? Email_source = notificaciones y email_token = ACQ5ZD5QUEITPND4SXWOHW3QNILSRA5CNFSM4A4G5SF2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEAOO5ZA # issuecomment-538767076>, o silenciar el hilo https://github.com/notifications/unsubscribe-auth/ACQ5ZDYU465DXI4KHBQH4KTQNILSRANCNFSM4A4G5SFQ .

¡Gracias @motss!

¿Hay un desglose de cuáles de las API de componentes web son actualmente compatibles con JSDOM? ¿Parece que se admite shadow DOM, pero no elementos personalizados (al menos en la rama de lanzamiento/repositorio)?

Yo también estaría interesado en esto :)

¿Fue útil esta página
0 / 5 - 0 calificaciones