Transcript from the "Modules & CJS Setup" Lesson
[00:00:00]
>> Next let's talk about modules and interrupt between a modern TypeScript code base and common JS libraries. Node still uses common JS as its defaults module format although modernization efforts are underway, it'll be a long time before we can forget about this older way of writing JavaScript code.
[00:00:24] This is one of the trickiest aspects of getting dependencies to work nicely with your own code. And I have helped seasoned experts overcome the stumbling blocks I'm about to show you. So this is a lot of tips and tricks. They're valuable even for you experienced TypeScript practitioners. The reason we even need to talk about this is because until 2015, there was no standardized module format for JavaScript projects.
[00:00:54] As a result, several community based solutions emerged. We have CommonJS which was popular in the node community, AMD and UMD and these were more popular for browser based apps. Because of all the bundling technology we use for browser based apps, these two types of modules AMD and UMD have sort of gone away, right?
[00:01:21] Very few people write code like that, although sometimes we have these as a compiled target we don't really see that code. Common JS has stuck around and it's largely as I said, because of node, right. Node still consumes common JS libraries. And the core libraries of node are written in this format, so we need to know how to consume them.
[00:01:47] It's important to understand that I'm assuming and I encourage you to only write ES modules these days. You should be writing your own new code, using the new standard. Because that's a good way for you to be forward compatible, right? That your code will have a longer shelf life.
[00:02:09] First, everything you're used to seeing around model imports and exports in the Java script world, also works for TypeScript. So here are a bunch of examples. We can go through them very quickly. But they all work in TypeScript and JavaScript alike. So we've got multiple named imports. We have a default import, a named export a default export, these are re-exports, right?
[00:02:37] We're sort of passing lemon and lime which originate in citrus, into our module and then export them out as if they're our named exports. All of this works in TypeScript. Although it is not super common in JavaScript projects, you're going to see things like this a lot in the TypeScript world, because often this is the solution to common JS entrop.
[00:03:06] So we see up here you have this module called berries. We're getting strawberry and raspberry from it. This would let you get a name space with all of the different berries hanging off of it. There's even a capability to alias a name space re-export, which is something TypeScript does today, and something JavaScript has just added to their draft specification for the language for the year 2021.
[00:03:33] So soon you will start to see this as a feature that you can use broadly across JavaScript. Great so that's stuff that works in JavaScript, also works in TypeScript let's talk about the more interesting thing. What are the special import concerns, you need to know about, as you take on authoring a library or an app in TypeScript?
[00:03:57] Well first, if you're writing code that looks like this, or if If you're looking to write typed code that does the same thing as this, often you can just use this namespace import, right. So when we say I'm gonna grab this namespace fs from this module, if you've ever used this fs module which is one of nodes core modules, it's just a collection of functions hanging off of this fs thing.
[00:04:23] There's like read file sync, right file sync, read JSON, write JSON and the fs is just sort of a way to hold them all together. And we can import it just like this. Or you could import using a named imports like specific things from fs. Almost all common JS interrupt can be handled in this way.
[00:04:48] But there's one case that will really throw you for a loop if you encounter it and that's the case where whatever the module is exporting as its single export, it is not something that looks like a namespace. So let's look at this case here, where we have a function and effectively in our common JS code, we would see something like this, right?
[00:05:15] module.exports equal In this whole function. Now we can't represent a function as a namespace, and by the way this is the equivalent code in TypeScript to accomplish this here. So if we try to consume it in this way, we're going to get an error message. And this says that if we wish to use ACMA script, imports and exports with this module, we have to turn a compiler flag on called esModuleInterop.
[00:05:45] And this error message is telling the truth. But what it's not telling us, is that there's an alternate solution to this problem and that is, for us to use an import that is not aligned with the ACMA script standard. And this will save us from having to turn this flag on.
[00:06:04] Why would I want to leave this flag off? Well, this is what I call a viral option. So viral options like es module and allowsSyntheticDefaultImports, if those are necessary, if you have to turn those on in order to make your types work, everyone who consumes your types will also have to turn them on.
[00:06:28] So particularly when I'm writing library code, I jumped through some serious hoops to make sure that I leave these off. And that means that if you're using one of my libraries, it's your decision whether you want to enable these or not. I don't want to force that decision on everyone who uses my code.
[00:06:48] So I call these viral options because when one library enables them, anyone who consumes it also has to turn it on. Thankfully, we have another solution to this problem, right. And that's the non ECMO script import. Here is how it works. So we still have this code here remains the same as when we last saw it.
[00:07:10] But here is the key thing, the key idea. This looks kind of like a common JS Import. But one words different, right? We're used to saying seeing const createbanana = require(" /fruits'), but we see import instead of const. This is what makes it kind of a unique thing, right?
[00:07:32] It's the imports used to be this way in very old versions of TypeScript, and you can still use them. And you can see the type information still comes along for the ride here. So given that in this situation we have to choose between a bunch of non ideal possibilities either forcing consumers of our code to turn some compiler option on, or doing this, I would sooner do this because I like the idea as a library author, I think it's part of my job to like absorb some of that pain for downstream consumers of my code.
[00:08:09] So I will almost always do something like this compared to forcing people to change their compiler settings. If we look at what this code compiles out to, thankfully, it's exactly what we would want. We can see module.exports = createbanana. And if we look up, that's what we were trying to accomplish here.
[00:08:36] And here, we can see that this is required, just the way we would hope to see common Jas code, if we have written it in regular JavaScript, right. It's just consuming the entire module as a single thing, invoking it directly because it's a function, we're good to go.
[00:08:58] Just on the topic of making sure your types don't require special compiler options, a quick reminder that VS code will download type information in the background as it sees users consuming a library. And there's a possibility that even if they're not depending on your library's types like directly, they'll start to see some autocomplete information in their authoring environment.
[00:09:26] So it's important especially if you're doing open source work, it's important to be good citizen. Make sure that if you publish types, those types of work. And make sure that you don't spread those viral options, and put that burden on your consumers cuz it may be a very tricky thing for them to straighten out, if they start to see red squiggles under their code cuz TypeScript is unhappy.
[00:09:52] Finally, I wanna talk about importing things that are not JavaScript modules. So if anyone in the class uses web pack or parcel or snow pack, anything like that, the way these bundlers work, is your JavaScript modules occasionally will point to non JavaScript things, that need to sort of be packaged in the same layer of assets as their respective JavaScript modules.
[00:10:22] So here let's imagine we have a react component that needs an image file, well, you could import an image like this. Here's a link to the Webpack documentation where I copied this line from. Now TypeScript is not happy with this by default. It's saying, I can't find a module named file.png, and that's because there is no TypeScript module called file.png, not in this environment.
[00:10:50] So we need some way to tell TypeScript that whenever we import a png file, it should be regarded as a JavaScript module with a default export, that is of type string. This can be accomplished if you create a file called global.d.ts, and you're gonna wanna create something called a module declaration.
[00:11:13] So in plain language, this could be taken to mean, I hereby state that a module exists and the name of that module is some arbitrary text followed by .png. And the default export of this module is going to be a value whose type is string. As a result of having this little piece of type information, elsewhere in your project, you're gonna see that this exact same line that was failing before, it's now not only error free, but we can see that the value of this IMG thing, it is of type string, which is great, that's ripe that's ready to put into an image tag source equals this, right?