DEV Community

Cover image for Stop Fighting Your Build Config: Building Modular Libraries the Easy Way
Alfredo Salzillo
Alfredo Salzillo

Posted on

Stop Fighting Your Build Config: Building Modular Libraries the Easy Way

If you've ever tried to build a library that exports individual components—think @acme/ui/button or @acme/utils/date—you know the pain. Keeping your package.json exports in sync, managing complex input globs, and maintaining a predictable output structure is a tedious, error-prone manual process.

Enter modular-library: a zero-config utility designed specifically to generate modular outputs for modern bundlers like Vite, Rollup, and Rolldown.

What is a "Modular Library"?

Unlike a monolithic library where you import everything from a single entry point, a modular library is split into small, focused modules.

The standard way:
import { Button } from "my-ui-lib";
Problem: Often requires evaluating the full package entry, even if you only need one component.

The modular way:
import Button from "my-ui-lib/button";
Solution: Loads only the code for the button. It’s faster, cleaner, and ensures perfect tree-shaking for the consumer.

Why use modular-library?

Building this manually usually means fighting your build tool's configuration as your source tree grows. modular-library automates the heavy lifting:

  1. Zero-Config: It handles the mapping from src/ to dist/ automatically.
  2. Predictable Structure: Your output mirrors your input without manual path mapping.
  3. Modern Stack Support: Works out of the box with Vite, Rollup, and Rolldown.
  4. CLI Validation: Includes a CLI tool to verify that your package.json exports actually match your build output.

Quick Start

modular-library requires Node.js 22 or newer.

npm install modular-library
Enter fullscreen mode Exit fullscreen mode

Using it with Vite

Instead of manually defining every entry point in your vite.config.ts, you can use the plugin to handle the glob-based inputs.

// vite.config.ts
import { defineConfig } from "vite";
import modularLibrary from "modular-library/vite";

export default defineConfig({
  plugins: [modularLibrary()],
  build: {
    lib: {
      entry: ["src/**/*.ts"],
      formats: ["es"],
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

Making it Consumable

To let users import your modules easily, add an exports map to your package.json:

{
  "exports": {
    "./*": "./dist/*.js"
  }
}
Enter fullscreen mode Exit fullscreen mode

The "Check" Command

One of the coolest features is the built-in CLI. After you run your build, you can validate that your exports are correct and that no files are missing:

npx modular-library check
Enter fullscreen mode Exit fullscreen mode

This prevents the "broken package" scenario where you ship a library but a subpath import fails because the file wasn't generated correctly.

Customizing your Output

If you need to change how the paths are generated (for example, putting everything in a modules/\ folder), the plugin provides a simple transformOutputPath\ hook:

plugins: [
  modularLibrary({
    relative: "src/", // The base path to strip
    transformOutputPath: (path) => \`modules/\${path}\`,
  }),
],
Enter fullscreen mode Exit fullscreen mode

Conclusion

The goal of modular-library is to let you focus on writing code instead of managing build artifacts. By automating the sync between your source files and your distribution targets, it makes maintaining a high-performance, tree-shakeable library accessible to everyone.

Check out the source on GitHub and start modularizing your workflow!

Found this helpful? Follow me for more insights on modern JavaScript tooling and library development!

Top comments (0)