DEV Community

Cover image for From Frustration to Automation
BarbWire
BarbWire

Posted on

From Frustration to Automation

Overcoming Boilerplate Fatigue in JavaScript Libraries

The architectural journey behind building a lightweight workspace wizard for wiring and testing JS libraries

Every single time I wanted to build a small utility or a custom JavaScript library, the exact same headache occurred. I would spend the first hours tweaking configuration files and handling local package symlinks instead of writing actual code — or even worse, I would start to code my library and come to the point to test it, only to have my creative process completely interrupted by setup chores.

After doing this for the fifth time, I had enough. I just wanted to build features without losing my momentum. Here is the story of my journey automating this pain away, the technical hurdles of writing a zero-dependency CLI wrapper, and how I plan to transform the setup into an extensible, plugin-based architecture.

The Frustration of Manual Setup

Setting up a modern JavaScript library with a reliable local testing loop is far from trivial these days. To get a clean development environment, I found myself manually repeating these exact steps every single time:

  • Configuring Vite Multi-Entry Mode: Wrestling with Rollup options to ensure both ESM and CJS bundles build correctly with proper subpath exports (e.g., my-lib/modifiers).
  • The Symlink Nightmare: Setting up a local, lightweight test app in the same folder and forcing it to consume the local library package. Doing this dependency-free without npm link breaking down constantly is a massive chore.
  • The Watch Mode Battle: Making sure that changes in the library instantly trigger updates in the test app via a seamless file watcher.
  • Testing Setup: Configuring a quick test runner (index.test.js using Vitest) inside the library structure right away so testing isn't an afterthought.

This repetitive setup felt like factory assembly work. It completely drained my creative energy before the actual project even started.

The Process: Building a Zero-Dependency Wrapper in Pure Node.js

When I decided to write a CLI tool to automate this, I made a strict rule for myself: zero third-party dependencies. I didn’t want my tool to pull down hundreds of megabytes of external packages just to scaffold a project.

This meant skipping trendy CLI frameworks like Clack or Inquirer. Instead, I built the interactive wizard using pure, native Node.js modules like readline, fs, and child_process.

Wrestling with native Node APIs brought some unique engineering challenges:

1. Intercepting Vite's Internal Automation

Instead of reinventing the wheel, I wanted to delegate the foundation of the project scaffolding to Vite's official creator tool via child_process. However, automation workflows hate interactive prompts.

During execution, Vite's initializer tries to take over the terminal control loop and eagerly asks the user if they want to run or install immediately. If a user says "yes", it interrupts my wrapper script, stopping it from performing vital post-scaffolding tasks.

The Solution: I had to structure the tool's documentation and internal flow to explicitly instruct users to answer NO to Vite's immediate run prompt. By preventing Vite from running its install step prematurely, my script safely regains control to execute the clean-up, structure generation, folder renaming, and global symlinking using native fs commands.

2. The Package Manager Dilemma (npm vs. pnpm vs. yarn)

I didn't want to force developers into a single ecosystem. Handling dependencies across npm, pnpm, and yarn meant translating execution commands dynamically under the hood. Managing global symlinks programmatically across three different CLI APIs using raw Node subprocesses required a lot of trial, error, and meticulous string parsing.

The Result: A Lightweight Monorepo Workspace

The final product of this engineering journey is create-lib-workspace. It automatically scaffolds a perfectly wired development monorepo workspace with zero overhead:

my-project/
├── app/                  # Pure static consumer app to see your lib in action
│   ├── index.html        
│   └── main.js           # Directly imports and tests your library locally
└── core-lib/             # Your actual Library (Vite-powered)
    ├── src/
    │   ├── index.js      # Main entry point
    │   ├── index.test.js # Vitest-ready testing structure
    │   └── modifiers/    # Pre-configured secondary entry point sub-module
    ├── package.json      # Hardcoded with proper ESM/CJS exports
    └── vite.config.js    # Pre-configured multi-entry build setup
Enter fullscreen mode Exit fullscreen mode

Key Features of the Architecture:

  • Interactive Wizard: Built entirely on native Node.js APIs to guide you through naming and setup.
  • Multi-Package Manager Support: Seamless compatibility with pnpm, npm, or yarn.
  • Automated Symlinking: Connects the test app to the library completely dependency-free, creating an instant local testing loop.
  • Multi-Entry & Subpaths: The generated boilerplate comes with a modifiers/ subpath pre-configured so you can see exactly how to export separate modules from day one.

See it in Action

You can run it directly right now using executors without a global install:

# Using pnpm
pnpm dlx create-lib-workspace

# Using npm
npx create-lib-workspace

# Using yarn
yarn dlx create-lib-workspace
Enter fullscreen mode Exit fullscreen mode

(Remember: When Vite asks if you want to run or install immediately, hit **NO* so the native script can finish wiring the workspace components together!)*

Once it completes, you simply spin up the library watcher in one terminal window (npm run watch) and the test app server in another (npx vite --open), giving you a real-time, hot-reloading library development environment.

Next Steps: Moving to a True Plugin Architecture

While the current tool solves the immediate boilerplate fatigue using hardcoded internal structures, the next major evolutionary step for this package is extensibility.

Instead of keeping everything bundled inside a single, rigid core configuration, I want to redesign the package to support an isolated plugin ecosystem. The core library package will handle the baseline monorepo lifecycle, while specialized features will be separated into their own dedicated dependencies.

Under this new architecture, if a user wants to build a specialized workspace, they will be able to pull in independent ecosystem plugins. This will allow the community to write and hook their own plugins directly into the core execution engine, transforming it from a static template generator into a modular library workbench.

Lessons Learned & Open Source

Building this taught me a lot about the friction points in modern JavaScript tooling, and reminded me how powerful native Node.js can be without a heavy node_modules folder backing it up.

You can check out the full source code and documentation on GitHub:
🔗 GitHub Repository: create-lib-workspace

I am really curious to hear from other developers: How do you approach designing plugin systems for CLI utilities? What kind of independent plugins would you want to see built for a core workspace setup like this? Let's discuss below!

Top comments (0)