Getting Started with Jiti
Jiti is a tool that lets you run TypeScript files directly in Node.js without compiling them first. The UnJS team built this lightweight loader, and it now powers major projects like Nuxt, Docusaurus, Tailwind CSS, and ESLint with over 60 million monthly downloads.
Jiti handles everything you need from a modern runtime loader. It transforms TypeScript code, resolves ES modules, loads JSON files, and caches everything intelligently. You can drop it into any existing Node.js project without changing your architecture, which makes it perfect for everything from simple scripts to complex applications.
This guide shows you how to use Jiti in your Node.js projects. You'll learn how to set it up, customize it for your needs, and optimize it for the best performance.
Prerequisites
You need a recent version of Node.js and npm on your machine before starting. This guide assumes you know the basics of TypeScript and understand how CommonJS and ES modules differ.
Getting started with Jiti
Create a new Node.js project to practice the concepts you'll learn. Start by setting up a fresh project:
Enable ECMAScript module support:
Install the latest version of jiti. These examples work with version 2.x, the current stable release:
Create a loader.js file in your project root:
This code imports the createJiti function and exports a Jiti instance that can load TypeScript, ESM, CommonJS, and JSON files without any extra setup.
You'll learn about customization options later. For now, use the exported loader in a new index.js file:
Create the TypeScript file you're importing:
Save both files and run the program:
You'll see this output:
Notice that Jiti automatically detected your TypeScript file, transformed it instantly, and ran it without any compilation step. This shows Jiti's main strength: it makes development convenient while keeping execution efficient.
Understanding Jiti's module resolution
One of the biggest headaches in modern JavaScript development is dealing with different module systems. Some packages use CommonJS (module.exports), others use ES modules (export), and you often need to mix TypeScript files with JavaScript files. Jiti solves this by understanding all these formats and making them work together automatically.
Let's see how this works in practice with some real examples.
Working with mixed module formats
In most projects, you'll encounter different module formats. Maybe you have an old configuration file using CommonJS, some new TypeScript modules using ES module syntax, and third-party packages that use various formats. Normally, this creates import/export headaches, but Jiti handles it seamlessly.
Here's a practical example. First, create a traditional CommonJS module:
This is the old-school way of exporting in Node.js. The module.exports syntax has been around since the beginning and many existing projects still use it.
Now create a modern TypeScript module using ES module syntax:
This module uses TypeScript interfaces (which only exist during development) and ES module exports. Notice how different this looks from the CommonJS version - it uses export instead of module.exports.
Now here's where Jiti shines. You can import both of these completely different module formats in the same file:
The magic happens in these two lines. When you call jiti('./commonjs-module.js'), Jiti sees it's a CommonJS file and automatically converts the module.exports to work with your ES module setup. When you call jiti('./esm-module.ts'), Jiti compiles the TypeScript, handles the type definitions, and converts the ES module exports to be compatible.
Run this example:
Both imports work perfectly! You got the configuration object from the CommonJS file and the exported values from the TypeScript ES module. Without Jiti, you'd need to either convert all your files to the same format or use complex workarounds.
JSON and dynamic imports
Jiti doesn't just handle JavaScript and TypeScript - it also loads JSON files directly and supports async imports for more complex scenarios.
Create a JSON configuration file:
JSON files can't be imported directly in Node.js without special handling, but Jiti treats them as first-class modules:
The first example shows synchronous JSON loading - jiti('./config.json') automatically parses the JSON file and returns a JavaScript object. You can immediately access properties like config.appName.
The second example shows async loading with jiti.import(). This is useful when you want to load modules conditionally, handle errors gracefully, or load modules that might not exist. The async version also works better with ES module imports that might have side effects.
Run this to see both in action:
The key insight here is that Jiti eliminates the friction between different file types and module systems. You don't need to think about whether something is CommonJS, ES modules, TypeScript, or JSON - Jiti handles the conversion automatically.
CLI usage
One of Jiti's most powerful features is its command-line interface. Instead of setting up complex build processes, you can execute TypeScript files directly with the jiti command. This is perfect for scripts, automation tasks, and development workflows.
Running TypeScript files directly
The simplest way to use Jiti is to run TypeScript files as if they were JavaScript:
Let's create an app.ts with a practical example:
This example shows TypeScript interfaces, typed functions, and array methods working together. The Task interface ensures type safety, while getIncompleteTasks has explicit input and return types.
Run it directly:
When you run this command, Jiti loads your TypeScript file, compiles it in memory with full type checking, and executes the result immediately. No separate compilation step needed.
Environment variables for debugging
You can control Jiti's behavior with environment variables:
Enable debug mode to see what Jiti is doing:
This debug output shows Jiti's configuration, whether it found a cached version of your file, and how long the transformation took. The cached file (90b55b14.mjs) gets reused on subsequent runs for better performance.
Use a custom cache directory:
This debug output shows Jiti's configuration, whether it found a cached version of your file, and how long the transformation took. The cached file (90b55b14.mjs) gets reused on subsequent runs for better performance.
Use a custom cache directory:
This stores compiled files in /tmp/jiti-cache instead of the default location, useful for Docker containers or CI/CD pipelines.
Configuring Jiti options
Now that you've seen Jiti's basic capabilities, you need to understand how to configure it for real projects. Jiti has many options that control how it transforms code, handles caching, and resolves modules. Getting these settings right makes the difference between a slow development experience and a fast, smooth workflow.
The basic createJiti(import.meta.url) setup works for simple examples, but production applications need more control.
Essential configuration options
Let's create a more sophisticated loader that shows the most important configuration options:
Each of these options solves a specific problem:
cache: true is the most important setting. When enabled, Jiti stores compiled versions of your files in node_modules/.cache/jiti. This means the first time you load a TypeScript file, Jiti compiles it and saves the result. Every subsequent load uses the cached version, making it much faster.
requireCache: false controls whether modules stay in Node.js's internal cache. During development, you want this set to false so you can make changes to your TypeScript files and see them immediately. In production, you might want true for better performance.
interopDefault: true fixes a common problem when mixing ES modules and CommonJS. Without this, you might need to access exports as result.default instead of just result. This option makes the behavior more predictable.
debug: true shows you exactly what Jiti is doing - which files it's transforming, how long it takes, and whether it's using cached versions. This is invaluable during development but should be disabled in production.
Let's see the difference caching makes:
Run this example:
The debug output reveals several important insights. The first two lines show the initialization of both Jiti instances. Notice that the cached version shows fs-cache: true while the uncached version shows fs-cache: false.
The [cache] [hit] line shows that Jiti found a cached version of example.ts at a specific path with a hash (4f2e888f). This hash is generated from the content of your TypeScript file - if you change the file, the hash changes, and Jiti knows to recompile.
The timing results show the performance difference: the first cached load takes 3.184ms (including compilation and cache writing), but the second cached load only takes 0.196ms - over 16 times faster! The uncached version takes similar time for both loads because it recompiles every time.
In a real application with dozens of TypeScript files, this performance difference becomes dramatic. Caching can turn a 5-second startup time into a 300-millisecond startup time.
Understanding these configuration options is essential because they directly impact your development experience. Poor configuration can make your development server painfully slow, while good configuration makes TypeScript feel as fast as JavaScript.
Final thoughts
This guide has shown you how Jiti transforms TypeScript development by eliminating the compile step. You've learned to load TypeScript files directly, mix different module formats seamlessly, execute scripts via CLI, and optimize performance through smart configuration.
The tool continues to evolve with new features and optimizations. Check the official documentation for the latest updates and advanced use cases.