直接用字串渲染 DOM?

緣起

故事是這樣的:有一串像是 HTML 的文字給你,然後你要用它渲染成 HTML。這要怎麼做呢?

這個情境很好用:如果後端要給你 HTML 文字、或是想玩玩爬蟲或其他功能的話,其實都很關鍵。

題外話,後端要給 HTML 文字,之後寫一篇來講。

機制

那麼瀏覽器有沒有呢?還真有:DOMParser。這個物件能把輸入的字串變成有效的 HTML DOM 文件。

接著我們可以這樣做:parseFromString(string, type)──

例如 MDN 提供的:

const parser = new DOMParser();
const htmlString = "<strong>Beware of the leopard</strong>";
const doc3 = parser.parseFromString(htmlString, "text/html");
console.log(doc3.body.firstChild.textContent);

這個範例可以顯示 Beware of the leopard 這文字。

實做

現在,假設我們想要 MDN 站內的連結,都顯示簡介。

我們先寫一個請求站內 HTML 字串的函式:

const get_html_string = async (url) =>
{
    const request = await fetch(url).then(res => res.text());
    return request;
};

這樣就可以用非同步的方法,去請求站內 HTML 字串了。

如果是站外的連結,因為有 CORS 的問題,所以可能比較不太可能。

總之,再繼續從 https://developer.mozilla.org/en-US/docs/Web/API/AbortController 觀察我們需要的文字。

(圖……再看看吧)

觀察下來,我們需要的是「The AbortController interface represents a controller object that allows you to abort one or more Web requests as and when desired.」這段文字。觀察一下,就用 document.querySelector(".main-page-content p").innerText 取得吧。

const parse_string = (html) =>
{   // `?.` 可以讓 undefined 的物件被略過
    const doc = new DOMParser().parseFromString(html, "text/html");
    return doc.querySelector(".main-page-content p")?.innerText ?? "";
};

然後把他們接在一起:

const main = async (url) =>
{
    const html = await get_html_string(url);
    const the_string = parse_string(html);
    return parse_string(html);
};

再把文字綁定在 title 屬性:

const add_title = async(dom) =>
{
    const t = await main(dom.href);
    dom.title = t;
};

最後只要把所有合條件的連結,都呼叫 add_title 就可以囉:

const links = [...document.querySelectorAll("a")].filter(a=> /developer.mozilla.org/g.test(a.href) );
links.forEach( add_title(dom) );

實做 TL;DR

合起來就是這樣:

const get_html_string = async (url) =>
{
    const request = await fetch(url).then(res => res.text());
    return request;
};

const parse_string = (html) =>
{   // `?.` 可以讓 undefined 的物件被略過
    const doc = new DOMParser().parseFromString(html, "text/html");
    return doc.querySelector(".main-page-content p")?.innerText ?? "";
};

const main = async (url) =>
{
    const html = await get_html_string(url);
    const the_string = parse_string(html);
    return the_string;
};

const add_title = async(dom) =>
{
    const t = await main(dom.href);
    dom.title = t;
};

const links = [...document.querySelectorAll("a")].filter( a =>
    /developer.mozilla.org/g.test(a.href)
);
links.forEach( dom => add_title(dom) );

這程式還有很多問題,不過先到此為止。

其他

XSS 呢?

XSS:只要把 HTML 貼到文件內並執行 JavaScript 的話,執行的 JavaScript 可以被加料,然後搞爛你的電腦。

我看了一下,結論是 DOMParser 有沙盒機制能阻止 XSS。只要不白目的把東西貼到 innerHTML 的話,看起來應該是可以的。

node.js 能不能用?

看來不行。因為有很多機制要處理

另,我個人推薦裡面列出的 jsdom 套件。

結語

直接用字串渲染為 DOM 的話,你可以用 DOMParser API。這個 API 用途廣泛,是個相當不錯的前端功能。

參考資料