TypeScript Module Resolution Examples
A set of example projects exploring TypeScript module resolution with various settings and situations.
Table of contents:
- About this project
- Introduction to TS module resolution
- TypeScript Options
About this project
This was initially created for a talk at Michigan TypeScript Developers. // TODO: add link to recording.
The repository is roughly organized around TypeScript settings, with subfolders for possible values for those settings
and examples demonstrating various aspects of module resolution. There are
README.md files in most examples to
explain the concept being demonstrated. Usually you can run
npm run build in each example folder to generate
example.d.ts type definition files and see a log of the module resolution process that TypeScript uses printed
to the terminal.
Feel free to submit issues or pull requests. I hope for this to be a learning resource for TypeScript developers who
want to understand the complex topic of TypeScript module resolution. So with that, let’s start learning!
Introduction to TS module resolution
Let’s begin by defining what we mean by a “module”. When we write TypeScript code,
we typically don’t write everything in one single file. Instead, we break up our code
a “script” or a “module”.
and does not have any exports. It might operate on the global scope, or it might not.
ES Modules (ESM). Node.js historically used CJS and has started supporting ESM since version 12.
Modern browsers also support ES modules, but they use slightly different rules for resolving
imports from other modules. To learn more about ES modules in the browser, I recommend reading the
In TypeScript, most files you write will be considered a module. By default, any file that uses
export is treated as a module. Starting in version 4.7, in some situations TypeScript will also check for a package.json file containing
"type": "module". The module detection behavior can be
adjusted with the
When we need to use code from another TypeScript file than the one we are writing, we generally
import statement (or
import() function). We might write something like:
import foo from 'moduleA';
TypeScript will then need to find what module we mean by “moduleA”, in order to determine what type
“foo” should have.
This is important. We are talking about types when we talk about TypeScript module resolution.
It’s true that TypeScript can also compile
.ts files into
.js files, converting our TypeScript into
So, TypeScript does its best to try to match the same module resolution rules that the final runtime
will use, so that the type information it gathers is complete and accurate. Because there are different
runtimes with different rules, TypeScript exposes some options that we can set in order to match up with
the runtime environment.
These are some options which TypeScript uses to adjust its module resolution. Please see the
TypeScript config reference for details on each option
and the minimum TypeScript version required for support.
Note that some module-related options, such as
This is the most direct way to change TypeScript’s module resolution mode. It has three current possible values,
and more are being developed to better meet the needs of bundlers and browser-native modules.
The currently-supported values are (as of TS 4.9):
"NodeNext"(currently these mean the same thing)
“Classic” mode is rarely used in modern projects, and is not explored in this repository. For details on how it works,
see the TypeScript module resolution guide.
“Node” is likely the most commonly-used option, and mimics the behavior of Node 11 and below.
moduleResolution/Node examples and README.
“NodeNext” is intended to evolve as Node.js adds new versions and new features, but for now it is the same as “Node16”,
which adds support for the Node.js style of ES modules. There are some differences in TypeScript Module resolution
compared to “Node”, and these are explored in
See the README there to begin.
New options in development
The TypeScript team identified that the current
moduleResolution options are not flexible enough for those using
bundlers or writing ESM to be directly consumed in browsers, so they are creating new options:
"Hybrid"(final naming TBD): designed primarily for bundlers, with some behavior customizable by settings.
"Minimal": A common-denominator for resolution features supported in browsers, Node, Deno, and bundlers.
There are no examples of these in this repo, but I intend to add them once they are released.
Provides a way to inform TypeScript that when compiled, non-relative imports can be resolved relative to this path.
It is similar to the (discouraged)
NODE_PATH option for Node.js.
Allows customizing the “sub” extensions TS will examine, which can be particularly useful when targeting React Native
as a runtime. For example,
"moduleSuffixes": [".ios", ".native", ""] will look for files ending with
This is an experimental feature, parts of which are available only in nightly versions, which can change the
“resolution mode” of globals or imported types, allowing CJS types to be used in ESM files, and vice-versa.
community-supplied types or custom-written types to
describe that project. The examples in
misc/ambient-modules explore different ways that ambient modules can be
configured and used.