Skip to content

[Blazor] Move Service worker to static web assets in preparation for compressing and fingerprinting everything #39177

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 4, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,6 @@ Copyright (c) .NET Foundation. All rights reserved.
</GeneratePublishWasmBootJsonDependsOn>
</PropertyGroup>

<Import Project="Microsoft.NET.Sdk.BlazorWebAssembly.ServiceWorkerAssetsManifest.targets" Condition="'$(ServiceWorkerAssetsManifest)' != ''" />

<Target Name="_BlazorWasmNativeForBuild" DependsOnTargets="_GatherBlazorFilesToBuild;WasmBuildApp" Condition="'$(UsingBrowserRuntimeWorkload)' == 'true'" />

<Target Name="_GatherBlazorFilesToBuild">
Expand Down
1 change: 1 addition & 0 deletions src/BlazorWasmSdk/Tasks/AssetsManifestFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
namespace Microsoft.NET.Sdk.BlazorWebAssembly
{
#pragma warning disable IDE1006 // Naming Styles
// Only used in legacy builds (5.0 and earlier)
public class AssetsManifestFile
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace Microsoft.NET.Sdk.BlazorWebAssembly
{
// Only used in legacy builds (5.0 and earlier)
public partial class GenerateServiceWorkerAssetsManifest : Task
{
[Required]
Expand Down
5 changes: 3 additions & 2 deletions src/RazorSdk/Razor.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
"src\\RazorSdk\\Tool\\Microsoft.NET.Sdk.Razor.Tool.csproj",
"src\\Resolvers\\Microsoft.DotNet.NativeWrapper\\Microsoft.DotNet.NativeWrapper.csproj",
"src\\StaticWebAssetsSdk\\Tasks\\Microsoft.NET.Sdk.StaticWebAssets.Tasks.csproj",
"src\\WasmSdk\\Tasks\\Microsoft.NET.Sdk.WebAssembly.Tasks.csproj",
"test\\Microsoft.NET.Sdk.BlazorWebAssembly.AoT.Tests\\Microsoft.NET.Sdk.BlazorWebAssembly.AoT.Tests.csproj",
"test\\Microsoft.NET.Sdk.BlazorWebAssembly.Tests\\Microsoft.NET.Sdk.BlazorWebAssembly.Tests.csproj",
"test\\Microsoft.NET.Sdk.Razor.Tests\\Microsoft.NET.Sdk.Razor.Tests.csproj",
"test\\Microsoft.NET.Sdk.Razor.Tool.Tests\\Microsoft.NET.Sdk.Razor.Tool.Tests.csproj",
"test\\Microsoft.NET.TestFramework\\Microsoft.NET.TestFramework.csproj",
"src\\WasmSdk\\Tasks\\Microsoft.NET.Sdk.WebAssembly.Tasks.csproj"
"test\\Microsoft.NET.TestFramework\\Microsoft.NET.TestFramework.csproj"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ Copyright (c) .NET Foundation. All rights reserved.
-->

<Project>
<UsingTask TaskName="Microsoft.NET.Sdk.BlazorWebAssembly.GenerateServiceWorkerAssetsManifest" AssemblyFile="$(_BlazorWebAssemblySdkTasksAssembly)" />

<UsingTask
TaskName="Microsoft.AspNetCore.StaticWebAssets.Tasks.GenerateServiceWorkerAssetsManifest"
AssemblyFile="$(StaticWebAssetsSdkBuildTasksAssembly)"
Condition="'$(StaticWebAssetsSdkBuildTasksAssembly)' != ''" />

<PropertyGroup>
<ResolveStaticWebAssetsInputsDependsOn>$(ResolveStaticWebAssetsInputsDependsOn);_AddServiceWorkerAssets</ResolveStaticWebAssetsInputsDependsOn>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -550,4 +550,6 @@ Copyright (c) .NET Foundation. All rights reserved.

<Import Project="Microsoft.NET.Sdk.StaticWebAssets.Compression.targets" Condition="'$(CompressionEnabled)' == 'true'" />

<Import Project="Microsoft.NET.Sdk.StaticWebAssets.ServiceWorkerAssetsManifest.targets" Condition="'$(ServiceWorkerAssetsManifest)' != ''" />

</Project>
33 changes: 33 additions & 0 deletions src/StaticWebAssetsSdk/Tasks/ServiceWorker/AssetsManifestFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.StaticWebAssets.Tasks
{
#pragma warning disable IDE1006 // Naming Styles
public class AssetsManifestFile
{
/// <summary>
/// Gets or sets a version string.
/// </summary>
public string version { get; set; }

/// <summary>
/// Gets or sets the assets. Keys are URLs; values are base-64-formatted SHA256 content hashes.
/// </summary>
public AssetsManifestFileEntry[] assets { get; set; }
}

public class AssetsManifestFileEntry
{
/// <summary>
/// Gets or sets the asset URL. Normally this will be relative to the application's base href.
/// </summary>
public string url { get; set; }

/// <summary>
/// Gets or sets the file content hash. This should be the base-64-formatted SHA256 value.
/// </summary>
public string hash { get; set; }
}
#pragma warning restore IDE1006 // Naming Styles
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.Serialization.Json;
using System.Security.Cryptography;
using Microsoft.Build.Framework;

namespace Microsoft.AspNetCore.StaticWebAssets.Tasks
{
public partial class GenerateServiceWorkerAssetsManifest : Task
{
[Required]
public ITaskItem[] Assets { get; set; }

public string Version { get; set; }

[Required]
public string OutputPath { get; set; }

[Output]
public string CalculatedVersion { get; set; }

public override bool Execute()
{
using var fileStream = File.Create(OutputPath);
CalculatedVersion = GenerateAssetManifest(fileStream);

return true;
}

internal string GenerateAssetManifest(Stream stream)
{
var assets = new AssetsManifestFileEntry[Assets.Length];
Parallel.For(0, assets.Length, i =>
{
var item = Assets[i];
var hash = item.GetMetadata("FileHash");
var url = item.GetMetadata("AssetUrl");

if (string.IsNullOrEmpty(hash))
{
// Some files that are part of the service worker manifest may not have their hashes previously
// calcualted. Calculate them at this time.
using var sha = SHA256.Create();
using var file = File.OpenRead(item.ItemSpec);
var bytes = sha.ComputeHash(file);

hash = Convert.ToBase64String(bytes);
}

assets[i] = new AssetsManifestFileEntry
{
hash = "sha256-" + hash,
url = url,
};
});

var version = Version;
if (string.IsNullOrEmpty(version))
{
// If a version isn't specified (which is likely the most common case), construct a Version by combining
// the file names + hashes of all the inputs.

var combinedHash = string.Join(
Environment.NewLine,
assets.OrderBy(f => f.url, StringComparer.Ordinal).Select(f => f.hash));

using var sha = SHA256.Create();
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(combinedHash));
version = Convert.ToBase64String(bytes).Substring(0, 8);
}

var data = new AssetsManifestFile
{
version = version,
assets = assets,
};

using var streamWriter = new StreamWriter(stream, Encoding.UTF8, bufferSize: 50, leaveOpen: true);
streamWriter.Write("self.assetsManifest = ");
streamWriter.Flush();

using var jsonWriter = JsonReaderWriterFactory.CreateJsonWriter(stream, Encoding.UTF8, ownsStream: false, indent: true);
new DataContractJsonSerializer(typeof(AssetsManifestFile)).WriteObject(jsonWriter, data);
jsonWriter.Flush();

streamWriter.WriteLine(";");

return version;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<Compile Include="..\Microsoft.NET.Sdk.BlazorWebAssembly.Tests\BootJsonData.cs" Link="BootJsonData.cs" />
<Compile Include="..\Microsoft.NET.Sdk.BlazorWebAssembly.Tests\WasmPublishIntegrationTestBase.cs" Link="WasmPublishIntegrationTestBase.cs" />
<Compile Include="..\Microsoft.NET.Sdk.BlazorWebAssembly.Tests\ServiceWorkerAssert.cs" Link="ServiceWorkerAssert.cs" />
<Compile Include="..\..\src\StaticWebAssetsSdk\Tasks\ServiceWorker\AssetsManifestFile.cs" Link="AssetsManifestFile.cs" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json;
using Microsoft.AspNetCore.StaticWebAssets.Tasks;

namespace Microsoft.NET.Sdk.BlazorWebAssembly.Tests
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,21 @@ public void Build_ServiceWorkerAssetsManifest_Works()
// Arrange
var expectedExtensions = new[] { ".pdb", ".js", ".wasm" };
var testAppName = "BlazorWasmWithLibrary";
var testInstance = CreateAspNetSdkTestAsset(testAppName);
var testInstance = CreateAspNetSdkTestAsset(testAppName)
.WithProjectChanges((p, doc) =>
{
if (Path.GetFileName(p) == "blazorwasm.csproj")
{
var itemGroup = new XElement("PropertyGroup");
var serviceWorkerAssetsManifest = new XElement("ServiceWorkerAssetsManifest", "service-worker-assets.js");
itemGroup.Add(serviceWorkerAssetsManifest);
doc.Root.Add(itemGroup);
}
});

var buildCommand = new BuildCommand(testInstance, "blazorwasm");
buildCommand.WithWorkingDirectory(testInstance.TestRoot);
buildCommand.Execute("/p:ServiceWorkerAssetsManifest=service-worker-assets.js")
buildCommand.Execute()
.Should().Pass();

var buildOutputDirectory = buildCommand.GetOutputDirectory(DefaultTfm).ToString();
Expand Down Expand Up @@ -136,10 +146,20 @@ public void Publish_UpdatesServiceWorkerVersionHash_WhenSourcesChange()
{
// Arrange
var testAppName = "BlazorWasmWithLibrary";
var testInstance = CreateAspNetSdkTestAsset(testAppName);
var testInstance = CreateAspNetSdkTestAsset(testAppName)
.WithProjectChanges((p, doc) =>
{
if (Path.GetFileName(p) == "blazorwasm.csproj")
{
var itemGroup = new XElement("PropertyGroup");
var serviceWorkerAssetsManifest = new XElement("ServiceWorkerAssetsManifest", "service-worker-assets.js");
itemGroup.Add(serviceWorkerAssetsManifest);
doc.Root.Add(itemGroup);
}
});

var publishCommand = new PublishCommand(testInstance, "blazorwasm");
publishCommand.Execute("/p:ServiceWorkerAssetsManifest=service-worker-assets.js").Should().Pass();
publishCommand.Execute().Should().Pass();

var publishOutputDirectory = publishCommand.GetOutputDirectory(DefaultTfm).ToString();

Expand All @@ -158,7 +178,7 @@ public void Publish_UpdatesServiceWorkerVersionHash_WhenSourcesChange()

// Assert
publishCommand = new PublishCommand(testInstance, "blazorwasm");
publishCommand.Execute("/p:ServiceWorkerAssetsManifest=service-worker-assets.js").Should().Pass();
publishCommand.Execute().Should().Pass();

var updatedVersion = File.ReadAllLines(serviceWorkerFile).Last();
var updatedMatch = Regex.Match(updatedVersion, "\\/\\* Manifest version: (.{8}) \\*\\/");
Expand All @@ -176,10 +196,20 @@ public void Publish_DeterministicAcrossBuilds_WhenNoSourcesChange()
{
// Arrange
var testAppName = "BlazorWasmWithLibrary";
var testInstance = CreateAspNetSdkTestAsset(testAppName);
var testInstance = CreateAspNetSdkTestAsset(testAppName)
.WithProjectChanges((p, doc) =>
{
if (Path.GetFileName(p) == "blazorwasm.csproj")
{
var itemGroup = new XElement("PropertyGroup");
var serviceWorkerAssetsManifest = new XElement("ServiceWorkerAssetsManifest", "service-worker-assets.js");
itemGroup.Add(serviceWorkerAssetsManifest);
doc.Root.Add(itemGroup);
}
});

var publishCommand = new PublishCommand(testInstance, "blazorwasm");
publishCommand.Execute("/p:ServiceWorkerAssetsManifest=service-worker-assets.js").Should().Pass();
publishCommand.Execute().Should().Pass();

var publishOutputDirectory = publishCommand.GetOutputDirectory(DefaultTfm).ToString();

Expand All @@ -194,7 +224,7 @@ public void Publish_DeterministicAcrossBuilds_WhenNoSourcesChange()

// Act && Assert
publishCommand = new PublishCommand(Log, Path.Combine(testInstance.TestRoot, "blazorwasm"));
publishCommand.Execute("/p:ServiceWorkerAssetsManifest=service-worker-assets.js").Should().Pass();
publishCommand.Execute().Should().Pass();

var updatedVersion = File.ReadAllLines(serviceWorkerFile).Last();
var updatedMatch = Regex.Match(updatedVersion, "\\/\\* Manifest version: (.{8}) \\*\\/");
Expand Down