Relative vs Non Relative Imports
Overview
In some cases, we want some functionality and features that we have implemented in our previous project, so for that, we can't rewrite the script again as it is considered as bad practice that results in the repetition of code. We can simply import by making the file exportable. To import other TypeScript files into an existing TypeScript file, we should know the concept of modules that helps us in achieving the same.
Relative Imports in TypeScript
A relative import is defined as resolved relative to the importing file and cannot resolve to an ambient modular declaration. We should use the relative modules for our modules that guarantee to maintain their relative location at the runtime.
Syntax and Implementation
A relative import is the one that starts with /, ./ or ../. Here is the syntax to declare a relative import:
Implementation
How to Resolve Relative Import
There are two possible module resolution strategies: Node and Classic. We can use the moduleResolution option to specify the module resolution strategy. If not specified, we will select the default Node for --module commonjs or classic otherwise.
Classic Resolution
This is used as TypeScript's default resolution strategy. This strategy is present for backward compatibility. A relative import in TypeScript can be resolved relative to the importing file. For example,
The import { x } from "./moduleX" in the source file /root/src/folder/X.ts will result in the following lookups:
- /root/src/folder/moduleX.ts
- /root/src/folder/moduleX.d.ts
Node Resolution
This resolution strategy attempts to take off the Node.js module resolution mechanism at runtime. Imports in Node.js are performed by calling a function named require. Relative paths are considered straightforward paths. For example,
Let's consider a file located at /root/src/moduleX.js, that contains the import var x = require("./moduleY");, Node.js resolves that import in the following order:
- Search the file named /root/src/moduleY.js if the file exists.
- Search the folder /root/src/moduleY if the folder contains a file named package.json that specifies a "main" module. If Node.js found the file /root/src/moduleY/package.json that contains {"main": "lib:/mainModule.js"}, then Node.js refers to /root/src/moduleY/lib/mainModule.js.
- Search the folder /root/src/moduleY if the folder contains a file named index.js that file is implicitly considered that folder's "main" module.
Non-Relative Imports in TypeScript
A non-relative import is defined as resolved relative to the baseUrl, or through path mapping. They can also resolve to ambient modular declarations. We can use non-relative paths when we import any of our external dependencies.
Syntax and Implementation
The imports that are not relative will be considered non-relative imports. Here is the syntax to declare a non-relative import:
Implementation
How to Resolve Non-Relative Import
Classic Resolution
For non-relative imports, the compiler walks through the directory tree starting with the directory containing the importing file, which tries to locate a matching definition file. For example,
A non-relative import to moduleX such as import { x } from "moduleX" in a source file /root/src/folder/Y.ts will result in attempting the following locations to locate "moduleX":
- /root/src/folder/moduleX.ts
- /root/src/folder/moduleX.d.ts
- /root/src/moduleX.ts
- /root/src/moduleX.d.ts
- /root/moduleX.ts
- /root/moduleX.d.ts
- /moduleX.ts
- moduleX.d.ts
Node Resolution
In this, Node will search for modules in special folders named node_modules. A node_modules folder can be of the same level as the current file, or higher up in the directory chain. The node will walk up the directory chain, and look through each node_modules until the node finds the module we tried to load.
Now, consider if root/src/moduleX.js instead used a non-relative path and had the import var x = require("moduleY");, Node will try to resolve moduleY to each of the locations until one worked.
- /root/src/node_modules/moduleY.js
- /root/src/node_modules/moduleY/index.js
- /root/node_modules/moduleY.js
- /root/node_modules/moduleY/index.js
- /node_modules/moduleY.js
- /node_modules/moduleY/index.js
When we specify a main property, the below paths are used:
- /root/src/node_modules/moduleY/package.json
- /root/node_modules/moduleY/package.json
- /node_modules/moduleY/package.json
Difference Between Relative and Non-Relative Imports
Here are some key differences between Relative and Non-Relative Imports:
Relative Imports | Non-Relative Imports |
---|---|
A relative import is defined as resolved relative to the importing file. | A non-relative import is defined as resolved relative to the baseUrl, or through path mapping. |
A Relative import cannot be resolved to an ambient modular declaration. | A Non-Relative import can be resolved to an ambient modular declaration. |
We can use the relative modules for our modules that guarantee to maintain their relative location at the runtime. | We can use non-relative paths when we import any of our external dependencies. |
Conclusion
- The module's alias saves us time and the frustration of dealing with ../ that is splattered everywhere.
- With TypeScript 3.8, we can import a type using the import statement, or using an import type.
- Module Resolution is a process that is used by the TypeScript compiler to figure out what an import refers to.
- The node module resolution is the most common process recommended to handle large TypeScript projects. If we have resolution problems with import s and exports in TypeScript, we can try setting the moduleResolution: "node" to fix this problem.
- We can use the noResolve compiler options to instruct the compiler not to add any files to the compilation that were passed onto the command line.