Library / SDK
unjs/unimport avatar
unjs/unimport

unimport turns a list of named exports into injected import statements

Unified utils for auto importing APIs in modules.

681 stars100 forksTypeScriptMIT

At a glance

What is it?
unimport is a TypeScript library that detects which globals a module uses and injects the matching import statements, exposed through unplugin for Vite, Rollup, Webpack and esbuild. Its strength is a configurable resolution pipeline, its cost is that the import graph stops being visible in the source file.
Who is it for?
Adopt unimport if you build Vue or React style codebases where the same helper is imported in dozens of files, and you want one place to decide what is auto-imported. Do not adopt it in libraries you publish, in codebases where explicit imports are a review requirement, or if your team relies on editor go-to-definition without generated declarations, because unimport relies on a dts file to make the injected names resolvable in the editor.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 12 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What problem unimport actually solves in a bundler

Auto import is a familiar idea. What unimport contributes is not the idea but a single implementation shared across bundlers. The project describes itself as unified utils for auto importing APIs in modules, used in nuxt and in unplugin-auto-import, and the features list names the specific surfaces it covers: auto import registration for Vite, Webpack or esbuild powered by unplugin, TypeScript declaration file generation, auto import for custom APIs defined under specific directories, and auto import for Vue templates.

The mechanism is detection plus injection. The library scans your source for identifiers that are used but not declared, then works out which configured export each one refers to, and inserts import statements at the top of the module. The programmatic entry point makes this visible: createUnimport takes a list of imports, and injectImports takes code and returns the code with the import statement prepended. The README gives the shape of the result, showing a console.log call coming back with an import of fooBar from test-id in front of it.

The point of that API is that unimport is not tied to one bundler. The plugin wrapper is a thin layer over a core that can also be called directly, which is what makes it usable inside other tools rather than only as a config plugin.

Resolution rules: named, default, namespace and the export = case

The configuration is where unimport gets its specificity, and each import kind has its own syntax. A named import pairs a name with an optional alias and a module:

ts
imports: [
  { name: 'ref', from: 'vue' },
  { name: 'useState', as: 'useSignal', from: 'react' },
]

That produces a statement using the alias form for the aliased entry. A default import uses the reserved name default, and a namespace import uses the wildcard name, so lodash can be pulled in either as a default binding or as a namespace object depending on which you configure.

The special case is export assignment, which the README ties to libraries authored with TypeScript's export = syntax. You write the equals sign as the name, and the injected statement is a normal default import, while the type declaration is added separately as a const of the corresponding import type.

Presets are the shorthand for grouping imports from one package, listing bare names rather than objects. Built-in presets exist for common libraries, and the README points to src/presets for the available options. There is also a scanning mode, available since v0.7.0, which reads the exports of a locally installed package and expands them into individual import entries, with an ignore option that accepts a string, a regular expression or a predicate function, and a cache that you can disable with cache: false.

Installing unimport and wiring it into Vite or Webpack

Installation is a single package. The README lists the three package managers, and any one of them installs the same library:

sh
npm install unimport

The package ships as an ESM package with a Node engine requirement of 18.12.0 or newer, and it exposes several entry points: the main export at the root, a separate ./unplugin entry, an ./addons entry, and a wildcard that passes any other path through. The main module field and the . entry both point at the built ESM bundle.

For a Vite or Rollup project the plugin is created from the unplugin entry and attached with the vite method:

ts
// vite.config.js / rollup.config.js
import Unimport from 'unimport/unplugin'

export default {
  plugins: [
    Unimport.vite({ /* plugin options */ })
  ]
}

Webpack uses the same import but the webpack method:

ts
// webpack.config.js
import Unimport from 'unimport/unplugin'

module.exports = {
  plugins: [
    Unimport.webpack({ /* plugin options */ })
  ]
}

If you are not using a bundler at all, the core API is importable directly, in either module style, and the example that follows shows a minimal instance that injects an import into a string of code.

Type declarations are the feature that keeps editors usable

Auto import without type information produces a codebase that looks broken to every tool that reads types. unimport handles this by generating a declaration file, and the switch is a single option on the plugin:

ts
Unimport.vite({
  dts: true // or a path to generated file
})

Passing true writes to a default location, and passing a path writes wherever you point it. The package.json declares a sideEffects of false, which is the correct marker for a library whose modules are safe to drop when nothing is imported from them, and the build is done with unbuild, with the typecheck script running vue-tsc with noEmit.

The generated declarations are also how the export = special case works. When an entry is declared with the equals sign, the injected import is a default import, and the declaration file records the binding as a const of the matching import type, so the alias keeps the right shape for the editor.

The cost of this arrangement is that the declaration file is generated output that must be regenerated when your import list changes, and that it is not the source of truth for a published library. Anything you ship to npm should carry its imports explicitly.

Directory scanning and the opt-out comment

Beyond static lists, unimport can register the named exports of your own local modules. Pointing dirs at a glob makes every named export under that path available without an import statement:

ts
Unimport.vite({
  dirs: [
    './composables/*',
  ]
})

Nested directories are handled with a glob pattern, and an individual entry can be an object that turns off type scanning for that subtree. The scan behaviour itself is configurable through dirsScanOptions, whose defaults are worth knowing: filePatterns matches '*.{ts,js,mjs,cjs,mts,cts}', fileFilter accepts everything, types scanning is on, and cwd is process.cwd().

Every scan needs an escape hatch, and unimport provides one in the form of a comment. A module containing the disable comment is skipped, and the comment keyword itself is configurable through the commentsDisable option, so a project can support its own marker such as @custom-imports-disable alongside the default.

The Vue template case needs its own addon. Because template usage happens in a different context than plain module code, the README says custom transformations are required, and the switch is addons.vueTemplate set to true. The README also carries a caveats section for that feature that is cut off in the available text, so the exact constraints on inlining a ref in a template are not documented in what you can read here.

Regex detection versus the AST parsers

The default parser is a regular expression, and the README is honest about the trade-off: in some cases RegExp might not detect all the entries, producing both false positives and false negatives. Two AST-based alternatives exist and you select one with the parser option:

ts
Unimport.vite({
  parser: 'oxc' // or 'acorn' or 'regex' (default)
})

The acorn parser assumes the input is valid vanilla JavaScript, so the README says it should usually be used after transformations and transpilation, and it requires installing acorn as an optional peer dependency. The oxc parser is powered by oxc-parser, accepts TypeScript and JSX, and is described as much faster than acorn; it needs either rolldown or oxc-parser installed alongside unimport, with rolldown preferred if both are present.

The peer dependencies are marked optional in package.json, which is the correct arrangement for something that must not force a parser on every consumer:

sh
npm install rolldown
# or
npm install oxc-parser

This is the single most likely source of surprise for a new user. With the default regex parser, a TypeScript file that declares a type-only name, or a file whose formatting defeats the pattern, can produce an import that does not compile, and the fix is to install the parser rather than to reformat the source.

The trade-off: invisible imports versus greppable code

Nothing in this design is a technical problem, so the honest question is what you give up. Every auto-imported name is a name your editor resolves through a generated declaration file rather than through an import statement in the file itself. Grep for a usage and you find the call site, not the source of the binding. Renaming a helper means changing it in one place and relying on the dts regeneration, not editing fifty import lines.

That trade suits application code built on a small set of framework helpers, where the same few names appear in every file and the boilerplate adds noise. It suits library authors less well, since a published package should expose explicit dependencies so consumers can see them, and it suits codebases with strict import lint rules less well too.

The alternative is the opposite approach: keep every import explicit and let an editor or a lint rule handle the repetition. The difference in approach is that unimport resolves names at build time inside the bundler pipeline, using a registry you configure, rather than relying on the developer's tooling to add imports as they type. unimport also sits underneath unplugin-auto-import and nuxt, so if you use either of those you are already using this machinery, and adopting unimport directly mainly buys you the shared core, the directory scanning and the parser choices.

Editorial conclusion

Adopt unimport if you build Vue or React style codebases where the same helper is imported in dozens of files, and you want one place to decide what is auto-imported. Do not adopt it in libraries you publish, in codebases where explicit imports are a review requirement, or if your team relies on editor go-to-definition without generated declarations, because unimport relies on a dts file to make the injected names resolvable in the editor. Verify first that your bundler plugin path (Unimport.vite or Unimport.webpack) is the one your config already uses, that dts is enabled or your editor will flag every auto-imported name as undefined, and that the parser you pick matches your source, since the default RegExp parser is documented to produce both false positives and false negatives.

Frequently asked questions

How do I install unimport and use it with Vite?

Install it with npm install unimport, then import Unimport from 'unimport/unplugin' in your config and add Unimport.vite({}) to the plugins array. The package requires Node 18.12.0 or newer and is published as an ESM package.

What does unimport generate for TypeScript type checking?

Pass dts: true to the plugin options, or give it a path, and unimport writes a TypeScript declaration file listing the auto-imported names. Without it, editors that rely on generated declarations will not resolve the injected identifiers.

Can unimport detect imports in TypeScript files with the default parser?

The default parser is RegExp based, and the README says it might not detect all entries, giving both false positives and false negatives. Setting parser to 'oxc' uses oxc-parser, which accepts TypeScript and JSX, and requires rolldown or oxc-parser to be installed.

How do I stop unimport from touching a specific file?

Add the disable comment to the module, which by default is written as // @unimport-disable. The keyword set is configurable through the commentsDisable option, so you can add your own marker such as '@custom-imports-disable'.

What licence is unimport released under and who uses it?

The package.json declares the MIT licence. The README states that unimport is used in nuxt and in unplugin-auto-import, and the version in package.json is 7.0.2.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. unjs/unimport on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/unjs-unimport.svg)](https://hysenlabs.com/projects/unjs-unimport)

Was this useful?