diff --git a/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.HtmlImportMap.targets b/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.HtmlImportMap.targets index e7280878381d..579338938557 100644 --- a/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.HtmlImportMap.targets +++ b/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.HtmlImportMap.targets @@ -97,7 +97,9 @@ Copyright (c) .NET Foundation. All rights reserved. @@ -165,7 +167,9 @@ Copyright (c) .NET Foundation. All rights reserved. diff --git a/src/StaticWebAssetsSdk/Tasks/WriteImportMapToHtml.cs b/src/StaticWebAssetsSdk/Tasks/WriteImportMapToHtml.cs index 1b9cf0f4316a..a7478f89018c 100644 --- a/src/StaticWebAssetsSdk/Tasks/WriteImportMapToHtml.cs +++ b/src/StaticWebAssetsSdk/Tasks/WriteImportMapToHtml.cs @@ -13,9 +13,15 @@ namespace Microsoft.AspNetCore.StaticWebAssets.Tasks; public partial class WriteImportMapToHtml : Task { + [Required] + public ITaskItem[] Assets { get; set; } = []; + [Required] public ITaskItem[] Endpoints { get; set; } = []; + [Required] + public bool IncludeOnlyHardFingerprintedModules { get; set; } + [Required] public string OutputPath { get; set; } = string.Empty; @@ -73,7 +79,7 @@ public override bool Execute() outputContent = _assetsRegex.Replace(outputContent, e => { string assetPath = e.Groups[1].Value + e.Groups[3].Value; - string fingerprintedAssetPath = urlMappings.TryGetValue(assetPath, out var value) ? value.Url : assetPath; + string fingerprintedAssetPath = GetFingerprintedAssetPath(urlMappings, assetPath); Log.LogMessage("Replacing asset '{0}' with fingerprinted version '{1}'", assetPath, fingerprintedAssetPath); return "\"" + fingerprintedAssetPath + "\""; }); @@ -99,7 +105,17 @@ public override bool Execute() return true; } - internal static List CreateResourcesFromEndpoints(IEnumerable endpoints) + private string GetFingerprintedAssetPath(Dictionary urlMappings, string assetPath) + { + if (urlMappings.TryGetValue(assetPath, out var asset) && (!IncludeOnlyHardFingerprintedModules || asset.IsHardFingerprinted)) + { + return asset.Url; + } + + return assetPath; + } + + internal List CreateResourcesFromEndpoints(IEnumerable endpoints) { var resources = new List(); @@ -107,17 +123,17 @@ internal static List CreateResourcesFromEndpoints(IEnumerable CreateResourcesFromEndpoints(IEnumerable a.ItemSpec == endpoint.AssetFile); + if (asset != null) + { + isHardFingerprinted = asset.GetMetadata("RelativePath").Contains("#[.{fingerprint}]!"); + } + + resources.Add(new ResourceAsset(endpoint.Route, label, integrity, isHardFingerprinted)); } } return resources; } - private static ImportMap CreateImportMapFromResources(List assets) + private ImportMap CreateImportMapFromResources(List assets) { Dictionary? imports = new(); - Dictionary>? scopes = new(); ; + Dictionary>? scopes = new(); Dictionary? integrity = new(); foreach (var asset in assets) { + if (IncludeOnlyHardFingerprintedModules && !asset.IsHardFingerprinted) + { + continue; + } + if (asset.Integrity != null) { integrity ??= []; integrity[$"./{asset.Url}"] = asset.Integrity; } + // Only fingerprinted assets have label if (asset.Label != null) { imports ??= []; @@ -178,11 +207,12 @@ private static Dictionary GroupResourcesByLabel(List imports, Dictionary> scopes, Dictionary integrity) diff --git a/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssetsFingerprintingTest.cs b/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssetsFingerprintingTest.cs index 85612bd71224..feb337d3c2c2 100644 --- a/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssetsFingerprintingTest.cs +++ b/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssetsFingerprintingTest.cs @@ -46,70 +46,105 @@ public void Build_FingerprintsContent_WhenEnabled() AssertBuildAssets(manifest1, outputPath, intermediateOutputPath); } - public static TheoryData WriteImportMapToHtmlData => new TheoryData + public static TheoryData WriteImportMapToHtmlData => new TheoryData { - { "VanillaWasm", "main.js", null }, - { "BlazorWasmMinimal", "_framework/blazor.webassembly.js", "_framework/blazor.webassembly#[.{fingerprint}].js" } + { "VanillaWasm", "main.js", "main#[.{fingerprint}].js", true, true }, + { "VanillaWasm", "main.js", null, false, false }, + { "BlazorWasmMinimal", "_framework/blazor.webassembly.js", "_framework/blazor.webassembly#[.{fingerprint}].js", false, true } }; [Theory] [MemberData(nameof(WriteImportMapToHtmlData))] - public void Build_WriteImportMapToHtml(string testAsset, string scriptPath, string scriptPathWithFingerprintPattern) + public void Build_WriteImportMapToHtml(string testAsset, string scriptPath, string scriptPathWithFingerprintPattern, bool fingerprintUserJavascriptAssets, bool expectFingerprintOnScript) { ProjectDirectory = CreateAspNetSdkTestAsset(testAsset); ReplaceStringInIndexHtml(ProjectDirectory, scriptPath, scriptPathWithFingerprintPattern); + FingerprintUserJavascriptAssets(fingerprintUserJavascriptAssets); var build = CreateBuildCommand(ProjectDirectory); - ExecuteCommand(build, "-p:WriteImportMapToHtml=true").Should().Pass(); + ExecuteCommand(build, "-p:WriteImportMapToHtml=true", $"-p:FingerprintUserJavascriptAssets={fingerprintUserJavascriptAssets}").Should().Pass(); var intermediateOutputPath = build.GetIntermediateDirectory(DefaultTfm, "Debug").ToString(); var indexHtmlPath = Directory.EnumerateFiles(Path.Combine(intermediateOutputPath, "staticwebassets", "importmaphtml", "build"), "*.html").Single(); var endpointsManifestPath = Path.Combine(intermediateOutputPath, $"staticwebassets.build.endpoints.json"); - AssertImportMapInHtml(indexHtmlPath, endpointsManifestPath, scriptPath); + AssertImportMapInHtml(indexHtmlPath, endpointsManifestPath, scriptPath, expectFingerprintOnScript: expectFingerprintOnScript); } [Theory] [MemberData(nameof(WriteImportMapToHtmlData))] - public void Publish_WriteImportMapToHtml(string testAsset, string scriptPath, string scriptPathWithFingerprintPattern) + public void Publish_WriteImportMapToHtml(string testAsset, string scriptPath, string scriptPathWithFingerprintPattern, bool fingerprintUserJavascriptAssets, bool expectFingerprintOnScript) { ProjectDirectory = CreateAspNetSdkTestAsset(testAsset); ReplaceStringInIndexHtml(ProjectDirectory, scriptPath, scriptPathWithFingerprintPattern); + FingerprintUserJavascriptAssets(fingerprintUserJavascriptAssets); var projectName = Path.GetFileNameWithoutExtension(Directory.EnumerateFiles(ProjectDirectory.TestRoot, "*.csproj").Single()); var publish = CreatePublishCommand(ProjectDirectory); - ExecuteCommand(publish, "-p:WriteImportMapToHtml=true").Should().Pass(); + ExecuteCommand(publish, "-p:WriteImportMapToHtml=true", $"-p:FingerprintUserJavascriptAssets={fingerprintUserJavascriptAssets}").Should().Pass(); var outputPath = publish.GetOutputDirectory(DefaultTfm, "Debug").ToString(); var indexHtmlOutputPath = Path.Combine(outputPath, "wwwroot", "index.html"); var endpointsManifestPath = Path.Combine(outputPath, $"{projectName}.staticwebassets.endpoints.json"); - AssertImportMapInHtml(indexHtmlOutputPath, endpointsManifestPath, scriptPath); + AssertImportMapInHtml(indexHtmlOutputPath, endpointsManifestPath, scriptPath, expectFingerprintOnScript: expectFingerprintOnScript); } - private void ReplaceStringInIndexHtml(TestAsset testAsset, string scriptPath, string scriptPathWithFingerprintPattern) + private void FingerprintUserJavascriptAssets(bool fingerprintUserJavascriptAssets) { - if (scriptPathWithFingerprintPattern != null) + if (fingerprintUserJavascriptAssets) + { + ProjectDirectory.WithProjectChanges(p => + { + if (p.Root != null) + { + var itemGroup = new XElement("ItemGroup"); + var pattern = new XElement("StaticWebAssetFingerprintPattern"); + pattern.SetAttributeValue("Include", "Js"); + pattern.SetAttributeValue("Pattern", "*.js"); + pattern.SetAttributeValue("Expression", "#[.{fingerprint}]!"); + itemGroup.Add(pattern); + p.Root.Add(itemGroup); + } + }); + } + } + + private void ReplaceStringInIndexHtml(TestAsset testAsset, string sourceValue, string targetValue) + { + if (targetValue != null) { var indexHtmlPath = Path.Combine(testAsset.TestRoot, "wwwroot", "index.html"); var indexHtmlContent = File.ReadAllText(indexHtmlPath); - var newIndexHtmlContent = indexHtmlContent.Replace(scriptPath, scriptPathWithFingerprintPattern); + var newIndexHtmlContent = indexHtmlContent.Replace(sourceValue, targetValue); if (indexHtmlContent == newIndexHtmlContent) - throw new Exception($"Script replacement '{scriptPath}' for '{scriptPathWithFingerprintPattern}' didn't produce any change in '{indexHtmlPath}'"); + throw new Exception($"String replacement '{sourceValue}' for '{targetValue}' didn't produce any change in '{indexHtmlPath}'"); File.WriteAllText(indexHtmlPath, newIndexHtmlContent); } } - private void AssertImportMapInHtml(string indexHtmlPath, string endpointsManifestPath, string scriptPath) + private void AssertImportMapInHtml(string indexHtmlPath, string endpointsManifestPath, string scriptPath, bool expectFingerprintOnScript = true) { var indexHtmlContent = File.ReadAllText(indexHtmlPath); var endpoints = JsonSerializer.Deserialize(File.ReadAllText(endpointsManifestPath)); var fingerprintedScriptPath = GetFingerprintedPath(scriptPath); - Assert.DoesNotContain($"src=\"{scriptPath}\"", indexHtmlContent); - Assert.Contains($"src=\"{fingerprintedScriptPath}\"", indexHtmlContent); + if (expectFingerprintOnScript) + { + Assert.DoesNotContain($"src=\"{scriptPath}\"", indexHtmlContent); + Assert.Contains($"src=\"{fingerprintedScriptPath}\"", indexHtmlContent); + } + else + { + Assert.Contains(scriptPath, indexHtmlContent); + + if (scriptPath != fingerprintedScriptPath) + { + Assert.DoesNotContain(fingerprintedScriptPath, indexHtmlContent); + } + } Assert.Contains(GetFingerprintedPath("_framework/dotnet.js"), indexHtmlContent); Assert.Contains(GetFingerprintedPath("_framework/dotnet.native.js"), indexHtmlContent); diff --git a/test/TestAssets/TestProjects/VanillaWasm/VanillaWasm.csproj b/test/TestAssets/TestProjects/VanillaWasm/VanillaWasm.csproj index 5f59d884b5f7..9676d0bfc24d 100644 --- a/test/TestAssets/TestProjects/VanillaWasm/VanillaWasm.csproj +++ b/test/TestAssets/TestProjects/VanillaWasm/VanillaWasm.csproj @@ -3,7 +3,4 @@ $(CurrentTargetFramework) true - - - diff --git a/test/TestAssets/TestProjects/VanillaWasm/wwwroot/index.html b/test/TestAssets/TestProjects/VanillaWasm/wwwroot/index.html index 32bb2959ea5f..8a30a27eb74e 100644 --- a/test/TestAssets/TestProjects/VanillaWasm/wwwroot/index.html +++ b/test/TestAssets/TestProjects/VanillaWasm/wwwroot/index.html @@ -8,7 +8,7 @@ - +