Configuring TypeScript 16 exercises

The moduleResolution Option in tsconfig.json

The module and moduleResolution options in the tsconfig.json file can be confusing.

Here we'll discuss the differences between the module and moduleResolution options and how to choose the right combination for your project.

Module and Module Resolution

The module setting in TypeSc

Loading explainer


00:00 I get asked a lot about module and module resolution. Now, module is basically tells TypeScript what kind of module code you want to be emitted. So when we choose module node next, it's actually gonna choose based on the file extension, based on the environment it thinks you're running in.

00:17 And module resolution is basically says when you have an import, how should that import be resolved throughout the application? Now, you might think, okay, module has a ton of different options. Module resolution has a ton of different options. How do I choose which combination to choose? Because there's a bunch of different combos.

00:36 Really, there are only two choices here. Either when you're transpiling your TypeScript yourself using TSC, or rather, you're always transpiling it yourself when you're using TSC to compile it, you should choose module node next and module resolution node next. When another transpiler is handling your module resolution,

00:54 handling your modules, you should do this. You should say module es next, module resolution bundler. Okay, so module node next, module resolution node next. When we're in this mode, we're assuming that we're transpiling our TypeScript ourselves. Now, we have here an example.ts file, which just exports a function,

01:13 and we have an index.ts file here. And we're also, by the way, assuming that you have type module inside your package.json. Don't worry about what this means. This basically tells TypeScript that you're operating in an es module context, not a CommonJS context. We have a whole chapter on basically all of this stuff, so don't worry about it yet.

01:32 So when you have module resolution node next, it's going to demand that you add a .js extension whenever you do any imports, or rather, when you import local files in your file system. So when we do do this, what we have is we have relative import paths need explicit file extensions in ECMAScript imports.

01:52 When module resolution is node 16 or node next, did you mean example.js? So it's telling us not to add example.ts, which might be a reference to the actual source file, but an example.js, which is a reference to when we compile example.ts,

02:10 we actually want to target the created file there. Now, this might look bloody awkward to you because we're going, what, example.js doesn't even exist yet. Doesn't even exist, and you're telling me that I have to reference .js here as my thing when I'm actually targeting a TS file.

02:29 That just seems stupid. Well, yeah, the reason for this is that TypeScript never wants to change runtime behavior. So when we have an example.ts here, sure, TypeScript could come in and say, ooh, okay, I see that you actually meant to reference a JS file that's being compiled,

02:48 but TypeScript never wants to actually change the path of an import. That's like a line they will not cross. And so you have to write example.js. But what if you actually want to import example from example here? Well, this is something that's actually permitted by bundlers, so by esbuild, swc, or babel,

03:07 something like that. And in that situation, you can say module esnext module resolution bundler. And when you set these values to this, then it's going to be a bit more permissive with what you can do with extensions. So you can say example.js if you want to, because that is a file that will exist. Pretty sure you can't do example.ts

03:27 because that won't actually exist at runtime. But you can do just example as well. And this means that when the bundler looks at this, the bundler will actually do the module resolution for you and find the JS file for you too. So this is perfectly acceptable. Now, most of the time, you're probably going to want

03:46 module resolution bundler if you're working on the front end, because you'll probably have a front end framework like Vite or Next.js, for instance, that is going to handle that. Or when you're working in the backend, you're probably going to want a module resolution node next because you're probably targeting node. So that's in general how that works.

04:04 Let's talk briefly about module too. When we're targeting node, then we will talk about how CommonJS and ESM modules kind of like get resolved from module node next. But module esnext mostly matches what the behavior you're going to see from your bundlers.

04:21 So there are some sort of like semantics that it helps with, but doing module node next doesn't really make sense along with module resolution bundler. Just, I can't remember the exact reason, but essentially this was the recommendation I got from the TS team. Is they said, okay, you should do module esnext

04:39 because it more closely represents what's happening inside the bundler. That's the assumptions it makes. It's sort of assuming that it's transpiling ES code and it's going to compile it down to ES code probably as well. So there you go. Module and module resolution are really not too complicated

04:56 if you just think, am I transpiling my code? Okay, going to need module node next, module resolution node next. If another bundler is handling it, I'm going to need module esnext, module resolution bundler.