Faster Static Site Workflows with Nx
How can Nx speed up your development workflow for Jamstack sites? In this episode, Adam Barrett will teach us all about it!
Links & Resources
Click to expand the full transcript
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 LENGSTORF: Hello, and welcome to another episode of Learn With Jason. Today, on the show, we have Adam Barrett. Adam, thanks so much for hanging out with us today.
For those of us who aren't familiar with your work, do you want to give us a little bit of background?
JASON LENGSTORF: Nice. When you do that consulting, is that all through Nrwl?
ADAM BARRETT: I was at another company before that. Nrwl, I've actually only been there since February. There's a whole other story, but we don't have to get into that. (Laughter).
JASON LENGSTORF: We can skip right ahead then. Let's talk about Nx in particular. Nx is something I've heard a little bit about. I've been it in use in a couple of repos. I haven't quite gotten the elevator pitch. What is Nx?
ADAM BARRETT: Nx is a suite of tools and basically, CLI or terminal tools, that sort of enhance the developer experience. It's not really about enduser experience. It's about helping developers with their code. Not to confuse mono repo with monolith. It's to avoid a lot of complexity that arises when you hey, I want to isolate and split off my packages." If you have them scattered, coordinating things can be tough and Nx helps you with that. It gives you tools to deal with that, but also tools that will overcome the problems you get with modern repos.
JASON LENGSTORF: I think that's a really interesting thing to dig into a little bit. So mono repos has have this promise. When you see it, it's kind of nice because you can see, all right, I'm going to this repo, there's a README and there's a packages folder and in that packages folder, there are 1, 2, 50, 100 different packages in there and each one of them its own. By keeping them in the same repo, all the code is in the same place.
What other benefits are there for that?
ADAM BARRETT: So, like, the code in the same place can maybe be a little I don't know dismissive. All the code's in the same place. I like to illustrate that with sort of this idea that, say you were working on a package. Three other packages depended on your package. You're like, I'm going to update my package. I've made some changes, they're breaking changes. I need to go to their repo, I need to make a PR and go through their CI and go to the next repo. When you've you do that for any package that depend on this package. With a mono repo, you've made the change, you can go to each of the individual packages and update those packages and it's all one CI process. If anything fails, I'm going to find out right now, oh, I missed a package. We had a dependency I wasn't aware of. Their tester is presumably going to fail and you know, hey, and I can add that before pushing this out.
Above that, though, Nx kind of goes this extra step and it says, we're going to focus on application development. A lot of mono repo tools, they will basically help you with, you know, library development. So, you'll have a bunch of individual libraries and maybe you have a bunch of different open source projects. But an application, it's kind of the same thing. It doesn't necessarily need to be on NPM. It doesn't necessarily need to be that way.
So, what Nx does is we have this concept our projects are split into two things: Apps and libraries. Apps are like the things you run and libraries are libraries and consumed by the apps and all your libraries don't necessarily have to be on NPM. They can be like, hey, this is a way we break out code. Yeah, that's where I think the mono repo thing really comes into focus is that, you know, mono repo's going to keep everything in one place and you can run things across the different packages and make sure your if you change one thing, you're only going to run tests for exactly what needed them. You want to build applications and those applications are then going to be deployed or what. Not necessarily a package up on NPM.
JASON LENGSTORF: I do think one of the things that is difficult is, like, a lot of the the tooling out there is either very, very obviously for websites, like, you know, you're using Webpack or the tooling is very, very obviously for libraries and I like the idea that, you know, you're coming at that with the acknowledgment that, like, these are two separate things but they have to exist together. If you're a big company, you're going to be shipping your website, which is, you know, that's your application. Maybe you've got a dashboard, you've got some marketing pages, your support portal. And those might be three separate applications and then you have your component library and your shared utility and your auth. If you are trying to put all of those through one tool, it's going to get weird because you're either trying to make a library piece of tooling function for apps or vice versa. And, yeah, so I can see that.
I really love, for example, using Rollup or Microbundle is really good for libraries. But Webpack or Parcel is what you would lean on for an application. And if you see repos that try to make it all done one, you end up with these very complex and confusing things so I like considering it at the tooling level. If I'm bringing in Nx, it's going to do that for me.
ADAM BARRETT: Yeah. And Nx does sort of try and stay neutral, at least in the more recent years. Like, you know, bundlers, like Rollup and Micro whatever the one is from the Preact. You can use those. You define, okay, well, my app is going to be bundled or Rollup or Webpack and my these libraries could be bundled with Rollup. That's okay. Those can be separate. Nx is going to give you one CLI command, like, "Nx build." In your app, you're going to be able to import, you know well, we can show you this later. You're going to be able to import your library and you aren't going to have to worry about, does it need to be built first?
JASON LENGSTORF: Interesting! Okay. On that note, maybe it's easier, now that we've got the highlevel, to start looking at some code. Does that make sense to you or is there something else you want to talk through?
ADAM BARRETT: I just want to talk about everything. (Laughter). I want to make people drop boops and stuff.
JASON LENGSTORF: That chat's all about it. You call for boops, you're going to get some boops.
Let's switch over to pairing. There's a question in the chat about Nx handling publishing. Nikki, I don't know why that didn't work. It's supposed to work. I think I ate your sound effect. Sorry about that. Follow Adam on Twitter and don't forget, we have live captioning for this show and every show at LWJ. It is provided by White Coat Captioning. Thank you so much to them, for being here. And that is made possible through the generous sponsorships of Netlify, Fauna, Sanity and Auth0, who all chip in to make this show more accessible to more people.
Thank you so much for the sub.
Okay. So, now, here we are. I'm at Nx.dev.
Thank you. Here we go. Oh, we started a hype train. Look at you go! Ben, thank you.
ADAM BARRETT: One thing I actually forgot is that, yes, Nx is definitely this is a product that was specifically made for largescale teams doing lots of big stuff, you know, with many teams working on the same stuff at the same time. But, I was going to show, today, how it's actually pretty cool, even if you're kind of like on a small team or by yourself and you just want to develop something that isn't the next Google or giant marketing engine. It's still actually pretty cool to kind of, like, use because of all the stuff it gives you out of the box.
JASON LENGSTORF: That's great news because it's just you and me today. You and me and the chat. I am on the Nx website. I'm in my terminal here, ready to roll. What should I do first?
ADAM BARRETT: Well, I mean, if you wanted to, like, look at docs, if you're that kind of developer, I would say, hey, yeah, check out the docs.
JASON LENGSTORF: Hold up! Look at this! Look at that animation? That is slick.
ADAM BARRETT: This is going to go really well if that's enough to make your smile. (Laughter).
JASON LENGSTORF: I'm easy.
ADAM BARRETT: I can tell you what to type and we can roll.
JASON LENGSTORF: No, no. So, what I'd like to do is I'd like to make sure we get through enough to show everything. Let's get a look at what our getting started path is here. If I wanted to get started, you wanted to do, was it React today?
ADAM BARRETT: For the for the the docs, you can definitely look at the React. Nx is really based around plugins. There's official plugins and community plugins. We have a Next.js plugin. We're going to use their, like, static export just to show how static sites can be done very cool. So, we're actually going to use the Next.js plugin. React stuff is pretty easy to go through. Kind of the same idea.
There's no real tutorial for what I'm going to give. There's a bunch of videos and stuff. There's a tutorial link.
JASON LENGSTORF: And I do like that you've provided, like, a "why Nx." One thing that a lot of libraries do is they assume you know why and they launch into "type this." Providing a little bit of context about why would you make this decision, why wouldn't you make this decision, what is this going to gain, that let's me make an informed decision. Props to your docs team for making that happen.
For today, you're going to be my docs. So, what should I do if I want to set up a repo here? Do I want to create a folder or is Nx going to create the folder for me?
ADAM BARRETT: Nx will create the folder for you. We're going to are you a yarn man or an NPX kind of guy?
JASON LENGSTORF: I can be either.
ADAM BARRETT: Let's do NPX, create workspace. One thing I saw a show a little while ago. YARN has this thing. Nx has the same thing.
JASON LENGSTORF: Wait, for real?
ADAM BARRETT: I was like, hey, they don't know something.
JASON LENGSTORF: I can give it any name you want?
ADAM BARRETT: You can hit "enter" and it'll ask you for a name.
JASON LENGSTORF: All right. Let's try this out.
ADAM BARRETT: The first thing you're going to be asked is the mono repo name. You're going to have apps and libraries in this mono repo so it's your organization name. This is more like "Adam Barrett" or something.
JASON LENGSTORF: Yeah. So this would be the company
ADAM BARRETT: Yeah, exactly.
JASON LENGSTORF: I'm going to use a somewhat confusing name in that sense because I want to make sure that this is easy for people to find. I'm going to find this something similar to the episode name, so, "faster static site workflow."
ADAM BARRETT: I'm going to warn you, you might have to type that in a bunch of times.
JASON LENGSTORF: In that case, I'm going to copy it.
ADAM BARRETT: When you do import, you import from your @orgname/.
JASON LENGSTORF: I'm going to bail on that. We'll do a shorter one. And instead...I'll call it, like
ADAM BARRETT: You don't want to make this org name willynilly.
JASON LENGSTORF: I gotcha. I'll call it "Nx demo."
ADAM BARRETT: Node apps, Angular. Next is the plugin we're going to use. We're going to put your first application in there. You could do that. I called my "blog" because I was like, I'm going to make a blog first. However you want to do it. If it's cool, we could do something simple like a blog. We're not showing off Next.js as much as we want to show off Nx. I wonder how confusing it is how much time I'm going to say "next" when I mean "Nx." You can choose the styles you like.
JASON LENGSTORF: Yeah. We can use style components. I usually just go plain CSS. Oh, what's up, y'all. Codephobia, thank you for the raid. We just ran the NPM init workspace command. All we've done is talk about what Nx is, which sounds like a pretty exciting tool for managing mono repos, splitting it out between applications, like code that runs in the browser or a Node app and code that works as libraries. We've given our org a name of "Nx demo. "We've created a blog or we're in the process of creating a blog. And now I have a question: Am I using Nx Cloud?
ADAM BARRETT: So, I want you to say "yes" to this. So, Nx Cloud is like so, Nx is our open source platform. It's available to everybody. Nx Cloud is like the attempt to monetize on that. You know how many open source product needs to be backed by a giant company or have a way to monetize. Nx Cloud is basically it's a way to sort of hook up your Nx to get even cooler stuff and right now, that involves faster builds and faster, you know, CI sort of stuff and some cool, nice printouts of what just built and what just happened and possibly more stuff in the future, too.
JASON LENGSTORF: Insights into your
ADAM BARRETT: Yeah. It's got a good, generous free plan off the start. The main thing, with Nx Cloud right now, Nx has this cool thing called Computation Caching. It caches the output. Because Nx controls your dependency tree, it knows the code that was changed, it didn't affect any of this. I'm going to spit this out. If you were to run your tests and immediately run them again without changing any code that will affect them, it'll grab from the cache and pass it. It'll do that locally, and that makes it tons faster. Nx Cloud offers this crazy thing where it'll take those computation caches, put them in the cloud and your team can sort of share them. It'll upload the cache to there and then, you know, you
JASON LENGSTORF: If I pull those changes
ADAM BARRETT: You pull the repo and run the test, it'll immediately look and go, "oh, cache hit." So it won't run through the entire build.
JASON LENGSTORF: That's the selling point, right? Especially you know, I remember when I worked at IBM, a lot of our tests would take 40 minutes to run. It was one of those things that just got prohibitive to the point where we just didn't run the test until the last possible minute. It was part of our final deployment step, which meant that usually we had a lot of issues and we had to, like, fix stuff. Knowing that this will cut the time down, that's a big selling point.
ADAM BARRETT: Mono repos, it's a huge issue. If you look at some of the notes that just printed out, there's one this is hey, is this your first time? You can go check out a thing. It's not installed globally. You could install it globally, if you want. Instead of typing NPX Nx every time, you could do "Nx." The top one is a sign-up for Nx Cloud.
JASON LENGSTORF: Yeah. It said it was enabled. Um, I can connect. So, I need to click this link, is that right?
ADAM BARRETT: Yeah, if that's clickable.
JASON LENGSTORF: Do I need the dot? It seems like I wouldn't.
ADAM BARRETT: So, yeah, this is basically claiming your workspace. You're going to have to sign up a new account for this. Which may be bad TV for awhile.
JASON LENGSTORF: Let me pull it off so I can generate a password. And, let's see...and so, there's a question, while I'm doing this, Nikki is asking: How is it priced? Is it based on
You hackers! You dirty hackers!
ADAM BARRETT: It's basically on time saved. It calculates, like, okay, how long did this take when it was originally run? Now that it happened in seconds, rather than minutes, I'll add those minutes to "time saved." I think you get 500 hours free and then after that, it starts to cost. I think it's $1 an hour time saved. And that maybe isn't you know, that isn't necessarily developer hours saved. If you were, for example, your CI, if your CI runs this build, it's going to save a lot of time because it'll always be pulling cache and that will count. It's builtin time save.
JASON LENGSTORF: That's a novel model. I don't think I've heard that before.
ADAM BARRETT: I'm not on the Nx Cloud team at Nrwl, but I think they do a lot of cool stuff. So, I think, yeah, all you need to do, the alreadyconnected is in there. The token's been put in there for you because of the URL you used.
JASON LENGSTORF: Do I need to connect it?
ADAM BARRETT: No. That, I think, will have already been done for you.
JASON LENGSTORF: Five hours per month for free.
ADAM BARRETT: I think I said 500. Sorry.
JASON LENGSTORF: If we don't build, it gets turned off? That's also good.
ADAM BARRETT: You still have your local cache, too, I think that still works. So, you're just not saving as much time.
JASON LENGSTORF: Tony, in the chat, is asking, "was that a private token?" Is that something I need to roll, now that I've shown it on stream?
ADAM BARRETT: That's a onetime access token and then it's done.
JASON LENGSTORF: Okay. Got it.
ADAM BARRETT: You even put it in the repo.
JASON LENGSTORF: Oh, it shows up the in repo?
ADAM BARRETT: You've claimed this space so I don't think anyone else can use it. You can add team members, and stuff, at this point. But I think setting it up for the first time is done.
JASON LENGSTORF: Okay, cool.
ADAM BARRETT: Okay. So, now, I guess, we can look at what we've made. So, like I said, we have this concept of there's projects, which are kind of the encompassing thing and there's apps
JASON LENGSTORF: Here's the access token. It's in the repo. It doesn't get "get ignored" or anything?
ADAM BARRETT: I don't think it does.
JASON LENGSTORF: I forgot to do a "get init."
You hackers! You dirty hackers!
ADAM BARRETT: I think Nx should have put a Git file in there.
JASON LENGSTORF: I have my dot file. My whole root directory is in Git. If I don't get "init," this whole directory, VS Code doesn't do it.
This did quite a bit for us. Recommended extensions. Fascinating. Look at that go. I didn't even know these are things. Am I going to regret this? I'm going to install them.
ADAM BARRETT: One of the opinions I wouldn't say Nx, but one of our opinions our official plugins hold is TypeScript. There's going to be a lot of TypeScript going on. Like I said, you've got apps and libs, libs are empty right now. Apps, you've got the Nx plug. And you've got, build in for you, endtoend tests, and those are done with Cypress. They get included so it's easy to run your endtoend tests whenever you want.
If you didn't realize it, your endtoend tests are another app you're running against your app, now you do.
We've got the you can actually start this up, if you want. So, you would run the command all your commands are going to be from the root project. You'll stay at the root of the whatever we named our org.
JASON LENGSTORF: Let me install Nx.
ADAM BARRETT: It should already be installed, actually. It'll be in the package JSON.
You want to do it globally? Sure. Shave off three characters every command.
JASON LENGSTORF: Why not? Give it a try. I've just installed Nx.10.3.3. And now, there it is. Anymore there's a whole bunch of help.
ADAM BARRETT: Yeah. So, um, you can run, in this case, Nx Serve, and then the name of your app and so this is, like, which app am I going to sort of start? This is sort of the development server kind of thing. So, Nx Serve, Next.Blog. And this should start up. The generator generating this app and put in this extra stuff. Ohhhh, what's all that jazz? It puts in this stuff for you that's just, like, a Splash page or whatever. You can see that if you click on there.
JASON LENGSTORF: I have so many browser windows open, so I have to open each of these separately.
ADAM BARRETT: Nothing special here, so far. This is just what you've got. Um...and so, yeah, I we can look at the pages code and we can start changing things.
JASON LENGSTORF: Yeah. So, a couple questions, in the chat here, and one from me. The first one is from Paul and he's asking: Is it possible to opt for, like, Flow instead of TypeScript or is that a builtin?
ADAM BARRETT: So, it's based on the plugin. Basically, plugins have schematics, which is a concept from the Angular world. Nx sort of first started out as Angular CLI extension, ng whatever. After a few years of building that out, it become a little more generic. It was like, hey, we can do that for anything. They introduced React and it's all kind of done through these things called schematics. If you think about them as code generators, plus code mods together. And so, our Next.js plugin provides schematics and builders and builders are things that run stuff. The schematics are what generate this and they include the TypeScript and stuff. TypeScript is also, I think, how Nx handles the imports from libraries to apps. So, though you can make a library and you can make it JS and you can make an app and make it JS only, I think you're sort of at least on the outer rim stuck with TypeScript.
I don't know any plugin that gives you a Flow. You could make a schematic, you could make a plugin that does that. I don't know of any that exist now. That was a long explanation, but I hope that made sense why I had to make it so long. I didn't want to just say, "no."
JASON LENGSTORF: My next question was actually going to be, like, things like Cypress just showed up here. If I wanted to use something else I like (Indiscernible) and Cypress so I'm happy these were the defaults. If we were using something else, we would just not use the Jest schematic and put in something else?
ADAM BARRETT: Some schematics basically call other schematics. Our Next.js schematic calls the Cypress schematic. It calls Jest to throw in Jest tests, too. You would have to write your own plugin or schematic. There's a thing called "workspace schematics." This isn't something we want to share. We just want our components to always be generated like this. We want our libraries to be generated like this. We want to be able to set up I don't know an alternative test, endtoend, but we want something with Puppeteer.
JASON LENGSTORF: This reminds me a little bit now granted, take this with a grain of salt because I don't know Angular that well, but it reminds me of the Angular CI.
ADAM BARRETT: It was a dropin replacement for Angular CI. It does more. But, when you start up an Angular app, for example, using Nx, it'll say "do you want to use the Angular CLI or the Nx CLI?" You want to choose the Nx CLI. There was a marketing thing to make the CLI faster by switching it to Nx. I think that's because of the computation caching. It's better in every way.
JASON LENGSTORF: This is as far as I can tell, this is not similar to Gatsby Cloud. This is for managing repos. It's going to be a different use case. You could even use them together.
ADAM BARRETT: For sure. I don't think there's a Gatsby plugin that I know about, but there might be a community one. There's not a Nrwl one. But a community one.
JASON LENGSTORF: If we do we can just knock this whole thing out and we'll say...hi, chat. And then we can drop all of this out. And let's just get down to here's our style page.
ADAM BARRETT: Yes.
JASON LENGSTORF: Okay. So, I have this still running, right. So, it compiled. So, we should see, over here...there we go! All right.
ADAM BARRETT: Because it's an Nx app, it'll have fast refresh. This isn't Nx doing anything. That's all Next.js giving you this sweet, little setup. Let's do something Nxy right now, I guess. Let's what would be the best thing to do? Let's make a library, like, a component library. So, the command, I guess, to generate if you can open up another one
JASON LENGSTORF: Yeah, we can
ADAM BARRETT: You don't necessarily have to stop that one.
JASON LENGSTORF: Oh, okay. I'll get that one going and we'll just open up a new tab.
ADAM BARRETT: Whatever. Whatever you'd like. What was I going to do? I was going to say, you should make a component. If you do, like um
JASON LENGSTORF: Why does it think I'm in a different 14, 15. You should be in 14. I got to figure out why those defaults get weird on me. Okay, I'm ready.
ADAM BARRETT: So, to generate a library, we have this "generate" commands. It would be "Nx generate," or you can shorten it to "g." We're going to do a React component library. This is kind of funny syntax, there's a VS Code GUI code. You'll do "@Nrwl/react" it'll be ": I guess we'll make a lib first and then add components to it. I guess we can hit that.
JASON LENGSTORF: Okay.
ADAM BARRETT: And now it should ask, "what do you want to call this library?" We can call it "components."
JASON LENGSTORF: Yeah. Works for me.
ADAM BARRETT: Whichever you like. I mean, we already went with
JASON LENGSTORF: Chaos! Chaos!
ADAM BARRETT: We'll have a crazy bug problem.
JASON LENGSTORF: I can do it again. (Laughter).
ADAM BARRETT: We'll find out. If you look in your directory structure, under your libs, you should have a components library.
JASON LENGSTORF: Annnnddd, there it is!
ADAM BARRETT: It's got Babel in there, ESLint. All this stuff you decided you want. And this component thing. We might want to put individual components in this, instead. So, the command, then, is, um...Nxg@Nrwlreactcomponent. I wonder if it lets you I wonder if it'll ask you if you hit "enter," will it is you what project you want to add that to?
JASON LENGSTORF: What is the name of the project?
"Components." Let's make a button.
JASON LENGSTORF: I guess we get to choose for each one.
ADAM BARRETT: That's intriguing to me, too. I almost wonder if I've messed something up.
JASON LENGSTORF: Now what's happened? Now we have a button. Here's our button. We're importing our styles. We get a style button. Welcome to button. But, that's good. So then, out here, it didn't affect this. But
ADAM BARRETT: You could probably even delete those three "components, components, components, one."
JASON LENGSTORF: That was a very "Steve Ballmer."
We when we initialized this, we are exporting our component with the export star from components. That was not the button I meant to hit. If we delete this one, we're cooking with a component library. So I can get rid of like you said, we'll drop this one out. Yes. Bye!
ADAM BARRETT: This is why you're so much better at it than I am. You told everyone what I was thinking but you actually said it coherently.
JASON LENGSTORF: This is great! We've got this button. So, let's make this button a button. Style button, style div. Why don't we make this a style button? And then we can "click me." Let's not worry about it. We'll make it one of
ADAM BARRETT: Leave that for the React tutorial thing.
So, now, to put that in the app, if you go to the page the index page of that app that we were working on the blog thing or whatever, the way you'd import this is you do import I don't know if we did the default export.
JASON LENGSTORF: It's not.
ADAM BARRETT: From this is where you would do "@yourorgname."
JASON LENGSTORF: What did we call this? "Nx demo."
ADAM BARRETT: And then the library name, which was "/components."
JASON LENGSTORF: It automatically picks up our org name are these the aliases Nx handles for us?
ADAM BARRETT: Yes. All this really did, at some point when we set it up, when we generated that library
JASON LENGSTORF: Oops, I broke it.
ADAM BARRETT: Was it actually Nx Demo?
JASON LENGSTORF: Export "button." Import "button." Where should I check?
ADAM BARRETT: If we go into workspace.json, where a lot of this stuff gets configured. So, that's the app and on the different projects. If you go to the very bottom, these are basically the different commands you can do for each of your libraries. Maybe not the right place.
JASON LENGSTORF: YAML, YAML, YAML, JSON, JSON!
ADAM BARRETT: You could find the library name. If you have it in folders, it'll be dashed, so this is a good place to find a library name. But I thought the org name was in here. I wonder if we got that wrong? There's also a TypeScript file
JASON LENGSTORF: The config?
ADAM BARRETT: Maybe. So, you can see in there
JASON LENGSTORF: Nx Demo Components. I wonder if I need to restart because we added that after this was running?
ADAM BARRETT: Sure. Maybe.
JASON LENGSTORF: It just may not have picked up that alias for us.
ADAM BARRETT: I do find when you change the toplevel TypeScript, a restart is usually a good idea.
JASON LENGSTORF: Give it a refresh. There it is!
ADAM BARRETT: Huzzah! It is pink by default!
JASON LENGSTORF: It sure is.
Let's make that readable.
So, now we can we're in our component library. And this is actually really exciting because, keep in mind what's happening right now, we are importing from a package that will eventually be published, but I'm making edits in it so if I make this display block and we'll set it as, like, "margin auto." And a width of, let's say, 300 pixels. All of that's liveupdating, despite we're across two separate libraries, so that's slick!
ADAM BARRETT: Yeah. And it makes it a pretty good way to deal with things. But if I might suggest something that might be even better, in my opinion. When I work, generally we're working on large teams, large apps. Even when I'm doing it by myself, I like to keep things really isolated and abstracted away. So, when I'm working on a UI component, I don't want to go in the app and go two levels deep and find my state there and work on the component there. I like to use something like Storybook. And so, if you would indulge me, you can do "Nx List" and this will sort of give you so, there's it's kind of a long list. But up at the top, they'll be the ones you have installed already.
JASON LENGSTORF: Oh, Community Plugins. Wow! I have Cypress, Jest, Linter.
ADAM BARRETT: These are the official ones because they're Nrwl. And there are community plugins, which are what other people make.
JASON LENGSTORF: There's a serverless one, that's kind of fun.
ADAM BARRETT: Nx is not limited to just the frontend, either.
JASON LENGSTORF: (Indiscernible) and Vue, if that's your thing. Svelte. Stencil. Go. Oh, nice.
ADAM BARRETT: Not that I had anything to do with that plugin, Svelte's my jam.
JASON LENGSTORF: I thought I copy/pasted it.
ADAM BARRETT: This is how Nx handles all the dependencies and is what enables it to do all the cool stuff like computation caching. Right from the root, you do NPM or YARN, install. And in this case, we want to NPM, install, @Nrwl/storybook.
So, while that goes and then basically what I'm going to do, here, is I'm going to give a command and then we're going to struggle our way through writing a story.
JASON LENGSTORF: I have not done much, if any, Storybook dev.
ADAM BARRETT: I have one up, nearby, to look at, in case. It just changed recently. They have a new version, it's a much simpler syntax. I forget how to write stories.
JASON LENGSTORF: Oh, wait! Here we go!
ADAM BARRETT: There's a flag you can add that makes it publishable. Honestly, you got to wonder if you want to make it publishable. Is this a component library that you want live? And maybe it is and you want to put your storybook up. Or maybe this is internal. You've got the shared UI team that works in this library and the auth team that works in this. Just depends on how you want to build stuff.
JASON LENGSTORF: Three fullblown apps here. Endtoend testing is an app that will test our site here. Our site is, you know, pretty straightforward right now. It's just our index page. But then this index page is able to pull custom components out of our component library and all of that was done, you know we've been at this for about 45 minutes now, including a bunch of chitchat and we've been able to get this running. We're editing one library and seeing it liveload in another.
What should I do next?
ADAM BARRETT: It's "Nx G" for "generate." And "@Nrwl/storybook." I think it's ":Configuration." You're going to use the React one.
JASON LENGSTORF: Cypress, we probably do want that?
ADAM BARRETT: Yes, because I think it's neat.
JASON LENGSTORF: Default path, it should have a required property name.
ADAM BARRETT: Oh! I guess we'll have to give it components there. Sorry, just write the word, "components" there, I think.
JASON LENGSTORF: Is this going to attach this to components?
ADAM BARRETT: This is going to set up Storybook just for our components library. It's also going to set up a Storybook sort of on the toplevel, too. Everybody can share this configuration for Storybook and each library can have separate configurations, if they need.
The Cypress thing is pretty cool. What it's going to add is some Cypress stuff. It's going to add an endtoend specifically for components. It'll start Storybook and hit the iframe components and hit the individual pages. Hopefully we'll have time to show that.
JASON LENGSTORF: Yeah. Very cool. All right. So, this is you know, and, like for anyone looking at this going, oh, wow, this is taking awhile to install, uh, maybe. But imagine how long this would take to set up. I think like, this is really, really interesting. Here's our Storybook. So, we've got stuff coming in here. Did it pull in there's our stories. Cool.
ADAM BARRETT: So the only thing we are going to have to and we might as well get going. We're going to have to write a story. By default, it looks for anything in the lib folder that has "whatever.stories.tsx or JSX. "We need to create "button.stories.tsx."
JASON LENGSTORF: It's in the�.Storybook?
ADAM BARRETT: Let's do that right now.
JASON LENGSTORF: I'm going to switch it to an MDX.
ADAM BARRETT: We're going to be typing a lot more.
JASON LENGSTORF: I'm ready.
ADAM BARRETT: What we need to do, for MDX, is we need to install the maybe it comes with it. We need to install the docs. Can you go to the very toplevel? There should be a�.storybook directory. It pulls this in. I think we need to add Storybook we can get rid of add on knobs. We want to add "@storybook controls." You're great at this.
JASON LENGSTORF: Like that?
ADAM BARRETT: Yeah. I think we're going to have to install those libraries. I don't think we have them in the package JSON yet.
JASON LENGSTORF: Got it. I'm in the root of the package.
ADAM BARRETT: Root of everything.
JASON LENGSTORF: Right. Yes. Root of the whole repo. And I'm installing Storybook controls and Storybook docs.
ADAM BARRETT: While that builds
JASON LENGSTORF: No!! Storybooks docs is not in the directory.
ADAM BARRETT: Did I get that name wrong? Good radio, sorry. Storybook docs maybe the chat goes.
JASON LENGSTORF: Is it singular?
ADAM BARRETT: Uhhhh...maybe? Sorry about this.
JASON LENGSTORF: There's an "addon docs."
ADAM BARRETT: Oh, there's an addon controls and addon docs. So, it's "addon dash" for both of those.
JASON LENGSTORF: So, let's fix that. Addon. Addon. Here we go.
ADAM BARRETT: We can start writing that MDX story now.
JASON LENGSTORF: Okay. I'm ready.
ADAM BARRETT: So okay, so we start with an import usually. Let me just find a version of it so I'm not making it up as I go. So, we want to, like, import meta and story and Canvas, all with capitals, from Storybook Addon Blocks.
JASON LENGSTORF: We're importing I only caught "canvas."
ADAM BARRETT: Meta, Canvas and Story. This is so much typing, I apologize.
JASON LENGSTORF: Oh, no, it's all good.
ADAM BARRETT: From @storybook/addondocs/blocks.
JASON LENGSTORF: We're digging in?
ADAM BARRETT: Yeah.
JASON LENGSTORF: Okay.
ADAM BARRETT: And then we can just import our button, I guess, from right next to us, just with a relative import. Man, I hope this works after all this. (Laughter). Uhhh just be "button" if we're in the right place, right?
JASON LENGSTORF: Should be. Button it'll work.
ADAM BARRETT: Maybe MDX is like, hey, I don't know what's going on? Anyway and then, we do "meta component" and we give it a title and a component prop. So, the type is whatever we want, our button.
JASON LENGSTORF: Button. Component
ADAM BARRETT: Component is where we'll actually pass the button as a prop. Yep. And then we can do a Canvas component with a Story child.
JASON LENGSTORF: Oohhhh, okay.
ADAM BARRETT: You can keep that text, too.
JASON LENGSTORF: Oh, Story child. Canvas. Story.
ADAM BARRETT: This is brave. And in the Story, it'll have some props. It'll have a name and well, and you can pass its args, but maybe let's not worry about args right now. A name a fine. Anything, I guess. Button or primary is the one that comes by default, I think. These are the various different stories you're going to have about "button." And then the child, here, is a function as a component, sort of thing.
JASON LENGSTORF: Render props thing?
ADAM BARRETT: Render props. Thank you. So, here's where you would get args in the top of that function and then you would put the button and you know whatever args you were going to pass here. That's for using things like the done trolls, which let you, like, change it in the sandbox, but I think we can just return "button."
JASON LENGSTORF: Okay. So, we'll just return "button." And down here, I can add whatever I want, right?
ADAM BARRETT: And above it and all around. I think the imports can go wherever they want, too.
JASON LENGSTORF: Let's try it. Let's see what happens.
ADAM BARRETT: Hopefully this works. Honestly, I'm not sure if it will because I feel like I I missed something important, but maybe the chat can point out it, too. (Laughter). This is set up by Webpack, I'm almost 100% sure because of the Webpack config there.
JASON LENGSTORF: That seems reasonable to assume.
ADAM BARRETT: We go "Nx Storybook," he thinks. Storybook components. So, we're basically starting I think that doesn't seem right.
JASON LENGSTORF: Let's see what happens! Nope! Okay. It doesn't like that one. Do I need to do, like, a serve or a run or something like that?
ADAM BARRETT: It is something.
JASON LENGSTORF: Cannot find module Nx Cloud. We installed that.
ADAM BARRETT: That's interesting. Uhhhh, not sure about that one. Cannot find maybe try and install that?
JASON LENGSTORF: Let's see...did we lose Nx Cloud, there it is.
ADAM BARRETT: We're good. We're gold. Let me find out what the command to start that Storybook is.
JASON LENGSTORF: Okay.
ADAM BARRETT: It is, uhhhh...oh, come on, brain! Oh, it should be "Nx Storybook Components," just like I thought.
JASON LENGSTORF: I'm wondering if maybe I think what happened is when we ran it last time, it corrupted the Node modules. It seems to be working now.
ADAM BARRETT: That seems like a big bug.
JASON LENGSTORF: That is one of my leastfavorite bugs in NPM. And I don't know if it's with the tooling, with NPM, something else because we're now five layers deep in context. But for whatever reason, every once in awhile, installing something with corrupt the NPM cache and you have to reinstall and all of a sudden, everything works.
So, let's give this a try and let's see what happens. We have oh, my goodness! Look at it go! Now, this is
ADAM BARRETT: Docs tab.
JASON LENGSTORF: Holy crap! So, this is really cool! Honestly, I don't know just in general, I don't know anything about Storybook, so this is really cool that we can do contextual docs.
ADAM BARRETT: You can add different stories for different states. There's this controls idea. Do you want to explore controls or move on to something else?
JASON LENGSTORF: I have what do you think, chat? Do you want to learn about Storybook or learn more about Nx? You have five seconds to respond. (Laughter). I've got more Nx.
ADAM BARRETT: Nx, okay. Let's abandon this thing. Let's go to the Cypress part of this, though, which I actually think is pretty cool. So, it's set up a Cypress app for you, in endtoend controls. We just need to add a test for our buttons component and we can add the most ridiculous of tests, just for fun, just to show it run. Oh, so we need to add a directory. Sorry, I didn't realize this didn't come automatically. We need an integration directory.
JASON LENGSTORF: What's the file?
ADAM BARRETT: Whatever we want it to be: Button.text.ts.
JASON LENGSTORF: So, this is different from this?
ADAM BARRETT: That's interesting, right? There's Jest tests, which we could run if you do "Nx Test Controls," it'll run all the Jest tests it can find.
JASON LENGSTORF: In components?
ADAM BARRETT: Components. I don't think they test anything out of the box.
JASON LENGSTORF: Is an element on the page, right? Which works. Good.
ADAM BARRETT: An interesting thing, there, is it took 10 seconds, right? If you run it again, it should be even fasting, presumably, because of the caching I was talking about before.
JASON LENGSTORF: Look at it go!
ADAM BARRETT: That's a small, but ridiculous thing of the caching.
JASON LENGSTORF: So if I do change this, let's say we'll say this up to to accept some text. So, the text would be a string if I'm remembering how TypeScript works. And we'll be able to say oh, boy, here we go. Text=clickme. Here we go. Can I write TypeScript? Find out this week on Learn With Jason!
ADAM BARRETT: Apparently, you're fine.
JASON LENGSTORF: That should have worked so now if I
ADAM BARRETT: It should take 10 seconds again, or whatever, because now it's going to actually run the test.
JASON LENGSTORF: Seven seconds. Not as fast. If I rerun it, cache. Boom, done. That's really slick. That's really nice that that just works.
Okay. So, then, we need to write our spec for Cypress. So, we're up here. I've got my button spec.
Holy buckets! Did that just work?
JASON LENGSTORF: It does work, Tony. (Laughter). What happens next?
ADAM BARRETT: It's Cypress, so you write "describe" and an (Indiscernible). I don't know if you know what I mean by that.
JASON LENGSTORF: This is as much as I know. Now I'm lost.
ADAM BARRETT: Do "cy.get" and we'll do in the string button. And then do just, like, "button." This is going "cy.get" is going to look for a button element. This Cypress test doesn't know anything about React. Apparently it can, but I'm not getting into that. I'm looking for a button and I'm going to assert it exists, so you should do ".should" and a string that is "exist." One thing that we I don't know what all the TypeScript trouble is, but, um
JASON LENGSTORF: These, I think
ADAM BARRETT: We can ignore it, hopefully.
JASON LENGSTORF: What don't you like? Show me my problems?
ADAM BARRETT: I don't know, actually. It might um...that's interesting, too. Um...
JASON LENGSTORF: (Indiscernible) is not assignable to type string? I need do it like this, right?
ADAM BARRETT: Oh, yeah. Sorry. Thanks.
JASON LENGSTORF: Okay.
ADAM BARRETT: Same with the "it," too. It's a string and a function. I guess we couldn't ignore it. Bazinga!
We actually need to go to the page, first. There's a "before each" that you should put a function in the describe, but before the "it." Actually, it doesn't matter where you put it.
JASON LENGSTORF: Before each...and that's just a regular function, right?
ADAM BARRETT: You can do "cy.visit" and then this takes a string and this is the fun part. So, we're going to do sort of a string that's a path going from root, so "/iframe.html."
JASON LENGSTORF: Just like that?
ADAM BARRETT: And then with a query param, a question mark: ID=. If you look at the browser, at the top of the URL, it'll say "button, dash, dash, primary. So that's our ID.
JASON LENGSTORF: Like that?
ADAM BARRETT: Yeah. So that should work. That's all I have in my little test at home. I don't know why, with TypeScript, it's bad. You should be able to go "Nx, e2e, componentse2e" because you're running the app. And I think his brain says, is this going to work? What this is going to do assuming it works is it's going to start up Storybook oh, did something not like it?
JASON LENGSTORF: It's a warning.
ADAM BARRETT: Cypress is going to go to the Storybook page, it's going to run its tests and then it's going to, um, display the results in the terminal, essentially.
JASON LENGSTORF: Opening Cypress. Here we go.
ADAM BARRETT: You can also just like every other test thing, just like the Jest test, you can watch it in Watch Mode, which can be nice sometimes. Looks like it passed.
Another cool thing, it recorded a video of it, too. That's a setting you can set in the endtoend test. That passed, by the way, the is pretty cool.
JASON LENGSTORF: Jeez!
ADAM BARRETT: If you go to "/dist, Cypress," there's basically a video of that test. If it was failing, for some reason and that's all Cypress, that has nothing to do with Nx, it's just cool.
It's just cool that Cypress does that out of the box. You can turn that off, too, if you don't like large video files filling up your disk folder. It's a config videos true or videos false.
JASON LENGSTORF: Uhhuh. Yeah, and we can see it run. So that's
ADAM BARRETT: So you can see what went wrong.
Your app has endtoend tests, which basically work the same way. These would be just for what's in the components library. The app would do your happy path or whatever you're going to test.
JASON LENGSTORF: So these don't get do these get so, this one got autosetup, which is pretty cool. It's already got, like, the visit is set up. It says it should display. This one is actually going to fail because
ADAM BARRETT: Right, email and all that stuff.
JASON LENGSTORF: So with the default, before I edited things, this would worked so we need to rewrite this. We changed the home page to be, "hi, chat." So let's make it say that. Where's "get greeting"?
ADAM BARRETT: That's a Cypress thing. You can make a login function or a whatever function so you don't have to do these things repetitively.
JASON LENGSTORF: In this particular case, that actually wouldn't work for what we've done so we have to (Indiscernible). But that means we can drop this out and I can do the exact same thing, except now, I'm going to do "next blog, Nx, e2e" and this should be faster because I've installed the binary. It should grab all this for us. It's going to start our app. There it goes.
ADAM BARRETT: Um, I guess that kind of also after this is run, we should probably do the thing about static sites, too. We should probably run an export one.
JASON LENGSTORF: Yeah, yeah, yeah.
ADAM BARRETT: It looks like it's going to fail.
JASON LENGSTORF: Oh, no, I screwed something up. It failed because I did the wrong thing. H1 within the element, but H2 never did? I don't believe you.
ADAM BARRETT: So, what's on that page?
JASON LENGSTORF: I don't know, let's go watch the video. (Laughter). Let's go to the tape. Okay, so, we get here...there's my H2. Don't you lie to me! Oohhh, rightclick the video. Nice! That's slick.
ADAM BARRETT: I should have mentioned that before.
JASON LENGSTORF: Video and then "reveal finder." That would have been faster. Nice. Good tip. Thank you.
Capital C, in Chat, that is correct. There we go. Let's do that. Run it one more time. This is going to pass. Like, I love what I love is, I would legit never, never set up tests for, like, a website. Like, I would just be like, ah, I'll just look at it. And so, the fact that this is done makes me want to use it, but, like, the extra time I would have to set up to, like, configure this all of my time, no way am I going to take that time. What I like about this is it's doing something I love you may have heard me repeat this at nauseam. It's making it easier than not doing them. I think that's a really good takeaway from here. It's well thought out and we're seeing the you know, the chat is showing appreciation for this and you can just see, like, how quickly this sets up good, like it also just sets up good, like, repo hygiene. It's wellstructured, we know where things are going to go, we know how to run things. If we move between teams, things are going to feel similar, like, you know, it's still a codebase, like, when we start writing code, we're going to have our own opinions, our own approaches, but the boilerplate will at least feel familiar.
ADAM BARRETT: Do you want to Nx Blog Export?
JASON LENGSTORF: Absolutely, I do. And we called this "Next Blog Nx."
ADAM BARRETT: Nx can't take any credit for this, this is basically Next.js running Export which is cool.
JASON LENGSTORF: I broke things. Oh, that's fine. This is good. This is the code doing what it's supposed to do because what I tried to do was make this required, but it's not required. It is optional. So, to do that, I do this?
ADAM BARRETT: After "text."
JASON LENGSTORF: You're almost at 100% on your TypeScript. I retained so little. (Laughter). Okay. So, now that I've actually made this optional, I believe it will have stopped yelling at me for that, at least. And, this so, here's a question: VS Code is yelling at me because this component doesn't exist yet. Is that something I need to configure?
ADAM BARRETT: There was a popup earlier, that asked you if you want to
JASON LENGSTORF: Yeah, I want to allow it. Now it still doesn't like this this component.
ADAM BARRETT: In the pages? Hmmmm.
JASON LENGSTORF: Let's go up to the app here. Pages, here. Why don't you like
ADAM BARRETT: Yeah, why don't you like that? If you looked on that import sorry, the that TypeScript file that we looked at before, that was there, "@Nx/component." Maybe you just need to restart TypeScript?
JASON LENGSTORF: I wonder if I need to turn on the TS Lint.
ADAM BARRETT: I don't remember turning anything on, myself. If you did "command+p," and type in "restart TypeScript, I think it is. Restart oh that'll take a few seconds to restart. I often when I've changed something in a TypeScript config file, I've needed to restart it to that TS Lint is deprecated. That is true. It's going to be a long process though, I wouldn't worry about getting off TSLint in a hurry.
JASON LENGSTORF: Appears to have fixed it. Look at it cooperating! Okay.
ADAM BARRETT: So, we exported yeah, we exported and it should be in "dist." And then it will be, like, the "dist" folder, the "apps" and then whatever we called it?
JASON LENGSTORF: Exported?
ADAM BARRETT: Yeah. You could serve that exported directory or pass it up to Netlify.
JASON LENGSTORF: We can go to app, Netlify, drop. And, let's just let's see let's use that tip I just learned: Reveal and finder. We've got is it public?
ADAM BARRETT: Exported, actually.
JASON LENGSTORF: Here's exported. So I'm just going to drop that right in there.
ADAM BARRETT: I'm going to be perfectly honest, I don't know what the other directories do.
JASON LENGSTORF: Look at that, that is slick! This is great. There's some stuff you can do so, the is a Next static output. They have the hybrid mode so we can hook it up to Next on Netlify, if we wanted to have the API layouts. I can see, that's a Nextspecific episode. I feel like what we're doing here we're trying to look at how we set these up.
But, yeah. What a what a fantastic amount of progress that we were able to make in the the 80 minutes that we've been working on this. You know, having functional Jest tests, having a Storybook with endtoend tests, having an app importing an external library while we div, and Jest and Cypress endtoend tests. I would have expected if you told me I needed to set up a new boilerplate for a team, I would have scoped weeks for what we just did in less than two hours. So, that is that is a serious powerup.
There's a question about, uh, NPM Publish. So, let's see well, there's two questions.
ADAM BARRETT: You don't need to publish the components library if you're just pushing out the app. If the app's the only thing importing, it's from the local library. If you wanted to push the components library, you could. You could set it up as publishing. It adds a package JSON into it, basically, or its little build factory. I actually don't really know how that works. (Laughter).
JASON LENGSTORF: Here is an here is an article that we'll include in the show notes. (Laughter).
ADAM BARRETT: But because I've never really had to do it, to be honest, because I'm just concerned with the app, usually. But you can do it.
JASON LENGSTORF: (Indiscernible) and Vue, there are schematics for it.
ADAM BARRETT: Plugins, technically. There is Vue community plugins for Vue
JASON LENGSTORF: How do I find the list?
ADAM BARRETT: Nx List. It's probably on that community page, as well, if you go to the community page.
JASON LENGSTORF: There's a Nuxt and a Vue. But, yeah. That's really slick. One thing that's interesting here, the fact we can have these isolated libraries without having to publish them, that is excellent because one of the things I think is really stressful for a lot of teams is I want that abstraction of something I have this whole spiel I'll go on for optimization and why we need to create really isolated and clear boundaries between codebases. If you don't force separation, it will eventually turn into soup. What I like about this is it lets me have my cake and eat it, too. I don't need to publish a repo. I don't need to publish a module, which means I don't have to figure out private module hosting and how do we do security around that. But I still get that abstraction of building it as a module and that, to me, is a huge win especially for teams that are working on, like, internal private codebases because, like, at IBM, we wrangled a lot of private NPM registries because everybody needed that modularity but we didn't have a way to make these things installable without that registry.
ADAM BARRETT: The easier you make it for people to breakout code, the more they'll do it and that's great. You know what I mean? It's so easy. Hey, we're doing the same thing in these three different libraries, let's break that into a fourth library. So easy to do.
If you had them in three separate you know NPM packages that had their separate CI, that would be a big pain to take that little piece of code out and share it.
JASON LENGSTORF: Right. If I'm setting up my own Jest tests and Cypress tests and repo config, I'm going to leave it in the app. So, this is yeah, I can see how this cuts down on copy pasta. This cuts down on people taking shortcuts, duplicating code across codebases because it is actually easier to abstract it than to deal with the copypaste and maintaining in three separate apps because you didn't want to abstract it into a library. That's really, really nice.
With that, we're coming to a close here. I think this was wonderful. I feel like we got some really powerful tools, moving forward, for setting up our libraries.
Where should someone go, if they want to take this forward?
ADAM BARRETT: I definitely think go to the Nx.dev, the easiest website to remember in the world. There's links to tutorials, links to other resources. There's blog posts about things. There's a "getting started" tutorial you can kind of walk through. Lots of different videos made by different people. There's an Egghead series right now that walks you through how to do development with Nx.
JASON LENGSTORF: Is it let's see
ADAM BARRETT: That's not the one I was thinking about.
JASON LENGSTORF: Let's dive in here. Let's find it. Is it the one from (Indiscernible)?
ADAM BARRETT: Yeah, I think so.
JASON LENGSTORF: I don't know what I'm doing to your name. I'm so sorry. Here's a whole playlist on it.
ADAM BARRETT: Yeah.
JASON LENGSTORF: That's a good one.
ADAM BARRETT: Scale React development within Nx. That's the one.
JASON LENGSTORF: We'll also throw this into the show notes.
Man, cool. Very cool. So, what if people want to followup with you. Let me drop your Twitter link.
ADAM BARRETT: That's mostly it. You can check out my GitHub. I have a nickname, Big AB.
Also, just to be rude and plug myself on something else, I'm a host of a podcast, "Of Dice and Men." You can go to ofdiceandmen.ca. It's three guys who talk about board games.
JASON LENGSTORF: I'm into it. I love stuff like this. I think it's great. I say that as somebody who occasionally sets up a camera in my kitchen and makes dinner on YouTube. (Laughter).
ADAM BARRETT: Those are the best ways to get ahold of me. Hopefully I'll do something cool, like a podcast some time.
JASON LENGSTORF: Chat, make sure you go follow Adam across all these different platforms and thank you, again, to our sponsors. We've had live captioning today, by White Coat Captioning. Thank you so much for your help. And that's made possible through the sponsorships of Netlify, Fauna, Auth0.
Also, while you're here, make sure you go and check out the schedule. We've got some really, really fun stuff coming up.
Later this week, David East is coming to the show. We are building a multiplayer soundboard. Part of the show is going to be you, at home, playing sound effects, at the same time, through the same speaker. It's going to be wild and, uh, I'm probably going to have to mute it. Please come hang out. The most of us there, the more chaos there will be and the harder we will laugh together.
Next week, I have Cassie Evans coming on. She's going to teach us how she builds these incrediblycomplex SVG animations. It's going to be superfun. Make sure you show up the that one, as well.
We've got so much fun stuff coming up on the show. Check out the schedule, sign up for the Google Calendar so you get notified.
With that, I think we're going to call this one good.
Adam, thank you one more time for hanging out with us today.
Chat, stay tuned. We're going to raid. We'll see you next time.
Closed captioning and more are made possible by our sponsors: