Skip to main content

Overview

Zayne Labs Toolkit is designed from the ground up to be fully tree-shakeable, ensuring you only bundle the code you actually use. This guide explains how tree-shaking works and how to get the most out of it.

How It Works

All toolkit packages are marked with "sideEffects": false, which tells bundlers that all modules can be safely tree-shaken.

ESM-Only Architecture

The toolkit provides only ESM (ECMAScript Module) builds, which are optimized for tree-shaking:
{
  "type": "module",
  "sideEffects": false,
  "exports": "./dist/esm/index.js"
}

Named Exports

All functions and utilities are exported as named exports from barrel files (index.ts), making them individually tree-shakeable:
packages/toolkit-core/src/index.ts
export * from "./checkIsDeviceMobile";
export * from "./common-utils";
export * from "./compare";
export * from "./constants";
export * from "./copyToClipboard";
export * from "./createBatchManager";
export * from "./createExternalStorageStore";
// ... and more

Package-Specific Exports

Core Package

The core package has a single entry point:
import { debounce, throttle, copyToClipboard } from "@zayne-labs/toolkit-core";
Only the imported functions will be included in your bundle.

React Package

The React package uses subpath exports for even better tree-shaking:
// Only hooks code is bundled
import { useToggle, useDebounce } from "@zayne-labs/toolkit-react";
Package exports configuration:
{
  "exports": {
    ".": "./dist/esm/hooks/index.js",
    "./utils": "./dist/esm/utils/index.js",
    "./zustand": "./dist/esm/zustand/index.js",
    "./zustand-compat": "./dist/esm/zustand/compatible/index.js"
  }
}

Type Helpers Package

Type helpers have zero runtime overhead:
import type { Prettify, ValueOf } from "@zayne-labs/toolkit-type-helpers";
Types are stripped during compilation and don’t affect bundle size.

Build Configuration

The toolkit uses tsdown for building, configured for optimal tree-shaking:
tsdown.config.ts
const sharedOptions = {
  clean: !isDevMode,
  dts: true,              // Generate .d.ts files
  entry: ["src/index.ts"],
  format: ["esm"],        // ESM only
  platform: "browser",
  sourcemap: !isDevMode,
  target: "esnext",
  treeshake: true,        // Enable tree-shaking
  tsconfig: "tsconfig.json",
};

Bundle Size Monitoring

The toolkit uses size-limit to monitor and enforce bundle size limits.

Package Size Limits

Core Package:
{
  "size-limit": [
    {
      "path": "./src/index.ts",
      "limit": "6.5 kb"
    }
  ]
}
React Package:
{
  "size-limit": [
    {
      "path": "./src/hooks/index.ts",
      "limit": "6.5 kb"
    },
    {
      "path": "./src/utils/index.ts",
      "limit": "3 kb"
    },
    {
      "path": "./src/zustand/index.ts",
      "limit": "2.5 kb"
    }
  ]
}
Type Helpers Package:
{
  "size-limit": [
    {
      "path": "./src/index.ts",
      "limit": "600 b"
    }
  ]
}

Best Practices

1

Use named imports

Always use named imports instead of namespace imports:
// Good - Tree-shakeable
import { debounce, throttle } from "@zayne-labs/toolkit-core";

// Bad - Imports everything
import * as toolkit from "@zayne-labs/toolkit-core";
2

Import from specific subpaths

For the React package, import from the most specific path:
// Good - Only utils are bundled
import { composeRefs } from "@zayne-labs/toolkit-react/utils";

// Less optimal - Might include hooks code
import { composeRefs } from "@zayne-labs/toolkit-react";
3

Use type imports

Always use the type keyword for type-only imports:
// Good - Zero runtime overhead
import type { Prettify } from "@zayne-labs/toolkit-type-helpers";

// Unnecessary - Same result but less explicit
import { Prettify } from "@zayne-labs/toolkit-type-helpers";
4

Install only what you need

Only install the packages you actually use:
# If you only need core utilities
pnpm add @zayne-labs/toolkit-core

# If you need React hooks
pnpm add @zayne-labs/toolkit-core @zayne-labs/toolkit-react

Bundler Configuration

Vite

Vite supports tree-shaking out of the box with ESM. No additional configuration needed:
vite.config.ts
import { defineConfig } from "vite";

export default defineConfig({
  build: {
    minify: "esbuild",
    target: "esnext",
  },
});

Webpack

Ensure your webpack config is optimized for tree-shaking:
webpack.config.js
module.exports = {
  mode: "production",
  optimization: {
    usedExports: true,
    sideEffects: true,
  },
};

Next.js

Next.js handles tree-shaking automatically with these packages:
next.config.js
module.exports = {
  transpilePackages: [
    "@zayne-labs/toolkit-core",
    "@zayne-labs/toolkit-react",
    "@zayne-labs/toolkit-type-helpers",
  ],
};

Verifying Tree-Shaking

Install and use a bundle analyzer to verify tree-shaking:
pnpm add -D @next/bundle-analyzer
# or
pnpm add -D webpack-bundle-analyzer
# or
pnpm add -D rollup-plugin-visualizer
Check that only the imported functions appear in your bundle.
Run a production build and check the output size:
pnpm build
Compare bundle sizes before and after adding toolkit packages.

Performance Tips

Lazy Loading

Dynamically import heavy utilities only when needed:
const { createStore } = await import(
  "@zayne-labs/toolkit-core"
);

Conditional Imports

Use conditional imports for platform-specific code:
if (typeof window !== "undefined") {
  const { checkIsDeviceMobile } = await import(
    "@zayne-labs/toolkit-core"
  );
}

Next Steps

Migration Guide

Learn how to migrate between versions

API Reference

Explore all available functions and hooks

Build docs developers (and LLMs) love