Skip to content

BunPackage

Edgar Mesquita edited this page Feb 9, 2026 · 2 revisions

BunPackage — npm Packages via .csproj

eQuantic.UI allows declaring npm package dependencies directly in .csproj files, keeping the developer experience 100% .NET with zero package.json, zero npm, and zero Node.js.

Quick Start

<!-- MyApp.csproj -->
<ItemGroup>
    <BunPackage Include="tailwindcss-animate" Version="1.0.7" />
</ItemGroup>

That's it. On dotnet build, the SDK automatically:

  1. Generates a temporary package.json in obj/eQuantic/npm/
  2. Installs packages using the embedded Bun runtime
  3. Creates a node_modules symlink for tool resolution
  4. Skips installation on incremental builds

How It Works

Build Pipeline

dotnet build
    │
    ├── 1. ResolveBunPath          ← Extracts embedded Bun from NuGet package
    │
    ├── 2. InstallBunPackages      ← NEW: Installs <BunPackage> items
    │       ├── Creates obj/eQuantic/npm/package.json
    │       ├── Runs: bun add <package>@<version>
    │       └── Symlinks: node_modules → obj/eQuantic/npm/node_modules/
    │
    ├── 3. CompileEQuanticUI       ← C# → TypeScript → JavaScript
    │
    ├── 4. CopyEQuanticRuntime     ← Copies runtime.js
    │
    └── 5. BuildCSS (Tailwind)     ← Generates CSS (can resolve installed packages)

File Layout

MyApp/
├── MyApp.csproj                    ← BunPackage declared here
├── node_modules/                   ← Symlink (gitignored)
│   └── → obj/eQuantic/npm/node_modules/
├── obj/
│   └── eQuantic/
│       └── npm/                    ← Hidden from developer
│           ├── package.json        ← Auto-generated
│           ├── bun.lock            ← Auto-generated
│           └── node_modules/       ← Real packages
└── src/
    └── styles.css                  ← @plugin "tailwindcss-animate" works!

The developer never sees or touches package.json. The node_modules symlink is gitignored and auto-created.

Usage Examples

Tailwind CSS Plugins

The most common use case — installing Tailwind plugins referenced via @plugin in your CSS:

<ItemGroup>
    <BunPackage Include="tailwindcss-animate" Version="1.0.7" />
</ItemGroup>
/* src/styles.css */
@import "tailwindcss";
@plugin "tailwindcss-animate";

Multiple Packages

Multiple BunPackage items are supported. Each runs as a separate bun add:

<ItemGroup>
    <BunPackage Include="tailwindcss-animate" Version="1.0.7" />
    <BunPackage Include="@tailwindcss/typography" Version="0.5.10" />
</ItemGroup>

CSS-Only Packages

Works equally well with CSS-only packages imported via @import:

<ItemGroup>
    <BunPackage Include="tw-animate-css" Version="1.2.5" />
</ItemGroup>
/* src/styles.css */
@import "tailwindcss";
@import "tw-animate-css";

MSBuild Target Details

The InstallBunPackages target lives in Sdk.targets:

<Target Name="InstallBunPackages"
    AfterTargets="ResolveBunPath"
    BeforeTargets="CompileEQuanticUI"
    Condition="'@(BunPackage)' != '' And '$(BunPath)' != ''">

Key Behaviors

Behavior Details
Incremental Skips if obj/eQuantic/npm/node_modules/ already exists
Clean build Runs bun add for each package on first build or after dotnet clean
Cross-platform Symlink on macOS/Linux, mklink /D on Windows
Isolation Packages live in obj/, not project root
No pollution No package.json, bun.lockb, or node_modules committed to git

Forcing Reinstall

To force a fresh install (e.g., after changing versions):

# Option 1: Delete the npm cache
rm -rf obj/eQuantic/npm

# Option 2: Full clean
dotnet clean
dotnet build

Design Decisions

Why not generate package.json at project root?

  • Pollutes the .NET project with npm artifacts
  • Developers might accidentally commit it
  • Confuses IDEs into treating it as a Node.js project
  • Violates the "100% .NET" principle

Why symlink instead of NODE_PATH?

  • Symlinks are universally supported by all tools (Tailwind CLI, PostCSS, etc.)
  • NODE_PATH has inconsistent behavior across tools
  • The symlink is transparent — if a tool looks for node_modules/, it finds them

Why bun add instead of bun install?

  • bun add works with a minimal {"private": true} package.json
  • MSBuild item batching (%(BunPackage.Identity)) naturally maps to individual bun add calls
  • Each package is installed with its exact version

Why not use bun x --install?

  • bun x is for running CLI tools, not for installing libraries
  • Packages referenced in CSS (@plugin, @import) need to exist in node_modules/
  • bun add is the correct semantic for "install this library"

Comparison

Approach package.json needed npm/Node.js needed Developer experience
Traditional npm Yes Yes Must manage two ecosystems
BunPackage No No Pure .NET, zero friction

Troubleshooting

"Could not resolve package" during Tailwind build

Symptom: Tailwind CLI can't find a plugin referenced in CSS

Cause: node_modules symlink missing or broken

Fix:

rm -rf obj/eQuantic/npm node_modules
dotnet build

Packages not updating after version change

Cause: node_modules already exists, so InstallBunPackages skips

Fix:

rm -rf obj/eQuantic/npm
dotnet build

Symlink permission error on Windows

Cause: Creating symlinks on Windows requires elevated permissions or Developer Mode

Fix: Enable Developer Mode in Windows Settings, or run terminal as Administrator

Related Documentation

Clone this wiki locally