Skip to content

Make @tabler/icons-react fully tree-shakeable#1551

Open
liamdon wants to merge 2 commits into
tabler:mainfrom
classdojo:feat/treeshakeable-icons-react
Open

Make @tabler/icons-react fully tree-shakeable#1551
liamdon wants to merge 2 commits into
tabler:mainfrom
classdojo:feat/treeshakeable-icons-react

Conversation

@liamdon

@liamdon liamdon commented Jun 26, 2026

Copy link
Copy Markdown

Note: these changes are currently published under tabler-icons-react-esm. If this PR is accepted I will deprecate the separate package and add a README note pointing back to this repo.

Motivation

Reliable tree-shaking and subpath imports for @tabler/icons-react are among the most-requested improvements to this package. An app importing a handful of icons shouldn't pull the whole set into its bundle, and people have repeatedly asked to be able to import a single icon by subpath or by dynamic import, and to improve bundling performance in dev mode:

This PR reworks the package's build to address all of these complaints, while keeping the icon set and component API 100% unchanged and maintaining CJS compatibility; existing import { IconArrowLeft } from '@tabler/icons-react' code keeps working with no changes.

Changes

The package now ships:

  • A full exports map enabling per-icon subpath imports (@tabler/icons-react/IconArrowLeft).
  • Dual ESM/CJS output that preserves one module per icon (preserveModules) instead of a single concatenated barrel, so bundlers can drop what you don't use.
  • Per-icon type declarations, so subpath imports and the wildcard subpath are fully typed.
  • A working dynamic-import entry keyed by icon name.

No changes to the icons themselves, the component implementation, or the public component API.

Three ways to import

1. Named import (recommended for app code) now tree-shakes reliably:

import { IconArrowLeft } from '@tabler/icons-react';

const App = () => <IconArrowLeft color="red" size={48} />;

2. Deep / subpath import guarantees one-icon load in any bundler without analysis:

import IconArrowLeft from '@tabler/icons-react/IconArrowLeft';

3. Dynamic import lazy-loads at runtime; native deep dynamic import, or a kebab-case-keyed loader map for names coming from a CMS/config:

const { default: IconArrowLeft } = await import('@tabler/icons-react/IconArrowLeft');

// or, by icon name:
import dynamicIconImports from '@tabler/icons-react/dynamic';
const { default: IconHome } = await dynamicIconImports['home']();

Tree-shaking & bundler compatibility

Because each icon is its own ES module and the package is marked sideEffects: false, named imports tree-shake out of the box in:

  • Vite / Rollup
  • esbuild
  • webpack 5 (production builds)
  • Next.js — additionally works with experimental.optimizePackageImports: ['@tabler/icons-react']

For older toolchains, or to guarantee a minimal bundle regardless of bundler analysis, the subpath import form above always loads exactly one icon.

One behavioral note (backward compatibility)

The only removal: the icons / iconsList namespace re-exports were dropped from the package root, because re-exporting every icon from the entry point defeats tree-shaking. The icon-name list now lives at a dedicated subpath:

import iconsList from '@tabler/icons-react/icons-list';

Verification

  • pnpm --filter @tabler/icons-react build succeeds (ESM + CJS + types + dynamic maps).
  • pnpm --filter @tabler/icons-react test — 9/9 pass, no type errors.

liamdon added 2 commits June 26, 2026 14:39
Ship a full `exports` map with per-icon deep imports, dual ESM/CJS output
that preserves one module per icon, per-icon type declarations, and a
working dynamic-import entry. The icon set and component API are unchanged.

- Named imports from the root tree-shake in modern bundlers; deep imports
  (`@tabler/icons-react/IconArrowLeft`) guarantee a one-icon load in any
  bundler.
- `@tabler/icons-react/dynamic` exposes a kebab-case-keyed loader map for
  runtime lazy-loading.
- The `icons`/`iconsList` namespace re-exports were removed from the package
  root (they defeated tree-shaking); the icon-name list now lives at the
  `@tabler/icons-react/icons-list` subpath.
The `accessible` source SVG draws the inner dot as a `<path>` and uses
normalized arc notation for the outer ring, but the inline snapshot still
expected the older `<circle>` form. Regenerate it so the test matches the
icon source. Pre-existing mismatch, independent of the tree-shaking change.
@vercel

vercel Bot commented Jun 26, 2026

Copy link
Copy Markdown

@liamdon is attempting to deploy a commit to the Tabler Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

Copy link
Copy Markdown

✅ All icons are valid!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant