jiti

A zero-dependency runtime that transpiles TypeScript and ESM on the fly so Node.js can require() or import them directly.

Library
npm
v2.7.0
2,970 stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum. How we score it →
58 /100 Fair
Development Activity 48
Maintenance 32
Community 52
Maturity 60
Momentum 40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation. How we score it →
83 /100 Excellent
Architecture 88
Code Quality 85
Innovation 78
Learning Curve 80

jiti is a runtime for Node.js that gives seamless TypeScript and ESM support without a separate build step. It intercepts require() and import() calls, detects when a file needs transforming (TypeScript, JSX, or mismatched CJS/ESM syntax), transpiles it with a lazily-loaded Babel pipeline, caches the result to disk, and hands back a fully interoperable module — smart syntax detection means untouched JavaScript files pass straight through with no added cost.

It ships as a genuinely zero-dependency package: every dependency it needs at runtime, including Babel itself, is pre-bundled into the published lib/ output, keeping install size small and avoiding version drift in consuming projects. It is used as an internal loader by a large slice of the JavaScript ecosystem — Nuxt, Tailwind CSS, ESLint, Docusaurus, Histoire, Nitro, and dozens of other tools depend on jiti to load user config files and scripts written in TypeScript — and it is also usable standalone via npx jiti ./file.ts or as a global ESM loader through node --import jiti/register.

What You Get

  • Async import() and sync require() replacements - jiti.import() and the callable jiti() instance load TypeScript/ESM/CJS files interchangeably, matching Node’s native APIs.
  • Smart syntax detection - files that don’t need transforming (plain JS) are passed straight through, avoiding unnecessary Babel overhead on hot paths.
  • Filesystem transpile cache - transformed output is written to node_modules/.cache/jiti (or a temp dir) with etag-based invalidation, so repeated runs skip re-transpiling unchanged files.
  • Global ESM loader registration - import "jiti/register" or node --import jiti/register hooks into Node’s module loader so the whole process gains TS/ESM support.
  • CLI runner - npx jiti ./script.ts executes a TypeScript file directly with no project setup.
  • Configurable resolution - custom alias maps, tsconfig.json paths resolution, and control over which node_modules packages are transformed vs. left native.

Common Use Cases

  • Loading TypeScript config files at runtime - tools like Nuxt, Tailwind, and ESLint use jiti internally to require() a user’s nuxt.config.ts or tailwind.config.ts without a pre-build.
  • Running TypeScript scripts without a build step - npx jiti ./script.ts for one-off scripts, codemods, or CI tasks.
  • Building CLI tools that accept user-authored TS plugins - a CLI can jiti.import() a user’s .ts plugin file and get back a normal module.
  • Bridging CJS and ESM in mixed codebases - jiti’s interopDefault proxy smooths over default export mismatches between require() and import() semantics.
  • Global process-wide TS/ESM support - registering jiti/register as a Node loader so an entire process can import/require TypeScript without per-call wrapping.

Under The Hood

Architecture The package is organized as a small set of single-purpose modules under src/ (roughly 1,300 lines total, per the project’s own AGENTS.md): jiti.ts is the createJiti() factory that wires shared context (options, aliases, native-require handles) and returns the callable jiti instance; require.ts implements the synchronous require-replacement path, branching on protocol prefixes (node:, file:, data:), builtin modules, virtual modules, and an experimental Bun “try-native-first” fast path before falling through to resolution and evaluation; resolve.ts handles module resolution with alias and tsconfig-paths support; eval.ts evaluates transformed source for both sync and async contexts; transform.ts and babel.ts orchestrate a lazily-loaded Babel pipeline (only pulled in when a file actually needs transforming); and cache.ts manages the etag-keyed filesystem transpile cache. The design deliberately keeps a shared Context object threaded through every function rather than using global state, which is what lets a single process host multiple independently-configured jiti instances.

Tech Stack Written in TypeScript, built with rspack (primary) or rolldown as an alternative bundler, and published as dual ESM/CJS output (lib/jiti.mjs / lib/jiti.cjs) plus a CLI entry (lib/jiti-cli.mjs). The published package has zero runtime dependencies — everything it needs, including a full Babel toolchain (@babel/core, @babel/plugin-transform-typescript, @babel/plugin-transform-react-jsx, decorator and explicit-resource-management plugins, etc.), plus small utilities like mlly, pathe, get-tsconfig, and yoctocolors, is bundled into the lib/ output at build time rather than left as installable dependencies. Tests run under Vitest with coverage, plus separate native-mode suites for Bun, Deno, and Node’s own --experimental-strip-types.

Code Quality The project has an extensive test suite: dozens of fixture directories under test/fixtures (TypeScript, JSX, ESM, CJS interop, tsconfig-paths, import maps, top-level-await, explicit-resource-management, and more) drive snapshot-style fixture tests, alongside dedicated suites for the CLI, the global register hook, virtual modules, and native-runtime parity across Bun/Deno/Node. Error handling favors explicit checks and descriptive thrown errors (e.g. rejecting data: URLs in sync context) over silent fallbacks. CI runs lint (ESLint + Prettier), a type-check pass (tsgo --noEmit), and the full multi-runtime test matrix on every change, and the repo has both AGENTS.md and CLAUDE.md files documenting its own architecture for maintainers.

What Makes It Unique jiti’s niche is being the de facto shared implementation for “let this tool load a TypeScript config/plugin file without a build step” — rather than every framework reinventing its own ad-hoc TS-loading shim, a large share of the JavaScript tooling ecosystem (Nuxt, Tailwind, ESLint, Docusaurus, Nitro, Histoire, and others) depends on jiti directly for this. Its smart syntax detection (skipping transformation for files that are already valid JS) and its filesystem cache give it a performance profile suited to being invoked on every process start, which is a harder constraint than a one-time build-time transpiler faces.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers