HTMLにHTMLを含める


インスパイアド
この記事についてのコメント



FJones

これは、htmlincludeの(かなり粗野な)メソッドの上で、ウェブコンポーネントのために非常に面白いユースケースとして私を攻撃します.jsまた、これは非常に多くのCSP問題を打つようです.例えば、含まれるファイルから任意のスクリプトタグや外部リソースを読み込むのに苦労するでしょう.
挑戦のような音!ここでは、デザインの目標です
  • 単純な、フロントエンドのみHTML API他のHTMLドキュメント内のHTMLフラグメントを含むようにhtmlinclude.js
  • HTML Boilerplateは含まれていないHTMLフラグメントに必要です.例えば、<div></div> 罰金-必要はありません<!DOCTYPE html><html lang="en"><head><title>title</title></head><body><div></div></body></html>
  • 複数の子フラグメントを問題なくレンダリングします.例えば、<div>1</div> <div>2</div> だけでなく<div><div>1</div> <div>2</div></div> does
  • 一度レンダリングinclude-html コンポーネントはDOMに存在しません
  • リソースが正しく設定されている限り、Crossヘッダーを含む
  • ランscript 同じ起源のコンテンツのタグsanitize 属性セット
  • 動かないscript タグまたは何か他のクロス起源のコンテンツから
  • これ以上のADOがない場合は、実装です.

    起源


    この関数を使用して、含まれる内容が同じ原点から確認されます.もしそうでなければ、サードパーティにスクリプトを注入することができないように、それは間違いなく消毒を必要とします.
    /** @param {string} src */
    const isSameOrigin = (src) =>
        new URL(src, window.location.origin).origin === window.location.origin
    
    第2のパラメータを提供することによってbase URL constructor , 我々は解決するsrc 現在の原点からの相対パスです.そして、origin その二つは同じだ.
    例えば、
  • new URL('./bar.html', 'https://foo.co') 解決するhttps://foo.co/bar.html , その中でorigin まだhttps://foo.co , だから結果はtrue
  • new URL('https://baz.co/quux.html', 'https://foo.co') 解決するhttps://baz.co/quux.html . The base この場合のパラメータはsrc 既に完全修飾されています.The origin is https://baz.co , 異なるhttps://foo.co , だから結果はfalse
  • セーフHTML


    これは、必要に応じてHTMLを消毒するための関数です.
    /** @param {{ sanitize?: boolean } = {}} */
    const safeHtml = ({ sanitize } = {}) =>
        /** @param {string} html */
        (html) => {
            const sanitized = sanitize !== false ? DOMPurify.sanitize(html) : html
    
            return Object.assign(sanitized, {
                __html: sanitized,
            })
        }
    
    使用する DOMPurify , 広く使用され、戦いのHTMLソリューションのソリューションをテストした.
    使用Object.assign 文字列でString 追加のプロパティを追加したオブジェクト.加えることによって__html プロパティは、直接我々の結果を使用してdangerouslySetInnerHTML 我々が欲しいならば、我々はまだ要素のものにそれを直接割り当てることができますinnerHTML , それはまだ文字列です.sort of .
    const result = safeHtml()('<hr/>')
    
    result // String {"<hr>", __html: "<hr>"}
    result.valueOf() // "<hr>"
    '' + result // "<hr>"
    

    Webコンポーネント


    Webコンポーネントそのものです.
    class IncludeHtml extends HTMLElement {
        async connectedCallback() {
            const forceSanitize = Boolean(this.attributes.sanitize)
            const src = this.attributes.src.value
    
            if (!this.innerHTML.trim()) {
                this.textContent = 'Loading...'
            }
    
            const res = await fetch(src)
    
            const html = safeHtml({
                sanitize: !isSameOrigin(src) || forceSanitize,
            })(await res.text())
    
            const range = document.createRange()
    
            // make rendering of fragment context-aware
            range.selectNodeContents(this.parentElement)
    
            this.replaceWith(range.createContextualFragment(html))
        }
    }
    
    customElements.define('include-html', IncludeHtml)
    
    使用 range.createContextualFragment 実行するHTMLフラグメントを作成することもできますscript タグは、レンダリング(我々はまだ離れてそれらをsanitizedしていない仮定)に存在する.range.selectNodeContents 周囲のコンテキストを意識した方法で、レンダリングが期待通りに動作することを意味します.例えば、aを挿入しようとするとtr テーブルの外側には何も表示されませんが、テーブル内で期待通りに動作します.
    使用によってthis.replaceWith , コンテンツのレンダリングとして、DOMからWebコンポーネントをすぐに削除します.

    用途


    最後に、使用中のコンポーネントの例を示します.
    <nav>
        <include-html src="./includes/nav.html"></include-html>
    </nav>
    
    <main>
        <!--
            Including from 3rd-party source works
            (if CORS headers set properly on the source)
        -->
        <include-html
            src="https://dinoipsum.herokuapp.com/api/?format=html&paragraphs=2&words=15"
        ></include-html>
    </main>
    
    <footer>
        <include-html sanitize src="./includes/footer.html"></include-html>
    </footer>
    
    レンダリングされた出力を見ることができ、このライブCodesandboxで自分自身を試してみましょう.
    読書ありがとう!どのような改善は、APIや機能になりますか?