Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 0 additions & 22 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,28 +57,6 @@ jobs:
- name: Create the package
run: dotnet pack ${{ env.PROJECT }} --no-build --no-restore --configuration Release -o ${{ env.OUTPUT_PATH }}

- name: Publish to NuGet
if: github.event_name == 'release'
working-directory: ${{ env.OUTPUT_PATH }}
run: dotnet nuget push *.nupkg -s https://api.nuget.org/v3/index.json --skip-duplicate --api-key ${{ secrets.NUGET_API_KEY }}

- uses: avakar/tag-and-release@v1
if: github.event_name == 'release'
name: Create Release Tag v${{ steps.package_version.outputs.version }}
id: release
with:
tag_name: v${{ steps.package_version.outputs.version }}
prerelease: ${{ steps.check_prerelease.outputs.prerelease }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Release information for v${{ steps.package_version.outputs.version }}
if: github.event_name == 'release'
run: |
echo Release Id: ${{ steps.release.outputs.id }}
echo Upload URL: ${{ steps.release.outputs.upload_url }}
echo Release URL: ${{ steps.release.outputs.html_url }}

- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
strategy:
fail-fast: false
matrix:
language: [ 'csharp', 'javascript' ]
language: [ 'csharp' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support

Expand Down
5 changes: 2 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ jobs:

- name: Setup .NET Core SDK ${{ matrix.dotnet-version }}
uses: actions/setup-dotnet@v4

with:
dotnet-version: ${{ matrix.dotnet-version }}

Expand Down Expand Up @@ -58,12 +57,12 @@ jobs:
- name: Create the package
run: dotnet pack ${{ env.PROJECT }} --no-build --no-restore --configuration Release -o ${{ env.OUTPUT_PATH }}

# Get a short-lived NuGet API key
# Get a short-lived NuGet API key
- name: NuGet login (OIDC → temp API key)
uses: NuGet/login@v1
id: login
with:
user: ${{ secrets.NUGET_USER }} # Recommended: use a secret like ${{ secrets.NUGET_USER }} for your nuget.org username (profile name), NOT your email address
user: ${{ secrets.NUGET_USER }}

# Push the package
- name: NuGet push
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,4 @@ CodeCoverage/
*.VisualState.xml
TestResult.xml
nunit-*.xml
*.csproj.lscache
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ A production Vite build emits a manifest that maps each logical entry (e.g. `ind

`ViteManifestService` loads this manifest from `{WebRootPath}/.vite/manifest.json`, resolves an entry, prefixes the files with a configurable base path, and renders the tags. The `<vite>` tag helper is a thin wrapper over the service.

Resolution follows the entry's `imports` graph transitively: CSS contributed by shared/split chunks (which Vite records against those chunks, not the entry) is collected and rendered, and each imported chunk is emitted as a `<link rel="modulepreload">` hint alongside the entry `<script>`. Imports are deduplicated and cycle-guarded.

## Setup

Two things wire it up:
Expand Down Expand Up @@ -138,8 +140,9 @@ Returned URLs are prefixed with the supplied base path. Pass `""` as the base pa
| `GetEntry(entry)` | `ViteManifestEntry?` | Resolve + cache lookup (`~/` tolerated). |
| `GetCssFiles()` / `(entry, basePath)` / `(ViteManifestEntry, basePath)` | `IReadOnlyList<string>`| Resolved CSS URLs. |
| `GetJsFile()` / `(entry, basePath)` / `(ViteManifestEntry, basePath)` | `string?` | Resolved JS URL. |
| `GetModulePreloadFiles(ViteManifestEntry, basePath)` | `IReadOnlyList<string>`| Transitively-imported chunk URLs (for modulepreload). |
| `RenderCss(entry, basePath, preload)` | `IHtmlContent` | CSS `<link>` tags only. |
| `RenderJs(entry, basePath)` | `IHtmlContent` | Module `<script>` tag only. |
| `RenderJs(entry, basePath)` | `IHtmlContent` | `<link rel="modulepreload">` for imported chunks + the module `<script>` tag. |
| `RenderEntry(entry, basePath, preloadCss, assets, devServer)` | `IHtmlContent` | Full render; handles dev-server + not-found cases. |

## Development mode
Expand Down
83 changes: 83 additions & 0 deletions Vite.dotnet.Tests/ManifestTestContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Vite.Configuration;
using Vite.Services;

namespace Vite.dotnet.Tests;

/// <summary>
/// Builds a <see cref="ViteManifestService"/> backed by a real manifest.json written
/// into a throwaway web-root directory, then cleans the directory up on dispose.
/// The service reads <c>{WebRootPath}/.vite/manifest.json</c> once and caches it, so
/// each test gets its own isolated context.
/// </summary>
public sealed class ManifestTestContext : IDisposable
{
private readonly string _webRoot;

private ManifestTestContext(string webRoot, ViteManifestService service)
{
_webRoot = webRoot;
Service = service;
}

public ViteManifestService Service { get; }

public static ManifestTestContext Create(string manifestJson, ViteManifestOptions? options = null)
{
var webRoot = Path.Combine(Path.GetTempPath(), "vite-dotnet-tests", Guid.NewGuid().ToString("N"));
var viteDir = Path.Combine(webRoot, ".vite");
Directory.CreateDirectory(viteDir);
File.WriteAllText(Path.Combine(viteDir, "manifest.json"), manifestJson);

var env = new FakeWebHostEnvironment { WebRootPath = webRoot };
var service = new ViteManifestService(
env,
NullLogger<ViteManifestService>.Instance,
Options.Create(options ?? new ViteManifestOptions()));

return new ManifestTestContext(webRoot, service);
}

/// <summary>Creates a context whose manifest file does not exist on disk.</summary>
public static ManifestTestContext CreateWithoutManifest(ViteManifestOptions? options = null)
{
var webRoot = Path.Combine(Path.GetTempPath(), "vite-dotnet-tests", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(webRoot);

var env = new FakeWebHostEnvironment { WebRootPath = webRoot };
var service = new ViteManifestService(
env,
NullLogger<ViteManifestService>.Instance,
Options.Create(options ?? new ViteManifestOptions()));

return new ManifestTestContext(webRoot, service);
}

public void Dispose()
{
try
{
if (Directory.Exists(_webRoot))
{
Directory.Delete(_webRoot, recursive: true);
}
}
catch (IOException)
{
// Best-effort cleanup; a locked temp file must not fail the test run.
}
}

private sealed class FakeWebHostEnvironment : IWebHostEnvironment
{
public string WebRootPath { get; set; } = "";
public IFileProvider WebRootFileProvider { get; set; } = new NullFileProvider();
public string ApplicationName { get; set; } = "Vite.dotnet.Tests";
public string ContentRootPath { get; set; } = "";
public IFileProvider ContentRootFileProvider { get; set; } = new NullFileProvider();
public string EnvironmentName { get; set; } = "Production";
}
}
32 changes: 32 additions & 0 deletions Vite.dotnet.Tests/Vite.dotnet.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Vite.dotnet\Vite.dotnet.csproj" />
</ItemGroup>

</Project>
Loading
Loading