Skip to content

Instantly share code, notes, and snippets.

@WebDevLuke
Last active April 1, 2019 15:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save WebDevLuke/f7d344017358d5eedc1392e4b01fb57d to your computer and use it in GitHub Desktop.
Save WebDevLuke/f7d344017358d5eedc1392e4b01fb57d to your computer and use it in GitHub Desktop.
ITCSS HTTP2.0 Exploratory Template
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1" />
<!-- Imports ITCSS settings / tools / generic / elements layers -->
<link rel="stylesheet" href="static/core.min.css">
</head>
<body>
<!-- Simple button component which only require its own styles and logic -->
<link rel="stylesheet" href="static/components/button.min.css">
<script src="static/components/button.min.js"></script>
<button class="c-button"></button>
<!-- Much more elaborate pattern involving objects / component / utilities -->
<link rel="stylesheet" href="static/objects/expander.min.css">
<link rel="stylesheet" href="static/utility/spacing.min.css">
<link rel="stylesheet" href="static/utility/widths.min.css">
<script src="static/expander.min.js"></script>
<div class="o-expander u-margin-bottom-regular">
<div class="o-expander__heading">
<div class="u-width-8/12">
<link rel="stylesheet" href="static/components/typography.min.css">
<h1 class="c-type-alpha">My Expander Component</h1>
</div>
<div class="u-width-4/12">
<button class="o-expander__toggle">Open</button>
</div>
</div>
<div class="o-expander-content">
<link rel="stylesheet" href="static/objects/box.min.css">
<div class="o-box o-box--spacing-small">
My Expander Content
<link rel="stylesheet" href="static/objects/media.min.css">
<div class="o-media">
<div class="o-media__image">IMG</div>
<div class="o-media__content">Media Object</div>
</div>
</div>
</div>
</div>
<!-- Would second usage of the same pattern be the same, except imports? -->
<div class="o-expander u-margin-bottom-regular">
<div class="o-expander__heading">
<div class="u-width-8/12">
<h1 class="c-type-alpha">My Expander Component</h1>
</div>
<div class="u-width-4/12">
<button class="o-expander__toggle">Open</button>
</div>
</div>
<div class="o-expander-content">
<div class="o-box o-box--spacing-small">
My Expander Content
<div class="o-media">
<div class="o-media__image">IMG</div>
<div class="o-media__content">Media Object</div>
</div>
</div>
</div>
</div>
<!-- If another pattern uses same objects or utilties, would we always keep the imports present, for easy copy/paste? As wouldn't they just use cache from previous import? Or would we remove the imports if they are already imported as part of previous elements which use the object/utility -->
<link rel="stylesheet" href="static/objects/box.min.css">
<div class="o-box o-box--spacing-small c-my-component">
My Custom Component
<link rel="stylesheet" href="static/objects/media.min.css">
<div class="o-media">
<div class="o-media__image">IMG</div>
<div class="o-media__content">Media Object</div>
</div>
</div>
<!-- Compiled JavaScript, placed at end of document so it doesn't block loading of everything else -->
<!-- Any global level JS -->
<script src="static/core.min.js"></script>
</body>
</html>
@csswizardry
Copy link

csswizardry commented Apr 1, 2019

CSS

While the CSS-in-body pattern is pretty baller, it’s actually not that reliable just yet. Unfortunately, although Chrome does support the progressive render of the UI (i.e. instead of blocking rendering of the entire page based on synchronous CSS, it will only block rendering of the page after the CSS file), it does—in my opinion—misprioritise the in-body CSS. This means that the late-needed CSS is actually pulled way up the request stack and dispatched with a priority matching that of the head CSS. The practical upshot of which is that this technique isn’t all that effective yet. I raised a bug for this just last week.

The second thing you need to be sure to do is de-dupe the CSS files. Don’t import button.min.css once for every button that appears in the HTML. I would only recommend the CSS-in-body technique for milestone components that will only appear once, such as your nav, carousel, footer, etc. Any components that appear a lot (cards, buttons, form elements, etc.) you should bundle into one big package in the head. This means your in-body CSS usually becomes the exception rather than the rule.

The next thing is the worry of cumulative recalc-style events. Every time a new stylesheet arrives, we append to the CSSOM. Every time we change the CSSOM, we have to perform a style-recalc for everything. The cumulative aspect is basically the fact that we don’t just recalc-style for the new CSS that arrived, but we have to recalc the new CSS added to the existing CSS, so if we take your example, we’d end up with this (browser tasks are the comments):

<link rel="stylesheet" href="static/core.min.css">
<!-- Recalc core.min.css -->

<link rel="stylesheet" href="static/components/button.min.css">
<!-- Recalc button.min.css + core.min.css -->

<link rel="stylesheet" href="static/objects/expander.min.css">
<!-- Recalc expander.min.css + button.min.css + core.min.css -->

<link rel="stylesheet" href="static/utility/spacing.min.css">
<!-- Recalc spacing.min.css + expander.min.css + button.min.css + core.min.css -->

<link rel="stylesheet" href="static/utility/widths.min.css">
<!-- Recalc widths.min.css + spacing.min.css + expander.min.css + button.min.css + core.min.css -->

<link rel="stylesheet" href="static/components/typography.min.css">
<!-- Recalc typography.min.css + widths.min.css + spacing.min.css + expander.min.css + button.min.css + core.min.css -->

<link rel="stylesheet" href="static/objects/box.min.css">
<!-- Recalc box.min.css + typography.min.css + widths.min.css + spacing.min.css + expander.min.css + button.min.css + core.min.css -->

<link rel="stylesheet" href="static/objects/media.min.css">
<!-- Recalc media.min.css + box.min.css + typography.min.css + widths.min.css + spacing.min.css + expander.min.css + button.min.css + core.min.css -->

<link rel="stylesheet" href="static/objects/box.min.css">
<!-- Recalc box.min.css + media.min.css + box.min.css + typography.min.css + widths.min.css + spacing.min.css + expander.min.css + button.min.css + core.min.css -->

<link rel="stylesheet" href="static/objects/media.min.css">
<!-- Recalc media.min.css + box.min.css + media.min.css + box.min.css + typography.min.css + widths.min.css + spacing.min.css + expander.min.css + button.min.css + core.min.css -->

Hopefully you can see the severity of the cumulative effect?

So if you’re gonna do the in-body CSS:

  1. Keep an eye on waterfalls, particularly in Chrome.
  2. Don’t go full-in-body too fast—de-dupe things and only use it for milestone components.
  3. Beware snowballing recalc styles.

JS

I’d not recommend having so many blocking-scripts in the page. A regular, synchronous script blocks the parser while the script downloads and executes, meaning we can’t continue to construct any of, say, o-expander while we’re downloading and executing button.min.js.

JS is a funny one, beacuse its interaction with the DOM and parser mean there is quite a large matrix of ways to load it. Ideally you’d bundle the smallest possible of JS that needs to block rendering. If you’re doing a server-side render, you’ll find that almost all of your JS doesn’t actually need to be loaded synchronously and could take a defer.

I always refer to it as: JS needs to be loaded to its lowest common denominator. If you have a 100KB JS file, and only 1KB of it actually needs to block rendering, you still need to load the entire file synchronously. This means you have 99KB of JS needlessly on the critical path, which sucks. You need to load the whole file to its lowest common denominator. Look at every JS file you’re bundling, see which indiviual bits of it do really need to block, then move them into a sync file. Put a defer attribute on whatever remains.

A crude way of refactoring this is to take your big, blocking bundle and stick defer on it. Reload the page and look at what gives you conole errors. Move the problematic bit(s) of JS into a blocking file, refresh, and hopefully stuff is fixed. Blocking JS is a Bad Thing™, so keep it to a minimum.

@WebDevLuke
Copy link
Author

Thanks for the feedback on this Harry, it's given me a lot to think about. Clearly I was taking the in-body approach too literally, ha. Have 2 follow up questions, one for CSS and a minor one for JS.

CSS

In the gist, I've included a core.min.css link which imports a generic/elements bundle, and as per your comments, ought to include objects, utilities and non-milestone component files aswell, which makes sense.

In a HTTP2 context, because requests are so cheap, would it be worth moving away from a bundled approach, compiling each layer item as a separate CSS stylesheet, and then including them in the <head> (Excluding any single-use milestone components , as covered), like so:-

<head>
   <link rel="stylesheet" href="static/generic/reset.min.css">
   <link rel="stylesheet" href="static/generic/box-sizing.min.css">
   <link rel="stylesheet" href="static/element/images.min.css">
   <link rel="stylesheet" href="static/element/page.min.css">
   <link rel="stylesheet" href="static/object/media.min.css">
   <link rel="stylesheet" href="static/component/button.min.css">
   <link rel="stylesheet" href="static/utility/spacing.min.css">
   <!-- You get the idea -->
</head>

I'm thinking mainly from a cache perspective, as a bundle would cache bust on every update, whilst importing the files individually only impacts the cache for that particular file. Or would the benefits of this be outweighed be the performance cost of the more frequent style recalcs?

JS

Just a quick one, is there any rule of thumb which you use to judge if a JavaScript snippet would need to be featured in a blocking context or an async context? The most obvious one to me would be blocking JS for logic belonging to components / UI featured above the fold, and then async for any UI logic below the fold and thereafter, as from a user's perspective, it's not important if any below the fold components aren't initialised as they aren't visible anyway on page load.

@csswizardry
Copy link

CSS

In a HTTP2 context, because requests are so cheap, would it be worth moving away from a bundled approach…

Don’t go full H/2 just yet. A handful of larger bundles will work better than one file per component (except in the cases with in-body CSS we already discussed). Smaller files achieve suboptimal compression deltas, so you won’t save as much via Gzip as you would with a few key bundles. I’d recommend bundling very infrequently changing stuff like Normalize.css, reset, grid system together; have an app.css or similar that contains your header, footer, buttons etc. that changes on a per-release basis, then have per-page CSS where sensible (e.g. login.css), then the milestone components for in-body use cases.

JS

Just a quick one, is there any rule of thumb which you use to judge if a JavaScript snippet would need to be featured in a blocking context or an async context?

  • <script>: Needs to initialise core functionality for other packages or in-page JS to use (e.g. jQuery); needs to set up a context for the page to be rendered correctly (e.g. Modernizr).
  • <script async>: Doesn’t depend on any other files (async will execute the moment it arrives, so if two files rely on each other but arrive in the opposite order than you expect, you’ll get errors). Use for content not needed for first render, but that you’d like to be functional as soon as possible (e.g. self-contained JS for your nav).
  • <script defer>: Executes just before the DOMContentLoaded event, so runs after the HTML has been parsed. Useful for anything that isn’t needed immediately or even ASAP, such as starting your carousel animating, or loading your social media widgets, etc.

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