스크립트 태그가 포함된 HTML 삽입(SSG/SSR 지원)
이 경우 외부 서비스에서 제공하는 스크립트 탭을 포함하는 내용을 표시하려는 요구에 대응합니다.
dangerouslySetInnerHTML의 제한
dangerouslySetInnerHTMLReact를 사용하여 HTML을 임의의 DOM 요소의 하위 요소로 삽입할 수 있습니다.
그러나 이 기능은 내부에서 사용innerHTML.
innerHTML
XSS 공격에 대한 대책으로 표시<script>
를 실행하지 않습니다.(단, 입소 처리 프로그램으로 스크립트를 실행할 수 있음)따라서 스크립트 태그가 포함된 HTML은
dangerouslySetInnerHTML
에서 설정할 수 있지만 이 스크립트는 실행되지 않습니다.appeendChild와createContextualFragment를 이용하여
WebAPI
appendChild
방법은 특정 노드에 하위 노드를 추가하는 방법입니다.이 방법으로 추가된 노드는 일반 노드와 같이 실행되기 때문에 추가된 스크립트 탭도 실행됩니다.
문자열로 전송되는 HTML에서 요소를 생성할 때
createContextualFragment
방법을 사용합니다.HTML 태그가 포함된 문자열에서 지정된 범위의 시작점을 시작점으로 문서 세그먼트를 생성하는 방법입니다.React의 위조 코드는 다음과 같습니다.
const HTMLComponent = ({ htmlString }) => {
const divRef = useRef();
useLayoutEffect(() => {
if (!divRef.current) {
return;
}
const fragment = document
.createRange()
.createContextualFragment(htmlString);
divRef.current.appendChild(fragment);
}, [htmlString]);
return <div ref={divRef} />;
};
그러나 상술한 코드는 대응할 수 없는 몇 가지 모델이 있다.HubSpot의 삽입 코드와 같은 외부 파일의 읽기 및 관련 내연 스크립트가 있다면
<script charset="utf-8" type="text/javascript" src="//js.hsforms.net/forms/shell.js"></script>
<script>
hbspt.forms.create({
portalId: "xxxxxx",
formId: "xxxxxx"
});
</script>
appendChild
가 추가되면 두 개의 스크립트 탭이 동시에 실행되기 때문에 두 번째 내연 스크립트hbspt
는 전역 변수가 아닌 상태에서 실행됩니다.이 문제는 외부 파일을 읽을 스크립트 탭을 추가하거나 실행한 다음, 완성된 후에 다른 요소를 추가해서 해결할 수 있습니다.
const HTMLComponent = ({ htmlString }) => {
const divRef = useRef();
useLayoutEffect(() => {
if (!divRef.current) {
return;
}
(async () => {
const scriptStrings = htmlString.match(
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi
);
let updatedHtmlString = htmlString;
await scriptStrings.reduce(async (acc, current) => {
await acc;
const scriptFragment = document
.createRange()
.createContextualFragment(current);
const scriptElement = scriptFragment.querySelector('script');
if (scriptElement.src === '') {
return Promise.resolve();
}
updatedHtmlString = updatedHtmlString.replace(current, '');
if (
Array.from(document.querySelectorAll('script')).some(
se => se.src === scriptElement.src
)
) {
return Promise.resolve();
}
return new Promise(resolve => {
scriptElement.addEventListener('load', () => {
resolve();
});
document.head.appendChild(scriptElement);
});
}, Promise.resolve());
const fragment = document
.createRange()
.createContextualFragment(updatedHtmlString);
divRef.current.appendChild(fragment);
})();
}, [htmlString]);
return <div ref={divRef} />;
};
외부 파일의 스크립트 태그를 읽으려면 머리글에 추가하고 원래 HTML 태그에서 제거합니다.같은 원본을 읽을 스크립트 탭이 이미 존재하면 다중 읽기를 방지하기 위해 처리를 건너뜁니다.
스크립트 탭을 불러온 후 남은 HTML을 삽입하면 외부 파일에 의존하는 내연 스크립트를 문제없이 실행할 수 있습니다.
SSG/SSR에서 컨텐츠 재현 안 함
useLayoutEffect
는 서버에서 실행되지 않으므로 SSP/SSG를 실행할 때 재현되지 않습니다.이 문제에 대해서는 삽입할 요소에 대한 초기 값을 설정해서 대응합니다.
설정
const HTMLComponent = ({ htmlString }) => {
const divRef = useRef();
const initialHTMLString = useMemo(() => {
const scriptStrings = htmlString.match(
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi
);
return scriptStrings.reduce((acc, current) => {
return acc.replace(current, '');
}, htmlString);
}, [htmlString]);
useLayoutEffect(() => {
if (!divRef.current) {
return;
}
(async () => {
const scriptStrings = htmlString.match(
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi
);
let updatedHtmlString = htmlString;
await scriptStrings.reduce(async (acc, current) => {
await acc;
const scriptFragment = document
.createRange()
.createContextualFragment(current);
const scriptElement = scriptFragment.querySelector('script');
if (scriptElement.src === '') {
return Promise.resolve();
}
updatedHtmlString = updatedHtmlString.replace(current, '');
if (
Array.from(document.querySelectorAll('script')).some(
se => se.src === scriptElement.src
)
) {
return Promise.resolve();
}
return new Promise(resolve => {
scriptElement.addEventListener('load', () => {
resolve();
});
document.head.appendChild(scriptElement);
});
}, Promise.resolve());
const fragment = document
.createRange()
.createContextualFragment(updatedHtmlString);
divRef.current.innerHTML = '';
divRef.current.appendChild(fragment);
})();
}, [htmlString]);
return (
<div
ref={divRef}
dangerouslySetInnerHTML={{
__html: initialHTMLString,
}}
/>
);
};
htmlString
에서 스크립트 탭initialHTMLString
을 제거하면 미리 렌더링된 HTML을 불러올 때 인라인 스크립트가 실행되지 않습니다.예를 들어 HubSpot의 내장 스크립트를 포함하지 않으면 미리 렌더링된 HTML이 읽을 때 스크립트를 실행하고 폼을 추가합니다. 응용 프로그램의 시작과 높은 모의 처리
useLayoutEffect
를 통해 폼이 사라진 후에 다시 추가됩니다.총결산
dangerouslySetInnerHTML
에 스크립트 태그가 포함된 HTML을 삽입할 수 있지만 innerHTML
의 사양에 따라 스크립트 태그를 실행하지 않습니다.appendChild
및 createContextualFragment
를 사용하여 스크립트 태그가 포함된 HTML을 삽입할 수 있습니다.이번 데이터의 입력은 특정한 내용 관리자만 로그인할 수 있기 때문에 이 실시 방식을 채택하였다.
불특정 다수의 사용자가 입력, 열람을 필요로 하는 상황에서 적당한 위성을 확보한다.
Reference
이 문제에 관하여(스크립트 태그가 포함된 HTML 삽입(SSG/SSR 지원)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/akira_miyake/articles/0f5a00b035f9fe2d93e2텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)