Skip to content

Client side include feature for HTML #2791

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
TakayoshiKochi opened this issue Jun 27, 2017 · 271 comments
Open

Client side include feature for HTML #2791

TakayoshiKochi opened this issue Jun 27, 2017 · 271 comments
Labels
addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest

Comments

@TakayoshiKochi
Copy link
Member

TakayoshiKochi commented Jun 27, 2017

Spun off from HTML modules discussion

There are certain amount of interest that including HTML snippet into an HTML document, without using JavaScript. That would be similar to <iframe>, but more lightweight and merged into the same document.

It would work as a naive way to have your header and footer sections defined in one place.


(Edit by @zcorpan: also see #3681)

@TakayoshiKochi
Copy link
Member Author

TakayoshiKochi commented Jun 27, 2017

I personally do not buy this much (sorry!), as we have enough primitives (fetch, DOM APIs, maybe Custom Elements) to realize a equivalent feature very easily. Other than ease of use, what is the benefit of having this in the platform?

@mathiasbynens
Copy link
Member

How would this differ from HTML Imports?

@TakayoshiKochi
Copy link
Member Author

TakayoshiKochi commented Jun 27, 2017

HTML Imports load a HTML document, via <link rel="import" href=...> and its contents are never rendered without DOM manipulation via script. The document is stored in $(link).import property. HTML Imports have more, like <script> is executed etc.

This idea is about inserting HTML snippet in HTML document. e.g.

main document

<include src="header.html"></include>
Awesome contents
<include src="footer.html"></include>

header.html

<h1>Welcome!</h1>

footer.html

<footer>Copyright 2017 by me</footer>

will result in

<h1>Welcome!</h1>
Awesome contents
<footer>Copyright 2017 by me</footer>

@rianby64
Copy link

I was always dreaming to see this feature in the browser. This tag, as exposed by @TakayoshiKochi should allow to put some HTML content in the DOM in a simple way. I think the <include> tag should stay and not be replaced.

I would like to propose the following:

<include id="my-include" src="an_URL.html"></include>

And the event could be:

var included = document.querySelector('#my-include');
included.addEventListener('load', e => {
  // ...
});

included.loaded.then((included_) => {
  // here you see that
  // included_ === included
  // and this promise is ready once the HTML code
  // from included.src has been fetched and appended to the DOM
});

Reproducing the behavior from document.currentScript I found easy to use document.currentInclude, so if a script is executed inside an <include> then it should know where it is.

So, an include has a small set of features

  • load event
  • loaded promise
  • currentInclude (or a better name)

Hope this idea will be useful.

@rianby64
Copy link

There are some questions around this tag that I'd like to expose too.

  • How to resolve the src path if it's relative?
  • What about if src changes. The fetched content should change too?
  • If an include contains some tags?
<include src="an_URL.html">
  <div class="preloader">...</div>
</include>

After fetching, the innerHTML content should be replaced?

  • In which order should be dispatched the load event? from most nested up to the top?

@domenic
Copy link
Member

domenic commented Jun 27, 2017

I don't think we should do this. The user experience is much better if such inclusion is done server-side ahead of time, instead of at runtime. Otherwise, you can emulate it with JavaScript, if you value developer convenience more than user experience.

I'd encourage anyone interested in this to create a custom element that implements this functionality and try to get broad adoption. If it gets broad adoption we can consider building it into the platform, as we have done with other things like jQuery -> querySelectorAll.

@domenic domenic added addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest labels Jun 27, 2017
@rianby64
Copy link

@domenic I tried to develop this idea as a custom element for my projects, and found that it's possible to achieve HTML import, but there are some things that made that solution hard to debug. For instance, beforescriptexecute was removed or even not implemented. Because of that I was forced to turn all my scripts into "inline" scripts.

I'll keep on spreading the word with more cases about how to split the code into small pieces without using extra JS effort.

@Yay295
Copy link
Contributor

Yay295 commented Jun 27, 2017

What's the actual purpose of this? As domenic mentioned, you can already do this quite easily server-side, so why do we need an HTML element to do it less effectively?

@rianby64
Copy link

Personally, I found this feature very useful in my projects. But, this is only my personal opinion. And, what @domenic said sounds fair. The only thing that I'd like to repeat is the absence of beforescriptexecute event, that forces me to turn all the scripts into inline scripts. All other primitives are enough to implement this functionality into a custom element.

I'll be happy to share with you @Yay295 or anybody else my experience with this feature, the pros and cons, but that chat should be outside this issue.

@brandondees
Copy link

I think it would be quite useful for any cases where we want DRY html authoring but not the burden of running code server side or requiring JS. It's actually what I naively expected html imports to do at first.

The use cases may be relegated primarily to the realm of small, static-only websites but I think it's a huge advancement for those cases. Simple static-only sites are a large number of websites, or sites that probably should be purely static but cannot be for reasons such as requiring server side rendering to DRY shared fragments such as header/footer, etc. I'm thinking of all the shared web hosting site builder tools and a large number of wordpress sites (a security/maintenance nightmare for typical site owners in my experience) and things along those lines. These kinds of sites are typically owned/maintained by the least tech-savvy operators and are therefore likely under-represented in these kinds of platform-advancement discussions. I'm aware that dynamic rendering or static build tools can get the job done, but those are inaccessible tools to a majority of simple website owners (again, in my personal experience).

The JS-free aspect gets back into the philosophy of progressive enhancement including "site basically works without scripting enabled" and I think that's still important, personally, particularly when we have Brave browser picking up steam with JS disabled by default for security/privacy purposes.

I may try to take a stab at faking this using a custom element backed by fetch, but it wouldn't fill the same gap IMHO and would merely be a demonstration for illustrating the convenience it can provide to the page authoring experience once it's all set up.

@brandondees
Copy link

I might also comment that I would expect client side includes to do something efficient with caching based on server headers or whatever, minimizing the UX cost of the extra round trips after first load (and I would presume we could also use link rel=preload etc. to great effect for load time beyond the first page). With http/2 implemented appropriately the UX cost of this feature should go away entirely.

@grepsedawk
Copy link

grepsedawk commented Jun 29, 2017

I want to jump in and mention that PHP (Personal Home Page) was literally created to solve this problem "In the most simple way possible". This could be simply done on the browser/markup level so much easier.
Imagine if the client could cache the entire HEADER and FOOTER and only need to d/l the main content... Sounds like pretty dang powerful feature to me!

@rianby64
Copy link

HTML import feature is what big frameworks offer indirectly. I think, if we've this feature then we've more possibilities to write nice things in a simple way. If HTML imports will be present right into the browser then I'll feel that it is a complete framework.

@AshleyScirra
Copy link

Further to @brandondees' point, I think I'd point out that offline-first PWAs using Service Worker very much encourage a client-side approach. For example in our PWA (editor.construct.net), despite it being a large and complex web app, we generate virtually nothing on the server side. This is the obvious way to design something that keeps working offline, because everything is static and local, and there's no need for a server to be reachable, especially if all the server is doing is a trivial substitution of content that could easily be done client side. So I think there are actually some significant use cases where you might want to process an include client-side, and "just do it on the server" doesn't cover everything.

@TakayoshiKochi
Copy link
Member Author

FYI, there was a same discussion happened at WICG/webcomponents#280

@brandondees
Copy link

I've implemented my own very quick-and-dirty demonstration here devpunks/snuggsi#109 to begin experimenting with the pros/cons this feature might have, and we're attempting to keep track of other related efforts for reference as well. @snuggs took it beyond the most basic proof of concept and appears to have brought it close to general production-readiness.

I had a discussion recently with a colleague whose initial impression was that this concept merely re-invents server-side includes, which should otherwise be easy enough to work with for most content authors, but I think there are some significant subtle differences still. It's not clear to me why server side includes have not been well leveraged in commonly used website building tools, and I think the reasons boil down to a lack of accessible (read: free) and user-friendly (enough for non tech-savvy users) authoring tools supporting that technology, and lack of standardization. There can be performance benefits from automatically leveraging client side caching of partial documents, which is something I was always baffled by the absence of since I first began learning web dev. New page loads for a given site can retrieve primarily only the portions of the document that are unique, without the need to re-transmit boilerplate sections such as header, navigation, footer, sidebars, etc. without even getting into how the same kinds of benefits also apply when using web component templates.

@TakayoshiKochi
Copy link
Member Author

TakayoshiKochi commented Jul 21, 2017

Oops - sorry about closing accidentally.

I had not been sure about the advantage of client-side processing against server-side include (including PHP's include(), which sounds popular but I don't have any data), but PWA (especially, using service worker to save client-server roundtrips) story in Ashley's #2791 (comment) sounds one of the good reasons of having client-side processing of HTML being okay.

@snuggs
Copy link
Member

snuggs commented Jul 21, 2017

Indeed @TakayoshiKochi we created a super simple <include- src=foo.html> iteration utilizing the DOMParser. Methinks this is how polyfills are (not doing a good job of) handling HTMLImports.

I'd encourage anyone interested in this to create a custom element that implements this functionality and try to get broad adoption. If it gets broad adoption we can consider building it into the platform, as we have done with other things like jQuery -> querySelectorAll.

I concur with @domenic. on providing a sound iteration/adption/developer ergonomics being worked on in this pull request.

The algoritm was as simple as follows. Also works with nested dependencies due to custom elements lifecycle reactions:

  Element `import-html`

  (class extends HTMLElement {

    onconnect () {
      this.innerHTML = 'Content Loading...'
      this.context.location = this.getAttribute `src`

      let headers = new Headers({'Accept': 'text/html'})

      fetch (this.context.location, {mode: 'no-cors', headers: hdrs})
        .then (response => response.text ())
        .then (content => this.parse (content))
        .catch (err => console.warn(err))
    }

    parse (string) {
      let
        root = (new DOMParser)
          .parseFromString (string, 'text/html')
          .documentElement

      , html = document.importNode (root, true)

      , head = html.querySelector
          `head`.childNodes

      , body = html.querySelector
          `body`.childNodes

      this.innerHTML = ''
      this.append ( ... [ ... head, ... body ] )
    }
})

Any caveats to DOMParser would be great. Especially older versions of IE.

Hope this helps @TakayoshiKochi

/cc @brandondees

@rianby64
Copy link

rianby64 commented Jul 21, 2017

I was thinking last days since @TakayoshiKochi opened this issue. And found really interesting how to integrate this feature include with Worker, <link>, <iframe> and so on, also don't forget to take in count CORS... Looks too hard to achieve the goal of HTML import in a simple way. If <base> could be more flexible, then this feature could be done "as we have enough primitives".

@Yay295
Copy link
Contributor

Yay295 commented Jul 21, 2017

Ignoring the fact that that code doesn't work, at all, you're really overthinking it. Here's a complete HTML test page. Just change the source to include.

<!DOCTYPE html>
<html>
    <head>
        <script>
            class include extends HTMLElement {
                connectedCallback() {
                    fetch(this.getAttribute('src'), {mode: 'cors', credentials: 'same-origin'})
                        .then(response => response.text())
                        .then(text => this.outerHTML = text)
                        .catch(err => console.warn(err));
                }
            }

            customElements.define('include-html', include);
        </script>
    </head>
    <body>
        <!-- Include the partial HTML. -->
        <!-- If the included HTML has includes, they will be included too. -->
        <include-html id="test" src="to_include.html" />

        <!-- No problems here either. It just logs an error if this happens. -->
        <!-- script>document.getElementById('test').remove()</script -->
    </body>
</html>

This should be a void element in my opinion. There's nowhere to put any nested elements except after everything, so you might as well just put them outside the include instead.

p.s. "Any caveats to DOMParser would be great. Especially older versions of IE." is irrelevant considering custom elements currently only work in WebKit browsers.

@rianby64
Copy link

rianby64 commented Jul 21, 2017

@Yay295 Nice. But how to execute scripts that are present in src="to_include.html"?

The concept of HTML import should be more than just pasting static HTML, right?

@AshleyScirra
Copy link

I think the intent here is just to paste DOM content in to another document. HTML imports are a different feature.

@rianby64
Copy link

Ok @AshleyScirra . You're right. As consumer, If paste DOM content in to another document then I expect to see scripts, links, workers et al and other inclusions parsed and executed. Hope this feature will gain broad adoption.

@snuggs
Copy link
Member

snuggs commented Jul 21, 2017

Ignoring the fact your code doesn't work ...

  1. @Yay295 the code works fine. Was a snippet from the pr that was clearly referenced in the previous comment. Spared you the details.
  2. This code is also intended to be used as a polyfill of sorts for the crappy implementation of webcomponentsjs polyfill that currently breaks for reasons outside of this thread. Therefore to be clear we PERSONALLY need a bonafied Document not a string.
  3. tried your method but ran into a few issues on different (ancient) platforms. Have you tried (with scripts) more than just your browser @Yay295 ? Just curious.
  4. was the fastest past could think of that runs external scripts and styles. DOMParser is fairly "ancient" based off spec. (thanks for the refactor tho 😎 will add it to our pull request if HTML Imports keels over)

/cc @brandondees @pachonk

@jakearchibald
Copy link
Contributor

jakearchibald commented Nov 14, 2024

You are talking about writing HTML using a new standard written in JSON

I'm not. I think you missed or misunderstood the "would allow for" bit.

An attribute-based solution, like <include src="…"> would allow for a src that's HTML. Whereas a solution like stream.pipeTo(element.getWritable()) would allow streams to come from places other than the network. It also allows for streams that originated in another data format (such as JSON, binary, whatever) but are transformed into HTML by the developer before they hit the element's writable.

I'm not saying that everyone should be doing this, but it would be possible. I'm definitely not saying new formats should be standardised as part of this effort. I'm showing that a lower-level solution is more versatile and enables more use-cases than a higher-level attribute-based solution.

@maherbo
Copy link

maherbo commented Nov 14, 2024

@jakearchibald

An attribute-based solution, like <include src="…"> would allow for a src that's HTML. Whereas a solution like stream.pipeTo(element.getWritable()) would allow streams to come from places other than the network.

Can't these two solutions be able to live together, side-by-side? How would you include a document fragment without JavaScript, only in pure HTML?

HTML does support embedded content (img, video, iframe, embed, object, etc...). The real question here is why can't you embed a document fragment in the current HTML document? You can embed a whole HTML document, with <iframe> and <object>, but not document fragments.

The objective is to be pure HTML, JavaScript-free. It is to easily manage the caching system to eliminate redundant data requested. It is also to easily manage the content server-side; for example, not having to modify multiple HTML documents that should have the same content written in them, like a menu for example.

A client-side include is really about writing efficiently HTML documents without relying on JavaScript. A browser should be able to build an HTML document from different document fragments. It really is just a cut-and-paste process.

@jakearchibald
Copy link
Contributor

Can't these two solutions be able to live together,

Yes, like I said:

It would also simplify higher level solutions like the one you maintain, and leave the door open to a higher level platform version. https://extensiblewebmanifesto.org/

@bkardell
Copy link
Contributor

I don't think @jakearchibald's comment is an either/or even - if you answer/create the low level thing it would force the answers it seems you'll need for the high level thing (in fact, N potential high level things).. .I don't think that means don't simultaneously try to do the obvious high level thing, or wait until it's shipped and we have lots of things using it... At least, if there is an obvious seeming high level thing we can kind of agree on (I'm not sure it's this, but maybe?).

@justinfagnani
Copy link

@justinfagnani let's say the new API is:

stream.pipeTo(element.getWritable());

What happens if you try to getWritable() when there's already a stream writing to the element? Possibilities:

  • You get two streams piping text into the same parser. This would be a huge footgun and security issue.
  • You get two streams piping text into two parsers connected to the same element. This is less bad but still pretty weird.
  • The second call fails since the element is locked. This is ok, but it's possible for a faulty call to lock an element forever.
  • The first writable is put into an errored state, meaning the second writable 'takes over'.

I hadn't yet heard new ideas related to potentially locking an element for writing in a long time (outside of the old display locking discussions that became content-visibility). Without this new concept, I would assume that the behavior would just have to be the second option.

If the desired behaviour is one of the last two, then it raises the question: What happens if innerHTML is set on the element while there's an active writer? It could be:

  • As you say: just let the innerHTML operation happen in the middle of the streaming parsing.
  • Act like innerHTML gets a new writer internally. Which, depending on the above would make setting innerHTML a no-op, or put the current writer into an errored state.

The first option is already there spec-wise, and as you say, you can already 'call' innerHTML when the document's parser is open. But, if we have this new concept of "an active writer for an element", is element.innerHTML operating in the same space, so should they interact somehow?

So I agree here that this question would need to be settled first, since we don't have either the concept of an active writer, or mid-document streaming updates, and their interaction is important. Without this idea of an active writer, I was assuming that the first option here is the way to go.

Is there a place that getWritable() is discussed yet?

@jakearchibald
Copy link
Contributor

Is there a place that getWritable() is discussed yet?

Nah I was just making shit up on the spot 😄

@trusktr
Copy link

trusktr commented Apr 9, 2025

We need this. Explainer and live demo:

HTML Includes

What's the actual purpose of this? As domenic mentioned, you can already do this quite easily server-side, so why do we need an HTML element to do it less effectively?

If you have a static website, introducing a server just for multi-file code organization can be complete overkill for many use cases.

A JavaScript based solution also requires JavaScript and is non-standard. Even CSS has @import (we need @import-once to make it better).

HTML Includes works without JS, and being standardized would mean common support across apps, IDEs (types, intellisense), and build tools (f.e. bundlers for optimization, which @domenic will love to use instead of loading multiple HTML files).


It is 100% worth having this built-into browsers.

Currently (see the live demo) it looks like this:

<template inlined src="./some.html"></template>

but I'm starting to think include is better than inlined:

<template include src="./some.html"></template>

Also, it is a <template> and not:

  • <link rel=include href="some.html"> because adding src to template is useful for non-include templates too <template shadowrootmode="open" src="my-shadow-content.html">
  • <include src="some.html"> because this is not fallback capable, meaning that in old browsers that don't have includes yet, the <include> element will be live in the DOM. Not a biggies, but <template> already has semantics and implementation in place.
    • In particular, the code paths for <template shadowrootmode> are already in place: the element gets removed at parse time. With minimal changes, a <template include> would be removed at parse time and the content inserted into a parent instead of a shadowroot, that's the only difference.
      • We could decide to make the element stay in the DOM, but I don't think it is necessary, at least not for an MVP (trying to do it with JavaScript would be pointless, you can just use fetch(html) in JavaScript, so it would not do anything if appended via JS, just as with <template shadowrootmode>.

By default, it is synchronous, and easy to understand.

We could add an async attribute for that in v2 if it would be worth it, but without async this is still a big win for development (and very easy to optimize with bundlers when needed).

Simplicity

The mental model, at least for a first version, is very easy to understand: the HTML from separate file gets "inlined" or "included" in place exactly as if you wrote it there in the first place (and hence why the default is synchronous).

This code,

<!-- some.html -->
<p>hello</p>
<template include src="./some.html"></template>
<p>world</p>

would be practically the same is having written this:

<!-- some.html -->
<p>hello</p>
<p>world</p>

And that's practically the whole spec! Let's keep this as simple as possible for a first version.

@annevk
Copy link
Member

annevk commented Apr 9, 2025

Synchronous fetching is a big no-no. That's not happening.

@sashafirsov
Copy link

@trusktr

I'm starting to think include is better than inlined

IMO the tag or element which defines the DCE tag can serve the inline already. By providing the blank value.

<template src="xxx" tag />

@sashafirsov
Copy link

@annevk ,

Synchronous fetching is a big no-no.

The DCE could serve not just rendering but some prep logic for other page content. It should be up to developer to define the hydration level and priority. As SCRIPT does with defer and other attributes.

Another thing, the hydration scope and priorities have not beed discussed in the scope of DCE discussion yet.

From my experience, I would need to hold page content DCE rendering till the moment the page level template with headers/footers are loaded. Only then it makes a sence for other DCE to be rendered. You can imagine quite a bit of such container-child scenarios.

@jakearchibald
Copy link
Contributor

@sashafirsov it might just be me, but I'm unfamiliar with terms like "DCE", "hydration level", "hydration scope". They don't seem to be web platform concepts, so I'm struggling to understand your comment.

@dy
Copy link

dy commented Apr 11, 2025

Declarative Custom Elements.

@sashafirsov
Copy link

@jakearchibald , html include is a particular case for DCE( declarative custom element ) - a proposal in baking for no-js web components development.

The hydration is a pattern which is used by most of scalable apps. Virtual scrolling and server side rendering are the particular cases for that.

All of mentioned are under heavy discussions in w3c. I bet you familiar as with concepts as with discussions existence. Taking your comment as sarcasm than.

@sashafirsov
Copy link

why to use blank tag attribute instead of omission as in <template shadowrootmode="open">...?

With src provided, the use of template without instantiation ( html include ) can be needed to be used by reference. Either by custom element in JS or declaratively via src="#id". If the instantiation happen without blank tag attribute ( or any explicit attribute as preposed earlier include or inlined ), it would be unwanted side effect.

@bburns
Copy link

bburns commented Apr 21, 2025

@trusktr yes, just focus on making the simplest version possible!

We can already convert a stylesheet from inline to external, and a script from inline to external, so should be able to convert html from inline to external. eg

<!-- index.html -->
<p>hello</p>
<html src="./world.html" />

<!-- world.html -->
<p>world</p>

It would just ignore all stylesheets and scripts in the fragment.

@trusktr
Copy link

trusktr commented May 1, 2025

Synchronous fetching is a big no-no. That's not happening.

@annevk Sync fetching would be very bad indeed. That's not what I meant, and not what we want.

Let me rephrase:

The need

Fetching should be async, but for sanity the default execution model should be that the execution is "down the page" linearly, the same as with existing non-module <script>s, inlined <style>s, and <link>ed styles. Perhaps "linear execution" is a much better term (similar to await in JS offering linear execution of a function's logic).

Without a linear execution model by default, content will pop into the page in a random order, introducing unwanted FOUC by default, and would require JavaScript for handling the situation appropriately.

Using JavaScript would defeat the purpose of the proposal.

How

The parser can continue parsing forward and fetching files in parallel (async), while the execution catches up. While fetching external files, further includes (or non-module scripts and linked styles) can be also fetched in parallel.

The end goal for default behavior is that externalizing a piece of HTML would essentially behave the same as if it were written inline. The only difference would be slightly more time for the page to execute due to network requests. To the user, the only "difference", before and after assuming the same exact network speed, would be as if the connection to the website got slower by some amount (depending on the number of externalized pieces of HTML and hence the number of additional network requests).

Optimization

The above model is very easy to optimize in a way that does not require much thought: a bundler would simply inline the content (recursively, in case of includes inside includes, or other non-module scripts and linked styles), without there being a difference in the final behavior of the page apart from speed differences (no random popping/FOUC of content before optimization/bundling, same visible behavior after but faster).

@trusktr
Copy link

trusktr commented May 1, 2025

Some people have mentioned that using <template> for this is not the right semantic, and would prefer <include>, proposing it would make implementation easier.

I would like the capability that I'd gain as an end user of the feature, but people with more experience on the browser implementation side may know what's the best approach from a technical perspective (<link> vs <template> vs new element like <include>), and I'd like it in whatever syntax form works.

Do any implementers here have thoughts on the easiest implementation path?

@trusktr
Copy link

trusktr commented May 1, 2025

It would just ignore all stylesheets and scripts in the fragment.

@bburns Wouldn't it be easier to simply run existing HTML parser logic on the included HTML without making new logic that clips out styles and scripts?

Plus, then the include would not be the same as writing HTML inline. The goal is to simply put HTML in a separate file. Otherwise it would be unexpected to move HTML to a separate file and it no longer working.

@trusktr
Copy link

trusktr commented May 1, 2025

@chriscoyier coincidentally wrote about this yesterday:

Seeking an Answer: Why can’t HTML alone do includes?

@poef
Copy link

poef commented May 1, 2025

I've made a javascript implementation using <link rel="simply-include" href="embed.html">--see: https://github.com/SimplyEdit/simplyview/blob/v3/src/include.mjs, and have been using it for some years now. It mimics how the browser would parse and handle the HTML if it was embedded directly in the HTML, including order of execution of <script> tags. It runs asynchronous, and will trigger any time a <link rel="simply-include"> enters the dom.
I've had no issues with infinite recursion, it is just not a thing. Just like <iframes> could potentially recurse infinitely, but it is easily spotted and fixed.
I've had no need to convert scripts to inline scripts. I did have to insert guard scripts that trigger an event after each script load, to make the script loading/running follow the correct order.
When handling a <link rel="simply-include"> I remove the original link element from the DOIM, so it can't trigger again. It may be better to set an attribute on it instead.
The only issue I've really had with it is that it is difficult to debug javascript code that is included in this way, at least in Firefox. You can't easily navigate to those scripts in the debugger. Chrome handles this much better, so it can be fixed.
Security issues are generally handled with CORS and SCP headers. I haven't found any issues that aren't already there when you include a <script src="//other.domain/evil.js">.

I don't think my implementation is what you would build into the browser, but I do think it could be used to explore potential issues and consequences of implementint a native <include> feature.

As for use-cases. I found it invaluable when working with micro-services. Each service gets its own UI code, on its own URL, and a container application can just include that. This can be achieved with things like web-components as well, but this feals much more web-like. If the service fails, the include isn't resolved, so there is no UI, but the other services keep working like normal. There should probably be a way to show that the include failed, just like a normal 404 page inside an iframe.

@bburns
Copy link

bburns commented May 2, 2025

@trusktr Wouldn't it be easier to simply run existing HTML parser logic on the included HTML without making new logic that clips out styles and scripts?

I was assuming the reason this wasn't already a feature was due to corner cases in doing that, so... it would be nice just to be able to include static html - the main page could include the stylings and js.

So you could have something simple like

Element Inline External
html <html>foo</html> <html src="foo.html"></html>
style <style>foo</style> <style src="foo.css"></style>
script <script>foo</script> <script src="foo.js"></script>

instead of

Element Inline External
html <html>foo</html> <object data="foo.html"></object>
style <style>foo</style> <link rel="stylesheet" href="foo.css" />
script <script>foo</script> <script src="foo.js"></script>

which is hard to remember!

I didn't even know about the 'object' tag until someone just posted it here (since deleted?).

@maherbo
Copy link

maherbo commented May 2, 2025

@bburns , yes <object>, that is how I load my HTML document fragments with my solution to this problem. Using this feature doesn't require much effort to extend user agent capabilities to include document fragments.

@mildred
Copy link

mildred commented May 5, 2025

If <template/> elements allowed for a src attribute, includes would be already possible using the power of the shadow DOM:

It is already possible to inline shadow DOM trees using the <template/> elements, and shadow DOM permits to have a DOM tree that is both isolated but can be integrated with the page too. It is not full includes as CSS works a bit differently, but this also allows for scoped CSS out of the box.

I wrote a website using this idea using a load-file custom element: https://github.com/mildred/example-html-includes-with-custom-elements

@zcorpan
Copy link
Member

zcorpan commented May 5, 2025

I think #2142 may be a necessary primitive for this feature.

@justinfagnani
Copy link

justinfagnani commented May 5, 2025

If <template/> elements allowed for a src attribute, includes would be already possible using the power of the shadow DOM:

<template> is not the right element for this feature. <template> is semantically incorrect because we're not marking a special subtree of children to be inert or moved.

And <template> exists for its special parsing behavior, and we're not looking for special parsing behavior of this element's children here. in fact, it would be really useful to display an <include> element's children while the included content is loading, and to potentially slot the children once the content is loaded. <template> would prevent those types of features.

@DanielTate

This comment has been minimized.

@robbiespeed
Copy link

robbiespeed commented May 6, 2025

in fact, it would be really useful to display an <include> element's children while the included content is loading, and to potentially slot the children once the content is loaded.

@justinfagnani This is similar to what I had in mind when suggesting the persistent fragment proposal use a element (something which I think you suggested as well), as it could facilitate the desired behaviour of html includes (comment). Both features share common needs of allowing placement anywhere in the DOM (inside ul, table, etc), as well as the contents appearing as if they had no wrapping element with regards to CSS (child selectors work as if the contents where a child of the nearest parent element exclusive of the wrapper itself).

Example:

<fragment src="/content.html">
  <span class="spinner"></span>
  Loading...
</fragment>

It would be quite nice if 2 highly requested features like this could have a unified solution. With potential of also addressing out of order streaming.

<fragment src="#content"></fragment>
<!-- Later in the document -->
<template id="content">
  Hello World
</template>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest
Development

No branches or pull requests