MvcFrontendKit.Cli
1.0.0
dotnet tool install --global MvcFrontendKit.Cli --version 1.0.0
dotnet new tool-manifest
dotnet tool install --local MvcFrontendKit.Cli --version 1.0.0
#tool dotnet:?package=MvcFrontendKit.Cli&version=1.0.0
nuke :add-package MvcFrontendKit.Cli --version 1.0.0
MvcFrontendKit
MvcFrontendKit is a Node.js-free frontend bundling toolkit for ASP.NET Core MVC / Razor applications.
It wraps esbuild behind a simple .NET + YAML workflow:
- Dev: Serve raw JS/CSS from
wwwrootwith cache-busting query strings. - Prod: Build fingerprinted bundles (
/dist/js,/dist/css) viaesbuild, driven by a singlefrontend.config.yaml. - No Node / npm required: Uses a native
esbuildbinary under the hood. - Razor-friendly: Provides HTML helpers / tag helpers for layouts, views, partials, and components.
- Spec-driven: Behavior is fully defined in
SPEC.md.
This is for ASP.NET devs who want modern bundling without committing to the full Node toolchain.
Status: v1.0 - Production-ready.
Installation
1. Install the NuGet package
dotnet add package MvcFrontendKit
This is the only package required for both runtime and production builds. It includes:
- Razor HTML helpers for your views
- MSBuild targets that automatically run esbuild during
dotnet publish -c Release - Platform-specific esbuild binaries (no Node.js required)
2. Install the CLI tool (optional)
The CLI provides commands for development compilation, production builds, and diagnostics (dev, watch, build, init, check):
# Global install (available everywhere)
dotnet tool install --global MvcFrontendKit.Cli
# Or local install (per-project, tracked in .config/dotnet-tools.json)
dotnet new tool-manifest # if you don't have one yet
dotnet tool install MvcFrontendKit.Cli
Note: The CLI is not required for production builds. Production bundling is handled automatically by MSBuild targets during
dotnet publish -c Release. However, the CLI is useful for:
- Development: Compile TypeScript/SCSS with
dotnet frontend devordotnet frontend watch- Standalone builds: Build bundles without running MSBuild with
dotnet frontend build(useful for CDN workflows)- Diagnostics: Validate configuration and assets with
dotnet frontend check
3. Generate configuration
# Creates frontend.config.yaml with sensible defaults
dotnet frontend init
If you don't have the CLI installed, you can copy the template from the SPEC.md or let the MSBuild target auto-generate a default config on first build.
4. Register services in Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
// Add MvcFrontendKit services
builder.Services.AddMvcFrontendKit();
var app = builder.Build();
// ... rest of your app configuration
5. Update your layout
Add helpers to your _Layout.cshtml or equivalent:
<head>
<meta charset="utf-8" />
<title>@ViewData["Title"] - MyApp</title>
@* Dev: import map for bare imports, Prod: bundled *@
@Html.FrontendImportMap()
@* Global + view-specific CSS *@
@Html.FrontendGlobalStyles()
@Html.FrontendViewStyles()
</head>
<body>
@RenderBody()
@* Global + view-specific JS *@
@Html.FrontendGlobalScripts()
@Html.FrontendViewScripts()
@RenderSection("Scripts", required: false)
</body>
6. Develop and build
Development (with TypeScript/SCSS):
# Compile TS/SCSS once dotnet frontend dev # Or watch for changes (recommended) dotnet frontend watchThen run your app with
dotnet runordotnet watch run.Development (plain JS/CSS only): Just run
dotnet run- no compilation needed.Production: Run
dotnet publish -c Release. Bundles built automatically towwwroot/distwithfrontend.manifest.json.
Features (high level)
Dev vs Prod flow
- Dev:
- Raw JS/CSS from
wwwroot/jsandwwwroot/css type="module"for JS (usewindow.myFunc = myFuncto expose globals)?v={File.GetLastWriteTimeUtc(path).Tickscache-busting
- Raw JS/CSS from
- Prod:
- Bundled + minified JS/CSS into
/dist/jsand/dist/css - Fingerprinted filenames and a JSON manifest
- Helpers emit
<script>/<link>tags pointing at bundles
- Bundled + minified JS/CSS into
- Dev:
Modes
singlemode — one global JS + one global CSS bundleareasmode — one global bundle plus one bundle per Area (intentionally minimal in v1)viewsmode — per-view bundles driven by conventions + overrides (recommended)
Config-driven (YAML)
frontend.config.yamlcontrols:mode,webRoot,appBasePathglobal.js/global.cssviews.conventionsandviews.overridescomponents(named reusable JS/CSS chunks)cssUrlPolicy(relative vs root-relative URLs,@importhandling)importMap(Dev import map, Prod strategy:bundlevsexternal)cleanDistOnBuild, bundle size warning thresholds, etc.
Components
- Named components (e.g.
datepicker) with optional JS and/or CSS - Dependency graph with cycle detection
- Per-request deduplication — a component used multiple times renders its tags only once
- Named components (e.g.
Import maps for Dev
- Support for
import { ref } from 'vue'style bare imports during Development - Production strategy is explicit (
prodStrategy: bundleorexternal)
- Support for
CSS handling
- Global CSS bundle built via a virtual entry file with ordered
@importstatements - Default policy enforces safe, root-relative URLs like
/img/foo.png - Optional
allowRelativemode for advanced layouts @importresolution (and failure) handled viaesbuild
- Global CSS bundle built via a virtual entry file with ordered
Error-first behavior
- Invalid YAML → build fails with line/column info
- Missing JS/CSS declared in config → build fails
- Invalid or missing manifest in Prod → app startup fails (no silent fallback to Dev mode)
TypeScript & SCSS Support (zero-config)
- TypeScript (
.ts,.tsx) compiled automatically via esbuild - SCSS/Sass (
.scss,.sass) compiled automatically via bundled Dart Sass - Just use the file extensions - no configuration needed
- TypeScript (
See SPEC.md for the full formal specification.
HTML Helpers Reference
MvcFrontendKit provides HTML helpers for rendering script and link tags in your Razor views.
Layout Helpers
Use these in your _Layout.cshtml:
@* Import map for bare module imports (Dev only) *@
@Html.FrontendImportMap()
@* Global CSS (from global.css in config) *@
@Html.FrontendGlobalStyles()
@* View-specific CSS (convention or override) *@
@Html.FrontendViewStyles()
@* Global JS (from global.js in config) *@
@Html.FrontendGlobalScripts()
@* View-specific JS (convention or override) *@
@Html.FrontendViewScripts()
@* Debug panel (renders only in Development) *@
@Html.FrontendDebugInfo()
Component Helper
Use in views to load named components:
@* Load a component defined in frontend.config.yaml *@
@Html.FrontendComponent("datepicker")
@* Load multiple components *@
@Html.FrontendComponent("calendar")
@Html.FrontendComponent("modal")
Components are deduplicated per-request - calling @Html.FrontendComponent("datepicker") multiple times only renders the tags once.
Area Helper
For area-specific bundles (when using areas mode):
@* In Areas/Admin/_ViewStart.cshtml or layout *@
@Html.FrontendAreaScripts("Admin")
@Html.FrontendAreaStyles("Admin")
Helper Output
Development:
<script type="module" src="/js/site.js?v=638123456789"></script>
<link rel="stylesheet" href="/css/site.css?v=638123456789">
Production:
<script src="/dist/js/global.a1b2c3d4.js"></script>
<link rel="stylesheet" href="/dist/css/global.e5f6g7h8.css">
TypeScript & SCSS Support
MvcFrontendKit automatically compiles TypeScript and SCSS files - no configuration required.
TypeScript
Place .ts or .tsx files anywhere you would normally place .js files:
wwwroot/
js/
site.ts # Global TypeScript entry
Home/
Index.ts # Per-view TypeScript
components/
calendar.tsx # Component with JSX
The tool automatically:
- Detects
.ts/.tsxextensions - Applies esbuild's native TypeScript loader
- Compiles to JavaScript during bundling
Example config:
global:
js:
- wwwroot/js/site.ts # TypeScript works directly
views:
overrides:
"Views/Home/Index":
js:
- wwwroot/js/Home/Index.ts
SCSS/Sass
Place .scss or .sass files anywhere you would normally place .css files:
wwwroot/
css/
site.scss # Global SCSS entry
Home/
Index.scss # Per-view SCSS
components/
calendar.scss # Component SCSS
The tool automatically:
- Detects
.scss/.sassextensions - Compiles to CSS using bundled Dart Sass (no Node.js required)
- Passes the compiled CSS to esbuild for bundling and minification
Example config:
global:
css:
- wwwroot/css/site.scss # SCSS works directly
views:
overrides:
"Areas/Admin/Settings/Index":
css:
- wwwroot/css/custom/admin-settings.scss
Mixed Projects
You can mix JavaScript/TypeScript and CSS/SCSS freely:
global:
js:
- wwwroot/js/vendor.js # Plain JavaScript
- wwwroot/js/app.ts # TypeScript
css:
- wwwroot/css/reset.css # Plain CSS
- wwwroot/css/theme.scss # SCSS
Development Workflow
For development, use the dev or watch command to compile TypeScript and SCSS files:
# One-time compilation
dotnet frontend dev
# Watch mode (recommended)
dotnet frontend watch
This compiles your source files to .js and .css next to the originals, which are then served by the development helpers with cache-busting.
Important Notes
- Development vs Production: Use
dotnet frontend devduring development for fast compilation. Production builds (dotnet publish -c Release) handle bundling, minification, and fingerprinting automatically. - Compilation only: This provides TS→JS and SCSS→CSS compilation. For editor features like IntelliSense and type checking, install appropriate tools (TypeScript language server, Sass extension, etc.)
- No tsconfig.json required: esbuild handles TypeScript compilation without a tsconfig. For strict type checking during development, you can add a tsconfig and run
tsc --noEmitseparately. - SCSS imports:
@importand@usestatements in SCSS are resolved relative to the file, and thecssRootdirectory is added to the load path.
Quick Start
Add MvcFrontendKit to your web project (NuGet package) and the CLI tool.
Generate a default config:
dotnet frontend initThis creates a commented
frontend.config.yamlat the project root.Minimal example config:
mode: single webRoot: wwwroot appBasePath: "/" global: js: - wwwroot/js/site.js css: - wwwroot/css/site.cssUse helpers in your layout:
<head> <meta charset="utf-8" /> <title>@ViewData["Title"] - MyApp</title> @* Global CSS (Dev: /css, Prod: /dist/css) *@ @Html.FrontendGlobalStyles() </head> <body> @RenderBody() @* Global JS (Dev: /js, Prod: /dist/js) *@ @Html.FrontendGlobalScripts() @RenderSection("Scripts", required: false) </body>In Development:
- Helpers emit tags for raw files:
/css/site.css?v=.../js/site.js?v=...
- Helpers emit tags for raw files:
In Production:
dotnet frontend check # diagnostic dotnet publish -c Releaseesbuildruns under the hood and builds/dist/js/...and/dist/css/...- A
frontend.manifest.jsonis generated - Helpers read the manifest and switch to bundle URLs.
CLI Commands
The CLI tool provides diagnostics, build utilities, and development compilation:
# Initialize configuration
dotnet frontend init
dotnet frontend init --force # Overwrite existing
# Compile TypeScript/SCSS for development
dotnet frontend dev # One-time compilation
dotnet frontend dev --verbose # Show detailed output
# Watch for changes and recompile
dotnet frontend watch # Continuous watch mode
dotnet frontend watch --verbose # Show detailed output
# Validate configuration and assets
dotnet frontend check # Basic check
dotnet frontend check --verbose # Detailed output
dotnet frontend check --all # Check all discoverable views
dotnet frontend check --view "Areas/Admin/Settings/Index" # Diagnose specific view
# Build bundles (standalone, useful for CDN workflows)
dotnet frontend build # Build to wwwroot/dist/
dotnet frontend build --dry-run # Preview bundles without building
dotnet frontend build --verbose # Show detailed build output
Development Compilation (dev)
The dev command compiles TypeScript and SCSS files to JavaScript and CSS for development (one-time).
# Compile all TypeScript/SCSS files from frontend.config.yaml
dotnet frontend dev
# Show compilation details
dotnet frontend dev --verbose
How it works:
- Reads
frontend.config.yamlto find all TypeScript (.ts,.tsx) and SCSS (.scss,.sass) files - Compiles TypeScript to JavaScript using esbuild (fast, native compilation)
- Compiles SCSS to CSS using Dart Sass (bundled, no Node.js required)
- Output files are placed next to source files (e.g.,
site.ts→site.js) - Source maps are generated for debugging
Watch Mode (watch)
The watch command compiles and then monitors for changes:
# Start watching for changes
dotnet frontend watch
# With detailed output
dotnet frontend watch --verbose
- Monitors your
jsRootandcssRootdirectories for changes - Automatically recompiles when
.ts,.tsx,.scss, or.sassfiles change - Shows compilation results in real-time
- Press
Ctrl+Cto stop watching
Example workflow:
# Terminal 1: Start your ASP.NET app
dotnet watch run
# Terminal 2: Watch and compile frontend assets
dotnet frontend watch
This gives you:
- Hot reload for C# code (via
dotnet watch) - Auto-compilation for TypeScript/SCSS (via
frontend watch) - Browser refresh to see changes
View Diagnostics (--view and --all)
When a view's JS/CSS isn't loading, use diagnostics to understand why:
dotnet frontend check --view "Areas/Admin/Settings/Index"
Output shows:
- Resolution method (explicit override vs convention)
- Matched convention pattern
- Files found/expected
- Import validation results
- What would be bundled in production
Import Path Validation
The check command automatically validates relative imports in JS files:
// These imports are validated:
import { helper } from './utils.js';
import shared from '../shared/common.js';
// Broken imports are reported:
// ✗ Broken import in index.js: ./missing-file.js
Use --skip-imports to disable import validation.
Build Preview (--dry-run)
Preview what will be bundled without actually building:
dotnet frontend build --dry-run
Shows:
- All bundles that would be created
- Input files and sizes
- Estimated output sizes after minification
Configuration Reference
The frontend.config.yaml file controls all bundling behavior. Here's a complete reference:
Core Settings
# Schema version (for future migrations)
configVersion: 1
# Bundling mode: "single", "areas", or "views" (recommended)
mode: views
# Base path for URL generation (for sub-path deployments)
appBasePath: "/"
# Directory paths
webRoot: wwwroot # Web root directory
jsRoot: wwwroot/js # JavaScript source directory
cssRoot: wwwroot/css # CSS source directory
libRoot: wwwroot/lib # Third-party libraries
# Production output paths
distUrlRoot: /dist # URL prefix for bundles
distJsSubPath: js # Subdirectory for JS bundles
distCssSubPath: css # Subdirectory for CSS bundles
Global Assets
global:
js:
- wwwroot/js/site.ts # Global JS (TypeScript supported)
- wwwroot/js/vendor.js # Plain JS also works
css:
- wwwroot/css/site.scss # Global CSS (SCSS supported)
- wwwroot/css/reset.css # Plain CSS also works
Views Configuration
views:
# Auto-discovery by convention
jsAutoLinkByConvention: true
cssAutoLinkByConvention: true
# JS file conventions (tried in order)
conventions:
- viewPattern: "Views/{controller}/{action}"
scriptBasePattern: "wwwroot/js/{controller}/{action}"
# CSS file conventions
cssConventions:
- viewPattern: "Views/{controller}/{action}"
cssPattern: "wwwroot/css/{controller}/{action}"
# Explicit overrides (bypass conventions)
overrides:
"Views/Home/Index":
js:
- wwwroot/js/home/custom-index.ts
css:
- wwwroot/css/home/custom-index.scss
Components
components:
datepicker:
js:
- wwwroot/js/components/datepicker.ts
css:
- wwwroot/css/components/datepicker.scss
depends:
- calendar # Load calendar component first
calendar:
js:
- wwwroot/js/components/calendar.ts
css:
- wwwroot/css/components/calendar.scss
Use components in views with @Html.FrontendComponent("datepicker").
Areas
areas:
Admin:
js:
- wwwroot/js/Areas/Admin/admin.ts
css:
- wwwroot/css/Areas/Admin/admin.scss
isolate: false # When true, global JS/CSS is NOT emitted for this area
Area Isolation:
When isolate: true is set for an area, @Html.FrontendGlobalStyles() and @Html.FrontendGlobalScripts() will return empty for views in that area. This is useful for areas with completely different styling or JavaScript frameworks (e.g., a separate admin panel with its own design system).
areas:
Admin:
isolate: true # Global assets skipped for Admin area
js:
- wwwroot/js/Areas/Admin/admin-framework.ts
css:
- wwwroot/css/Areas/Admin/admin-design-system.scss
Import Maps (Dev)
importMap:
enabled: true
prodStrategy: bundle # "bundle" (default) or "external"
entries:
vue: /lib/vue/vue.esm-browser.js
lodash: /lib/lodash-es/lodash.js
Allows bare imports in development:
import { ref } from 'vue';
CSS URL Policy
cssUrlPolicy:
# Fail build if relative URLs (../img) found in CSS
allowRelative: false
# Resolve @import statements
resolveImports: true
Output Options
output:
cleanDistOnBuild: true # Remove dist folder before build
Esbuild Options
esbuild:
jsTarget: es2020 # JavaScript target version
jsFormat: iife # "iife" (default) or "esm"
jsSourcemap: true # Generate source maps
cssSourcemap: true # Generate CSS source maps
jsFormat options:
iife(default): Wraps bundle in(function(){...})();- works with regular<script>tagsesm: Preserves ES module syntax - requirestype="module"on script tags
CDN Configuration
cdn:
baseUrl: "https://cdn.example.com/assets" # CDN base URL for asset URLs
enableSri: false # Enable SRI hashes (future)
When cdn.baseUrl is set:
- Manifest URLs are prefixed with the CDN base URL
- Example:
/dist/js/global.abc123.jsbecomeshttps://cdn.example.com/assets/dist/js/global.abc123.js
CDN Workflow:
- Run
dotnet frontend buildto generate bundles inwwwroot/dist/ - Upload the
dist/folder contents to your CDN - Set
cdn.baseUrlin config to match your CDN URL - Rebuild to update manifest with CDN URLs
- Deploy your app - HTML helpers will emit CDN URLs
Debugging
MvcFrontendKit provides two debugging mechanisms to help troubleshoot asset resolution issues during development.
HTML Debug Comments (automatic)
In the Development environment, all HTML helpers automatically emit HTML comments showing resolution details:
<script type="module" src="/js/Home/Index.js?v=638123456789"></script>
These comments show:
- Helper name: Which helper generated the output
- Mode: Development (raw files) or Production (manifest)
- View key: The resolved view key (e.g.,
Views/Home/Index) - Resolution method: Override (from config) or Convention (auto-discovered)
- File list: All files being loaded
Note: Debug comments are automatically suppressed in Production—no configuration needed.
Debug Panel (@Html.FrontendDebugInfo())
For a visual debug overlay, add @Html.FrontendDebugInfo() to your layout:
<body>
@RenderBody()
@Html.FrontendGlobalScripts()
@Html.FrontendViewScripts()
@* Shows debug panel in Development environment only *@
@Html.FrontendDebugInfo()
</body>
The debug panel displays:
- Current view key
- Manifest key
- Resolved JS/CSS files
- Whether using production manifest or development mode
Note: The helper renders nothing in Production environment, so it's safe to leave in your layout.
Upgrading
MvcFrontendKit automatically handles most upgrade scenarios. When you update to a new version:
Version marker detection: The tool writes a
.mvcfrontendkit-versionfile towwwroot/dist/. On each build, it checks if the version has changed and automatically performs a clean build if needed.SDK cache cleanup: The build process automatically cleans the ASP.NET SDK's static web assets compression cache (
obj/**/compressed/) to prevent stale reference errors.
Manual clean (if needed)
In rare cases where you encounter build errors about missing fingerprinted files, run:
# Full clean
dotnet clean -c Release
rm -rf wwwroot/dist
rm -rf obj/*/compressed
# Then rebuild
dotnet publish -c Release
gitignore recommendations
Add the version marker to your .gitignore:
wwwroot/dist/
wwwroot/frontend.manifest.json
Repository layout
MvcFrontendKit/
LICENSE
README.md
SPEC.md # this repo’s internal design spec
.gitignore
src/
MvcFrontendKit/ # core library (config + manifest + helpers)
MvcFrontendKit.Cli/ # CLI: 'dotnet frontend'
tests/
MvcFrontendKit.Tests/
Contributing
The behavior of this project is defined in SPEC.md.
- Please read
SPEC.mdbefore proposing changes to core behavior. - For new features, open an issue and describe:
- Your scenario
- How it fits into existing modes (
single,areas,views) - Any config changes you propose
Pull requests should:
- Keep public APIs consistent with the spec
- Include tests where it makes sense
Acknowledgments
MvcFrontendKit is built on the shoulders of these excellent open-source projects:
- esbuild by Evan Wallace (GitHub) - An extremely fast JavaScript/TypeScript bundler
- Dart Sass by Google and the Sass team (GitHub) - The reference implementation of Sass
Both tools are bundled as native binaries, requiring no Node.js installation.
See THIRD-PARTY-NOTICES.md for full license information.
License
This project is licensed under the MIT License - see the LICENSE.md file for details
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
| Version | Downloads | Last Updated | |
|---|---|---|---|
| 1.0.0 | 111 | 12/6/2025 | |
| 1.0.0-preview.29 | 347 | 11/30/2025 | |
| 1.0.0-preview.28 | 365 | 11/30/2025 | |
| 1.0.0-preview.26 | 230 | 11/29/2025 | |
| 1.0.0-preview.24 | 92 | 11/29/2025 | |
| 1.0.0-preview.23 | 98 | 11/29/2025 | |
| 1.0.0-preview.22 | 95 | 11/29/2025 | |
| 1.0.0-preview.21 | 91 | 11/28/2025 | |
| 1.0.0-preview.20 | 75 | 11/28/2025 | |
| 1.0.0-preview.19 | 85 | 11/28/2025 | |
| 1.0.0-preview.18 | 91 | 11/28/2025 | |
| 1.0.0-preview.17 | 97 | 11/28/2025 | |
| 1.0.0-preview.15 | 101 | 11/28/2025 | |
| 1.0.0-preview.14 | 110 | 11/28/2025 | |
| 1.0.0-preview.13 | 117 | 11/28/2025 | |
| 1.0.0-preview.12 | 141 | 11/28/2025 | |
| 1.0.0-preview.11 | 162 | 11/28/2025 | |
| 1.0.0-preview.10 | 170 | 11/28/2025 | |
| 1.0.0-preview.9 | 172 | 11/28/2025 | |
| 1.0.0-preview.5 | 174 | 11/28/2025 | |
| 1.0.0-preview.4 | 192 | 11/27/2025 | |
| 1.0.0-preview.3 | 195 | 11/27/2025 | |
| 1.0.0-preview.2 | 189 | 11/27/2025 | |
| 1.0.0-preview.1 | 193 | 11/27/2025 |