Tag: Syntax

A Guide to the Responsive Images Syntax in HTML

This guide is about the HTML syntax for responsive images (and a little bit of CSS for good measure). The responsive images syntax is about serving one image from multiple options based on rules and circumstances. There are two forms of responsive images, and they’re for two different things:

If your only goal is…

Increased Performance

Then what you need is…

<img   srcset=""   src=""   alt="" >

There is a lot of performance gain to be had by using responsive images. Image weight has a huge impact on pages’ overall performance, and responsive images are one of the best things that you can do to cut image weight. Imagine the browser being able to choose between a 300×300 image or a 600×600. If the browser only needs the 300×300, that’s potentially a 4× bytes-over-the-wire savings! Savings generally go up as the display resolution and viewport size go down; on the smallest screens, a couple of case studies have shown byte savings of 70–90%.

If you also need…

Design Control

Then what you need is…

<picture>   <source srcset="" media="">   <source srcset="" media="">   <img src="" alt=""> </picture>

Another perfectly legit goal with responsive images is not just to serve different sizes of the same image, but to serve different images. For example, cropping an image differently depending on the size of the screen and differences in the layout. This is referred to as “art direction.”

The <picture> element is also used for fallback image types and any other sort of media query switching (e.g. different images for dark mode). You get greater control of what browsers display.


There is a lot to talk about here, so let’s go through both syntaxes, all of the related attributes and values, and talk about a few related subjects along the way, like tooling and browsers.


Using srcset

The <img srcset="" src="" alt=""> syntax is for serving differently-sized versions of the same image. You could try to serve entirely different images using this syntax, but browsers assume that everything in a srcset is visually-identical and will choose whichever size they think is best, in impossible-for-you-to-predict ways. So I wouldn’t reccomend it.

Perhaps the easiest-possible responsive images syntax is adding a srcset attribute with x descriptors on the images to label them for use on displays with different pixel-densities.

<img    alt="A baby smiling with a yellow headband."   src="baby-lowres.jpg"   srcset="baby-highres.jpg 2x" >

Here, we’ve made the default (the src) the “low res” (1×) copy of the image. Defaulting to the smallest/fastest resources is usually the smart choice:. We also provide a 2× version. If the browser knows it is on a higher pixel-density display (the 2x part), it will use that image instead.

Demo
<img    alt="A baby smiling with a yellow headband."   src="baby-lowres.jpg"   srcset="     baby-high-1.jpg 1.5x,     baby-high-2.jpg 2x,     baby-high-3.jpg 3x,     baby-high-4.jpg 4x,     baby-high-5.jpg 100x   " >

You can do as many pixel-density variants as you like.

While this is cool and useful, x descriptors only account for a small percentage of responsive images usage. Why? They only let browsers adapt based on one thing: display pixel-density. A lot of times, though, our responsive images are on responsive layouts, and the image’s layout size is shrinking and stretching right along with the viewport. In those situations, the browser needs to make decisions based on two things: the pixel-density of the screen, and the layout size of the image. That’s where w descriptors and the sizes attribute come in, which we’ll look at in the next section.

Using srcset / w + sizes

This is the good stuff. This accounts for around 85% of responsive images usage on the web. We’re still serving the same image at multiple sizes, only we’re giving the browser more information so that it can adapt based on both pixel-density and layout size.

<img    alt="A baby smiling with a yellow headband."   srcset="     baby-s.jpg  300w,     baby-m.jpg  600w,     baby-l.jpg  1200w,     baby-xl.jpg 2000w   "   sizes="70vmin" >

We’re still providing multiple copies of the same image and letting the browser pick the most appropriate one. But instead of labeling them with a pixel density (x) we’re labelling them with their resource width, using w descriptors. So if baby-s.jpg is 300×450, we label it as 300w.

Using srcset with width (w) descriptors like this means that it will need to be paired with the sizes attribute so that the browser will know how large of a space the image will be displaying in. Without this information, browsers can’t make smart choices.

Demo

Creating accurate sizes

Creating sizes attributes can get tricky. The sizes attribute describes the width that the image will display within the layout of your specific site, meaning it is closely tied to your CSS. The width that images render at is layout-dependent rather than just viewport dependent!

Let’s take a look at a fairly simple layout with three breakpoints. Here’s a video demonstrating this:

Demo

The breakpoints are expressed with media queries in CSS:

body {   margin: 2rem;   font: 500 125% system-ui, sans-serif; } .page-wrap {   display: grid;   gap: 1rem;   grid-template-columns: 1fr 200px;   grid-template-areas:     "header header"     "main aside"     "footer footer"; }  @media (max-width: 700px) {   .page-wrap {     grid-template-columns: 100%;     grid-template-areas:       "header"       "main"       "aside"       "footer";   } } @media (max-width: 500px) {   body {     margin: 0;   } }

The image is sized differently at each breakpoint. Here’s a breakdown of all of the bits and pieces that affect the image’s layout width at the largest breakpoint (when the viewport is wider than 700px):

The image is as wide as 100vw minus all that explicitly sized margin, padding, column widths, and gap.
  • At the largest size: there is 9rem of explicit spacing, so the image is calc(100vw - 9rem - 200px) wide. If that column used a fr unit instead of 200px, we’d kinda be screwed here.
  • At the medium size: the sidebar is dropped below, so there is less spacing to consider. Still, we can do calc(100vw - 6rem) to account for the margins and padding.
  • At the smallest size: the body margin is removed, so just calc(100vw - 2rem) will do the trick.

Phew! To be honest, I found that a little challenging to think out, and made a bunch of mistakes as I was creating this. In the end, I had this:

<img    ...     sizes="     (max-width: 500px) calc(100vw - 2rem),      (max-width: 700px) calc(100vw - 6rem),     calc(100vw - 9rem - 200px)   " />

A sizes attribute that gives the browser the width of the image across all three breakpoints, factoring in the layout grid, and all of the surrounding gap, margin, and padding that end up impacting the image’s width.

Now wait! Drumroll! 🥁🥁🥁That’s still wrong. I don’t understand why exactly, because to me that looks like it 100% describes what is happening in the CSS layout. But it’s wrong because Martin Auswöger’s RespImageLint says so. Running that tool over the isolated demo reports no problems except the fact that the sizes attribute is wrong for some viewport sizes, and should be:

<img   ...   sizes="     (min-width: 2420px) 2000px,      (min-width: 720px) calc(94.76vw - 274px),      (min-width: 520px) calc(100vw - 96px),      calc(100vw - 32px)   " >

I don’t know how that’s calculated and it’s entirely unmaintainable by hand, but, it’s accurate. Martin’s tool programmatically resizes the page a bunch and writes out a sizes attribute that describes the actual, observed width of the image over a wide range of viewport sizes. It’s computers, doing math, so it’s right. So, if you want a super-accurate sizes attribute, I’d recommend just putting a wrong one on at first, running this tool, and copying out the correct one.

For an even deeper dive into all this, check out Eric Portis’ w descriptors and sizes: Under the hood.

Being more chill about sizes

Another option is use the Horseshoes & Hand Grenades Method™ of sizes (or, in other words, close counts).

For example, sizes="96vw" says, “This image is going to be pretty big on the page — almost the full width — but there will always be a little padding around the edges, so not quite. Or sizes="(min-width: 1000px) 33vw, 96vw" says, “This image is in a three-column layout on large screens and close to full-width otherwise.” Practicality-wise, this can be a sane solution.

You might find that some automated responsive image solutions, which have no way of knowing your layout, make a guess — something like sizes="(max-width: 1000px) 100vw, 1000px". This is just saying, “Hey we don’t really know much about this layout, but we’re gonna take a stab and say, worst case, the image is full-width, and let’s hope it never renders larger than 1000px”.

Abstracting sizes

I’m sure you can imagine how easy it is to not only get sizes wrong, but also have it become wrong over time as layouts change on your site. It may be smart for you to abstract it using a templating language or content filter so that you can change the value across all of your images more easily.

I’m essentially talking about setting a sizes value in a variable once, and using that variable in a bunch of different <img> elements across your site. Native HTML doesn’t offer that, but any back end language does; for instance, PHP constants, Rails config variables, the React context API used for a global state variable, or variables within a templating language like Liquid can all be used to abstract sizes.

<?php   // Somewhere global   $ my_sizes = ""; ?>  <img   srcset=""   src=""   alt=""   sizes="<?php echo $ my_sizes; ?>" />

“Browser’s choice”

Now that we have a sizes attribute in place, the browser knows what size (or close to it) the image will render at and can work its magic. That is, it can do some math that factors in the pixel density of the screen, and the size that the image will render at, then pick the most appropriately-sized image.

The math is fairly straightforward at first. Say you’re about to show an image that is 40vw wide on a viewport that is 1200px wide, on a 2x pixel-density screen. The perfect image would be 960 pixels wide, so the browser is going to look for the closest thing it’s got. The browser will always calculate a target size that it would prefer based on the viewport and pixel-density situations, and what it knows from sizes, and compare that target to what it’s got to pick from in srcset. How browsers do the picking, though, can get a little weird.

A browser might factor more things into this equation if it chooses to. For example, it could consider the user’s current network speeds, or whether or not the user has flipped on some sort of “data saver” preference. I’m not sure if any browsers actually do this sort of thing, but they are free to if they wish as that’s how the spec was written. What some browsers sometimes choose to do is pull from cache. If the math shows they should be using a 300px image, but they already have a 600px in local cache, they will just use that. Smart. Room for this sort of thing is a strength of the srcset/sizes syntax. It’s also why you always use different sizes of the same image, within srcset: you’ve got no way to know which image is going to be selected. It’s the browser’s choice.

This is weird. Doesn’t the browser already know this stuff?

You might be thinking, “Uhm why do I have to tell the browser how big the image will render, doesn’t it know that?” Well, it does, but only after it’s downloaded your HTML and CSS and laid everything out. The sizes attribute is about speed. It gives the browser enough information to make a smart choice as soon as it sees your <img>.

<img   data-sizes="auto"   data-srcset="     responsive-image1.jpg 300w,     responsive-image2.jpg 600w,     responsive-image3.jpg 900w"   class="lazyload"  />

Now you might be thinking, “But what about lazy-loaded images?” (as in, by the time a lazy-loaded image is requested, layout’s already been done and the browser already knows the image’s render size). Well, good thinking! Alexander Farkas’ lazysizes library writes out sizes attributes automatically on lazyload, and there’s an ongoing discussion about how to do auto-sizes for lazy-loaded images, natively.

sizes can be bigger than the viewport

Quick note on sizes. Say you have an effect on your site so that an image “zooms in” when it’s clicked. Maybe it expands to fill the whole viewport, or maybe it zooms even more, so that you can see more detail. In the past, we might have had to swap out the src on click in order to switch to a higher-res version. But now, assuming a higher-res source is already in the srcset, you can just change the sizes attribute to something huge, like 200vw or 300vw, and the browser should download the super-high-res source automatically for you. Here’s an article by Scott Jehl on this technique.

↩️ Back to top


Using <picture>

Hopefully, we’ve beaten it into the ground that <img srcset="" sizes="" alt=""> is for serving differently-sized versions of the same image. The <picture> syntax can do that too, but the difference here is that the browser must respect the rules that you set. That’s useful when you want to change more than just the resolution of the loaded image to fit the user’s situation. This intentional changing of the image is usually called “art direction.”

Art Direction

<picture>   <source      srcset="baby-zoomed-out.jpg"     media="(min-width: 1000px)"   />   <source      srcset="baby.jpg"     media="(min-width: 600px)"   />   <img      src="baby-zoomed-in.jpg"      alt="Baby Sleeping"   /> </picture>

This code block is an example of what it might look like to have three stages of an “art directed” image.

  • On large screens, show a zoomed-out photo.
  • On medium screens, show that same photo, zoomed in a bit.
  • On small screens, zoom in even more.

The browser must respect our media queries and will swap images at our exact breakpoints. That way, we can be absolutely sure that nobody on a small screen will see a tiny, zoomed-out image, which might not have the same impact as one of the zoomed-in versions.

Here’s a demo, written in Pug to abstract out some of the repetitive nature of <picture>.

Art direction can do a lot more than just cropping

Although cropping and zooming like this is the most common form of art direction by far, you can do a lot more with it. For instance, you can:

Sky’s the limit, really.

Combining source and srcset

Because <source> also uses the srcset syntax, they can be combined. This means that you can still reap the performance benefits of srcset even while swapping out visually-different images with <source>. It gets pretty verbose though!

<picture>   <source      srcset="       baby-zoomed-out-2x.jpg 2x,       baby-zoomed-out.jpg     "     media="(min-width: 1000px)"   />   <source      srcset="       baby-2x.jpg 2x,       baby.jpg     "     media="(min-width: 600px)"   />   <img      srcset="       baby-zoomed-out-2x.jpg 2x     "     src="baby-zoomed-out.jpg"     alt="Baby Sleeping"   /> </picture>

The more variations you create and the more resized versions you create per variation, the more verbose this code has to get.

Fallbacks for modern image formats

The <picture> element is uniquely suited to being able to handle “fallbacks.” That is, images in cutting-edge formats that not all browsers might be able to handle, with alternative formats for browsers that can’t load the preferred, fancy one. For example, let’s say you want to use an image in the WebP format. It’s a pretty great image format, often being the most performant choice, and it’s supported everywhere that the <picture> element is, except Safari. You can handle that situation yourself, like:

<picture>   <source srcset="party.webp">   <img src="party.jpg" alt="A huge party with cakes."> </picture>

This succeeds in serving a WebP image to browsers that support it, and falls back to a JPEG image, which is definitely supported by all browsers.

Here’s an example of a photograph (of me) at the exact same size where the WebP version is about 10% (!!!) of the size of the JPEG.

How do you create a WebP image? Well, it’s more of a pain in the butt than you’d like it to be, that’s for sure. There are online converters, command line tools, and some modern design software, like Sketch, helps you export in that format. My preference is to use an image hosting CDN service that automatically sends images in the perfect format for the requesting browser, which makes all this unnecessary (because you can just use img/srcset).

WebP isn’t the only player like this. Safari doesn’t support WebP, but does support a format called JPG 2000 which has some advantages over JPEG. Internet Explorer 11 happens to support an image format called JPEG-XR which has different advantages. So to hit all three, that could look like:

<picture>   <source srcset="/images/cereal-box.webp" type="image/webp" />   <source srcset="/images/cereal-box.jp2" type="image/jp2" />   <img src="/images/cereal-box.jxr" type="image/vnd.ms-photo" /> </picture>

This syntax (borrowed form a blog post by Josh Comeau) supports all three of the “next-gen” image formats in one go. IE 11 doesn’t support the <picture> syntax, but it doesn’t matter because it will get the <img> fallback which is in the JPEG-XR format it understands.

Estelle Weyl also covered this idea in a 2016 blog post on image optimization.

↩️ Back to top


Where do you get the differently-sized images?

You can make them yourself. Heck, even the free Preview app on my Mac can resize an image and “Save As.”

The Mac Preview app resizing an image, which is something that literally any image editing application (including Photoshop, Affinity Designer, Acorn, etc.) can also do. Plus, they often help by exporting the variations all at once.

But that’s work. It’s more likely that the creation of variations of these images is automated somehow (see the section below) or you use a service that allows you to create variations just by manipulating the URL to the image. That’s a super common feature of any image hosting/image CDN service. To name a few:

Not only do these services offer on-the-fly image resizing, they also often offer additional stuff, like cropping, filtering, adding text, and all kinds of useful features, not to mention serving assets efficiently from a CDN and automatically in next-gen formats. That makes them a really strong choice for just about any website, I’d say.

Here’s Glen Maddern in a really great screencast talking about how useful Image CDNs can be:

Design software is booming more aware that we often need multiple copies of images. The exporting interface from Figma is pretty nice, where any given selection can be exported. It allows multiple exports at once (in different sizes and formats) and remembers what you dod the last time you exported.

Exporting in Figma

Automated responsive images

The syntax of responsive images is complex to the point that doing it by hand is often out of the question. I’d highly recommend automating and abstracting as much of this away as possible. Fortunately, a lot of tooling that helps you build websites knows this and includes some sort of support for it. I think that’s great because that’s what software should be doing for us, particularly when it is something that is entirely programmatic and can be done better by code than by humans. Here are some examples…

  • Cloudinary has this responsive breakpoints tool including an API for generating the perfect breakpoints.
  • WordPress generates multiple versions of images and outputs in the responsive images syntax by default.
  • Gatsby has a grab-bag of plugins for transforming and implementing images on your site. You ultimately implement them with gatsby-image, which is a whole fancy thing for implementing responsive images and other image loading optimizations. Speaking of React, it has component abstractions like “An Almost Ideal React Image Component” that also does cool stuff.
  • Nicolas Hoizey’s Images Responsiver Node module (and it’s Eleventy plugin) makes a ton of smart markup choices for you, and pairs nicely with a CDN that can handle the on-the-fly resizing bits.
  • These are just a few examples! Literally anything you can do to make this process easier or automatic is worth doing.
Here’s me inspecting an image in a WordPress blog post and seeing a beefy srcset with a healthy amount of pre-generated size options and a sizes attribute tailored to this theme.
A landing page for gatsby-image explaining all of the additional image loading stuff it can do.

I’m sure there are many more CMSs and other software products that help automate away the complexities of creating the responsive images syntax. While I love that all this syntax exists, I find it all entirely too cumbersome to author by hand. Still, I think it’s worth knowing all this syntax so that we can build our own abstractions, or check in on the abstractions we’re using to make sure they are doing things correctly.

Related concepts

  • The object-fit property in CSS controls how an image will behave in its own box. For example, an image will normally “squish” if you change the dimensions to something different than its natural aspect ratio, but object-fit can be used to crop it or contain it instead.
  • The object-position property in CSS allows you to nudge an image around within its box.

What about responsive images in CSS with background images?

We’ve covered exactly this before. The trick is to use @media queries to change the background-image source. For example:

.img {   background-image: url(small.jpg); } @media    (min-width: 468px),   (-webkit-min-device-pixel-ratio: 2),    (min-resolution: 192dpi) {   .img {     background-image: url(large.jpg);   } }

With this CSS syntax, depending on the browser conditions, the browser will only download one of the two images, which achieves the same performance goal that the responsive images syntax in HTML does. If it helps, think of the above as the CSS equivalent of the <picture> syntax: the browser must follow your rules and display what matches.

If you’re looking to let the browser choose the best option, like srcset/sizes, but in CSS, the solution is ultimately going to be the image-set() function. There’s two problems with image-set(), today, though:

  • Support for it isn’t there yet. Safari’s implementation leads the pack, but image-set() has been prefixed in Chrome for eight years, and it’s not there at all in Firefox.
  • Even the spec itself seems behind the times. For example, it only supports x descriptors (no w, yet).

Best to just use media queries for now.

Do you need to polyfill?

I’m pretty meh on pollyfilling any of this right this moment. There is a great polyfill though, called Picturefill, which will buy you full IE 9-11 support if you need that. Remember, though, that none of this stuff breaks to the point of not displaying any image at all in non-supporting browsers, assuming you have an <img src="" alt=""> in there somewhere. If you make the (fairly safe) assumption that IE 11 is running on a low-pixel-density desktop display, you can make your image sources reflect that by default and build out from there.

Other important image considerations

  • Optimizing quality: The point of responsive images is loading the smallest, most impactful resource that you can. You can’t achieve that without effectively compressing your image. You’re aiming for a “sweet spot” for every image, between looking good and being light. I like to let image hosting services solve this problem for me, but Etsy has a really great writeup of what they’ve been able to accomplish with infrastructure that they built themselves.
  • Serving from CDNs: Speaking of image hosting services, speed comes in many forms. Fast servers that are geographically close to the user are an important speed factor as well.
  • Caching: What’s better than loading less data over the network? Loading no data at all! That’s what HTTP caching is for. Using the Cache-Control header, you can tell the browser to hang on to images so that if the same image is needed again, the browser doesn’t have to go over the network to get it, which is a massive performance boost for repeat viewings.
  • Lazy loading: This is another way to avoid loading images entirely. Lazy loading means waiting to download an image until it is in or near the viewport. So, for example, an image way far down the page won’t load if the user never scrolls there.

Other good resources

(That I haven’t linked up in the post already!)

Browser Support

This is for srcset/sizes, but it’s the same for <picture>.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

Chrome Firefox IE Edge Safari
38 38 No 16 9

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
81 68 81 9.0-9.2

The post A Guide to the Responsive Images Syntax in HTML appeared first on CSS-Tricks.

CSS-Tricks

, , , ,

How to Modify Nodes in an Abstract Syntax Tree

One of the more powerful concepts I’ve stumbled across recently is the idea of abstract syntax trees, or ASTs. If you’ve ever studied alchemy, you may recall that the whole motivation for alchemists was to discover some way to transform not-gold into gold through scientific or arcane methods.

ASTs are kind of like that. Using ASTs, we can transform Markdown into HTML, JSX into JavaScript, and so much more.

Why are ASTs useful?

Early in my career, I tried to change files using a find-and-replace method. This ended up being fairly complicated, so I tried using regular expressions. I ended up abandoning the idea because it was so brittle; the app broke all the time because someone would enter text in a way I hadn’t anticipated and it would break my regular expressions causing the whole app to fall down.

The reason this was so hard is that HTML is flexible. That makes it extremely hard to parse using regular expressions. String-based replacement like this is prone to breaking because it might miss a match, match too much, or do something weird that results in invalid markup that leaves the page looking janky.

ASTs, on the other hand, turn HTML into something far more structured, which makes it much simpler to dive into a text node and do replacements on only that text, or to mess with elements without needing to deal with the text at all.

This makes AST transformation safer and less error-prone than a purely string-based solution.

What are ASTs used for?

To start, let’s take a look at a minimal document using a couple lines of Markdown. This will be saved as a file called home.md, which we’ll save in the content folder of our website.

# Hello World!  ![cardigan corgi](<https://images.dog.ceo/breeds/corgi-cardigan/n02113186_1030.jpg>) An adorable corgi!  Some more text goes here.

Assuming we know Markdown, we can infer that when this Markdown is parsed, it’ll end up being an <h1> that says, “Hello World!” and a <p> that says, “This is some Markdown.”

But how does it get transformed from Markdown to HTML?

That’s where ASTs come in!

Because it supports multiple languages, we’re going to use the unist syntax tree specification and, more specifically, the project unified.

Install the dependencies

First, we need to install the dependencies required to parse the Markdown into an AST and convert it to HTML. To do that, we need to make sure we’ve initialized the folder as a package. Run the following command in your terminal:

# make sure you’re in your root folder (where `content` is) # initialize this folder as an npm package npm init  # install the dependencies npm install unified remark-parse remark-html

If we assume our Markdown is stored in home.md, we can get the AST with the following code:

const fs = require('fs'); const unified = require('unified'); const markdown = require('remark-parse'); const html = require('remark-html');  const contents = unified()   .use(markdown)   .use(html)   .processSync(fs.readFileSync(`$ {process.cwd()}/content/home.md`))   .toString();  console.log(contents);

This code takes advantage of Node’s built-in fs module, which allows us to access and manipulate the filesystem. For more information on how this works, check out the official docs.

If we save this as src/index.js and use Node to execute this script from the command line, we’ll see the following in our terminal:

$  node src/index.js  <h1>Hello World!</h1> <p><img src="<https://images.dog.ceo/breeds/corgi-cardigan/n02113186_1030.jpg>" alt="cardigan corgi"> An adorable corgi!</p> <p>Some more text goes here.</p>

We tell unified to use remark-parse to turn the Markdown file into an AST, then to use remark-html to turn the Markdown AST into a HTML — or, more specifically, it turns it into something called a VFile. Using the toString() method turns that AST into an actual string of HTML we can display in the browser!

Thanks to the hard work of the open-source community, remark does all the hard work of turning Markdown into HTML for us. (See the diff)

Next, let’s look at how this actually works.

What does an AST look like?

To see the actual AST, let’s write a tiny plugin to log it:

const fs = require('fs'); const unified = require('unified'); const markdown = require('remark-parse'); const html = require('remark-html');  const contents = unified() 	.use(markdown)   .use(() => tree => console.log(JSON.stringify(tree, null, 2))) 	.use(html) 	.processSync(fs.readFileSync(`$ {process.cwd()}/content/home.md`)) 	.toString();

The output of running the script will now be:

{   "type": "root",   "children": [     {       "type": "heading",       "depth": 1,       "children": [         {           "type": "text",           "value": "Hello World!",           "position": {}         }       ],       "position": {}     },     {       "type": "paragraph",       "children": [         {           "type": "image",           "title": null,           "url": "<https://images.dog.ceo/breeds/corgi-cardigan/n02113186_1030.jpg>",           "alt": "cardigan corgi",           "position": {}         },         {           "type": "text",           "value": " An adorable corgi!",           "position": {}         }       ],       "position": {}     },     {       "type": "paragraph",       "children": [         {           "type": "text",           "value": "Some more text goes here.",           "position": {}         }       ],       "position": {}     }   ],   "position": {} }

Note that the position values have been truncated to save space. They contain information about where the node is in the document. For the purposes of this tutorial, we won’t be using this information. (See the diff)

This is a little overwhelming to look at, but if we zoom in we can see that each part of the Markdown becomes a type of node with a text node inside it.

For example, the heading becomes:

{   "type": "heading",   "depth": 1,   "children": [     {       "type": "text",       "value": "Hello World!",       "position": {}     }   ],   "position": {} }

Here’s what this means:

  • The type tells us what kind of node we’re dealing with.
  • Each node type has additional properties that describe the node. The depth property on the heading tells us what level heading it is — a depth of 1 means it’s an <h1> tag, 2 means <h2>, and so on.
  • The children array tells us what’s inside this node. In both the heading and the paragraph, there’s only text, but we could also see inline elements here, like <strong>.

This is the power of ASTs: We’ve now described the Markdown document as an object that a computer can understand. If we want to print this back to Markdown, a Markdown compiler would know that a “heading” node with a depth of 1 starts with #, and a child text node with the value “Hello” means the final line should be # Hello.

How AST transformations work

Transforming an AST is usually done using the visitor pattern. It‘s not important to know the ins and outs of how this works to be productive, but if you’re curious, JavaScript Design Patterns for Humans by Soham Kamani has a great example to help explain how it works. The important thing to know is that the majority of resources on AST work will talk about “visiting nodes,” which roughly translates to “find part of the AST so we can do stuff with it.” The way this works practice is that we write a function that will be applied to AST nodes matching our criteria.

A few important notes about how it works:

  • ASTs can be huge, so for performance reasons we will mutate nodes directly. This runs counter to how I would usually approach things — as a general rule I don’t like to mutate global state — but it makes sense in this context.
  • Visitors work recursively. That means that if we process a node and create a new node of the same type, the visitor will run on the newly created node as well unless we explicitly tell the visitor not to.
  • We’re not going to go too deep in this tutorial, but these two ideas will help us understand what’s going on as we start to mess with the code.

How do I modify the HTML output of the AST?

What if we want to change the output of our Markdown, though? Let’s say our goal is to wrap image tags with a figure element and supply a caption, like this:

<figure>   <img     src="<https://images.dog.ceo/breeds/corgi-cardigan/n02113186_1030.jpg>"     alt="cardigan corgi"   />   <figcaption>An adorable corgi!</figcaption> </figure>

To accomplish this, we’ll need transform the HTML AST — not the Markdown AST — because Markdown doesn’t have a way of creating figure or figcaption elements. Fortunately, because unified is interoperable with multiple parsers, we can do that without writing a bunch of custom code.

Convert a Markdown AST to an HTML AST

To convert the Markdown AST to an HTML AST, add remark-rehype and switch to rehype-stringify for turning the AST back to HTML.

npm install remark-rehype rehype-stringify

Make the following changes in src/index.js to switch over to rehype:

const fs = require('fs'); const unified = require('unified'); const markdown = require('remark-parse'); const remark2rehype = require('remark-rehype'); const html = require('rehype-stringify');  const contents = unified() 	.use(markdown)   .use(remark2rehype) 	.use(() => tree => console.log(JSON.stringify(tree, null, 2))) 	.use(html) 	.processSync(fs.readFileSync('corgi.md')) 	.toString();  console.log(contents);

Note that the HTML variable changed from remark-html to rehype-stringify — both turn the AST into a format that can be stringified to HTML

If we run the script, we can see the image element now looks like this in the AST:

{   "type": "element",   "tagName": "img",   "properties": {     "src": "https://images.dog.ceo/breeds/corgi-cardigan/n02113186_1030.jpg",     "alt": "cardigan corgi"   },   "children": [],   "position": {} }

This is the AST for the HTML representation of the image, so we can start changing it over to use the figure element. (See the diff)

Write a plugin for unified

To wrap our img element with a figure element, we need to write a plugin. In unified, plugins are added with the use() method, which accepts the plugin as a first argument and any options as a second argument:

.use(plugin, options)

The plugin code is a function (called an “attacher” in unified jargon) that receives option. These options are used to create a new function (called a “transformer”) that receives the AST and does work to, er, transform it. For more details on plugins, check out the plugin overview in the unified docs.

The function it returns will receive the entire AST as its argument, and it doesn’t return anything. (Remember, ASTs are mutated globally.) Create a new file called img-to-figure.js in the same folder as index.js, then put the following inside:

module.exports = options => tree => {   console.log(tree); };

To use this, we need to add it to src/index.js:

const fs = require('fs'); const unified = require('unified'); const markdown = require('remark-parse'); const remark2rehype = require('remark-rehype'); const html = require('rehype-stringify'); const imgToFigure = require('./img-to-figure');  const contents = unified()   .use(markdown)   .use(remark2rehype)   .use(imgToFigure)   .processSync(fs.readFileSync('corgi.md'))   .toString();  console.log(contents);

If we run the script, we’ll see the whole tree logged out in the console:

{   type: 'root',   children: [     {       type: 'element',       tagName: 'p',       properties: {},       children: [Array],       position: [Object]     },     { type: 'text', value: 'n' },     {       type: 'element',       tagName: 'p',       properties: {},       children: [Array],       position: [Object]     }   ],   position: {     start: { line: 1, column: 1, offset: 0 },     end: { line: 4, column: 1, offset: 129 }   } }

(See the diff)

Add a visitor to the plugin

Next, we need to add a visitor. This will let us actually get at the code. Unified takes advantage of a number of utility packages, all prefixed with unist-util-*, that allow us to do common things with our AST without writing custom code.

We can use unist-util-visit to modify nodes. This gives us a visit helper that takes three arguments:

  • The entire AST we’re working with
  • A predicate function to identify which nodes we want to visit
  • A function to make any changes to the AST we want to make

To install, run the following in your command line:

npm install unist-util-visit

Let’s implement a visitor in our plugin by adding the following code:

const visit = require('unist-util-visit');    module.exports = options => tree => {     visit(       tree,       // only visit p tags that contain an img element       node =>         node.tagName === 'p' && node.children.some(n => n.tagName === 'img'),       node => {         console.log(node);       }     ); };

When we run this, we can see there’s only one paragraph node logged:

{   type: 'element',   tagName: 'p',   properties: {},   children: [     {       type: 'element',       tagName: 'img',       properties: [Object],       children: [],       position: [Object]     },     { type: 'text', value: ' An adorable corgi!', position: [Object] }   ],   position: {     start: { line: 3, column: 1, offset: 16 },     end: { line: 3, column: 102, offset: 117 }   } }

Perfect! We’re getting only the paragraph node that has the image we want to modify. Now we can start to transform the AST!

(See the diff)

Wrap the image in a figure element

Now that we have the image attributes, we can start to change the AST. Remember, because ASTs can be really large, we mutate them in place to avoid creating lots of copies and potentially slowing our script down.

We start by changing the node’s tagName to be a figure instead of a paragraph. The rest of the details can stay the same for now.

Make the following changes in src/img-to-figure.js:

const visit = require('unist-util-visit');  module.exports = options => tree => {   visit(     tree,     // only visit p tags that contain an img element     node =>     node.tagName === 'p' && node.children.some(n => n.tagName === 'img'),     node => {       node.tagName = 'figure';     }   ); };

If we run our script again and look at the output, we can see that we’re getting closer!

<h1>Hello World!</h1> <figure><img src="<https://images.dog.ceo/breeds/corgi-cardigan/n02113186_1030.jpg>" alt="cardigan corgi">An adorable corgi!</figure> <p>Some more text goes here.</p>

(See the diff)

Use the text next to the image as a caption

To avoid needing to write custom syntax, we’re going to use any text passed inline with an image as the image caption.

We can make an assumption that usually images don’t have inline text in Markdown, but it’s worth noting that this could 100% cause unintended captions to appear for people writing Markdown. We’re going to take that risk in this tutorial. If you’re planning to put this into production, make sure to weigh the trade-offs and choose what’s best for your situation.

To use the text, we’re going to look for a text node inside our parent node. If we find one, we want to grab its value as our caption. If no caption is found, we don’t want to transform this node at all, so we can return early.

Make the following changes to src/img-to-figure.js to grab the caption:

const visit = require('unist-util-visit');  module.exports = options => tree => {   visit(     tree,     // only visit p tags that contain an img element     node =>     node.tagName === 'p' && node.children.some(n => n.tagName === 'img'),     node => {       // find the text node       const textNode = node.children.find(n => n.type === 'text');         // if there’s no caption, we don’t need to transform the node       if (!textNode) return;         const caption = textNode.value.trim();         console.log({ caption });       node.tagName = 'figure';     }   ); };

Run the script and we can see the caption logged:

{ caption: 'An adorable corgi!' }

(See the diff)

Add a figcaption element to the figure

Now that we have our caption text, we can add a figcaption to display it. We could do this by creating a new node and deleting the old text node, but since we’re mutating in place it’s a little less complicated to just change the text node into an element.

Elements don’t have text, though, so we need to add a new text node as a child of the figcaption element to display the caption text.

Make the following changes to src/img-to-figure.js to add the caption to the markup:

const visit = require('unist-util-visit');  module.exports = options => tree => {   visit(     tree,     // only visit p tags that contain an img element     node =>       node.tagName === 'p' && node.children.some(n => n.tagName === 'img'),     node => {       // find the text node       const textNode = node.children.find(n => n.type === 'text');        // if there’s no caption, we don’t need to transform the node       if (!textNode) return;        const caption = textNode.value.trim();       // change the text node to a figcaption element containing a text node       textNode.type = 'element';       textNode.tagName = 'figcaption';       textNode.children = [         {           type: 'text',           value: caption         }       ];        node.tagName = 'figure';     }   ); };

If we run the script again with node src/index.js, we see the transformed image wrapped in a figure element and described with a figcaption!

<h1>Hello World!</h1> <figure><img src="<https://images.dog.ceo/breeds/corgi-cardigan/n02113186_1030.jpg>" alt="cardigan corgi"><figcaption>An adorable corgi!</figcaption></figure>  <p>Some more text goes here.</p>

(See the diff)

Save the transformed content to a new file

Now that we’ve made a bunch of transformations, we want to save those adjustments to an actual file so we can share them.

Since the Markdown doesn’t include a full HTML document, we’re going to add one more rehype plugin called rehype-document to add the full document structure and a title tag.

Install by running:

npm install rehype-document

Next, make the following changes to src/index.js:

const fs = require('fs'); const unified = require('unified'); const markdown = require('remark-parse'); const remark2rehype = require('remark-rehype'); const doc = require('rehype-document'); const html = require('rehype-stringify');  const imgToFigure = require('./img-to-figure');  const contents = unified() 	.use(markdown) 	.use(remark2rehype) 	.use(imgToFigure)     .use(doc, { title: 'A Transformed Document!' }) 	.use(html) 	.processSync(fs.readFileSync(`$ {process.cwd()}/content/home.md`)) 	.toString();   const outputDir = `$ {process.cwd()}/public`;    if (!fs.existsSync(outputDir)) {     fs.mkdirSync(outputDir);   }     fs.writeFileSync(`$ {outputDir}/home.html`, contents);

Run the script again and we’ll be able to see a new folder in root called public, and inside that we’ll see home.html. Inside, our transformed document is saved!

<!doctype html><html lang="en"> <head> <meta charset="utf-8"> <title>A Transformed Document!</title> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> 	<h1>Hello World!</h1> 	<figure><img src="<https://images.dog.ceo/breeds/corgi-cardigan/n02113186_1030.jpg>" alt="cardigan corgi"><figcaption>An adorable corgi!</figcaption></figure> 	<p>Some more text goes here.</p> </body> </html>

(See the diff)

If we open public/home.html in a browser, we can see our transformed Markdown rendered as a figure with a caption.

Holy buckets! Look at that adorable corgi! And we know it’s adorable because the caption tells us so.

What to do next

Transforming files using ASTs is extremely powerful — with it, we’re able to create pretty much anything we can imagine in a safe way. No regexes or string parsing required!

From here, you can dig deeper into the ecosystem of plugins for remark and rehype to see more of what’s possible and get more ideas for what you can do with AST transformation, from building your own Markdown-powered static site generator; to automating performance improvements by modifying code in-place; to whatever you can imagine!

AST transformation is a coding superpower. Get started by checking out this demo’s source code — I can’t wait to see what you build with it! Share your projects with me on Twitter.

The post How to Modify Nodes in an Abstract Syntax Tree appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Two-Value Display Syntax (and Sometimes Three)

You know the single-value syntax: .thing { display: block; }. The value “block” being a single value. There are lots of single values for display. For example, inline-flex, which is like flex in that it becomse a flex container, but behaves like an inline-level element rather than a block-level element. Somewhat intuitive, but much better served by a two-value system that can apply that same concept more broadly and just as intuitively.

For a deep look, you should read Rachel Andrew’s blog post The two-value syntax of the CSS Display property. The spec is also a decent read, as is this video from Miriam:

This is how it maps in my brain

Choose block or inline, then choose flow, flow-root, flex, grid, or table. If it’s a list-item that’s a third thing.

You essentially pick one from each column to describe the layout you want. So the existing values we use all the time map out something like this:

Another way to think about those two columns I have there is “outside” and “inside” display values. Outside, as in, how it flows with other elements around it. Inside, as in, how layout happens inside those elements.

Can you actually use it?

Not really. Firefox 70 is first out of the gate with it, and there are no other signals for support from Chrome-land or Safari-land that I know about. It’s a great evolution of CSS, but as far as day-to-day usage, it’ll be years out. Something as vital as layout isn’t something you wanna let fail just for this somewhat minor descriptive benefit. Nor is it probably worth the trouble to progressively enhance with @supports and such.

Weirdnesses

  • You can’t block flow because that doesn’t really make sense. It’ll be reset to block flow-root.
  • There is implied shorthand. Like if you inline list-item, that’s really inline flow list-item whereas list-item is block flow list-item. Looks all fairly intuitive.
  • You still use stuff like table-row and table-header-group. Those are single-value deals, as is contents and none.
  • Column one technically includes run-in too, but as far as I know, no browser has ever supported run-in display.
  • Column two technically includes ruby, but I have never understood what that even is.

How we talk about CSS

I like how Rachel ties this change to a more rational mental and teaching model:

… They properly explain the interaction of boxes with other boxes, in terms of whether they are block or inline, plus the behavior of the children. For understanding what display is and does, I think they make for a very useful clarification. As a result, I’ve started to teach display using these two values to help explain what is going on when you change formatting contexts.

It is always exciting to see new features being implemented, I hope that other browsers will also implement these two-value versions soon. And then, in the not too distant future we’ll be able to write CSS in the same way as we now explain it, clearly demonstrating the relationship between boxes and the behavior of their children.

The post Two-Value Display Syntax (and Sometimes Three) appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]