Skip to content

Instantly share code, notes, and snippets.

@jhnns
Created April 25, 2017 15:34
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jhnns/e4c430a5b613861e0a46e3e580d1cd81 to your computer and use it in GitHub Desktop.
Save jhnns/e4c430a5b613861e0a46e3e580d1cd81 to your computer and use it in GitHub Desktop.
Introducing `<fragment>` element

Introducing <fragment> element

The problem

Web pages are often built of several high-level building blocks, like <Navigation>, <Content>, <Aside> and <Footer>. A typical web page looks roughly like this:

<html>
	<head>
		<Title />
		<Static-Assets />
		<Meta-Tags />
	</head>
	<body>
		<Navigation />
		<Content />
		<Aside />
		<Footer />
	</body>
</html>

Often, we know the structure of the page immediately or at least immediately after routing the request. In most cases, we don't need to query the database to determine the structure of the page. However, some of these elements contain dynamic content for which we need to query the database:

  • Probably <Title>
  • <Meta-Tags>
  • Sometimes <Navigation> if it depends on authentication state (logged in vs. logged out)
  • <Content>
  • Probably <Aside>

If we wanted to stream that HTML document, we needed to stop at the first dynamic element, although there are static elements already waiting in the line. The biggest problem here is even not visible: The HTTP status code. Often we neeed to wait for at least one database query before we can do anything, because HTTP trailers are not supported.

We could stream static assets via H2 push in the mean time, but in that case, the client needs to send a proper cache digest, otherwise we would waste precious bandwidth on already cached assets. We can also only do this if we know upfront that the client is going to request it. This excludes:

  • resolution dependent assets (2x images, desktop stylesheets)
  • authentication dependent assets

The solution?

What if we could define placeholders for document fragments that reference their final representation, similar to an <img> tag:

<html>
	<head>
		<fragment src="./title.html" />
		<fragment src="./assets.html" />
		<fragment src="./meta-tags.html" />
	</head>
	<body>
		<fragment src="/user/123/navigation.html" />
		<fragment src="./content.html" />
		<fragment src="./aside.html" />
		<fragment src="/footer.html" />
	</body>
</html>

If that HTML document would be on the page http://example.com/org/abc, the HTML document would actually be built of 7 fragments:

  • http://example.com/org/abc/title.html
  • http://example.com/org/abc/assets.html
  • http://example.com/org/abc/meta-tags.html
  • http://example.com/user/123/navigation.html
  • http://example.com/org/abc/content.html
  • http://example.com/org/abc/aside.html
  • http://example.com/footer.html

How is that similar to an <img> tag? The intrinsic size of the content is unknown before downloading it. The content is streamed and included into the DOM as the bytes are coming in (similar to replaced elements). It differs in the way that the aspect ratio is unknown until the end.

We could send out the main document immediately after the structure is known and the browser would request the remaining fragments as needed.

Benefits

Location indepedent streaming

Every page content could be streamed as soon as possible, regardless of the location inside the document.

Granular caching

We could cache static parts of the document more granular. A small change in one part of the document would not invalidate the whole document.

Less HTML for mobile

Using <fragment> and combining it with the <source> element, we could load more content if we have more screen real estate:

<fragment>
    <source src="nav-desktop.html" media="(min-width: 1000px)">
    <source src="nav.html">
</fragment>

Objections

What about <iframe>?

iframes solve a different use-case: They represent a nested browsing context with its own session history, JS execution context, etc. They also don't propogate their intrinsic size to the embedding document, we need to specify a fixed aspect ratio upfront.

What about HTML imports?

HTML imports require JS in order to replace HTML elements. The goal of this proposal is to find a declarative way that doesn't involve JS. This way, the browser is able to initiate the requests as soon as possible without the need to load, parse or execute JS.

What about Web Components?

Web Components also require JS and are intended for building components that may be repeated multiple times on the page, which also includes nesting. The <fragment> element is intended as high-level building block (maybe only inside the root document?) to stream parts of the document as it is initially parsed by the browser. Its use diminishes as soon as there is a JS rendering runtime loaded.

It could still be useful, though, if pages are streamed from a Service Worker.

Page jumps

As fragments are streamed, it may occur that the page jumps on the x and the y axis. On the x axis, this can be prevented by using CSS layout techniques that are not dependent on the content (CSS grid or percentage width). On the y axis, it's a little bit harder. It can be solved by specifying a fixed aspect ratio if there is one. If the aspect ratio is unknown, it's best to be handled by the browser via scroll anchoring.

But we still don't know the status code

Yes, that's still a problem. We can't send the main document before knowing the status code. That's why HTTP trailers would be really nice :). In the meantime, we can use H2 push to push the fragments in order to leverage the bandwidth.

Without a cache digest, we would still ignore the browser's cache in this case. But the current situation isn't any better as a small change invalidates the whole document.

Thoughts

Polyfill

This could probably be polyfilled with JS by inlining a small script at the beginning of the document that resolves new fragments.

@kalm42
Copy link

kalm42 commented Mar 27, 2021

+1

@tbeckenhauer
Copy link

Yeah this would be nice.

@mmiglioranza22
Copy link

+1

@radsoc
Copy link

radsoc commented Feb 22, 2024

+1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment