HTML-in-HTML 포함: 반복

에서 영감을 받다
이 기사에 대한 님의 의견:








FJones






이것은 htmlinclude.js의 (조잡한) 방법에 대한 웹 구성 요소의 매우 흥미로운 사용 사례로 저를 놀라게 합니다. 또한 이것이 상당히 많은 CSP 문제를 일으킬 것으로 보입니다. 예를 들어 포함된 파일에서 스크립트 태그나 외부 리소스를 로드하는 데 어려움을 겪을 것 같습니다.



도전처럼 들린다! 설계 목표는 다음과 같습니다.
  • htmlinclude.js와 유사한 다른 HTML 문서 내에 HTML 조각을 포함하는 간단한 프런트 엔드 전용 HTML API
  • 포함된 HTML 조각에 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>
  • 만큼 잘 작동합니다.
  • 일단 렌더링되면 include-html 구성 요소가 DOM에 더 이상 존재하지 않습니다
  • .
  • CORS 헤더가 리소스에 올바르게 설정되어 있는 한 원본 간 콘텐츠 포함을 허용합니다
  • .
  • script 속성이 설정되지 않은 경우 동일 출처 콘텐츠에서 태그를 실행합니다sanitize
  • .
  • 교차 원본 콘텐츠에서 태그 또는 기타 위험한 항목을 실행하지 않음script

  • 더 이상 고민하지 않고 구현은 다음과 같습니다.

    isSameOrigin



    이 기능을 사용하여 포함된 콘텐츠의 출처가 동일한지 확인합니다. 그렇지 않은 경우 제3자가 스크립트를 삽입할 수 있는 것을 원하지 않기 때문에 확실히 정리가 필요합니다.

    /** @param {string} src */
    const isSameOrigin = (src) =>
        new URL(src, window.location.origin).origin === window.location.origin
    


    base constructor에 두 번째 매개변수URL를 제공하여 현재 원점을 기준으로 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로 해석됩니다. base가 이미 정규화되었으므로 이 경우 src 매개변수는 무시됩니다. originhttps://baz.co이고 https://foo.co와 다르므로 결과는 false가 됩니다.

  • safeHTML



    필요한 경우 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 속성을 추가하면 원하는 경우 React의 dangerouslySetInnerHTML와 함께 결과를 직접 사용할 수 있지만 여전히 문자열이기 때문에 요소의 innerHTML에 직접 할당할 수 있습니다... sort of .

    const result = safeHtml()('<hr/>')
    
    result // String {"<hr>", __html: "<hr>"}
    result.valueOf() // "<hr>"
    '' + result // "<hr>"
    


    IncludeHtml 웹 구성 요소



    다음은 웹 구성 요소 자체입니다.

    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 을 사용한다는 것은 렌더링 시 존재하는 모든 script 태그를 실행하는 HTML 조각을 생성할 수 있음을 의미합니다(아직 제거하지 않았다고 가정). range.selectNodeContents는 렌더링이 주변 컨텍스트를 인식하는 방식으로 예상대로 작동함을 의미합니다. 예를 들어 테이블 외부에 tr를 삽입하려고 하면 아무 것도 렌더링되지 않지만 테이블 내에서는 예상대로 작동합니다.
    this.replaceWith 를 사용하면 콘텐츠가 렌더링될 때 즉시 DOM에서 웹 구성 요소를 제거합니다. 이는 백엔드 템플릿 프레임워크에서 기대하는 것과 유사합니다.

    용법



    마지막으로 사용 중인 구성 요소의 몇 가지 예는 다음과 같습니다.

    <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 또는 기능에 어떤 개선 사항이 있습니까?

    좋은 웹페이지 즐겨찾기