Build a Color Contrast Checker with Eleventy Serverless
with Ben Myers
Resources & Links
Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.
JASON: Hello, everyone, and welcome to another episode of "Learn with Jason." Today on the show we're bringing back Ben Myers. Ben, how are you doing?
BEN: I'm doing great. And yourself, Jason?
JASON: I'm doing so good. So happy to have you back. I'm really looking forward to today for a few reasons, one it's always great to have you around. Two, we're going to play with Eleventy, as we learned through painful experience on this show, means we're going to do puns all day. And, three, we're going to play with serverless functions which are all things that I enjoy.
BEN: Yeah, absolutely.
JASON: So, before we get too deep into here, do you want to give us background on yourself for those not familiar with your work?
BEN: Sure, I am a software developer at Microsoft, I focus on the front end, and, specifically, I focus on accessibility and core technologies. I'm an advocate for web accessibility, both, like, internally at my company, as well as folks probably know me from -- I host a weekly stream called "Some Antics," where I bring on guests in a format very, very, very inspired from "Learn with Jason." I bring on guests to teach something about building great user experiences for the web in a hands-on way with a focus on accessibility and core web technology. Can you tell I've rehearsed that elevator pitch, Jason? Man, but, yeah, so, I stream about accessibility. I blog about accessibility. Every once in a while, Eleventy comes up. That's another favorite subject of mine. And, so, I'm super, super thrilled to be bringing that to Learn With Jason today.
JASON: I'm super happy to have you here, because I think it's going to be a lot of fun, and, yeah, I think -- color contrast checkers are something that I am fascinated by. I've watched, at one point Chris Bascardi tried to build one, it was doing luminosity checking. It was -- I think it wasn't even HSL. It was something that was really, really cool stuff. And, yeah, just a lot of exciting things happening in that space. But it's also one of those things, where I am so dependent on the robots, because I don't really know how to check color contrast. I just put my two values into a form, and if it gives me a thumbs up, I continue about my business. So, I'm really excited to talk about that, and kind of learn how it all works today.
BEN: Yeah, so, unfortunately, we're not going to get a whole lot into the science of the color contrast, or the math. This isn't so much about color contrast itself, so much as using color contrast as a use case for serverless. That said, color contrast is a fascinating, complex subject. I know you've had Todd Libby on to talk about color contrast. But in itself is a huge subject with a ton of nuance, and it's an ever-growing field, ever-changing field. So, we're going to be using the standard color contrast formula from WCAG2, that's been embedded all the places, user tested, but it's an ever-growing science. And you may hear in the coming years, Web Content Accessibility Guidelines might move to a new color contrast algorithm that's supposed to make scoring a little more natural for folks.
JASON: Sure, sure, sure. Yeah, and I do think that is a good one. Oh, and Brian is already sharing links in the chat to that Todd Lippy episode -- why do I keep saying lippy? Libby, with a "B." All right, so, let's maybe start with the basics. So, the foundation for everything that we're going to be doing today is Eleventy.
JASON: Maybe let's just start there. What is Eleventy, and why should people be excited about it?
JASON: You know, what I love about that argument, though, is what we -- I feel like what we've done in our heads a little bit as developers, is we've said, like, just HTML and CSS is not enough, right. And I think at some point we kind of flipped the script on this a little bit, where we stopped thinking that, like, at the bottom of most of what we're doing, I'm trying to put some words on a page and maybe a picture or two, so that I can convey information from me to you.
JASON: Yeah, yeah. Absolutely. And that's actually something that I've really loved to see as time has gone on, is that the way that we can write with HTML, it's starting to feel like even if you don't apply CSS to the page, you can really create expressive documents now with just HTML. You know, you mentioned that detailed summary element. I live and die by that thing. I use that so much, because I always want to just toggle extra information on screen, where it's like, look, I know that you don't really need this context. But I like to have it, and I figure some nerd out there also wants to have it. So, let me say, like, if you want to go deeper, here's some side stuff. I don't really want to spin up another page for that.
BEN: And you want that with sensible defaults, too. You don't want to figure out how do I accessibly implement an accordion. Let me figure that out. That's the stuff I live and die by, right. You want something that's easy to use and provides a sensible default experience out of the box that should be roughly accessible. There's, you know, HTML is not always accessible out of the box, but it's almost definitely better than rolling your own solution, unless you've done a lot of research. So, yeah, just leverage that. The pit of success, right, make things incredibly easy to create a robust performance, accessible experience, just by using some well-named tags.
BEN: Sorry, what JS?
JASON: No JS, like shipping no JS.
JASON: Latest and greatest and up to datest. I love it.
BEN: It's the hot new slang. It's the latest, greatest, up to datest slang, Jason. Welcome, you've got the youths. Anyways, this is my stream now.
JASON: (Laughing). Oh, my goodness. All right, so -- So, yeah, I think this is some really exciting stuff. I think from my standpoint, what I'm hearing is that this is a great way for certain use cases to hit the right compromise of you get all the functionality you need for a dynamic content, for being able to respond to user input, but you're not trading off by saying the rest of the content on your site that isn't dynamic or responding to user input. That doesn't have to be rendered dynamically. Doesn't have to be --
BEN: Uh-huh, yeah. Exactly.
JASON: That is pretty powerful stuff. That gives me as a developer quite a bit of flexibility to go a long ways with what somebody might consider a static site generator. Not really so static anymore, but gives me that same benefit of build once and cache forever.
BEN: Absolutely. And you don't have to overhaul your whole project to make it work. So long as your Eleventy project is in 1.0, you can opt in to Eleventy Serverless. And Eleventy Serverless is going to be opt in on a template by template basis, which for those who are not into Eleventy spaces, I'll quickly define a template. Which is a file that has your content and is parsed with a templating language, such as nonjux, liquid, to insert your data into. So, it's a content file that gets transformed into, usually, HTML. So, when I'm saying you opt into Eleventy Serverless on a template by template basis, non-Eleventy folks should probably interpret that as you're opting into it on a page by page basis or page type by page type. Whole site isn't being served serverlessly, it's being served exactly as it was, except for this one page that's now dynamic.
JASON: So, I mean, this sounds really promising. I'm feeling pretty hyped about it. So, let me take the counter stance here and just ask, where is this going to fall down? When is this not the right solution?
BEN: Yeah, I still think the highly dynamic experiences are not really what Eleventy is for. There are projects that are trying to solve that. You had Ben Holmes on recently. My doppelganger. To talk about island's architecture and there are projects that exist, but if you're going high dynamic, high interactive experiences, Eleventy Serverless isn't going to save the day for you. You're still better suited at going to tools more focused around that interactive experience.
JASON: For sure.
BEN: That's a gap there. Maybe not even a gap, that's not what Eleventy is for. Projects don't have to be for every project. I'm not saying build everything with Eleventy or build everything without Eleventy. Eleventy is really, really great for content that's mostly static, right, could be represented as just HTML.
JASON: You're touching on something that's very near and dear to my heart. As I've gotten more experienced, as I climbed up what you would call the developer maturity curve, I've started to realize that any time that I smell somebody saying that this tool will solve all your problems, this tool will accomplish whatever you need it to, I immediately start to pull back a little bit and go, okay, it's time for me to do more research. Clearly, somebody telling me this is a universal solution is selling something.
JASON: Where I see a really experienced developer is looking at tools pragmatically and recognizing you're going to reach for different tools for different outcomes. Even in relatively similar spaces, you're going to find that it's more suitable to use one framework for e-commerce, versus another for paid subscription. Both, I need people to come to a website and give me money. But the use cases are just different enough that it might be a different tool that makes that more functional, more available, right?
JASON: So, yeah, I love to hear people say that. You should wait, your use case, chat. Can't just go to me and say what should I use, and I rattle off whatever thing I'm excited about today is. Or go to Ben and ask the same question. You got to kind of weigh what problem does this solve. So, today, we're solving the problem that I have mostly static content, some of it will vary based on user input, but it won't vary every time it loads. It varies once per unique URL.
JASON: And I would my build times to be fast, despite having a large possible number of combinations in that variable user input. Potentially varied.
BEN: Absolutely. So, a way to view Eleventy Serverless is absolutely, like, not the cure-all. But, rather, it closes a gap that Eleventy had, which is that Eleventy was not built to deal with dynamic inputs. Now Eleventy is built to deal with dynamic inputs. If that was a need you have, you no longer have to look elsewhere. You could better consider Eleventy for your needs.
JASON: Yeah, no, that's great. So, at this point, I think probably the best thing we can do is start showing everybody how it works. So, first, let me do a quick shout-out to the chat. Y'all, thank you all for showing up. I see a whole bunch of folks in here, Michael Chan, David, Brian Robinson, dropping links all day, Brittney, Steph, Cyrus, what's up, thank you all so much for hanging out today. I know I didn't mention everybody. My memory is not that good, and I'm sorry. Thank you all for hanging out. Thank you for the sub, I saw real Tommy was the user name, thank you for subscribing. And I appreciate that a whole lot. Also, how do I look today, y'all? I'm using a new tool. I've been using Ping for a while. And then I switched over to a new camera. This is a Sony FX3, which I'm thrilled about. And then I also got a new way to hook it in, so that I can get 1080p at 30 frames per second, something like that, I don't know. Everything is very fancy. I'm only marginally understanding it, but I should be really sharp. So, shout-out to Ping for actually letting me stream at that high quality. I'll drop a quick link to Ping in the chat, for anybody who's streaming and wants to check this out. And with that, let's switch over into paired programming view and I'll give a couple more shout-outs. Here we go. Camera two. All right. So, first and foremost, we are having this episode live captioned, like all of our episodes. I broke something on the homepage of the site, so to see the captions, you can see those right over at StreamText.net. That's going to be all of the captions for today's show. These will also be included on the YouTube video and on the learnwithJason.dev site for the episode. That is made possible through the support of our sponsors, we have Netlify, Nx, and Backlight all kicking in to make this show a little more accessible, and to help me cover all the other costs of running this show, like, for example, upgrading camera gear and paying for streaming tools. So, thank you all very much for making the show possible. You are also talking to Ben Myers today. So, make sure you go and follow Ben on the Twitter. We're talking about Eleventy. Link has already been dropped, but I'll drop it again. And we talked a little bit about Todd Libby. Make sure we get one more link to Todd out there. Ben Holmes and Slinkity, there we go. That is now the extent of what I know. Let me hand it back over to you and ask what should we do next?
BEN: So, I created a bit of a starter repo for us to work with. The reason being, that I wanted us to not spend a whole lot of time on the Eleventy setup. I also wanted to just preload things with styles. This repo is not terribly fancy at all. It's just got a few -- it's got styles wired up, basically, and has a template already. So, we're going to be adding Eleventy to this. So, yeah, and, in fact, might as well put a link to that in the chat, as well, because why not? Cool, I am allowed to post links in this chat. All too powerful. Yeah, this is -- I tried to keep things as pared down, minimal, and un-fancy as possible. I personally don't like it when pre-existing things get in the way of learning what it is we're learning. So, as you can see, we've got one template in here, which is index.md. And it's, you know, it's fairly boring. It's going to help us get started. We have a layout called page.HTML. If you pop that open, we've got ourselves a fairly standard HTML boilerplate. This contains our fonts and our style, as well. So, this is basically all we really have setup-wise. I've preloaded a bunch of things just to look pretty.
JASON: And, yeah, some pretty straightforward styles it looks like. Nothing too much, about 100 lines of CSS here. And then in our Eleventy.js, all we did was move everything into a source folder. No other bits, right?
BEN: You can see we've got add, pass through, copy, so if we change our styles, they'll get loaded into the new build. Overall, a fundamentally un-fancy project to start off with. Didn't want to spend a lot of time on your Eleventy boilerplate, more or less. Yeah, if you click the get started link, it will break. We're going to create it. I've preloaded it with query parameters, because we'll see. So, yeah. Any questions about this boilerplate before we get started?
JASON: No. I'm feeling pretty solid about this. You know, it all makes sense to me. Anything that we write in here will get dropped into this page.HTML as the content tag. Our styles are getting loaded right here. And I understand how the CSS and HTML relationship works. Anything that's in source is what Eleventy is going to target. And, yeah, I think I've got about as much context as I can get. Chat, if you've got questions, as always, please drop them in there, we'll answer them as we go.
BEN: So, I thought what we could build is a template for our color contrast ratio. Not even start it as serverless. Make a hard-coded template with hard-coded values and add in the dynamic stuff as we go.
JASON: Perfect, I'm ready.
BEN: So, let's go ahead and create a new template. Call it contrast.HTML. Markdown is the most common, but for today, we're going to get -- we're going to stay as close to the markup as possible, and I find it easier to do this as an HTML file. We'll start with a bit of front matter. We're going to add in our layout. This will get us the styles on the main tag and everything. Oh, there we go.
JASON: Very helpful extensions happening right now.
BEN: Yeah. Let's go ahead and add ourselves an H1, probably have that be something like hash ffff, and 0000. Probably the wrong number of "Fs" and zeros. But you know what I mean. Contrast ratio of -- and underneath it, we're going to use an element that's not often talked about. Folks in the chat are going to be screaming that I'm about to bring up the DL. I'm not, I'm not going to bring up the DL. That brand was so a few months ago. Instead, I'm going to introduce you to the output element, which is designed to represent the results of a form submission.
JASON: I've literally never seen this before. This is entirely new to me.
BEN: Output element, for anyone unaware, represents the forms submission, usually a calculation. It also counts as a live region, which in accessibility speak means that as you update the output element, it will announce the changes to screen readers and other assistive technologies.
JASON: That is killer! Because that's a whole thing that we struggle with, right, when we're showing output. When we put it in a div, it doesn't tell anybody that we changed it, so then we find ourselves writing all these area attributes, and those are super confusing and easy to get wrong. So, if I just use this output element instead, it will work just like a div in terms of how I can style it, but will automatically trigger the assistive tech when it changes?
BEN: Yes. Actually, technically, it works more like a span, because --
JASON: Oh, so I'd have to make it display block and all that good stuff?
BEN: Yeah, and I've done that in the styles for you. If you're curious, the specs define a whole bunch of different kinds of elements for different kinds of content. So, there's like your phrasing content tags, which are your span, B, I, strong M, anchor, et cetera, those are things that you would more or less expect to see inside a paragraph tag. Then there are your sections elements and stuff like that. So technically, output is more like a span, but whatever.
JASON: Also, chat, are you making fun of the way I just said -- it's not area, is it? It's aria.
BEN: It's aria, typically.
JASON: Fine. Okay. So, inside of this, I can just put whatever I want?
BEN: Yeah, let's go ahead and put 21.00:1. If you're unfamiliar with color contrast ratios, a 21:1 ratio is the highest contrast ratio you can have. That's what you get if you compare black and white.
JASON: Okay, I think I misunderstood, you want this inside the H1?
BEN: That's its own thing. It will be uh outside the H1. We could put the output inside the paragraph tag if we want to be very correct, but, yeah, there we go. Very correct is good. Yeah, so, let's go ahead and save this and take a look at this. This is going to have been built, so that we can access it at /contrast/. There we go.
BEN: Yeah, decent looking -- what?
JASON: I -- okay.
BEN: Cool. So, now we've got our little template. But this is being built ahead of time, and it's being built with hard-coded values, right? So, this is not really what we want. This right here is a fundamentally useless page. Tells you exactly one contrast ratio, and it's the one that most accessible folks know off the top of their head. Black and white have a contrast ratio of 21:1, which is the max. So, we want to make it dynamic, to add serverless, so this gets rendered with whatever input you provide it.
BEN: Cool. So, let's go ahead and stop the dev server for this. And while you're at it, let's move the underscore site directory. That's the build output, because it's already created a contrast page for us, HTML page for there. We don't need that anymore. That's not going to be in the build. So, all right. Now to add serverless to our project. First of all, you're going to want to be on Eleventy 1.0. Which is what we have in the project. Let's go to our Eleventy.js file, which is -- yeah. So, this is our configuration for our project. This is how we tell Eleventy what to do, how to be, how to live its life. If you go up to the very top of this, we're going to import -- very, very top. We're going to go ahead and require something from the @Eleventy/Eleventy library. So, this is going to be a destructured import. Yeah, definitely go about it in that order. This right here is going to be a plug-in. Eleventy Serverless is technically a plug-in you have to add to your project. Start typing Eleventy, and it's going to be Eleventy Serverless bundler plug-in. All one word. So this is the plug-in that we're going to add. Now we have to add this to our configuration. So, yep, Eleventy config.addplug-in.
JASON: I love auto-complete.
BEN: You'll notice there's the js.com above this function. I recently -- that's now in my default install. Otherwise you do not get this for your Eleventy config. So, I highly, highly recommend this js doc setup, which I think we've found. We have a question in the chat about what's a color contrast checker. You feed it two colors, in our case two hex codes, and it tells you the ratio between them. Higher ratio means the colors have a higher contrast, which means for people with visual disabilities, it's going to be a lot easier for them to make out content with that higher ratio.
JASON: We can show this straight up, right, like if I computed -- I might have to add a color on it. Let me do element style. Color will go -- we can see the numbers getting smaller. This is very tiny. Let me try again. So, as I go lower, that's harder to read and see it, saying, hey, people can't read it. My eyes are straining a bit. If we get really low, oh, boy, can't see that at all. As we make it bigger, because this is a giant text, once we get above -- what is it, 3?
BEN: According to the Web Content Accessibility Guidelines, there's basically two main threshold ratios you need to care about. A 3:1 is sufficient for large text, which is -- there's a whole definition, but basically 18-pixel font, or 14 in bold. Something like that. I forget exactly what it is. Large text needs to be 3:1, normal text needs to be a 4.5:1. That's why contrast checkers like this are helpful. They give you the two numbers that you're looking to add to your design system and go, yep, these work well together.
JASON: This particular tool I use all the time. Throwing in the two values is hugely helpful. There's another question about the JS comments at the top here. You want to give us a quick overview how this works?
BEN: JS.com, I'm going to put a link in the chat. This is a special syntax for comments. You'll notice this comment block starts with two asterisks. This is a special syntax for comments that enables you to, effectively, provide metadata about the variables and functions that you're writing. You statically build documentation pages out of your code, if you included comments about what your code does. It's a special syntax meant for it. However, VSCode has a feature where it basically uses JS stock comments as hints for its type script server. As you're describing, oh, this is a string, or this is an array of numbers, or something like that, even though it's just a comment, it ends up under the surface being interpreted as, basically, types. That means you get intellisense. If you're like me, don't want to go into type script wholesale, this is an incremental way to benefit already. Your editor does it for you. It's such a time saver. It's lovely.
JASON: I feel like this was the moment where I opened my heart to type script, really, when I realized you could use these jsdoc inclusions. Because the thing I live about type script is I hit dot, I can see what's here, see the methods. That is inbelievably helpful, as you are starting to work. It's so, so, so good. And the full on migrate everything to TypeScript is handy --
BEN: Oh, no, your code is more expressive, too. Oh, no. This is absolutely going to make it through team review. Oh, no. Absolutely, take the time to learn it, because if you don't want to make the full TypeScript conversion, this itself is entirely worth it.
BEN: Shall we continue adding our serverless plug-ins?
JASON: We probably should.
BEN: So, as a second argument to the add plug-in method, we're going to pass an object. And for this object, we, unfortunately, don't get autocomplete for this, but we're going to have to give it two things. The first thing we're going to tell it is where our serverless functions are going to live. And because in this household we stand Netlify, we read the Netlify functions directory. So, let's pass it a property called functionsDir. And the second thing we're going to pass it is a bit curious. This is going to be a name. So, the property is name. And what this represents is the name of our serverless function. So, we may, depends as kind of a crunch stretch goal, we may add multiple instances of the serverless bundler plug-in to do different things, but you can actually add the serverless plug-in multiple times to your project and each instance will handle things in a different way, because they are associated with different serverless functions that can do different things like validate your parameters, for instance. Or cleanse your parameters. Right, no one wants to just willy-nilly introduce user input into their site. That's a bad idea for security reasons. So, maybe you want to do some validation. You can do that validation in your serverless function. So, now your serverless functions have, like, you have this flexibility being able to use multiple serverless functions for your Eleventy Serverless. So, let's give this the name On Request. Because, to start off, we're going to have it generate the page on every request. We're not going to start with caching with on-demand builders, because I think that should be a stretch goal. So, onrequest. So, let's save this and start our dev server and observe changes to our project.
JASON: Whoops. It's working anyways. Here we go.
BEN: All right. So, first of all, if you look at the underscore site directory in our code base, we are going to see that there is -- oh, oh, right. Okay, yes. So, there is this on-request serverless function. All of this stuff has been automatically generated for us. Really, the only thing we care about is the index.js page here. This is the serverless function that we control. Everything else gets auto-generated for us, and will be rebuilt with every CICD pipeline. So, we can ignore that. Git ignore, add a few lines real quick. So, the first line is going to be Netlify/functions/onrequest/starstar. We're going to follow this up with exclamation point, Netlify functions, onrequest, index.js. This ignores every file in our onrequest serverless function folder except index.js. That way Netlify is able to rebuild all the other things based on your project state. We don't touch those, so we let Netlify do the work for us.
BEN: Cool. So, everything else is exactly identical. Right, you can see in our site directory we still have all the same content we would have had. We have our contrast page, index.HTML. All are still being built ahead of time as it should be. We haven't changed anything about our project so far. We haven't even made any of our templates serverless. So, let's go ahead and do that real quick. Can you kill the dev server once more and delete your site directory once more? Promise we'll get to the point where we're not deleting our site, but for initial setup, what we now want to do is tell Eleventy that our contrast.HTML template is a serverless template. It should be rendered on request. And, so, to do this, you're right, it would be in the front matter. There's long been a permalink property that you can specify, and this is usually to set a different output path. So, you'd say I know the template is called this, but I want it to be accessible with a different URL.
JASON: By default it's going to use the file name. Contrast.HTML, so permalink is/contrast. If we said permalink like hello, then it ignores this, and this would be available here instead.
BEN: Yes. So, Eleventy Serverless is going to be permalink, but you're still going to have it the way you had it, but now you're going to go to a new line. Yep, you got two spaces. YAML's version of an indentation. And now what we're going to do -- JSON, JSON, yep --
BEN: You're welcome. So, what we're going to do now is put the name we gave our serverless function. So, this is on request. So, this specifically matches that name attribute in our .Eleventy js. Now we can give it a path, /contrast/.
BEN: And we're going to go ahead and save this. Hey, if someone tries to access the route/contrast/, then instead redirect them to our onrequest serverless function. And that's going to provide the output for us. We've created a YAML object. There's a whole lot of fun stuff you could do. You could technically provide an array of values. Multiple routes are governed by the same serverless function. But for today, here's how we're going to do it. So, let's go ahead and save this, rerun, and let's go to our /contrast route. There we go. If you go to that -- yeah, yeah, if you go to /contrast/, it came up exactly the way it has. However, if you check your terminal, you're going to see it actually said, yeah, that was redirecting you to the serverless function. If you were to check your underscore site, you won't actually find contrast.HTML in there anymore. The only thing -- yeah, so, that HTML does not exist anymore, but you were still served it, because it's now being generated by your serverless function. How are we doing so far?
JASON: We're killing it, even with me screwing around, you're doing a great job.
BEN: All right. But, you know, this still isn't very helpful for us. We need to start accepting some user input, right? So, 11 exposes information about the -- so, Eleventy exposes information about the user's input, typically query parameters and URL parameters, as data. Eleventy has this concept of data, which I would basically define as variables that are accumulated for every template that can be injected into your page using templating syntax. We'll see what that means. So, let's replace that first hex code you've got in your h1. Let's go ahead and replace that. And the octothorp, as well, with double curly boys. Double curly boys. I may also call them handle bars from time to time. But in here, I want you to stick, and this is not going to be trivial, Eleventy.serverless.query.foreground.
JASON: I can't even spell Eleventy. Serverless.query.foreground? How close should I get?
BEN: You got it. I'm going to reply to Cyrus here, who asked, you can access all the variables in all templates? Not quite. Eleventy has a whole order of operations for each template, so each template can have its unique set of data. This is a process called the Eleventy data cascade. And I see that Brian Robinson posted a link to a talk I gave about that. I'm also going to link a blog post that I'm really proud of about the data cascade. Unfortunately, we just don't have a whole lot of time to dive into that today, but, yeah. So, what this does, this says, hey, take the query parameters, the foreground and background query parameters, and plop that into our page. Yeah. And, so, yeah. That's what we've got. So, now if we go back to our local host in our browser, we can change those query parameters, and it should work for us. Oh, yeah, you're not actually supplying those query parameters now.
JASON: But this is good. It means now we do need to supply these. Now I can go with foreground. And let's change it. So, we'll go with --
BEN: You're not going to be able to use a hash symbol in an URL. Instead, you have to URL encode it, which is percent 23.
JASON: Okay, and then go with background, % 23. That's purple, so for contrast we want white, I think. There we go.
BEN: Yep. Okay, it's not very fancy. It's also right now very incorrect, because those two colors do not have a contrast ratio of 21:1, unless my math is horribly wrong. What say you we add some ratios?
JASON: Yeah, let's add some ratios. Also, David is asking when all the function code was generated, do we as developers have to set up deps, or is that all magic?
BEN: Both. Dependencies are mostly set up for you if you reference your dependencies in the Eleventy JS config file or in a global data file, which is going to be a little awkward very shortly, because we're going to use something called a template data file, which is the one instance we have to manage our own dependencies, which is fun. Let's go ahead and install a library to handle the ratio calculation for us. I like a library called get-contrast. Get-contrast, with a hyphen. Yep. And, so, now we have this. Let's go ahead and, because of that dependency management, so we make sure Eleventy picks us up and bundles up our dependency with the serverless function. Let's go into our .Eleventy.js file and require get-contrast, but not do anything with it. It's gross, I'm going to file an issue with Eleventy shortly, but for now this is kind of a work around we have to do to tell Eleventy that this is something we care about. Yep. And now what we're going to do is we're going to need to find some way to transform our foreground and background properties into a ratio value. And when you have data like our foreground and your background and you want to create something new with that data, you have really two options. There's a route called filters, which is basically functions that you can use in your templating syntax. Or you can create computed data, which is new data that leverages the old data. It's a lot like the computed props in view. That's the way I prefer to do it. That's the cleaner way, in my personal opinion.
JASON: And it also feels like given that we have this data already, rather than -- because my issue that I've run into with filters is that I find myself having to pass data into the filter that I already had somewhere else.
JASON: Because I can't get it -- the filters don't access the data. There's, like -- I find myself jumping through hoops. So, computed data sounds very promising. I haven't really worked on it with that.
BEN: If you're interested in learning more, there's a blog post I wrote about Eleventy data cascade in the chat. But to do this we're going to create what's called a template data file. So, next to the -- yeah -- yes, if you scroll down, there's a table of contents, there's a bit that tells you.
JASON: Here we go.
BEN: Step 7, computed data. That's what we're going to be doing, actually combining this with step 5, which is template data files. Next to your contrast HTML, let's create a new file called contrast.11tydata.js. This is a magic name, has to be exactly like this. This tells Eleventy this is a file that's going to supply some data. And the fact we called it contrast will tell Eleventy to link it with contrast.HTML. So, this is a way for us to specifically provide data to be used within the contrast.HTML template. So, yeah, I've recently -- we got B. Rob in the chat. He and I spoke at the Eleventy meetup not long ago, the same day. Afterwards, oh, computed data finally makes sense.
JASON: Nice, nice.
BEN: Not always straightforward. But let us go ahead -- I've got my notes here. Let's go ahead and do const contrast equals acquire get-contrast.
JASON: Thank you for the sub, Theo, I appreciate it.
BEN: Theo, hello, we are using Ping right now.
JASON: We are.
BEN: It's working like a charm. Let's do module.exports equals, then an object.
BEN: Yep. And in here you're going to have a property called Eleventy computed. This is how we signify to Eleventy that this is data that needs to be evaluated at the end of the data cascade, using all the other data that's been aggregated already. This is an object. And now let's create a new property in here called ratio. And this will be a function that receives a data argument. Yep. And in here, let's go ahead and de-structure foreground and background off of data.Eleventy.serverless.query.
JASON: Equals data.Eleventy --
JASON: Got it.
BEN: Yep. And we're going to just console log foreground and background for now.
JASON: Copy and paste these, so I don't have to type them out twice.
BEN: You got it. And let's go ahead and save this, and we'll check our terminal. I'm going to tell you now, this is actually going to break. Yep.
JASON: Boom, explosion.
BEN: Oh, no, it broke. It's saying can't find properties. It's trying to read query. So, when you're dealing with computed data, I wanted to show this, because this is a gotcha that tripped me up for so long. The night before I was due to give a talk about Eleventy Serverless for an event called the Eleventies, this here was tripping me up so much. Computed data, Eleventy wants to calculate basically the order of dependencies, because computed data can reference other computed data. So, it has to basically evaluate ahead of time what is the natural order of the data that has to be evaluated. That means it's going to try to evaluate your serverless data before you even have a serverless request. So, what I would say is before you de-structure, we're just going to do if not data.Eleventy.serverless, return one maybe. Just say that the ratio is one, if it's not a serverlessly invoked function. This should work for us.
JASON: Okay. Let's try again.
BEN: And now let's go ahead and, I guess, go to -- yeah.
JASON: There's our foreground and background. Let me go back to the one we added the foreground and background.
BEN: Look at that!
JASON: There we go.
BEN: So, we've successfully pulled our foreground and background. Now we need to get the ratio. To do this, we are going to return contrast.ratio. And we're going to pass it foreground and background. There we go. And let's go ahead and use this. Let's go into our contrast HTML template. Instead of 21.00 -- we're still going to need the :1, but replace with ratio. That's what we did, created a property called ratio that's calculated from the foreground and background data. It does break layout-wise, but it's functional.
JASON: So, if I go back to my -- go up here and do the black. There's our 21:1. So, we can fix this, though, by going in here. And, what, we want --
BEN: Can I propose more computed data? This is data that now depends on our ratio data. Let's open a new property in our Eleventy computed called formatted ratio.
JASON: Ah, I got you. And this is where I think that gotcha is going to become more visible to me. If I put this above ratio, it would explode, right?
BEN: No, no. Because Eleventy pre-evaluates the dependency chain and figures out what order things need to be in.
JASON: Oh, interesting. Okay. Do I want to do the same thing here?
BEN: No, because the only thing this is going to depend on is the ratio property. We're going to have computed data that depends on our ratio computed data. We just said, hey, if you need to panic, panic to the value of 1, which is a formatted number.
JASON: So if it panics, it will be 1:1?
BEN: Yeah, yeah. I figured that was a safe, fairly straightforward thing to do. Okay, so, let's return number, like Number. Data.ratio. After this, to fixed. After that, you don't really need any more decimal places of precision.
JASON: A lot of decimal places if you go too far here. So, now we're going say formatted ratio.
BEN: Yeah. Yeah, there you go.
JASON: Now it's forcing it. If I go to that one we had a really long one, 669933.
JASON: What was the class?
BEN: Fields. Yep. And in here we're going to have two more divs. And each div is going to have a label and a color input. And we should absolutely make sure that this is accessible. So, our label is going to have the -- yep, ID of let's say foreground and background. That's what we're going to do, have users pick that. ID is foreground, and name equals foreground. And the label text should also say foreground. All of this similar for the background.
JASON: I'm going to do this one more time.
BEN: Once more with feeling.
JASON: All right. And then we can capitalize that one.
BEN: And have a submit button, which is going to be outside of our fields div. So, yes. And let's just call this get ratio. You're going to see there's already some neat styles here. However -- oh, did I -- I might have broken something.
JASON: I'm zoomed in, as well.
BEN: I'm not going to spend time debugging the styles here, but let's go ahead and now use the two color pickers to select us a foreground and background.
JASON: Let's go with a lightish purple and a darkish really -- this is going to be real bad. You ready?
JASON: That is really nice. Can we also use the --
JASON: Data to preset these?
BEN: Yes, let's go to each of our inputs and say value equals. And then curly braces, handle bars, double curly boys, whatever you want to call it. And Eleventy.serverless.query.foreground or .background.
JASON: And now --
JASON: There's our colors.
JASON: If I hit back, it sticks. This is really nice.
BEN: So, let's make this even better. I want to make a pretty demo, we've got enough time. Let's make it a pretty demo. In your contrast.HTML template, let's open up a style tag. Doesn't really matter where you put this. Put it wherever you prefer. In here we're going to target the body. We're going to give it a background image. And this is going to be a linear gradient. And I prefer -75 degrees.
JASON: -75 degrees.
BEN: Yep. We might want to break these things out into new lines, because things are going to get long. Even though we're a style tag, this is still interpreted with all of our templating syntax. So, comma, and then a new line, and now double curly boys. Eleventy.serverless.query.background. And after this, it's going to be 50%. And then comma. And then the same line down below, but with foreground. Boom! Keep going to some pages, let me know what you think.
JASON: Look at this. This is cool. All right, let's do like a greenish and come over here and do a -- let's see, how offensive can we make these colors? Hold on, chat. Make it gross. What do I do? Go with this brownish color, and then we can take it to a mint green. That's going to be bad, right? That's got to be a bad color. Let's go over here.
JASON: This is good stuff.
BEN: Yeah, oh, yeah.
JASON: Hot dog stand colors, we can definitely do that.
BEN: What say you we deploy that.
JASON: That is aggressive.
BEN: Truly aggressive. However, it does meet color contrast requirements for large text.
JASON: It does do that. So, for me to deploy this, I'm going to need to fork it, I think.
JASON: Let me --
BEN: For anyone else who's using the template repo, there is a green "use this template" button you can use. That way you'll get not even a fork, but straight-up your own project with this.
JASON: Okay. So, we've got an origin upstream, or an origin repo of mine, an upstream repo of yours, and if I get at everything, we can say get, commit, and say add serverless contrast checker. Outstream origin, off it goes. And then I can Netlify. And I want to create an configure a new site. Put there on mine, call it Eleventy Serverless contrast checker.
BEN: I'll go ahead and answer the question from the chat. I do have both the stream and the Twitter. Let me put links to both of those in the chat. If you're interested in stuff like this, I have a weekly Twitch stream called Some Antics, that I'd love to see you in the chat for, while we're deploying this. There we go. Hopefully, that helps.
JASON: Yes, here's Ben's Twitter. And here is the Twitch stream. I think does my shout-out command still work? Let's find out.
BEN: Let's find out.
JASON: It does!
JASON: So, that is pushed, good.
BEN: All right.
JASON: And then I can go out to -- let's go look at the site here.
BEN: Hopefully, this should work.
JASON: Built nice and fast. Whole site build in 141 milliseconds. I just said "prosheshing." Sean Connery. 49 seconds, that is live on the Internet. Here we go. Here's our contrast checker. --
BEN: Click the get started link.
JASON: Yeah, we have a get started link.
BEN: There we go, cool.
JASON: Let's try a new one. This is going to fail contrast. Yeah! Now we can play with this. It's working!
BEN: And if you're ever curious about how the math behind this is working, the single most important factor in our color contrast algorithms is relative luminance, basically one color is light and one is dark. You can have a red and blue, which I can tell they are different colors, but the hue matters a lot less than the lightness and darkness of the colors. This is a low color contrast, even though to my eyes at least these are blatantly different colors. It's not how different they are. It's how readable one color is versus the other. And to do that well, you basically need to have one light and one dark.
JASON: Yeah. We've got a request from the chat for one that I assume is going to be really hard to look at. So, let's start there. And then we're going to add -- what's this one? We're going 11 -- oh, this is going to be rough. Yeah, that's --
BEN: While we're over here celebrating "The Fairly Odd Parents," right, listen, you brought the youths. I am not ripe in my age. I have a ways to go, and I have so many more, you know, '90s, late '90s kid nostalgia here. This is what you brought into this world, Jason. It's giving me a platform to do "Fairly Odd Parents" jokes.
JASON: I love it, I love it. I'm so onboard with this.
BEN: Let's see. I think we have enough time if we want to do a stretch goal for caching these pages.
JASON: Absolutely, let's do it.
BEN: So, let's -- and we're going to speed run this. I think we're going to have it by a separate template for cached pages. But overall is going to function identically. So, step one, go to our Eleventy js and add a new instance of the serverless bundler plug-in. I would call this cached. You can call it whatever you want. If you lock on the documentation page, they indicate possum, to say you can call it whatever you want, but cached, I think, works for me. And then let's go ahead and create a contrast cached.HTML maybe. This is going to be different in that when using Netlify's on-demand cache builder, you have to provide a different way of doing things. So, step one, yep, replaced that with cached. And our new route, let's call this -- let's remove the /contrast/. Let's call this ODB, for on-demand builders. After that, colon foreground/:background. And everywhere you see Eleventy.serverless.query, we need to replace the query with .path.
BEN: And then next up we need to make sure it has access to the same data as you would have in the contrast.HTML. So, we're going to copy over our -- yep, this is going to be contrast cached. Once again, instead of .query, this is .path, which I think you only change on line 10. And, finally, we need to go to our serverless function that's been created here.
JASON: Stop and restart.
BEN: Yes, yes. We'll also need to add a whole bunch of the serverless function stuff.
JASON: Wrong thing. I want to Netlify dev.
BEN: There we go.
JASON: Okay, now I have Netlify, cached, indexed.
BEN: Okay. We also, I forgot about this step. At the very bottom it tells you how to make this use on-demand builders, which is this comment block here. Comment out line 52. You're going to un-comment 54 and 55. And then it tells you, you also need to install @Netlify/functions. Yep.
BEN: And we're not going to be able to validate locally, because the caching aspect of on-demand builders doesn't work locally. We would, instead, want to validate through a deploy. What we've done here is we have instead provided a new way for us to access these pages. We say /ODB/1 hex code, slash a second hex code, and this will be cached now. We have to provide a new path, because we don't have access to query parameters anymore. Another thing that won't work anymore is our form, because we would need to reshape the URL.
BEN: Do we want to do that real quick?
JASON: Yeah, we got time, let's do it. So, down here.
BEN: Let me get my notes up for this again. All right, let's open up a script tag. Let's query select our form, and save it as color pickers or form. Form is better, yes, I like that. Yep.
JASON: Only got one, so we can be pretty loosy goosy with our selective.
BEN: Form.addeventlistener, submit. Prevent default. Yep. And const values equals object.fromentries. New form data. Form. Yep, like that. Const foreground equals values.foreground.replace. This is going to give us the hex codes with an octothorp, right, and we can't have those in our URL, can't have those pound signs in our URL. So, we have to replace with the URL encoded version. Replace the pound sign with %23. Similarly for background. All right. And window.location.href equals. And we can open a template string here. /ODB/foreground. /background. If all this works according to plan -- did you include a link in your permalink?
JASON: I think, let me double check. Yes.
BEN: Okay, cool, cool, cool. So, this should work. I think we can validate this locally. The only thing that won't work locally is the caching bit.
JASON: So, we can run it. Here we go, Netlify dev. That's going to fire up all our functions and stuff. We can even see here it's firing up the serverless files and doing all that good stuff. Here's our functions. And then when we come up here, we go to ODB, and we'll do like a %23.
BEN: There we go.
JASON: And if I change it --
BEN: Form should still work, fingers crossed.
JASON: It does.
BEN: Nice! All right. What say you we Yolo this.
JASON: Yeet! Chat, you've got the yeet emote now, too. Also, followers get emotes. No money, just following on Twitch, you should get special emotes on the channel. Maybe click the follow button and make some noise! Ha, look at the yeet!
JASON: Get at everything. Double check we did everything, added the cached non-index files to our get ignore. Get committee. Then we're going to get push. If we come back out here, we can see here that it automatically started building, because we pushed to GitHub. This should take us, hopefully, even less than 49 seconds this time, because now we've got some caching.
BEN: Yep. In the meantime, I kind of want to talk of other use cases for serverless I've seen. Both Stephanie Echols and Brian Robinson in the chat today have leveraged Eleventy Serverless to add search pages to their site, because those take arbitrary input and return arbitrary output. They both blog on Eleventy sites. So, this is a fairly natural fit. I know that there have been a couple of other experiments in this regard. I've been trying to push it to the absolute limits by building an on-demand Twitch chat generator, like themed Twitch chat generator using Eleventy Serverless that takes arguments through the URL to provide different logic. So, there is a wide variety of things you can do, and I think the more people kind of see how this stuff works -- hey, oh, I see, we got to do the -- yep.
JASON: Clean up a little bit, go to one of these. Go to one of these.
BEN: ODB instead of contrast.
JASON: ODB, that's right.
BEN: Oh, no!
JASON: URL not found. How dare you. Wait, why didn't that work?
BEN: That's a great question. I'm not too caught up about it, but that is -- let's see. Interesting. This is maybe one of the, you know, not to end on the note of like an error state, but this is one of the rooms for growth that I think Eleventy Serverless does actually have, which is that it can be very difficult to debug when your Eleventy Serverless functions go awry. And this part I did not test.
JASON: I think we've got a note from Zach, which is the generated -- it needs to include builders.
BEN: Oh, interesting, okay.
JASON: So, let's try this one more time, and if this one doesn't work, we'll open it up and dig into it. But, yeah. It might just be that we need to do a bump to the latest patch version of Eleventy to get that sorted. But -- did I push? I don't think I pushed. Oh, I needed to actually add that.
BEN: Oh, yes.
JASON: There we go.
JASON: Okay, we have missed a step somewhere, for reasons I don't --
JASON: -- that are not immediately apparent. Also looks like it's running them as functions. Does it say on-demand builders anywhere? I'm wondering if we -- we'll publish the fix once we get it sorted. In the meantime, we can rely on this very cool setup like this, where this is working. So, you all can go play with that to your heart's content. See how shocking you can make the color contrast combos. But, Ben, this has been fantastic. Let me do a quick shout-out to a couple things here. This show, like all of our shows, is being live captioned. We've had Ashly with us all day from White Coat Captioning, so thank you very much. That's available in the StreamText player, until I fix the home page. And our captioning and other show costs are covered by our sponsors, Netlify, Nx, and Backlight. Thank you all so much for kicking in to make this show better than it could be if I was on my own. While you're looking at things on the site, make sure you go and look at the schedule. There's so much good stuff coming up. I'm not -- I don't have time to read through it, but go read through it. You can add it on Google Calendar to get the details. Make sure you go and follow Ben. Ben, if people want to dig in deeper, we have a handful of links here to the Eleventy data cascade and other things. Is there anywhere else you want people to go and look at?
BEN: Yeah, I would say the documentation is at Eleventy.dev. I think it's always getting better. I also want to call out that there is an Eleventy Discord server that is a great place to ask questions. Twitter is good. You can reach me on Twitter. I stream weekly @SomeAnticsDev. There's also an Eleventy meetup, if you go to 11tymeetup.dev. This is a monthly meetup for people really interested in Eleventy. They are always looking for new speakers. So, if you want to learn, or if you want to speak there, you should absolutely, absolutely take a look at that. Yes, Stephanie is exactly right. There's an open CFP. Please, please, please, send the team your talk ideas. I've previously given this talk in ten minutes, but I've also talked at the Eleventy meetup about the data cascade and some of the internal workers of Eleventy. So, there's tons of great resources, and more resources are getting added all the time. Now that Eleventy is at 1.0, and Netlify is sponsoring its development full time, thank you so much, Netlify, I think you're only going to see more and more of that as time goes on. So, there's tons of places to learn about Eleventy and what's going on in that space.
JASON: Here we go. We're going all-in on open source. This is really, really exciting. Yeah. Okay, Ben, so many good resources today. Super exciting stuff. I love what we were able to build. And, as always, it's been a pleasure having you on. Chat, stay tuned. We're going to find somebody to raid. Thank you all so much for hanging out, and we will see you all next time.
BEN: Bye, y'all.