An MSBuild task package that integrates Bun - a fast all-in-one JavaScript runtime - into your .NET build process. This package allows you to run Bun commands as part of your .NET project build, enabling JavaScript/TypeScript bundling, minification, and other Bun-powered operations.
- Features
- Installation
- Runtime Options
- Usage
- Example: JavaScript/SCSS Build Script
- Development
- Requirements
- License
- Credits
- Contributing
- ✅ Cross-platform support (Windows, Linux, macOS - x64 and ARM64)
- ✅ Multiple runtime options: embedded packages or on-demand download
- ✅ Easy MSBuild integration
- ✅ Supports modern .NET / .NET Core (no .NET Framework support)
- ✅ Execute any Bun command during build
Install the main MSBuild task package:
dotnet add package Scarlet.Bun.MSBuildOr via Package Manager:
Install-Package Scarlet.Bun.MSBuildNote: The base package does not include any Bun runtime. You must choose a runtime option (see Runtime Options below).
After installing Scarlet.Bun.MSBuild, you need to provide the Bun runtime. There are three approaches to choose from based on your needs:
Download the Bun runtime automatically during build by setting the BunRuntimeDownload property:
<PropertyGroup>
<BunRuntimeDownload>true</BunRuntimeDownload>
<BunVersionDownload>1.3.6</BunVersionDownload> <!-- Optional: specify version -->
<BunRuntimeDirectory>$(MSBuildProjectDirectory)/runtimes</BunRuntimeDirectory>
</PropertyGroup>Advantages:
- Simplest configuration - just set a few properties
- No additional package dependencies to manage
- Each developer/CI agent only downloads the runtime package they need
- Runtime downloaded only once and cached locally for subsequent builds
- Can easily switch Bun versions by changing
BunVersionDownloadproperty
See Using Runtime Download for detailed configuration.
Install only the runtime package(s) you need for your target platform(s):
# For Windows x64
dotnet add package Scarlet.Bun.Runtime.windows-x64-baseline
# For Linux x64
dotnet add package Scarlet.Bun.Runtime.linux-x64-baseline
# For Linux ARM64
dotnet add package Scarlet.Bun.Runtime.linux-aarch64
# For macOS x64
dotnet add package Scarlet.Bun.Runtime.darwin-x64-baseline
# For macOS ARM64 (Apple Silicon)
dotnet add package Scarlet.Bun.Runtime.darwin-aarch64Note: Runtime packages are versioned independently from
Scarlet.Bun.MSBuild. Their package version corresponds to the bundled Bun version (e.g., package version1.3.6contains Bun1.3.6).
Advantages:
- Explicit control over which runtimes are included
- No runtime downloads during build (runtimes come from NuGet packages)
- Works offline
Trade-offs:
- Package with needed runtime version might be missing
Available Runtime Packages:
Use MSBuild conditions to reference only the runtime package matching the current build platform:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<!-- Reference Scarlet.Bun.MSBuild -->
<ItemGroup>
<PackageReference Include="Scarlet.Bun.MSBuild" Version="*" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
</ItemGroup>
<!-- Detect current platform -->
<PropertyGroup>
<IsWindows Condition="'$(OS)' == 'Windows_NT'">true</IsWindows>
<IsLinux Condition="Exists('/proc')">true</IsLinux>
<IsMacOS Condition="Exists('/System/Library/CoreServices/SystemVersion.plist')">true</IsMacOS>
</PropertyGroup>
<!-- Detect architecture -->
<PropertyGroup>
<IsARM64 Condition="'$(PROCESSOR_ARCHITECTURE)' == 'ARM64' OR '$(PROCESSOR_IDENTIFIER)' == 'ARM64'">true</IsARM64>
<IsX64 Condition="'$(PROCESSOR_ARCHITECTURE)' == 'AMD64' OR '$(PROCESSOR_IDENTIFIER)' == 'AMD64'">true</IsX64>
</PropertyGroup>
<!-- Conditionally reference runtime packages based on platform -->
<ItemGroup Condition="'$(IsWindows)' == 'true' AND '$(IsX64)' == 'true'">
<PackageReference Include="Scarlet.Bun.Runtime.windows-x64-baseline" Version="*" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
</ItemGroup>
<ItemGroup Condition="'$(IsLinux)' == 'true' AND '$(IsX64)' == 'true'">
<PackageReference Include="Scarlet.Bun.Runtime.linux-x64-baseline" Version="*" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
</ItemGroup>
<ItemGroup Condition="'$(IsLinux)' == 'true' AND '$(IsARM64)' == 'true'">
<PackageReference Include="Scarlet.Bun.Runtime.linux-aarch64" Version="*" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
</ItemGroup>
<ItemGroup Condition="'$(IsMacOS)' == 'true' AND '$(IsX64)' == 'true'">
<PackageReference Include="Scarlet.Bun.Runtime.darwin-x64-baseline" Version="*" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
</ItemGroup>
<ItemGroup Condition="'$(IsMacOS)' == 'true' AND '$(IsARM64)' == 'true'">
<PackageReference Include="Scarlet.Bun.Runtime.darwin-aarch64" Version="*" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
</ItemGroup>
</Project>NB! You can add them without conditions, but the runtime packages have big size.
Advantages:
- Builds work on any platform without modification
- Each developer/CI agent only downloads the runtime package they need
- No runtime downloads during build (runtimes come from NuGet packages)
- Deterministic builds with version-locked packages
- Works offline
Trade-offs:
- Package with needed runtime version might be missing
- More verbose project file configuration
- Need to maintain platform detection logic
Which option should I choose?
- Use Option 1 (Runtime Download) if you want a multi-platform with the simplest setup and configuration
- Use Option 2 (Single Runtime Package) if you want a single platform and want embedded runtime (without downloads)
- Use Option 3 (Conditional References) if you want a multi-platform and want embedded runtimes (without downloads)
Add the following to your .csproj file to run a Bun script during build:
<!-- Install dependencies -->
<Target Name="BunInstall" BeforeTargets="Build">
<MSBuild Projects="$(MSBuildProjectFullPath)"
Targets="Bun"
Properties="BunCommand=install;BunWorkingDirectory=$(MSBuildProjectDirectory)" />
</Target>
<!-- Build with different command -->
<Target Name="BunBuild" AfterTargets="BunInstall">
<MSBuild Projects="$(MSBuildProjectFullPath)"
Targets="Bun"
Properties="BunCommand=run;BunArguments=build.mjs;BunWorkingDirectory=$(MSBuildProjectDirectory)" />
</Target>If you prefer to download the Bun runtime dynamically instead of using embedded runtimes, you can enable the BunRuntimeDownload option as a global property:
<PropertyGroup>
<BunRuntimeDownload>true</BunRuntimeDownload>
<BunVersionDownload>1.3.6</BunVersionDownload>
<BunRuntimeDirectory>$(MSBuildProjectDirectory)/runtimes</BunRuntimeDirectory>
</PropertyGroup>
<!-- Install dependencies using downloaded runtime -->
<Target Name="BunInstall" BeforeTargets="Build">
<MSBuild Projects="$(MSBuildProjectFullPath)"
Targets="Bun"
Properties="BunCommand=install;BunWorkingDirectory=$(MSBuildProjectDirectory)" />
</Target>When using BunRuntimeDownload=true:
- The
BunRuntimeDirectoryproperty is required and specifies where to download the runtime - The
BunVersionDownloadproperty is optional (defaults to latest version if not specified) - Only the runtime for the current platform will be downloaded
- The runtime is cached in the specified directory and reused on subsequent builds
The BunRunTask supports the following parameters:
| Parameter | Required | Description | Default |
|---|---|---|---|
Command |
Yes | The Bun command to execute (e.g., "run", "install", "build") | - |
Arguments |
No | Arguments to pass to the Bun command | "" |
WorkingDirectory |
No | Working directory for command execution | Current directory |
RuntimeDirectory |
No | Path to the runtime directory containing Bun executables. If not specified, uses the default NuGet package structure. Required when using BunRuntimeDownload. |
null |
TimeoutMilliseconds |
No | Timeout in milliseconds (0 = no timeout) | 0 |
ContinueOnError |
No | Whether to continue build if command fails | false |
BunRuntimeDownload |
No | When true, downloads the Bun runtime from GitHub releases instead of using embedded runtimes | false |
BunVersionDownload |
No | Specific Bun version to download (e.g., "1.3.6"). If not specified, downloads latest version. Only used when BunRuntimeDownload=true. |
latest |
| Parameter | Description |
|---|---|
ExitCode |
The exit code of the executed command |
StandardOutput |
Standard output from the command |
StandardError |
Standard error from the command |
Here's an example build.mjs script that bundles JavaScript and compiles SCSS:
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { minify } from "terser";
import * as sass from "sass";
const scriptFilename = fileURLToPath(import.meta.url);
const scriptDirectory = path.dirname(scriptFilename);
const jsInputDir = path.join(scriptDirectory, "scripts");
const jsOutputFile = path.join(scriptDirectory, "wwwroot/js/bundle.min.js");
const scssInput = path.join(scriptDirectory, "styles/main.scss");
const scssOutput = path.join(scriptDirectory, "wwwroot/css/site.min.css");
async function buildJS() {
console.log("Building JS bundle...");
let files = fs
.readdirSync(jsInputDir)
.filter((f) => f.endsWith(".js"))
.sort();
let code = "";
for (const file of files) {
const filePath = path.join(jsInputDir, file);
console.log("Adding", filePath);
code += fs.readFileSync(filePath, "utf-8") + "\n";
}
const minified = await minify(code);
const outDir = path.dirname(jsOutputFile);
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
fs.writeFileSync(jsOutputFile, minified.code, "utf-8");
console.log("✓ JS bundle created");
}
function buildSCSS() {
console.log("Building SCSS...");
const result = sass.compile(scssInput, {
style: "compressed",
sourceMap: false,
});
fs.mkdirSync(path.dirname(scssOutput), { recursive: true });
fs.writeFileSync(scssOutput, result.css);
console.log("✓ CSS bundle created");
}
await buildJS();
buildSCSS();Don't forget to add dependencies in package.json:
{
"type": "module",
"dependencies": {
"terser": "^5.36.0",
"sass": "^1.83.4"
}
}dotnet buildUnit tests:
dotnet test tests/Scarlet.Bun.MSBuild.Tests/Scarlet.Bun.MSBuild.Tests.csprojIntegration tests:
dotnet test tests/Scarlet.Bun.MSBuild.IntegrationTests/Scarlet.Bun.MSBuild.IntegrationTests.csprojAll tests:
dotnet testdotnet pack src/Scarlet.Bun.MSBuild/Scarlet.Bun.MSBuild.csproj- .NET / .NET Core (no .NET Framework support)
- Supported on Windows, Linux, and macOS
This project is licensed under the MIT License - see the LICENSE file for details.
This package distributes Bun binaries, which include:
- Bun: MIT License - Copyright (c) Jarred Sumner and contributors
- JavaScriptCore/WebKit: LGPL-2.1 License - Bun statically links JavaScriptCore and WebKit components
Per the LGPL-2.1 license requirements, the complete source code and build instructions for Bun (including its statically linked JavaScriptCore components) are available at:
- Bun source: https://github.com/oven-sh/bun
- Patched WebKit/JavaScriptCore: https://github.com/oven-sh/webkit
To relink Bun with modifications to JavaScriptCore:
git clone https://github.com/oven-sh/bun
cd bun
git submodule update --init --recursive
make jsc
zig buildFor more information, see the Bun License Documentation.
- Built by ScarletKuro
- Uses Bun - a fast all-in-one JavaScript runtime
Contributions are welcome! Please feel free to submit a Pull Request.