Skip to content

Commit 77911cb

Browse files
authored
[Blazor] Move Service worker to static web assets in preparation for compressing and fingerprinting everything (#39177)
1 parent b888a7d commit 77911cb

11 files changed

+177
-13
lines changed

src/BlazorWasmSdk/Targets/Microsoft.NET.Sdk.BlazorWebAssembly.6_0.targets

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,6 @@ Copyright (c) .NET Foundation. All rights reserved.
8484
</GeneratePublishWasmBootJsonDependsOn>
8585
</PropertyGroup>
8686

87-
<Import Project="Microsoft.NET.Sdk.BlazorWebAssembly.ServiceWorkerAssetsManifest.targets" Condition="'$(ServiceWorkerAssetsManifest)' != ''" />
88-
8987
<Target Name="_BlazorWasmNativeForBuild" DependsOnTargets="_GatherBlazorFilesToBuild;WasmBuildApp" Condition="'$(UsingBrowserRuntimeWorkload)' == 'true'" />
9088

9189
<Target Name="_GatherBlazorFilesToBuild">

src/BlazorWasmSdk/Tasks/AssetsManifestFile.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
namespace Microsoft.NET.Sdk.BlazorWebAssembly
55
{
66
#pragma warning disable IDE1006 // Naming Styles
7+
// Only used in legacy builds (5.0 and earlier)
78
public class AssetsManifestFile
89
{
910
/// <summary>

src/BlazorWasmSdk/Tasks/GenerateServiceWorkerAssetsManifest.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace Microsoft.NET.Sdk.BlazorWebAssembly
99
{
10+
// Only used in legacy builds (5.0 and earlier)
1011
public partial class GenerateServiceWorkerAssetsManifest : Task
1112
{
1213
[Required]

src/RazorSdk/Razor.slnf

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@
1010
"src\\RazorSdk\\Tool\\Microsoft.NET.Sdk.Razor.Tool.csproj",
1111
"src\\Resolvers\\Microsoft.DotNet.NativeWrapper\\Microsoft.DotNet.NativeWrapper.csproj",
1212
"src\\StaticWebAssetsSdk\\Tasks\\Microsoft.NET.Sdk.StaticWebAssets.Tasks.csproj",
13+
"src\\WasmSdk\\Tasks\\Microsoft.NET.Sdk.WebAssembly.Tasks.csproj",
14+
"test\\Microsoft.NET.Sdk.BlazorWebAssembly.AoT.Tests\\Microsoft.NET.Sdk.BlazorWebAssembly.AoT.Tests.csproj",
1315
"test\\Microsoft.NET.Sdk.BlazorWebAssembly.Tests\\Microsoft.NET.Sdk.BlazorWebAssembly.Tests.csproj",
1416
"test\\Microsoft.NET.Sdk.Razor.Tests\\Microsoft.NET.Sdk.Razor.Tests.csproj",
1517
"test\\Microsoft.NET.Sdk.Razor.Tool.Tests\\Microsoft.NET.Sdk.Razor.Tool.Tests.csproj",
16-
"test\\Microsoft.NET.TestFramework\\Microsoft.NET.TestFramework.csproj",
17-
"src\\WasmSdk\\Tasks\\Microsoft.NET.Sdk.WebAssembly.Tasks.csproj"
18+
"test\\Microsoft.NET.TestFramework\\Microsoft.NET.TestFramework.csproj"
1819
]
1920
}
2021
}

src/BlazorWasmSdk/Targets/Microsoft.NET.Sdk.BlazorWebAssembly.ServiceWorkerAssetsManifest.targets renamed to src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.ServiceWorkerAssetsManifest.targets

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ Copyright (c) .NET Foundation. All rights reserved.
1111
-->
1212

1313
<Project>
14-
<UsingTask TaskName="Microsoft.NET.Sdk.BlazorWebAssembly.GenerateServiceWorkerAssetsManifest" AssemblyFile="$(_BlazorWebAssemblySdkTasksAssembly)" />
14+
15+
<UsingTask
16+
TaskName="Microsoft.AspNetCore.StaticWebAssets.Tasks.GenerateServiceWorkerAssetsManifest"
17+
AssemblyFile="$(StaticWebAssetsSdkBuildTasksAssembly)"
18+
Condition="'$(StaticWebAssetsSdkBuildTasksAssembly)' != ''" />
1519

1620
<PropertyGroup>
1721
<ResolveStaticWebAssetsInputsDependsOn>$(ResolveStaticWebAssetsInputsDependsOn);_AddServiceWorkerAssets</ResolveStaticWebAssetsInputsDependsOn>

src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.targets

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,4 +550,6 @@ Copyright (c) .NET Foundation. All rights reserved.
550550

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

553+
<Import Project="Microsoft.NET.Sdk.StaticWebAssets.ServiceWorkerAssetsManifest.targets" Condition="'$(ServiceWorkerAssetsManifest)' != ''" />
554+
553555
</Project>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.StaticWebAssets.Tasks
5+
{
6+
#pragma warning disable IDE1006 // Naming Styles
7+
public class AssetsManifestFile
8+
{
9+
/// <summary>
10+
/// Gets or sets a version string.
11+
/// </summary>
12+
public string version { get; set; }
13+
14+
/// <summary>
15+
/// Gets or sets the assets. Keys are URLs; values are base-64-formatted SHA256 content hashes.
16+
/// </summary>
17+
public AssetsManifestFileEntry[] assets { get; set; }
18+
}
19+
20+
public class AssetsManifestFileEntry
21+
{
22+
/// <summary>
23+
/// Gets or sets the asset URL. Normally this will be relative to the application's base href.
24+
/// </summary>
25+
public string url { get; set; }
26+
27+
/// <summary>
28+
/// Gets or sets the file content hash. This should be the base-64-formatted SHA256 value.
29+
/// </summary>
30+
public string hash { get; set; }
31+
}
32+
#pragma warning restore IDE1006 // Naming Styles
33+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Runtime.Serialization.Json;
5+
using System.Security.Cryptography;
6+
using Microsoft.Build.Framework;
7+
8+
namespace Microsoft.AspNetCore.StaticWebAssets.Tasks
9+
{
10+
public partial class GenerateServiceWorkerAssetsManifest : Task
11+
{
12+
[Required]
13+
public ITaskItem[] Assets { get; set; }
14+
15+
public string Version { get; set; }
16+
17+
[Required]
18+
public string OutputPath { get; set; }
19+
20+
[Output]
21+
public string CalculatedVersion { get; set; }
22+
23+
public override bool Execute()
24+
{
25+
using var fileStream = File.Create(OutputPath);
26+
CalculatedVersion = GenerateAssetManifest(fileStream);
27+
28+
return true;
29+
}
30+
31+
internal string GenerateAssetManifest(Stream stream)
32+
{
33+
var assets = new AssetsManifestFileEntry[Assets.Length];
34+
Parallel.For(0, assets.Length, i =>
35+
{
36+
var item = Assets[i];
37+
var hash = item.GetMetadata("FileHash");
38+
var url = item.GetMetadata("AssetUrl");
39+
40+
if (string.IsNullOrEmpty(hash))
41+
{
42+
// Some files that are part of the service worker manifest may not have their hashes previously
43+
// calcualted. Calculate them at this time.
44+
using var sha = SHA256.Create();
45+
using var file = File.OpenRead(item.ItemSpec);
46+
var bytes = sha.ComputeHash(file);
47+
48+
hash = Convert.ToBase64String(bytes);
49+
}
50+
51+
assets[i] = new AssetsManifestFileEntry
52+
{
53+
hash = "sha256-" + hash,
54+
url = url,
55+
};
56+
});
57+
58+
var version = Version;
59+
if (string.IsNullOrEmpty(version))
60+
{
61+
// If a version isn't specified (which is likely the most common case), construct a Version by combining
62+
// the file names + hashes of all the inputs.
63+
64+
var combinedHash = string.Join(
65+
Environment.NewLine,
66+
assets.OrderBy(f => f.url, StringComparer.Ordinal).Select(f => f.hash));
67+
68+
using var sha = SHA256.Create();
69+
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(combinedHash));
70+
version = Convert.ToBase64String(bytes).Substring(0, 8);
71+
}
72+
73+
var data = new AssetsManifestFile
74+
{
75+
version = version,
76+
assets = assets,
77+
};
78+
79+
using var streamWriter = new StreamWriter(stream, Encoding.UTF8, bufferSize: 50, leaveOpen: true);
80+
streamWriter.Write("self.assetsManifest = ");
81+
streamWriter.Flush();
82+
83+
using var jsonWriter = JsonReaderWriterFactory.CreateJsonWriter(stream, Encoding.UTF8, ownsStream: false, indent: true);
84+
new DataContractJsonSerializer(typeof(AssetsManifestFile)).WriteObject(jsonWriter, data);
85+
jsonWriter.Flush();
86+
87+
streamWriter.WriteLine(";");
88+
89+
return version;
90+
}
91+
}
92+
}

test/Microsoft.NET.Sdk.BlazorWebAssembly.AoT.Tests/Microsoft.NET.Sdk.BlazorWebAssembly.AoT.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
<Compile Include="..\Microsoft.NET.Sdk.BlazorWebAssembly.Tests\BootJsonData.cs" Link="BootJsonData.cs" />
4747
<Compile Include="..\Microsoft.NET.Sdk.BlazorWebAssembly.Tests\WasmPublishIntegrationTestBase.cs" Link="WasmPublishIntegrationTestBase.cs" />
4848
<Compile Include="..\Microsoft.NET.Sdk.BlazorWebAssembly.Tests\ServiceWorkerAssert.cs" Link="ServiceWorkerAssert.cs" />
49+
<Compile Include="..\..\src\StaticWebAssetsSdk\Tasks\ServiceWorker\AssetsManifestFile.cs" Link="AssetsManifestFile.cs" />
4950
</ItemGroup>
5051

5152
<ItemGroup>

test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/ServiceWorkerAssert.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Text.Json;
5+
using Microsoft.AspNetCore.StaticWebAssets.Tasks;
56

67
namespace Microsoft.NET.Sdk.BlazorWebAssembly.Tests
78
{

test/Microsoft.NET.Sdk.BlazorWebAssembly.Tests/WasmPwaManifestTests.cs

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,21 @@ public void Build_ServiceWorkerAssetsManifest_Works()
1818
// Arrange
1919
var expectedExtensions = new[] { ".pdb", ".js", ".wasm" };
2020
var testAppName = "BlazorWasmWithLibrary";
21-
var testInstance = CreateAspNetSdkTestAsset(testAppName);
21+
var testInstance = CreateAspNetSdkTestAsset(testAppName)
22+
.WithProjectChanges((p, doc) =>
23+
{
24+
if (Path.GetFileName(p) == "blazorwasm.csproj")
25+
{
26+
var itemGroup = new XElement("PropertyGroup");
27+
var serviceWorkerAssetsManifest = new XElement("ServiceWorkerAssetsManifest", "service-worker-assets.js");
28+
itemGroup.Add(serviceWorkerAssetsManifest);
29+
doc.Root.Add(itemGroup);
30+
}
31+
});
2232

2333
var buildCommand = new BuildCommand(testInstance, "blazorwasm");
2434
buildCommand.WithWorkingDirectory(testInstance.TestRoot);
25-
buildCommand.Execute("/p:ServiceWorkerAssetsManifest=service-worker-assets.js")
35+
buildCommand.Execute()
2636
.Should().Pass();
2737

2838
var buildOutputDirectory = buildCommand.GetOutputDirectory(DefaultTfm).ToString();
@@ -136,10 +146,20 @@ public void Publish_UpdatesServiceWorkerVersionHash_WhenSourcesChange()
136146
{
137147
// Arrange
138148
var testAppName = "BlazorWasmWithLibrary";
139-
var testInstance = CreateAspNetSdkTestAsset(testAppName);
149+
var testInstance = CreateAspNetSdkTestAsset(testAppName)
150+
.WithProjectChanges((p, doc) =>
151+
{
152+
if (Path.GetFileName(p) == "blazorwasm.csproj")
153+
{
154+
var itemGroup = new XElement("PropertyGroup");
155+
var serviceWorkerAssetsManifest = new XElement("ServiceWorkerAssetsManifest", "service-worker-assets.js");
156+
itemGroup.Add(serviceWorkerAssetsManifest);
157+
doc.Root.Add(itemGroup);
158+
}
159+
});
140160

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

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

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

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

163183
var updatedVersion = File.ReadAllLines(serviceWorkerFile).Last();
164184
var updatedMatch = Regex.Match(updatedVersion, "\\/\\* Manifest version: (.{8}) \\*\\/");
@@ -176,10 +196,20 @@ public void Publish_DeterministicAcrossBuilds_WhenNoSourcesChange()
176196
{
177197
// Arrange
178198
var testAppName = "BlazorWasmWithLibrary";
179-
var testInstance = CreateAspNetSdkTestAsset(testAppName);
199+
var testInstance = CreateAspNetSdkTestAsset(testAppName)
200+
.WithProjectChanges((p, doc) =>
201+
{
202+
if (Path.GetFileName(p) == "blazorwasm.csproj")
203+
{
204+
var itemGroup = new XElement("PropertyGroup");
205+
var serviceWorkerAssetsManifest = new XElement("ServiceWorkerAssetsManifest", "service-worker-assets.js");
206+
itemGroup.Add(serviceWorkerAssetsManifest);
207+
doc.Root.Add(itemGroup);
208+
}
209+
});
180210

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

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

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

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

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

0 commit comments

Comments
 (0)