 Hello, everybody. We'll get started in just a minute here. And my name is Ethan Arawood. I'm a software engineer at Microsoft. Pronouns are he-his. And today my talk is going to be on why you should maintain your type definitions for your JavaScript project. I'll be covering some of the ways that I've gone about it. So I'll be diving into some fun generic work that I got to do. Then I'll talk about some of the ways that you can support your custom type definitions. So to get started, the project and the context for this talk is Fastify. I'm a maintainer. I mainly focus on the TypeScript side of things. But Fastify, as some of you know, is a part of the OpenJS Foundation. And we really want to work to offer the best developer and user experience on all facets. Fastify, like I said, is open source. And it's entirely written in JavaScript. As you can see, it's 97.3% exactly. And the JavaScript chunk of this is the same JavaScript that you'll run when you consume Fastify. It is not compiled. It's not built. The ES6 code that we write is shipped directly to you when you download our package. This is a little bit of what it looks like. This is a JavaScript use of Fastify. So it's a lot like Express. If anyone's familiar with that, you instantiate the server, define some routes, and then kick it off. And though my talk today is not about JavaScript, but about TypeScript. And here at Fastify, we love TypeScript, even though it's not written in TypeScript. And this is because we see the natural progression of projects now, open source projects nowadays, need to support both sides. However, for us, rewriting it in TypeScript is not an option. So instead, we write a type definition file. In all TypeScript projects, even if they're written in TypeScript, we'll need to export some sort of type definition file. I'll take a quick break for HDMI. It's okay. Yeah, no worries. So while we wait, who in this room writes TypeScript already? All right, sweet. So who here is interested in writing TypeScript? Awesome. And has anyone ever written their own TypeScript definition file before? All right, so I see some shaky hands. Okay, should I redo it? All right, no worries. We'll get right back into it. Awesome. All right, so we'll resume from here. The Fastify definition file. I just asked the question, who here has ever written one by hand before? And I saw a couple hands, some shaky hands. And that's because, normally, this file is exported when you run the TypeScript compiler. But when you're writing JavaScript code, you don't get that luxury. So you have to write it yourself. In the recent three-point-something update, they actually did add the ability to export type definitions from JS doc. But that happened really recently, and I don't know a whole lot about it. So we'll kind of just skip over that detail, but just know that TypeScript is working hard to provide this kind of type support, even if your code is in JavaScript. But anyways, when you write a definition file, you need to ship this to your users. So it needs to live somewhere, either in a repository or somewhere else. One of the examples of a somewhere else is something you may be familiar with, definitively typed. This is that classic app types organization that you might NPM installing from. And definitely typed is a good project, except it has a big flaw that turns me away from it. When an API or a library or a module is being developed, it's not necessarily going to also get its definitively typed updates. And many of you may have seen this when a brand new update, especially a major update, comes out for a library, and the maintainers aren't maintaining the definitively typed part of the package. Those types are usually out of date, and the TypeScript users get left behind. But if you ship your types with your repo, within your actual export, then you can sort of control how those get updated and also be more safe of mind that you'll be shipping valid types with every either minor or major release. So that's what we do at Fastify. We ship our type definitions alongside the JavaScript. And as I said before, when you download Fastify, you're getting the handwritten JavaScript code. You're also getting the handwritten TypeScript code. And today we're going to dive into this function at the bottom of the screenshot. It's the only real export from Fastify is this factory function. And this function definition in TypeScript starts out a little bit big, but we'll break it down. First, there is an options object that you pass in, and the function returns a Fastify instance. But it also has this big block of generics. Anyone that has ever done a strictly typed language before will know what these are. And I love generics. I didn't used to. It can tell you that much. But after spending a lot of time with them, they are really powerful. And there's two patterns that I want to highlight today, and that is generic constraints and generic defaults. I don't know if these similar things are in other strictly typed languages, but they are in TypeScript. So let's break this down. First line here, raw server extends raw server base equals raw server default. This is actually implementing both of those patterns that I just said, constraining and defaulting. The constraint here is raw server extends raw server base. Raw server base is a list of types. In this case, it's the HTTP SN2 server types that are given from Node.js. It enforces that this generic is one of these four or an instance of one of these four. And this is important because when you create a facify server, as a library, we need you to give us the types for your server. And at default, which is the second part of us that equals raw server default, is HTTP.server. So by default, facify will return an HTTP Node server. You can, though, enable some things like HTTPS and HTTP2. And the reason why this is important is the request and reply and other properties will differ when you define a different server type. So in this line, the raw request, we have that similar pattern where it's extending something and it's equaling something, except this something now is an expression. As you can see by this blown up code, it is a ternary expression. And we're checking the raw server type again and we're determining what the request definition should be. Should it be an HTTP incoming message or an HTTP2 server request method? And this is that power of generics that I'm trying to highlight in TypeScript, is by just defining the server type, we can infer the remainder of your types. And if you see back on this definition for the function, we're really almost abusing this raw server generic. We're passing it down to almost everything because at the core, we're building an API server, a backend server, so that's really important. And when you get to start looking at some code, a default facify server is this simple. We don't have to actually pass anything if you're just doing with HTTP. And if you're doing HTTP2, all you have to do is pass in that type from the node core and turn on the HTTP2 flag in the facify options, which I want to go down more because the inference doesn't stop at the generic level. When you define a type, this is the facify server options type. Within the property itself, you can use these expressions to determine what it should be. In this case, we're saying is it should be true or should it be false? Now, in facify, the HTTP2 option actually needs to be specified if you are using an HTTP server, but because I couldn't figure out how to make the HTTP2 property optional or not optional on a expression-based way, at least we can enforce the user as passing the right boolean type to this property. So that's that for the generic constraining and defaulting that is available on the facify server part, but I want to dive into one more generic example here and we're going to take a look at this code. Jump down to the bottom of the code byte. You'll see it's server.get and I'm passing in four generic properties. Three of them are defaults. One of them is custom. Also, one of them is the body and we're defining a get route. Why would I ever need to specify the body of a get request? And this is something I realized after building the type system that maybe there's a way to do this better and in fact there is. Named generic parameters. In the JavaScript world, if you ever defined a function that has like eight arguments to it, sometimes it's better to put those all into one object and give them names. You can do the exact same thing in TypeScript with generics. You can make a singular generic object with named properties and then pass in your types or interfaces to those properties. This way, when you're defining a route that has some sort of generic property, like the custom query string, all you have to do is specify the query string. You don't have to specify the body, the params, or any custom headers if they're not being modified. So those are the three generic advanced patterns that we're really making use of in the specified type system, but now we're going to switch gears into how we can support a custom type definition system and everyone's favorite software engineering topic is testing and don't worry, we'll get to your second favorite one, documentation, just a bit. There is this library called TSD. It was originally shipped by Defintely Typed, but they have since deprecated their version of it and a new author, Sam, has created this API and it is wonderful. It's not even in version one yet, but it is one of my favorite parts about doing custom types is you can now write assertion-like tests for your type definitions. So you can actually expect a type to be returned from some call to your API. In this case, we're going back to the HTTP server example, whereby calling the default Facify, we expect an instance with these types. Similarly, if we pass in that HTTP server type, we expect it to be an HTTP to server. And at the bottom line, you can see the expect error line. If you set that property that also had that expression inside it to false, this will error out and we can test our type definition logic. And I think that's incredible because testing is very important, even for type definitions. And this is something I didn't consider before and not a lot of repositories are doing it, but now you can assert that your types are even stronger than they were before. There is a missing feature, features, though, and this is around coverage. And this is something I would love to see develop in the future, is how much of my type definition system am I testing? Similarly, how much of my JavaScript code have I defined type definitions for? These are two big questions in the sort of custom type definition space that I haven't found great answers to but would love for the rest of us to work on or hopefully develop in the future. So documentation, the next piece to this. As much as writing a great type system is fun and all, you have to write docs. But the best way to learn at least a specified type system is by example. And I think the best thing you can do for your users is define as many examples as possible for your type system or, or and I should say, you should include a detailed API guide. It doesn't have to be identical to the guide that you already have for your API, but rather the type definitions themselves. Think about all the generic properties you have. You should tell your user what they should be and how they're going to be used. References to the source code are really important, especially in TypeScript realm. I find myself, I find myself often inspecting the source types and being like, okay, can I see what this should look like? I'm inspecting. Maybe if you have really good API guides, you might not have to provide that, but it's still good to show, hey, this is where you can find the source if you want it. Frequently asked questions goes hand in hand with the examples and tutorials. Oftentimes users will hit a lot of the same problem cases, and if you can answer that for them before they even have to open a new issue, that can decrease the sort of troubles they might have. And then finally, links for contributing. The, the whole type system wouldn't be where it is in contributions from our open source developers. And so, if you're using the types and you find an error, it's best to open an issue or even better yet submit a PR. And making that an easy process by saying, hey, find something wrong, come here and contribute is going to help them a lot instead of them having to dig around your GitHub to try and find out where the repository is or how to open up a valid issue. If you're interested in seeing how the docs are coming along, I'm actually actively working on them right now. There's a PR open on our Facify repository. I have a couple examples up and I'm still working a lot on trying to make this as perfect as possible. The type system that I've defined is not available yet. It's, it's going to be in the next branch and which will be released as version three in 2020. But it's there and you can poke around it. And if you want to use it, you could use MPM link to link it locally and try it out or you could require or import, I guess the next branch directly if you are familiar with doing that. And so the last thing I want to leave you all with is while of this effort, it seems like a lot of work and it is to define your own type system, write a bunch of tests and write documentation for almost supplemental code. Like it's already hard enough that you're defining a library, but then going ahead and defining all these types on top of it, that can, that's a lot of effort. But I find for me, it's important because of like the community and its entirety. TypeScript is growing. There's no doubt about it. So if you want to support this growing massive developers, taking the time to define a strong type system can really help get your library and module from, you know, something small to something way larger than it has been. So with that, I would say thank you for coming to my talk. You can follow me on Twitter or GitHub and please feel free to chat with me anytime here online. Thank you very much.