Open-source project
ts-plugin/ts-import-plugin avatar
ts-plugin/ts-import-plugin

ts-import-plugin rewrites barrel imports into per-module paths at compile time

babel-import-plugin TypeScript Implement

603 stars36 forksTypeScriptMIT

At a glance

What is it?
ts-import-plugin is a TypeScript transformer that turns `import { Alert, Card as C } from 'antd'` into one import per component plus its style file, so a bundler can drop what you did not use. It plugs into ts-loader, awesome-typescript-loader or rollup, and it does not work with default or namespace imports.
Who is it for?
Adopt ts-import-plugin if you use a component library whose barrel entry pulls in more than you need, and you want per-module imports generated automatically rather than by hand at every call site. Skip it if your bundler already handles tree shaking well, since the plugin exists to fix the cases where it does not, and skip it if you rely on default or namespace imports, which the plugin explicitly does not support.
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 received new commits within the last day.
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 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The transform it performs on a named import

The mechanism is a compile-time rewrite of import statements, and the README shows both sides of it. This source.

ts
import { Alert, Card as C } from 'antd'

becomes this.

ts
import Alert from 'antd/lib/alert'
import 'antd/lib/alert/style/index.less'
import { default as C } from 'antd/lib/card'
import 'antd/lib/card/style/index.less'

Two things are happening at once. The named import is split so each component resolves to its own module path under the library's `lib` directory, and the style file for each component is imported alongside it, which is what a component library like antd expects. The alias `Card as C` is preserved, and it becomes a default import of the aliased module. The result is a set of imports that a bundler can analyse one by one, so a module you never referenced never enters the graph. This is the same trick `babel-plugin-import` plays in the JavaScript world, and the package description says so directly, calling this the TypeScript version of that plugin. What makes it a transformer rather than a Babel plugin is that it runs inside the TypeScript compiler pipeline, before the module is type-checked and emitted.

Named imports only, and the plugin says so up front

There is one hard limitation, and the README puts it above the fold in a block quote: this plugin is not work if your are using `import * as _ from 'lodash'` or `import _ from 'lodash'`. The grammar is rough, but the content matters. A default import has no component name to derive a path from, and a namespace import pulls in the whole module by definition, which is exactly what the plugin exists to prevent. So the transform only fires on named imports, and a codebase that leans on default imports from a component library gets nothing out of it. The lodash section of the README is even blunter about the edge case, with a notice that you should manual `import 'lodash/core'` in your project if you are using `import { chain } from 'lodash'`. If your import style is mixed, the plugin still helps on the named half, but the namespace half of your bundle stays as large as it was, which is worth knowing before you assume the problem is solved.

Wiring it into ts-loader

The ts-loader route is the documented default. The tsconfig needs the module setting, and the README gives the fragment.

js
//tsconfig.json
{
  ...
  "module": "ESNext",
  ...
}

The webpack config then passes the transformer into the loader as a `before` custom transformer.

js
// webpack.config.js
const tsImportPluginFactory = require('ts-import-plugin')

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.(jsx|tsx|js|ts)$/,
        loader: 'ts-loader',
        options: {
          transpileOnly: true,
          getCustomTransformers: () => ({
            before: [tsImportPluginFactory(/** options */)],
          }),
          compilerOptions: {
            module: 'es2015',
          },
        },
        exclude: /node_modules/,
      },
    ],
  },
  // ...
}

Two details are worth noticing. The loader is set to `transpileOnly: true`, which skips type checking during the build, so the transformer runs on the fast path. And the loader's own `compilerOptions` override sets the module to `es2015`, which is what the transformer expects to see when it rewrites the import into a form the bundler can analyse. The awesome-typescript-loader route, for versions 3.5.0 and above, has the same shape with `getCustomTransformers` and no compiler options override.

The same transformer under rollup

The rollup path uses the plugin as a standalone transformer rather than through a loader, and the README's example pairs it with `rollup-plugin-typescript2` or `@rollup/plugin-typescript`.

js
import typescript from 'rollup-plugin-typescript2' // or '@rollup/plugin-typescript'
import createTransformer from 'ts-import-plugin'

const transformer = createTransformer({
  libraryDirectory: 'es',
  libraryName: 'antd',
  style: true,
})

The transformer is then passed through the plugin's `transformers` option as a `before` entry, and the build output is an ESM bundle. The difference from the webpack route is who owns the compilation. In webpack, ts-loader owns it and asks the transformer to run first. In rollup, the TypeScript plugin owns it and the transformer is registered the same way. The `libraryDirectory` here is `es` rather than the default `lib`, because antd ships an ES module build in that directory, and the plugin needs to be told where to point. Note also that this example uses the default export name `createTransformer` while the webpack examples use `tsImportPluginFactory`, so the name you import depends on which entry point you are on.

The options that decide what path gets generated

The options object is where the library-specific knowledge lives. `libraryName` is the name of the library in the import, defaulting to `antd`. `libraryDirectory` is the directory within the library to replace the import with, defaulting to `lib`, and it accepts a function of the name if the layout is not uniform. `style` is a boolean, a string or a function of the path, defaulting to `false`, and it controls the style import. `libraryOverride` prepends the library name to the library directory by default, and setting it to true treats the library directory as the full path. `camel2DashComponentName`, defaulting to true, transforms a component name from camelCase to dashed, so `FooBar` becomes `foo-bar`, while `camel2UnderlineComponentName`, defaulting to false, does the same to snake_case. `failIfNotFound` is the safety valve. By default, if a component is not found in the library, the full library is imported instead; set it to true to fail the build. The README's own example is a good illustration of the combination.

js
tsImportPluginFactory({
  libraryName: 'antd',
  libraryDirectory: 'lib',
  style: true,
})

Options can also be an array, so one transformer can handle several libraries in the same project, each with its own rules.

Per-library recipes for antd, lodash and material-ui

The compatible libraries section is effectively a set of recipes, and the differences between them show why the options exist at all. For ant-design, the transformer takes `style: true` for less, `style: 'css'` for css, or no style at all. For lodash, the example turns style off, sets the library directory to `null` and disables the camel case conversion, with the earlier notice that you must import `lodash/core` manually yourself. For antd-mobile, the style extension is passed as a separate `styleExt` value, with `css.web` as one option and a note that antd-mobile recently changed styleExt, so if an error occurs with the previous value you should try the next one. For material-ui, the library directory is empty and the name conversion is off, and the icons package is handled with a `libraryDirectory` function that parses the import name into a path. For element-ui, the `style` option takes a function that builds the theme-chalk path from the component name.

A package built for several module systems

The packaging tells you this is a small tool with a wide surface. The manifest version is 3.0.0, the licence is MIT, and the description says it is the babel-plugin-import TypeScript version. The published files include `lib/`, `esm/` and `next/`, and the entry points follow: `main` points at `./index.js`, `module` at `./esm/index.js`, `jsnext:main` at `./next/index.js`, and `typings` at `./lib/index.d.ts`. The build scripts compile the TypeScript sources three times, to CommonJS, to ESM with esnext modules, and to a next build targeting ES2018, which is how a single package can be consumed by webpack, rollup and bundlers that expect the newer module field. The tests run under ava with c8 for coverage, linting uses eslint with a yml config, and formatting is handled by prettier with husky managing the git hooks. The repository also carries a `renovate.json` for dependency updates, a rollup config, a webpack config used to produce the bundle analyzer screenshot in the README, and a `CHANGELOG.md`. The last push was on 2026-10-18, while the most recent published release is v3.0.0 from 2023-04-15, so the tree is being worked on more recently than the last version bump.

Editorial conclusion

Adopt ts-import-plugin if you use a component library whose barrel entry pulls in more than you need, and you want per-module imports generated automatically rather than by hand at every call site. Skip it if your bundler already handles tree shaking well, since the plugin exists to fix the cases where it does not, and skip it if you rely on default or namespace imports, which the plugin explicitly does not support. Before you commit, set `failIfNotFound` to true and build once, because the default behaviour of silently falling back to the whole library is what hides a broken import path.

Frequently asked questions

What does ts-import-plugin do to an import statement?

It splits a named import into one import per component under the library's directory, and adds the style import for each component. The README shows `import { Alert, Card as C } from 'antd'` becoming `import Alert from 'antd/lib/alert'`, a less import for the alert style, and the same pattern for the aliased card.

Does ts-import-plugin work with default or namespace imports?

No. The README states plainly that the plugin is not work if you are using `import * as _ from 'lodash'` or `import _ from 'lodash'`. The transform applies to named imports, since a default or namespace import gives it no component name to resolve a path from.

How do I add ts-import-plugin to a webpack build?

Set the tsconfig module to `ESNext`, then pass the transformer into ts-loader through `getCustomTransformers` as a `before` entry, using `transpileOnly: true` and a compiler option of `module: 'es2015'`. The awesome-typescript-loader route uses the same `getCustomTransformers` hook.

What does the failIfNotFound option do?

By default, if a component is not found in the library, the full library is imported instead. Setting `failIfNotFound` to true makes the build fail in that case instead of silently pulling in the whole package.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. ts-plugin/ts-import-plugin 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/ts-plugin-ts-import-plugin.svg)](https://hysenlabs.com/projects/ts-plugin-ts-import-plugin)

Was this useful?