# jiti: runtime TypeScript and ESM support for Node.js

> jiti transpiles TypeScript and ESM at runtime so CommonJS tools can load modern config files. It is zero dependency, MIT licensed, and the README warns that its default interopDefault Proxy adds overhead on hot paths.

**unjs/jiti** — Runtime TypeScript and ESM support for Node.js

- Repository: https://github.com/unjs/jiti
- Stars: 2,973 · Forks: 117
- Language: TypeScript
- License: MIT
- Published: 2026-09-24 · Updated: 2026-09-24 · Language: en
- Canonical page: https://hysenlabs.com/projects/unjs-jiti

## What jiti solves for CommonJS tools that must load TypeScript

Node.js cannot require a .ts file, and many configuration files in the ecosystem are now written in TypeScript or as ESM. A CommonJS tool that reads those files has two options: shell out to a build step, or transpile at runtime. jiti does the second. The README describes it as runtime TypeScript and ESM support for Node.js, with a synchronous API to replace require() and an asynchronous API to replace import().

The audience is tool authors, not application developers. The README lists Docusaurus, ESLint, FormKit, Histoire, Knip, Nitro, Nuxt, the PostCSS loader, Rsbuild, Size Limit, Slidev, Tailwind CSS and others as users. Those are programs that load user-supplied config files, which is exactly the case where a build step is impossible because the file is not known until runtime.

The package is MIT licensed, has no dependencies, and exposes separate entry points for ESM, CommonJS, a global loader and a native alias. That split matters more than the feature list: it tells you the maintainers treat the async and sync paths as different products with different compatibility stories.

## How jiti loads a TypeScript file: transform, cache, resolve

When you call jiti.import() or the deprecated jiti() call, the module ID is resolved through jiti's own resolver, which understands custom aliases and, when enabled, TypeScript paths from tsconfig.json. The source is then read and passed through a transform function, which the README says defaults to Babel and is lazy loaded. The transformed code is evaluated, and the result is returned to the caller.

Two caches sit in front of that pipeline. The filesystem cache is on by default and writes transpiled source to node_modules/.cache/jiti when that directory exists, otherwise to a temporary directory. The runtime module cache is also on by default and integrates with Node.js native require.cache, so a second import of the same module returns the cached instance. Setting moduleCache to false is what the README suggests when you want to edit code and re-import the same module in one process.

There is a smart syntax detection step the README mentions in the feature list, described as a way to avoid extra transforms. That is the part that keeps plain JavaScript from paying the Babel cost. The README does not spell out the detection rules, so if you are debugging a file that is transformed when you expected it not to be, JITI_DEBUG=1 is the documented way to see what happened.

## Installing jiti and running a TypeScript entry file

The README shows the CLI path first, and it requires no install step if you are willing to use npx. The command below runs a TypeScript file directly, with the CLI handling the transform:

```bash
npx jiti ./index.ts
```

For programmatic use, install the package and create an instance. The first argument is the parent URL used for resolution, which is why the ESM example passes import.meta.url and the CommonJS example passes __filename:

```js
// ESM
import { createJiti } from "jiti";
const jiti = createJiti(import.meta.url);

const mod = await jiti.import("./path/to/file.ts");
const modDefault = await jiti.import("./path/to/file.ts", { default: true });
```

The second call shows the default shortcut, which the README describes as equivalent to mod?.default ?? mod. If you need a global loader instead of per-call imports, the README states that jiti/register requires Node.js greater than 20:

```bash
node --import jiti/register index.ts
```

Options can be passed as the second argument to createJiti, for example createJiti(import.meta.url, { debug: true }). Every documented option also has an environment variable, so JITI_DEBUG=1, JITI_FS_CACHE, JITI_MODULE_CACHE, JITI_SOURCE_MAPS, JITI_INTEROP_DEFAULT, JITI_ALIAS, JITI_TSCONFIG_PATHS and JITI_NATIVE_MODULES can be set without touching code. The alias option accepts inline JSON, and the README gives JITI_ALIAS='{"~/*": "./src/*"}' as the shape.

## The interopDefault Proxy and what it costs on hot paths

This is the most interesting design decision in the package, and the README is unusually direct about it. Since version 2.1, jiti combines module exports with the default export using an internal Proxy so that mixed CommonJS and ESM consumers see a consistent shape. The README notes that this changed behavior for anyone who migrated to 2.0.0 earlier and asks for issue reports.

The warning is specific: the option wraps all imported modules in a Proxy, which the README states adds roughly 25 to 50 nanoseconds of overhead per property access. For a config loader that reads a handful of keys once at startup, that is noise. For a module whose exports are dereferenced inside a loop, it is not. The README's own advice is to set interopDefault to false or JITI_INTEROP_DEFAULT=false for performance-critical hot paths.

That is a real trade-off rather than a footnote. Turning it off restores the pre-2.1 export shape, which is exactly the behavior some callers depend on. You cannot have both the compatibility wrapper and zero per-access cost, and the default favors compatibility.

## Where jiti is the wrong tool

jiti is a loader, not a compiler. It does not type-check. A .ts file with a type error will run under jiti because the transform strips types rather than verifying them. If your goal is catching type errors, you still need tsc or an equivalent checker in the pipeline.

The synchronous API is marked deprecated in the README, which is worth reading twice. The async jiti.import() and the global loader are the forward-looking paths. Code written today against jiti() is written against a surface the project has already flagged.

The filesystem cache is another boundary. It is on by default and writes transformed source to disk. In a read-only container, or in an environment where the temporary directory is not writable, that default needs attention. The README documents rebuildFsCache for invalidating the cache but does not document what happens when the cache location is unwritable.

Finally, jiti/register depends on Node.js global hooks, and the README states it requires Node.js greater than 20. On older runtimes that entry point is not available, and the per-call API is the fallback.

## jiti compared with tsx and other runtime loaders

The search data shows people comparing jiti with tsx, so the difference is worth stating plainly. Both load TypeScript at runtime. jiti's distinguishing choices are its zero-dependency build, its synchronous require() replacement, and its default-on filesystem cache. The synchronous path is the sharpest difference: a CommonJS tool that must load a config file during startup cannot await an import, and that is the constraint jiti was built around.

The jiti/native entry point is a different kind of alternative, and it is internal to the package. The README says you can alias jiti to jiti/native to depend directly on the runtime's import.meta.resolve and dynamic import() support, exposing the same API. That is a migration aid: it lets a caller write against jiti's interface now and switch the underlying implementation to native Node.js behavior later without rewriting call sites. If your Node.js version already handles your files natively, jiti/native is the honest way to stop paying for a transform you do not need.

## Maintenance, versioning and licence

The repository is not archived, and the last push was on 2026-09-22. The most recent release listed is v2.7.0 from 2026-05-05, preceded by v2.6.1 in October 2025 and v2.6.0 in September 2025. The README states that main is the active development branch and points to the jiti/v1 branch for legacy v1 documentation and code.

Upgrade cost is concentrated in the 2.1 interopDefault change. The README explicitly asks anyone who hit behavior changes after migrating to 2.0.0 to report them. If you are on v1, the v1 branch is where the old documentation lives, and the API surface has moved since. If you are on 2.0.x, the Proxy-based interop is the one behavior to test before moving forward.

The licence is MIT, declared in package.json. That is permissive and imposes no source-disclosure requirement on your own code. It is not legal advice; if you redistribute jiti inside a product with its own licence obligations, read the LICENSE file in the repository rather than this summary.

## Conclusion

Adopt jiti if you maintain a CommonJS tool that must load TypeScript or ESM configuration files, or if you want a synchronous require() replacement without a build step. Do not adopt it as a general application bundler or as a replacement for a real compile step in production code. Before committing, verify which entry point you need (jiti, jiti/register, jiti/native), check whether interopDefault's Proxy overhead matters in your hot path, and confirm your Node.js version supports global hooks if you plan to use jiti/register.

## FAQ

### What is jiti used for?

jiti provides runtime TypeScript and ESM support for Node.js, so a program can load .ts or ESM files without a separate build step. The README lists config-loading tools such as ESLint, Docusaurus, Nuxt and Tailwind CSS among its users.

### What is jiti?

jiti is an MIT licensed npm package from the unjs organization that transpiles TypeScript and bridges ESM and CommonJS at runtime. It exposes a CLI, an async import API, a deprecated synchronous require() replacement, and a global ESM loader at jiti/register.

### Is jiti open source?

Yes. The repository is unjs/jiti, the licence declared in package.json is MIT, and the README links to the public repository for the transform implementation and issue reporting.

## Sources

- [Issues](https://github.com/unjs/jiti/issues)
- [License: MIT](https://github.com/unjs/jiti/blob/main/LICENSE)
- [README](https://github.com/unjs/jiti/blob/main/README.md)
- [Releases](https://github.com/unjs/jiti/releases)
- [unjs/jiti on GitHub](https://github.com/unjs/jiti)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/unjs-jiti
