2 điểm bởi GN⁺ 2025-05-05 | 1 bình luận | Chia sẻ qua WhatsApp
  • Nhu cầu tránh lặp lại cùng một phần header trên nhiều trang là rất cơ bản, nhưng HTML không có thẻ include gốc để xử lý trực tiếp việc này
  • Các lập trình viên từ lâu đã giải quyết cùng một vấn đề bằng những cách vòng như JavaScript fetch, chỉ thị phía máy chủ, trình tạo site tĩnh, ngôn ngữ template, ngôn ngữ backend và Web Components
  • <iframe> là cách gần với HTML thuần nhất về mặt kỹ thuật, nhưng không phù hợp cho mục đích này vì các vấn đề về hiệu năng, khả năng truy cập và tính dễ dùng, đồng thời nhìn chung khá gượng ép
  • CSS có thể import CSS và JavaScript có thể import JavaScript, nhưng HTML lại không thể import HTML, khiến tính nhất quán của nền tảng web có vẻ bị lung lay
  • Ảnh hưởng tới preload scanner, hiện tượng layout bị xô lệch do tải bất đồng bộ, include lồng nhau hoặc tuần hoàn, số lượng request tăng, giới hạn theo domain và khả năng nhu cầu không đủ lớn vẫn là các rào cản chuẩn hóa

Nhu cầu cơ bản: tái sử dụng các mảnh HTML lặp lại

  • Vấn đề bộc lộ rõ khi cần chèn cùng một header vào ba trang index.html, about.html, contact.html
  • Thay vì sao chép cùng một đoạn mã ba lần, việc tạo header một lần rồi include ở nhiều trang là điều rất tự nhiên
  • Khi số lượng trang tăng lên hàng nghìn, đây không còn chỉ là chuyện tiện lợi mà trở thành vấn đề tránh trùng lặp mã nguồn

Nhiều cách giải quyết đã tồn tại sẵn

  • Việc lấy các mảnh HTML rồi chèn vào trang đã có thể thực hiện ở nhiều công cụ và tầng khác nhau
    • JavaScript có thể dùng fetch để lấy HTML và insertAdjacentElement để chèn vào
    • Cũng có Server Side Includes, một dạng chỉ thị web server lâu đời
    • Có thể xử lý bằng tính năng của trình tạo site tĩnh như Jekyll include
    • Các task runner như gulp-include cũng dùng được
    • Những ngôn ngữ template như Handlebars partials thường cung cấp tính năng include
    • Ngôn ngữ backend cũng có thể tạo HTML động, như include của PHP
    • Thậm chí còn có cách làm bằng Web Component chuyên cho include
  • <iframe> về mặt kỹ thuật là một cách dùng HTML thuần để lấy HTML khác, nhưng với mục đích này thì có vấn đề lớn về hiệu năng, khả năng truy cập và tính dễ dùng
  • Cũng có lựa chọn là không dùng include, mà dựa vào chức năng tìm/thay thế mạnh mẽ trong trình soạn thảo

Nhưng bản thân HTML thì lại không có

  • Tất cả các cách trên đều không phải kiểu “dùng một thẻ HTML để lấy HTML và đặt nó vào đúng vị trí này”
  • Giống như <img> lấy ảnh và đặt nó vào vị trí tương ứng, HTML không có một thẻ khai báo trực tiếp kiểu “lấy HTML này và chèn vào đây”
  • Trong ShopTalk Show, câu hỏi này cũng từng được đem ra thảo luận bởi những người như Jake Archibald và Dave Rupert

Điểm lệch so với dòng phát triển sẵn có của nền tảng web

  • Các tiêu chuẩn web và trình duyệt thường hay hấp thụ vào nền tảng những việc mà lập trình viên đã phải lặp đi lặp lại bằng cách riêng
  • Với xử lý ngày tháng vốn từng phải dựa vào JavaScript bên thứ ba, nay đã có Temporal
  • Với nhu cầu chuyển trang vốn thường do framework xử lý, đã có View Transition API
  • Với mảng thư viện dùng để đặt phần tử an toàn, nay đã có CSS anchor positioning
  • Trong bối cảnh gần như mọi website đều cần tái sử dụng các mảnh HTML và mỗi nơi lại dùng một công cụ phi chuẩn khác nhau, việc thiếu HTML include trông giống như một khoảng trống bất thường

Vì sao HTML include khó trở thành tiêu chuẩn

  • Từ góc nhìn của trình duyệt và hệ sinh thái, HTML include kéo theo nhiều gánh nặng
    • Có thể làm hỏng preload scanner, từ đó ảnh hưởng xấu đến hiệu năng web
    • Nếu buộc phải hoạt động bất đồng bộ, trải nghiệm giao diện có thể bị rung lắc hoặc nhảy bố cục trong lúc tải
    • Nó có thể làm phát sinh độ phức tạp, ảnh hưởng tới sự đơn giản hay tính “thuần” của HTML
    • Việc xử lý include lồng nhau và include tuần hoàn có thể rất khó
    • Các nhà cung cấp dịch vụ web hosting có thể phản đối vì số lượng request tăng lên
    • Không giống ảnh, CSS hay JavaScript, HTML có thể cần các giới hạn nghiêm ngặt hơn khi được lấy từ domain khác
    • Có thể còn tồn tại những vấn đề khác chưa được liệt kê ở đây
    • Cũng có khả năng trên thực tế nhu cầu cho tính năng này không lớn đến vậy
  • Đây giống một quá trình tìm lý do vì sao HTML không thể trực tiếp include HTML, hơn là đưa ra một câu trả lời đã được chốt hẳn

1 bình luận

 
GN⁺ 2025-05-05
Ý kiến trên Hacker News
  • Về mặt lịch sử, HTML là một ứng dụng của SGML, và SGML có thể làm include
    Có thể định nghĩa các “entity” mới, và nếu tạo một “system” entity thì sau đó có thể tham chiếu đến nó để được thay thế. SGML rất phức tạp nên đã có nhiều nỗ lực nhằm đơn giản hóa HTML, và trong quá trình đó tính năng này cũng bị loại bỏ

    • Sau đó có một giai đoạn ngắn đi qua phía XML với XHTML, và XML có XInclude, nhưng đó không phải là tính năng bắt buộc
    • Đây là tài liệu tham khảo khá thú vị nên tôi định tìm hiểu thêm
      Thẻ đó có vẻ dùng để bao gồm hoặc nhúng một trang HTML khác. Trang HTML được nhúng: https://www.w3schools.com/tags/tag_object.asp
    • Bản thân điều đó đã là một bề mặt tấn công hoàn chỉnh rồi
      https://en.wikipedia.org/wiki/Billion_laughs_attack
    • Nó cũng từng có trong DTD dùng ở HTML 4 trở xuống và trong XML, và có lẽ bắt nguồn từ SGML
  • Đó là cái hang thỏ mà tôi rơi vào từ cuối thập niên 90 và đến giờ vẫn chưa thoát ra được
    Tôi từng là quản trị viên web của trang Analog Science Fiction, và phát điên vì phải tạo ra vô số trang tĩnh có cùng header và sidebar. Trong lúc tìm hiểu, tôi phát hiện ra server-side include của Apache, và đã có thể làm theo kiểu DRY từ trước cả khi biết tới từ DRY. Có người nói iframe là đủ, nhưng thực ra không đủ. iframe không tự giãn theo kích thước nội dung, còn giải pháp phía server thì cần có server. Tôi không hiểu vì sao lại không nên có một cách đơn giản ở phía client, và đây là câu hỏi đáng để cân nhắc vào lúc chúng ta đang sửa rất nhiều điều khó chịu trong phát triển web

    • Server-side include là tuyệt nhất
      Vào giữa thập niên 90, khi tôi bắt đầu làm “web stuff” với một người bạn, khái niệm DRY gần như là thứ được hiểu một cách tự nhiên. ISP quay số lúc đó không chặn việc dùng .htaccess trên không gian web của người dùng, nên tôi có thể bật server-side include, và sau đó còn tìm ra cách bật cả CGI. Tôi thậm chí còn viết một web shell sơ khai bằng Perl để dò khám phá máy chủ web
    • Vì lý do này mà tôi thích https://htmx.org
      Đây là một thư viện nhỏ 10KB bổ sung cho HTML những tính năng cốt lõi như import động cho HTML tĩnh
    • Phần “iframe không tự giãn theo nội dung” thực ra từng nằm trong kế hoạch ban đầu
      https://caniuse.com/iframe-seamless
    • Ngay từ năm 1996 trên Netscape cũng đã có thể làm kiểu này. Tôi vẫn đang vận hành một máy chủ website dùng theo cách đó
      Điều luôn khiến tôi khó chịu ở frame là nó cố tỏ ra quá thông minh. Khi nhấp chuột phải rồi tải lại, tôi không hề muốn chỉ tải lại mỗi HTML của frame. Tôi hiểu ý đồ muốn cache riêng, nhưng frame và cache là hai vấn đề khác nhau cần được giải quyết riêng, vậy mà chúng lại bị trộn vào nhau nên cả hai đều trở nên nửa vời. Tôi cho rằng HTML include nên hoạt động càng ngốc càng tốt. Chỉ cần dán văn bản include vào đúng vị trí include, rồi trình duyệt nhận phần văn bản kết quả là được. Nếu muốn cache riêng cùng một thanh điều hướng trên mọi trang, thì hãy thêm thuộc tính cache để giải quyết riêng bài toán đó. Có thể thuyết phục rằng include nên làm được nhiều hơn, nhưng cách hoạt động ngốc nghếch không phải lỗi mà là một tính năng
    • Giải pháp tối ưu là dùng template engine để tạo tài liệu tĩnh
  • Đề xuất tính năng này từng được gọi là HTML Imports, và được xây dựng như một phần của công việc về Web Components
    Khi đó có mô tả rằng “HTML Imports are a way to include and reuse HTML documents in other HTML documents”, và tài liệu kế hoạch nằm tại https://www.w3.org/TR/html-imports/

    • Nó khớp với những gì bình luận [1] của bài viết nói về việc thiếu nhu cầu, thiếu nhiệt tình từ phía vendor, v.v.
      Nhưng những lý do đó thực ra gần như là những phi-lý-do, vì chúng không thực sự giải thích được điều gì. Tính năng này đã được yêu cầu liên tục suốt 20 năm, và có đủ mọi triển khai shim được tạo bằng script hay backend engine, nên khó mà tin rằng nhu cầu lại thấp. Việc vendor từ chối cũng không giải thích được vì sao họ lại phản đối đến mức còn gỡ bỏ cả các triển khai đã tồn tại. “Tác động bảo mật” cũng nghe rất lạ, vì ngay cả hiện tại người ta vẫn có thể lấy HTML khác nguồn bằng thẻ script rồi document.write() ra. Nếu script dùng document.write() thì được, vậy tại sao một thẻ HTML làm đúng việc đó lại bị coi là vấn đề lớn? Tôi hiểu lo ngại bảo mật kiểu như phải ngăn việc sao chép nóng trang chủ Google ngay tại chỗ, nhưng chuyện đó có vẻ hoàn toàn có thể xử lý dễ dàng bằng CORS
      [1] https://frontendmasters.com/blog/seeking-an-answer-why-cant-...
    • HTML Imports cũng đi theo hướng tương tự, nhưng không giống tính năng mà bài blog đang nói đến
      HTML cần được import và hiển thị tại một vị trí cụ thể trong tài liệu, nhưng HTML Imports không làm được điều này nếu không có JavaScript. Xem chi tiết tại https://github.com/whatwg/html/issues/2791#issuecomment-3112...
    • Nói công bằng thì nó khá phức tạp
      Nếu tôi nhớ không nhầm thì sau khi import còn phải dùng JavaScript để khởi tạo template, chứ không phải kiểu chỉ cần một thẻ đơn giản là xong
    • Xem https://caniuse.com/imports thì Firefox cũng từng có dưới dạng cờ cấu hình
    • HTML Imports phức tạp hơn rất nhiều so với kiểu include mà bài viết này đang yêu cầu
  • Netscape 4 từng có tính năng này dưới dạng inflow layer
    https://web.archive.org/web/19970630074729fw_/http://develop...
    https://web.archive.org/web/19970630094813fw_/http://develop...

    • Theo tôi biết thì việc đổi thuộc tính SRC khá dễ gây crash, và tính năng này cũng sớm bị loại bỏ
      Tôi nhớ đã từng vọc nó ở bản beta, nhưng đến bản phát hành chính thức thì nó đã biến mất
    • Tôi luôn thắc mắc vì sao nó lại có tên là ILAYER, giờ thì đã hiểu
  • Tên của tính năng này là transclusion
    https://en.wikipedia.org/wiki/Transclusion
    Nó là một phần của Project Xanadu, và ban đầu được xem là một tính năng quan trọng của hypertext. Đặc biệt, MediaWiki sử dụng transclusion rất rộng rãi, và đôi khi khiến wiki có cảm giác như là hình thức thuần túy nhất của hypertext

    • Ward Cunningham, người tạo ra Wiki, từng cố xây dựng một wiki ưu tiên transclusion, nơi mọi người đều có không gian wiki riêng và dùng transclusion theo cách mang tính xã hội
      https://en.wikipedia.org/wiki/Federated_Wiki
      Nhưng nó đã không thực sự cất cánh
    • Tôi cho rằng transclusion thực sự còn có ý nghĩa nhiều hơn thế
      Trong Xanadu, có thể transclude chỉ một phần trích đoạn của một tài liệu sang tài liệu khác. Để làm điều này trong HTML thì cần có câu trả lời cho CSS. Trong một số tình huống cụ thể, có thể giải quyết bằng cách xác định thuộc tính nào cần được giữ nhất quán giữa tài liệu host, tài liệu guest, và phần guest được nhúng vào trong host, nhưng trong trường hợp tổng quát thì điều đó không rõ ràng. Nếu dùng cách tiếp cận bằng thẻ đơn giản, thì tài liệu guest phải được thiết kế để sống trong môi trường CSS mà host đã thiết lập. Một câu trả lời đơn giản khác là Shadow DOM, vì nhìn chung nó cho phép guest áp dụng style riêng mà không ảnh hưởng đến phần còn lại của tài liệu. Ngay cả trong trường hợp này, tôi nghĩ host vẫn có thể thêm một số style để tinh chỉnh guest
  • Tôi từng nghĩ đây có phải là điều mà frameset đúng nghĩa ngày xưa muốn làm không. Ý tôi là frameset thời HTML 4, chứ không phải iframe
    Ít nhất thì nó xử lý tốt chuyện tự động giãn kích thước, và người dùng cũng có thể chỉnh về kích cỡ họ muốn. Có rất nhiều lời chỉ trích nhắm vào frame [1], nhưng nó đã được dùng thành công ở những nơi hữu ích như tài liệu Java API [2]. Cuối cùng nó biến mất, theo tôi là vì quá thiếu linh hoạt đối với designer. Với các trang thông tin thì là đủ, nhưng các thanh cuộn thô kệch và cách chia màn hình hạn chế khiến nó không đáp ứng được yêu cầu thiết kế. Giờ đây trên mobile, một frameset nguyên bản như thế hẳn cũng không hoạt động tốt, nên có lẽ đã quá muộn để hồi sinh nó
    [1] <https://www.nngroup.com/articles/why-frames-suck-most-of-the...> - Thú vị là nhiều điều được nói trong đó giờ không còn áp dụng nữa, và mọi vấn đề từng bị chỉ ra ở frame hiện nay đều tồn tại trên web dưới những hình thức còn lộn xộn hơn
    [2] <https://www.eeng.dcu.ie/~ee553/ee402notes/html/figures/JavaD...>

    • Vấn đề của frameset còn mang tính nền tảng hơn nhiều
      Không thể deep link, nên những người truy cập qua bookmark, Google, hoặc các công cụ tìm kiếm đời trước sẽ rơi vào một trang không có điều hướng, và người ta đã cố lách điều đó bằng JavaScript, nhưng không thể tạo ra trải nghiệm tốt
  • Tính năng “include” được xem là thứ được xử lý ở phía máy chủ, tức là bên ngoài trình duyệt web
    HTML ở phía máy khách và thực chất không phải ngôn ngữ lập trình mà là cú pháp đánh dấu. Như bài viết đã nói, đây vốn là vấn đề đã được giải quyết. Với sinh viên thiết kế web, một trong những thứ đầu tiên họ gặp khi học PHP là include, và trong hầu hết CMS, include được giới thiệu ngay từ đầu dưới dạng template partial. Không có nhu cầu đặc biệt nào phải làm cho include hoạt động chỉ bằng HTML. HTML là một định dạng biểu đạt và nếu không có CSS cùng JS thì cũng không làm được điều gì thú vị

    • Việc nói rằng “include là tính năng phía máy chủ” không phải là lập luận cho rằng không nên có include phía máy khách
      Trên thực tế, HTML đã có sẵn một phiên bản tệ hơn là frames và iframes. Đối tượng tương ứng phía máy khách của server-side include khá tự nhiên với những gì mọi người làm bằng HTML
    • Điều khiến nó có cảm giác kỳ lạ là file HTML có thể nhúng script, font, hình ảnh, video, style, v.v. nhưng lại không thể nhúng HTML
      Có lẽ có thể viết bằng custom element, và nếu trên GitHub không có một kho tương tự thì tôi còn thấy ngạc nhiên hơn
    • Đúng là nhiều sinh viên bước vào PHP từ đó. Dù vậy, tôi vẫn tò mò vì sao không thể làm theo kiểu thẻ đơn giản
      Vốn đã có những nội dung được tải bất đồng bộ như hình ảnh hay nội dung ở phần dưới màn hình. Câu “HTML không phải ngôn ngữ lập trình mà là cú pháp đánh dấu” nghe khá giống flamebait, và nó là một ngôn ngữ khai báo được mỗi engine trình duyệt diễn giải
    • Tôi đồng ý với điều đã nói, nhưng HTML không phải là định dạng biểu đạt mà là ngôn ngữ mô tả tài liệu
      Nếu đang nói về việc tạo kiểu thì phần biểu đạt là do CSS đảm nhiệm
    • Câu “tính năng include là phía máy chủ” là đúng. Include phía máy chủ hoàn toàn tự nhiên, nhưng include phía máy khách có nghĩa là máy khách phải có khả năng sửa DOM gốc vào một thời điểm mà máy khách không thể biết trước
      Có hai lựa chọn. Thứ nhất là xử lý trong lúc parse HTML, tức trước khi tạo DOM, nhưng cách này đòi hỏi yêu cầu đồng bộ tới máy chủ để include nên không mong muốn. Thứ hai là sau khi DOM được tạo, một phần tử cụ thể xuất hiện trong DOM, đoạn mảnh được tải bất đồng bộ rồi phần tử DOM đó bị thay bằng mảnh bên ngoài, nhưng điều này làm vô hiệu cơ chế kiểm chứng cấu trúc DOM hiện có. Dù vậy, engine Sciter đã triển khai bằng chiến lược thứ nhất. HTML của Sciter thường đến từ tài nguyên ứng dụng cục bộ hoặc hệ thống tệp nên chi phí yêu cầu thêm các mảnh bổ sung là không đáng kể
      https://docs.sciter.com/docs/HTML/html-include
  • HTML include có nhiều vấn đề như những người khác đã nói
    Nếu main.html include child/include1.html, và bên trong child/include1.html có liên kết src="include2.html", thì khi người dùng nhấp vào họ nên đi đâu? Nếu đi đến include2.html thì theo tên gọi đây là trang dành cho include nên sẽ thiếu phần còn lại; nếu đi đến main.html thì lần này phải chỉ định thế nào rằng hãy dùng include2.html thay vì include1.html? Ngược lại, có thể để article1.html, article2.html, article3.html mỗi trang include header.html, footer.html, navi.html, nhưng khi đó nếu muốn thay đổi cấu trúc toàn cục của mọi bài viết thì phải sửa tất cả các bài. Nếu muốn thêm comments.html vào mọi bài thì cuối cùng bạn lại muốn tạo trang từ template, và đến lúc đó thì không còn cần include trong trình duyệt nữa. Cũng phát sinh chuyện header cần biết tiêu đề hoặc footer cần biết liên kết trước/sau, và cần có cách truyền thông tin giữa các include, để rồi cuối cùng lại quay về việc sinh trang. Xét kỹ thì HTML include rất có thể hầu như vô dụng trên thực tế cho đa số mục đích sử dụng

    • Những vấn đề này đều có cách giải quyết khá rõ ràng, tức là đều có thể xử lý được
      Ở đây đang trộn lẫn hai trường hợp sử dụng khác nhau. Một là tái sử dụng mảnh, hai là một đảo độc lập có thể nhúng vào. Trường hợp thứ hai đã có iframe xử lý, nên chỉ cần giải quyết trường hợp thứ nhất
    • Lập luận rằng include khiến include2.html mất phần còn lại cũng áp dụng y hệt cho các kiểu include khác
      Nếu người dùng nhấp vào liên kết src="include.css" thì cũng sẽ rối tinh lên. Nó có thể vẫn ổn với dữ liệu tĩnh, hình ảnh, CSS và nội dung HTML tĩnh
  • WHATWG có một issue mở liên quan đến việc này. Phần bình luận của bài blog cũng có nhắc đến
    Client side include feature for HTML
    https://github.com/whatwg/html/issues/2791

  • HTML từng có include, rồi nó mất dần sức hút
    Thực ra thuật ngữ “include” là một tính năng của XML, và đó cũng là thứ bài viết mong muốn. Trước XML, HTML từng có một cách tiếp cận khác, đó là frames. Vì frames làm được nhiều việc hơn XML include rất nhiều nên HTML không có thêm riêng tính năng đó. Frames mất dần sức hút vì nhiều vấn đề như lạm dụng, bảo mật, khả năng truy cập, v.v.

    • Không giống frameset, XML include dường như chưa từng được hỗ trợ đúng nghĩa trên nhiều trình duyệt, có lẽ là trên bất kỳ trình duyệt lớn nào
      Thỉnh thoảng tôi vẫn thích dùng nó, nhưng cần một bước biên dịch để đánh giá nó trước khi đưa cho người dùng hoặc trình duyệt