b-fuze / deno-dom

Browser DOM & HTML parser in Deno
https://jsr.io/@b-fuze/deno-dom
MIT License
423 stars 47 forks source link

How to narrow type of `querySelectorAll` results? #141

Closed vwkd closed 1 month ago

vwkd commented 1 year ago

I'm getting type errors when iterating over a node list of elements.

import { DOMParser } from "deno_dom";

const doc = new DOMParser().parseFromString(html, "text/html")!;

const anchors = doc.querySelectorAll("a");

for (const anchor of anchors) {
  const href = anchor.getAttribute("href");
  // Property 'getAttribute' does not exist on type 'Node'.
}

How can I narrow the types to an (anchor) element type?

The signature querySelectorAll(selectors: string): NodeList suggest NodeList is missing a generic type such that we can supply an element type to narrow it down.

Interestingly, the signature querySelector(selectors: string): Element | null always returns an element instead of a node. Does querySelector never return a non-element node? If so, then why can querySelectorAll?

b-fuze commented 1 year ago

You want to add a type assertion for Element:

import { DOMParser, Element } from "deno_dom";

// ...

for (const anchor of anchors) {
  const href = (anchor as Element).getAttribute("href");
}
vwkd commented 1 year ago

That's what I'm currently doing but it's feels not ideal. For example, if used multiple times it requires assertions at each place, or a dummy reassignment.

Would it be possible to make NodeList generic, such that one can supply NodeList<Element> and querySelectorAll<Element>("a")?

b-fuze commented 1 year ago

Yeah, that's basically #4. When I get some time I'll make it happen. After I do that I'll have to make the different HTML element classes as well

jakubdonovan commented 1 month ago

Yeah, that's basically #4. When I get some time I'll make it happen. After I do that I'll have to make the different HTML element classes as well

I'm a little confused. How do I narrow down the HTMLElement type to HTMLImageElement? While working with jsdom, I would do this to access the src attribute:

const images = [
    ...new Set(
      [...owlDemoEl.querySelectorAll<HTMLImageElement>("img")].map((img) => img.src),
    ),
  ];

But while using deno-dom, the same code would give me an error saying that src does not exist because img is typed as HTMLElement. Of course, I could do it like so, but it's not as clean in my opinion.

 const images = [
    ...new Set(
      [...owlDemoEl.querySelectorAll("img")].map((img) =>
        img.getAttribute("src")
      ),
    ),
  ];

What did I miss?

vwkd commented 1 month ago

This issue should have been closed with #162 and #175.

@jakubdonovan Specific element types and APIs like img.src are a slightly different issue and AFAIK not supported yet. See #72.