2P by GN⁺ | ★ favorite | 댓글 1개
  • 여러 페이지에서 같은 header를 반복하지 않으려는 요구는 기본적이지만, HTML에는 이를 직접 처리하는 네이티브 include 태그가 없음
  • 개발자들은 JavaScript fetch, 서버 지시문, 정적 사이트 생성기, 템플릿 언어, 백엔드 언어, Web Components 같은 우회 수단으로 같은 문제를 해결해 왔음
  • <iframe>은 순수 HTML에 가까운 방법이지만, 성능·접근성·사용성 측면에서 이 용도에 맞지 않고 전반적으로 어색함
  • CSS는 CSS를 import하고 JavaScript는 JavaScript를 import할 수 있는데, HTML은 HTML을 import하지 못해 웹 플랫폼의 일관성이 흔들려 보임
  • preload scanner 영향, 비동기 로딩에 따른 레이아웃 흔들림, 중첩·순환 include, 요청 증가, 도메인 제한, 수요 부족 등이 표준화 장벽으로 남아 있음

반복되는 HTML 조각을 재사용하려는 기본 요구

  • index.html, about.html, contact.html 세 페이지에 같은 header를 넣어야 하는 상황에서 문제가 드러남
  • 같은 코드를 세 번 복사하기보다 header를 한 번 만들고 여러 페이지에서 include하고 싶어지는 것이 자연스러움
  • 페이지가 수천 개로 늘어나면 이는 단순 편의가 아니라 코드 중복 방지 문제가 됨

이미 존재하는 다양한 해결책

  • HTML 조각을 가져와 삽입하는 방식은 이미 여러 도구와 계층에서 가능함
  • <iframe>은 기술적으로 순수 HTML만으로 다른 HTML을 가져오는 방법이지만, 이 용도에서는 성능·접근성·사용성 문제가 큼
  • 강력한 찾기/바꾸기 기능을 믿고 include 자체를 쓰지 않는 선택지도 있음

정작 HTML 자체에는 없음

  • 위 방법들은 모두 “HTML 태그 하나로 HTML을 가져와 이 위치에 넣는다”는 방식이 아님
  • <img>가 이미지를 가져와 해당 위치에 넣는 것처럼, HTML에는 “이 HTML을 가져와 여기에 넣는다”는 직접적인 선언형 태그가 없음
  • ShopTalk Show에서도 Jake Archibald, Dave Rupert와 같은 질문을 두고 논의가 이어졌음

웹 플랫폼의 기존 흐름과 어긋나는 지점

  • 웹 표준과 브라우저는 개발자들이 이미 반복해서 해결하던 일을 플랫폼 기능으로 흡수하는 경우가 많음
  • 날짜 처리를 위해 서드파티 JavaScript를 쓰던 흐름에는 Temporal이 등장함
  • 프레임워크로 처리하던 페이지 전환 요구에는 View Transition API가 대응함
  • 요소를 안전하게 배치하기 위해 쓰던 라이브러리 영역에는 CSS anchor positioning이 들어옴
  • 거의 모든 웹사이트가 HTML 조각 재사용을 필요로 하고 각자 다른 비표준 도구를 쓰는 상황에서, HTML include 부재는 이례적인 공백처럼 보임

HTML include가 표준이 되기 어려운 이유

  • 브라우저와 생태계 관점에서 HTML include에는 여러 부담이 따름
    • preload scanner를 망가뜨려 웹 성능에 나쁜 영향을 줄 수 있음
    • 비동기 방식이어야 한다면 로딩 중 화면이 흔들리거나 튀는 경험이 생길 수 있음
    • HTML의 단순성이나 순수성을 해치는 복잡성이 생길 수 있음
    • 중첩 include와 순환 include 처리가 어려울 수 있음
    • 웹 호스팅 업체가 요청 수 증가를 이유로 반대할 수 있음
    • 이미지·CSS·JavaScript와 달리 HTML은 다른 도메인에서 가져올 때 더 엄격한 제한이 필요할 수 있음
    • 이 목록에 없는 다른 문제가 있을 수 있음
    • 실제로는 이 기능에 대한 수요가 크지 않을 수도 있음
  • 확정 답변보다는, HTML이 왜 HTML을 직접 include하지 못하는지 따져보는 이유 탐색에 가까움

댓글과 토론

Hacker News 의견들
  • HTML은 역사적으로 SGML의 응용이었고, SGML에는 include가 가능했음
    새 “entity”를 정의할 수 있었고, “system” entity를 만들면 나중에 참조해서 치환되게 할 수 있었음. SGML은 복잡해서 HTML을 단순화하려는 여러 시도가 있었고, 그 과정에서 이 기능도 빠짐

    • XHTML로 잠깐 XML 쪽을 거쳤고, XML에는 XInclude가 있지만 필수 기능은 아님
    • 흥미로운 참고자료라 더 찾아볼 생각임
      해당 태그는 다른 HTML 페이지를 포함하거나 임베드하는 것처럼 보임. 임베드된 HTML 페이지: https://www.w3schools.com/tags/tag_object.asp
    • 그건 그 자체로 완전한 공격 표면
      https://en.wikipedia.org/wiki/Billion_laughs_attack
    • HTML 4 이하와 XML에서 쓰던 DTD에도 있었고, 아마 SGML에서 온 것으로 보임
  • 90년대 후반에 빠져든 토끼굴이었고 아직도 빠져나오지 못했음
    Analog Science Fiction 웹사이트 웹마스터였는데, 같은 헤더와 사이드바를 가진 정적 페이지를 엄청 만들다 보니 미칠 지경이었음. 조사하다가 Apache 서버 사이드 include를 발견했고, DRY라는 말을 알기 전부터 DRY하게 만들 수 있었음. iframe으로 충분하다는 말도 있지만 충분하지 않음. iframe은 콘텐츠 크기에 맞춰 늘어나지 않고, 서버 쪽 해법은 서버가 필요함. 왜 단순한 클라이언트 쪽 방법이 없으면 안 되는지 모르겠고, 웹 개발의 많은 짜증을 고치고 있는 지금 고려할 만한 질문임

    • 서버 사이드 include가 최고였음
      90년대 중반 친구와 “웹 stuff”를 만들기 시작했을 때도 DRY 개념은 그냥 자연스럽게 이해됐음. 당시 전화접속 ISP가 사용자 웹 공간에서 .htaccess 사용을 막지 않아서 서버 사이드 include를 켤 수 있었고, 나중에는 CGI도 켜는 법을 알아냈음. 웹서버 박스를 탐색하려고 Perl로 초보적인 웹셸까지 짰음
    • 이런 이유로 https://htmx.org를 좋아하게 됨
      정적 HTML의 동적 import 같은 핵심 기능을 HTML에 보강해 주는 10KB짜리 작은 라이브러리
    • “iframe은 콘텐츠에 맞춰 늘어나지 않는다”는 부분은 사실 원래 계획에 들어 있었음
      https://caniuse.com/iframe-seamless
    • 1996년 Netscape에서도 이런 걸 할 수 있었음. 아직도 이 방식을 쓰는 웹사이트 서버를 운영 중임
      프레임에서 늘 거슬렸던 건 너무 똑똑하게 굴었다는 점임. 우클릭해서 새로고침할 때 프레임 HTML만 다시 불러오길 원한 게 아니었음. 별도로 캐시하려는 의도는 알겠지만, 프레임과 캐시는 서로 다른 문제를 풀어야 하는데 둘을 섞어 버려 둘 다 애매해졌음. HTML include는 가능한 한 멍청하게 동작해야 한다고 봄. include 위치에 include 텍스트를 붙여 넣고, 브라우저가 그 결과 텍스트를 받는 방식이면 됨. 모든 페이지에 같은 내비게이션을 따로 캐시하고 싶다면 캐시 속성을 추가해 그 문제를 독립적으로 풀면 됨. include가 더 많은 일을 해야 한다고 설득할 수는 있겠지만, 멍청한 동작은 버그가 아니라 기능임
    • 최적의 해법은 템플릿 엔진으로 정적 문서를 생성하는 것임
  • 이 기능 제안은 HTML Imports라고 불렸고, Web Components 작업의 일부로 만들어졌음
    “HTML Imports are a way to include and reuse HTML documents in other HTML documents”라는 설명이 있었고, 계획 문서는 https://www.w3.org/TR/html-imports/에 있음

    • 글의 댓글 [1]에서 수요 부족, 벤더 열의 부족 등을 든 것과 맞아떨어짐
      하지만 이런 이유들은 실제로 아무것도 설명하지 못하는 비이유에 가까움. 이 기능이 20년 동안 계속 요청됐고, 스크립트나 백엔드 엔진 등으로 만든 온갖 shim 구현이 있는데 수요가 낮다는 건 믿기 어려움. 벤더 거부도 왜 이미 있던 구현까지 되돌릴 정도로 거부했는지는 설명하지 못함. “보안 영향”도 이상한데, 이미 script 태그로 교차 출처 HTML을 가져와 document.write()를 할 수 있기 때문임. 스크립트가 document.write() 하는 건 괜찮고 같은 일을 하는 HTML 태그는 왜 큰 문제가 되는지 궁금함. Google 홈페이지를 즉석 복제하는 식은 막아야 한다는 보안 우려는 이해하지만, 그건 CORS로 쉽게 풀 수 있어 보임
      [1] https://frontendmasters.com/blog/seeking-an-answer-why-cant-...
    • HTML Imports도 비슷한 방향이었지만 블로그 글이 말하는 기능과는 다름
      HTML은 문서의 특정 위치에 import되어 표시되어야 하는데, HTML Imports는 JavaScript 없이는 이걸 할 수 없었음. 자세한 내용은 https://github.com/whatwg/html/issues/2791#issuecomment-3112... 참고
    • 공정하게 말하면 꽤 복잡했음
      기억상 import한 뒤 JavaScript로 템플릿을 인스턴스화해야 했고, 그냥 단순한 태그 하나로 되는 방식은 아니었음
    • https://caniuse.com/imports를 보면 Firefox에도 설정 플래그로는 있었음
    • HTML Imports는 이 글이 요구하는 include보다 훨씬 복잡했음
  • Netscape 4에는 inflow layer로 이 기능이 있었음
    https://web.archive.org/web/19970630074729fw_/http://develop...
    https://web.archive.org/web/19970630094813fw_/http://develop...

    • 알기로는 SRC 속성을 바꾸는 동작이 꽤 크래시를 잘 냈고, 기능도 곧 제거됐음
      베타에서 가지고 놀았던 기억이 있는데, 정식 버전에서는 사라져 있었음
    • 왜 이름이 ILAYER였는지 늘 궁금했는데 이제 알겠음
  • 이 기능의 이름은 트랜스클루전(transclusion)
    https://en.wikipedia.org/wiki/Transclusion
    Project Xanadu의 일부였고, 원래 하이퍼텍스트의 중요한 기능으로 여겨졌음. 특히 MediaWiki는 트랜스클루전을 광범위하게 쓰며, 가끔 위키가 하이퍼텍스트의 가장 순수한 형태처럼 느껴짐

    • Wiki를 만든 Ward Cunningham은 모두가 자기 위키 공간을 갖고 사회적으로 transclusion을 쓰는, 트랜스클루전 우선 위키를 만들려고 한 적이 있음
      https://en.wikipedia.org/wiki/Federated_Wiki
      하지만 제대로 뜨지는 못했음
    • 진짜 transclusion은 그보다 더 많은 것을 의미한다고 봄
      Xanadu에서는 한 문서의 일부 발췌만 다른 문서에 transclude할 수 있었음. HTML에서 이걸 하려면 CSS에 대한 답이 필요함. 특정 상황에서는 호스트 문서, 게스트 문서, 호스트 안에 임베드된 게스트 사이에서 어떤 속성을 일관되게 할지 판단해 해결할 수 있지만, 일반적인 경우는 불명확함. 단순한 태그 방식이라면 게스트 문서가 호스트가 넣어 둔 CSS 환경 안에서 살아가도록 설계되어야 함. 또 다른 단순한 답은 Shadow DOM인데, 대체로 게스트가 나머지 문서에 영향을 주지 않고 자기 스타일을 적용할 수 있게 해줌. 이 경우에도 호스트가 게스트를 보정할 스타일을 일부 넣을 수는 있다고 봄
  • 오래전에 제대로 된 frameset이 하려던 일이 이거 아니었나 싶음. iframe 말고 HTML 4 시절의 frameset 말임
    적어도 크기 자동 확장은 잘 됐고 사용자가 원하는 크기로 조정할 수도 있었음. 프레임에 대한 비판은 많았지만 [1], Java API 문서 [2]처럼 유용한 곳에는 성공적으로 쓰였음. 결국 사라진 건 디자이너에게 유연성이 너무 부족했기 때문이라고 봄. 정보 페이지에는 충분했지만, 투박한 스크롤바와 제한된 화면 분할 때문에 디자이너 요구를 만족시키지 못했음. 지금은 모바일에서 있는 그대로의 frameset이 잘 동작하지 않을 테니 되살리기엔 너무 늦었음
    [1] <https://www.nngroup.com/articles/why-frames-suck-most-of-the...> - 여기 내용 중 많은 부분이 이제는 적용되지 않고, 프레임에서 지적된 모든 문제가 오늘날 웹에는 더 지저분한 형태로 존재한다는 점이 재미있음
    [2] <https://www.eeng.dcu.ie/~ee553/ee402notes/html/figures/JavaD...>

    • frameset의 문제는 훨씬 더 근본적이었음
      딥링크가 불가능해서 북마크나 Google, 또는 그 이전 검색엔진을 통해 들어온 사람은 내비게이션 없는 페이지에 떨어졌고, 이를 JavaScript로 우회하려 했지만 좋은 경험을 만들지 못했음
  • “include” 기능은 서버 쪽, 즉 웹브라우저 밖에서 처리되는 것으로 여겨짐
    HTML은 클라이언트 쪽이고, 사실상 프로그래밍 언어가 아니라 마크업 문법임. 글에서 말하듯 이 문제는 이미 풀린 문제임. 웹디자인 학생들이 PHP를 배우며 처음 접하는 게 include이고, 대부분 CMS에서는 include가 템플릿 partial이 되어 문서 초반에 설명됨. HTML만으로 include를 가능하게 만들 필요는 딱히 없음. HTML은 표현 형식이고 CSS와 JS 없이는 흥미로운 일을 하지 않음

    • “include는 서버 쪽 기능”이라는 말은 클라이언트 쪽 include가 있으면 안 된다는 논거가 아님
      실제로 HTML에는 frames와 iframes라는 더 나쁜 버전이 이미 있음. 서버 사이드 include의 클라이언트 쪽 대응물은 사람들이 HTML로 하는 일에 자연스럽게 들어맞음
    • 이상하게 느껴지는 이유는 HTML 파일이 스크립트, 폰트, 이미지, 비디오, 스타일 등은 포함할 수 있는데 HTML은 포함하지 못하기 때문
      아마 커스텀 요소로 코딩할 수 있을 것이고, GitHub에 비슷한 저장소가 없으면 오히려 놀랄 것 같음
    • 많은 학생들이 PHP에 입문하는 계기라는 말은 맞음. 그래도 왜 단순 태그 방식은 안 되는지 궁금함
      이미지나 화면 아래쪽 콘텐츠처럼 이미 비동기로 로드되는 콘텐츠도 있음. “HTML은 프로그래밍 언어가 아니라 마크업 문법”이라는 말은 flamebait에 가깝고, 브라우저 엔진마다 해석되는 선언형 언어
    • 말한 내용에는 동의하지만, HTML은 표현 형식이 아니라 문서 서술 언어
      스타일링을 말한 거라면 표현은 CSS가 담당함
    • “include 기능은 서버 쪽”이라는 말이 맞음. 서버 쪽 include는 완전히 자연스럽지만, 클라이언트 쪽 include는 클라이언트가 알 수 없는 시점에 원래 DOM을 수정할 수 있어야 한다는 뜻임
      선택지는 두 가지임. 첫째, HTML 파싱 시점, 즉 DOM 생성 전 처리하는 방법인데 include를 위해 서버에 동기 요청이 필요해서 바람직하지 않음. 둘째, DOM 생성 후 특정 요소가 DOM에 나타나고, 조각을 비동기로 로드한 뒤 그 DOM 요소를 외부 조각으로 교체하는 방법인데 기존 DOM 구조 검증 메커니즘을 무력화함. 다만 Sciter 엔진에서는 첫 번째 전략으로 구현했음. Sciter의 HTML은 보통 로컬 앱 리소스나 파일 시스템에서 오기 때문에 추가 조각 요청 비용이 무시할 만함
      https://docs.sciter.com/docs/HTML/html-include
  • HTML include에는 다른 사람들이 말했듯 여러 문제가 있음
    main.htmlchild/include1.html을 포함하고, child/include1.html 안에 src="include2.html" 링크가 있다면 사용자가 클릭했을 때 어디로 가야 하는가? include2.html로 가면 이름상 include용 페이지라 나머지가 빠져 있을 것이고, main.html로 간다면 이번에는 include1.html이 아니라 include2.html을 쓰라고 어떻게 지정할 것인가? 반대로 article1.html, article2.html, article3.html이 각각 header.html, footer.html, navi.html을 포함하게 할 수도 있지만, 그러면 모든 기사 구조를 전역 변경하려면 모든 기사를 수정해야 함. 모든 기사에 comments.html을 추가하려면 결국 다시 템플릿으로 페이지를 생성하고 싶어지고, 그 시점에는 브라우저 include가 필요 없어짐. 헤더가 제목을 알아야 하거나 푸터가 이전/다음 링크를 알아야 하는 문제도 생기고, include 사이에 정보를 주고받는 방법이 필요해져 결국 페이지 생성으로 돌아가게 됨. 따져보면 HTML include는 대부분의 용도에서 실질적으로 쓸모없을 가능성이 큼

    • 이런 문제들은 꽤 명확한 해법이 있는, 모두 풀 수 있는 문제임
      여기에는 두 가지 다른 사용 사례가 섞여 있음. 하나는 조각 재사용이고 다른 하나는 임베드 가능한 독립 섬임. 후자는 이미 iframe이 처리하므로, 전자만 해결하면 됨
    • include2.html이 나머지를 잃는 include 논리는 다른 include에도 똑같이 적용됨
      사용자가 src="include.css" 링크를 클릭하면 엉망이 될 것임. 정적 데이터, 이미지, CSS, 정적 HTML 콘텐츠에는 괜찮을 수 있음
  • WHATWG에 이와 관련된 열린 이슈가 있음. 블로그 글의 댓글 섹션에도 언급됨
    Client side include feature for HTML
    https://github.com/whatwg/html/issues/2791

  • HTML에는 include가 있었고, 인기가 사라졌음
    실제 “include”라는 용어는 XML 기능이고, 글이 바라는 것도 그 기능임. HTML에는 XML 이전에 생긴 다른 접근법이 있었는데, 그것이 frames였음. frames는 XML include보다 훨씬 많은 일을 했기 때문에 HTML은 그 기능을 따로 얻지 않았음. frames는 오용, 보안, 접근성 등 여러 문제로 인기를 잃었음

    • Frameset과 달리 XML include는 많은 브라우저, 어쩌면 주요 브라우저 어디에서도 제대로 지원된 적이 없었던 것 같음
      가끔 쓰는 건 여전히 좋아하지만, 사용자나 브라우저에 넘기기 전에 이를 평가하는 컴파일 단계가 필요함