Skip to content
Closed
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 @@ -222,10 +222,12 @@ Copyright (c) .NET Foundation. All rights reserved.
CandidateAssets="@(_CompressedStaticWebAssets)"
>
<Output TaskParameter="Assets" ItemName="_CompressionBuildStaticWebAsset" />
<Output TaskParameter="AssetDetails" ItemName="_ResolveBuildCompressedStaticWebAssetsDetails" />
</DefineStaticWebAssets>

<DefineStaticWebAssetEndpoints
CandidateAssets="@(_CompressionBuildStaticWebAsset)"
AssetFileDetails="@(_ResolveBuildCompressedStaticWebAssetsDetails)"
ExistingEndpoints="@(StaticWebAssetEndpoint)"
ContentTypeMappings="@(StaticWebAssetContentTypeMapping)"
>
Expand All @@ -240,6 +242,7 @@ Copyright (c) .NET Foundation. All rights reserved.

<ApplyCompressionNegotiation
CandidateEndpoints="@(StaticWebAssetEndpoint)"
AssetFileDetails="@(_ResolveBuildCompressedStaticWebAssetsDetails)"
CandidateAssets="@(_CompressionCurrentProjectBuildAssets)"
>
<Output TaskParameter="UpdatedEndpoints" ItemName="_UpdatedCompressionBuildEndpoints" />
Expand Down Expand Up @@ -291,7 +294,7 @@ Copyright (c) .NET Foundation. All rights reserved.

</ResolveCompressedAssets>

<ItemGroup>
<ItemGroup Condition="'$(IsPackable)' == 'true'">
<_StaticWebAssetExcludedFromPack Include="@(_CompressedStaticWebAssets)" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Copyright (c) .NET Foundation. All rights reserved.
targets into their packages are expected to disable the generation of $(PackageId).props files and
to manually import build\Microsoft.AspNetCore.StaticWebAssets.props in their custom props files.
-->
<Target Name="GenerateStaticWebAssetsPackFiles" AfterTargets="GenerateStaticWebAssetsManifest">
<Target Name="GenerateStaticWebAssetsPackFiles" Condition="'$(IsPackable)' == 'true'" AfterTargets="GenerateStaticWebAssetsManifest">

<ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,16 @@ Copyright (c) .NET Foundation. All rights reserved.
for example, assets from packages as a result of a publish (no-build) invocation. Those assets were already taken
into account when we built the build manifest that we are about to load for resuming the publish process. -->
<PropertyGroup>
<_ShouldLoadBuildManifestAndUpdateAssets>false</_ShouldLoadBuildManifestAndUpdateAssets>
<_ShouldLoadBuildManifestAndUpdateAssets
Condition="@(_CachedBuildStaticWebAssets) == '' or @(_CachedBuildStaticWebAssetDiscoveryPatterns) == '' or @(_CachedBuildStaticWebAssetReferencedProjectsConfiguration) == ''">true</_ShouldLoadBuildManifestAndUpdateAssets>
<_HasStaticWebAssetsProjectReferences Condition="@(ProjectReference) != ''">true</_HasStaticWebAssetsProjectReferences>
<_HasCachedBuildStaticWebAssets Condition="@(_CachedBuildStaticWebAssets) == ''">false</_HasCachedBuildStaticWebAssets>
<_HasCachedBuildStaticWebAssetEndpoints Condition="@(_CachedBuildStaticWebAssetEndpoints) == ''">false</_HasCachedBuildStaticWebAssetEndpoints>
<_HasCachedBuildStaticWebAssetDiscoveryPatterns Condition="@(_CachedBuildStaticWebAssetDiscoveryPatterns) == ''">false</_HasCachedBuildStaticWebAssetDiscoveryPatterns>
<_HasCachedBuildStaticWebAssetReferencedProjectsConfiguration Condition="@(_CachedBuildStaticWebAssetReferencedProjectsConfiguration) == ''">false</_HasCachedBuildStaticWebAssetReferencedProjectsConfiguration>
<_ShouldLoadBuildManifestAndUpdateAssets>false</_ShouldLoadBuildManifestAndUpdateAssets>
<_ShouldLoadBuildManifestAndUpdateAssets Condition="'$(_HasCachedBuildStaticWebAssets)' == 'false' or
'$(_HasCachedBuildStaticWebAssetEndpoints)' == 'false' or
'$(_HasCachedBuildStaticWebAssetDiscoveryPatterns)' == 'false' or
('$(_HasStaticWebAssetsProjectReferences)' == 'true' and '$(_HasCachedBuildStaticWebAssetReferencedProjectsConfiguration)' == 'false')">true</_ShouldLoadBuildManifestAndUpdateAssets>
</PropertyGroup>

<ItemGroup>
Expand Down Expand Up @@ -131,11 +138,11 @@ Copyright (c) .NET Foundation. All rights reserved.
<ItemGroup>
<_ReferencedProjectPublishStaticWebAssetsUpdateCandidates
Include="@(_ReferencedProjectPublishStaticWebAssetsItems)"
Condition="'%(_ReferencedProjectPublishStaticWebAssetsItems.ResultType)' == 'StaticWebAsset'" />
Condition="'%(_ReferencedProjectPublishStaticWebAssetsItems.ResultType)' == 'StaticWebAsset'" />

<_ReferencedProjectPublishStaticWebAssetEndpointsUpdateCandidates
Include="@(_ReferencedProjectPublishStaticWebAssetsItems)"
Condition="'%(_ReferencedProjectPublishStaticWebAssetsItems.ResultType)' == 'StaticWebAssetEndpoint'" />
Condition="'%(_ReferencedProjectPublishStaticWebAssetsItems.ResultType)' == 'StaticWebAssetEndpoint'" />
</ItemGroup>

<UpdateExternallyDefinedStaticWebAssets Condition="$(_HasProjectsWithStaticWebAssetPublishTargets)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ Copyright (c) .NET Foundation. All rights reserved.
<!-- Manifest paths -->
<_StaticWebAssetsManifestBase Condition="'$(_StaticWebAssetsManifestBase)' == ''">$(IntermediateOutputPath)</_StaticWebAssetsManifestBase>
<StaticWebAssetBuildManifestPath>$(_StaticWebAssetsManifestBase)staticwebassets.build.json</StaticWebAssetBuildManifestPath>
<StaticWebAssetsBuildManifestCacheFilePath>$(StaticWebAssetBuildManifestPath).cache</StaticWebAssetsBuildManifestCacheFilePath>
<StaticWebAssetPackManifestPath>$(_StaticWebAssetsManifestBase)staticwebassets.pack.json</StaticWebAssetPackManifestPath>
<StaticWebAssetDevelopmentManifestPath>$(_StaticWebAssetsManifestBase)staticwebassets.development.json</StaticWebAssetDevelopmentManifestPath>
<StaticWebAssetEndpointsBuildManifestPath>$(_StaticWebAssetsManifestBase)staticwebassets.build.endpoints.json</StaticWebAssetEndpointsBuildManifestPath>
Expand Down Expand Up @@ -620,26 +621,37 @@ Copyright (c) .NET Foundation. All rights reserved.
DiscoveryPatterns="@(StaticWebAssetDiscoveryPattern)"
Assets="@(StaticWebAsset)"
Endpoints="@(StaticWebAssetEndpoint)"
ManifestPath="$(StaticWebAssetBuildManifestPath)">
ManifestPath="$(StaticWebAssetBuildManifestPath)"
ManifestCacheFilePath="$(StaticWebAssetsBuildManifestCacheFilePath)">
</GenerateStaticWebAssetsManifest>

<GenerateStaticWebAssetEndpointsManifest
Source="$(PackageId)"
ManifestType="Build"
Assets="@(StaticWebAsset)"
Endpoints="@(StaticWebAssetEndpoint)"
ManifestPath="$(StaticWebAssetEndpointsBuildManifestPath)">
ManifestPath="$(StaticWebAssetEndpointsBuildManifestPath)"
CacheFilePath="$(StaticWebAssetBuildManifestPath)">
</GenerateStaticWebAssetEndpointsManifest>

<GenerateStaticWebAssetsDevelopmentManifest
DiscoveryPatterns="@(StaticWebAssetDiscoveryPattern)"
Assets="@(StaticWebAsset)"
Source="$(PackageId)"
ManifestPath="$(StaticWebAssetDevelopmentManifestPath)">
ManifestPath="$(StaticWebAssetDevelopmentManifestPath)"
CacheFilePath="$(StaticWebAssetBuildManifestPath)">
</GenerateStaticWebAssetsDevelopmentManifest>

<ItemGroup>
<_CachedBuildStaticWebAssets Condition="'@(_CachedBuildStaticWebAssets)' == ''" Include="@(StaticWebAsset)" />
<_CachedBuildStaticWebAssetEndpoints Condition="'@(_CachedBuildStaticWebAssetEndpoints)' == ''" Include="@(StaticWebAssetEndpoint)" />
<_CachedBuildStaticWebAssetReferencedProjectsConfiguration Condition="'@(_CachedBuildStaticWebAssetReferencedProjectsConfiguration)' == ''" Include="@(StaticWebAssetProjectConfiguration)" />
<_CachedBuildStaticWebAssetDiscoveryPatterns Condition="'@(_CachedBuildStaticWebAssetDiscoveryPatterns)' == ''" Include="@(StaticWebAssetDiscoveryPattern)" />
</ItemGroup>

<ItemGroup>
<FileWrites Include="$(StaticWebAssetBuildManifestPath)" />
<FileWrites Include="$(StaticWebAssetsBuildManifestCacheFilePath)" />
<FileWrites Include="$(StaticWebAssetDevelopmentManifestPath)" />
<FileWrites Include="$(StaticWebAssetEndpointsBuildManifestPath)" />
</ItemGroup>
Expand Down Expand Up @@ -670,10 +682,12 @@ Copyright (c) .NET Foundation. All rights reserved.
BasePath="$(StaticWebAssetBasePath)"
AssetMergeSource="$(StaticWebAssetMergeTarget)">
<Output TaskParameter="Assets" ItemName="StaticWebAsset" />
<Output TaskParameter="AssetDetails" ItemName="_ResolveProjectStaticWebAssetsDetails" />
</DefineStaticWebAssets>

<DefineStaticWebAssetEndpoints
CandidateAssets="@(StaticWebAsset)"
AssetFileDetails="@(_ResolveProjectStaticWebAssetsDetails)"
ExistingEndpoints="@(StaticWebAssetEndpoint)"
ContentTypeMappings="@(StaticWebAssetContentTypeMapping)"
>
Expand Down
43 changes: 37 additions & 6 deletions src/StaticWebAssetsSdk/Tasks/ApplyCompressionNegotiation.cs
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.Diagnostics;
using System.Globalization;
using Microsoft.Build.Framework;
using Microsoft.NET.Sdk.StaticWebAssets.Tasks;

Expand All @@ -15,13 +16,27 @@ public class ApplyCompressionNegotiation : Task
[Required]
public ITaskItem[] CandidateAssets { get; set; }

public ITaskItem[] AssetFileDetails { get; set; }

[Output]
public ITaskItem[] UpdatedEndpoints { get; set; }

public Func<string, long> TestResolveFileLength;

private Dictionary<string, ITaskItem> _assetFileDetails;

public override bool Execute()
{
if (AssetFileDetails != null)
{
_assetFileDetails = new(AssetFileDetails.Length, OSPath.PathComparer);
for (int i = 0; i < AssetFileDetails.Length; i++)
{
var item = AssetFileDetails[i];
_assetFileDetails[item.ItemSpec] = item;
}
}

var assetsById = CandidateAssets.Select(StaticWebAsset.FromTaskItem).ToDictionary(a => a.Identity);

var endpointsByAsset = CandidateEndpoints.Select(StaticWebAssetEndpoint.FromTaskItem)
Expand Down Expand Up @@ -55,10 +70,6 @@ public override bool Execute()
}

Log.LogMessage("Processing compressed asset: {0}", compressedAsset.Identity);

var length = TestResolveFileLength != null
? TestResolveFileLength(compressedAsset.Identity)
: new FileInfo(compressedAsset.Identity).Length;
StaticWebAssetEndpointResponseHeader[] compressionHeaders = [
new()
{
Expand All @@ -72,9 +83,10 @@ public override bool Execute()
}
];

var quality = ResolveQuality(compressedAsset);
foreach (var compressedEndpoint in compressedEndpoints)
{
if (compressedEndpoint.Selectors.Any(s => string.Equals(s.Name,"Content-Encoding", StringComparison.Ordinal)))
if (compressedEndpoint.Selectors.Any(s => string.Equals(s.Name, "Content-Encoding", StringComparison.Ordinal)))
{
Log.LogMessage(MessageImportance.Low, $" Skipping endpoint '{compressedEndpoint.Route}' since it already has a Content-Encoding selector");
continue;
Expand Down Expand Up @@ -102,7 +114,7 @@ public override bool Execute()
{
Name = "Content-Encoding",
Value = compressedAsset.AssetTraitValue,
Quality = Math.Round(1.0 / (length + 1), 12).ToString("F12")
Quality = quality
};
Log.LogMessage(MessageImportance.Low, " Created Content-Encoding selector for compressed asset '{0}' with size '{1}' is '{2}'", encodingSelector.Value, encodingSelector.Quality, relatedEndpointCandidate.Route);
var endpointCopy = new StaticWebAssetEndpoint
Expand Down Expand Up @@ -201,6 +213,25 @@ public override bool Execute()
return true;
}

private string ResolveQuality(StaticWebAsset compressedAsset)
{
long length;
if(_assetFileDetails != null && _assetFileDetails.TryGetValue(compressedAsset.Identity, out var assetFileDetail))
{
length = long.Parse(assetFileDetail.GetMetadata("FileLength"));
}
else if (TestResolveFileLength != null)
{
length = TestResolveFileLength(compressedAsset.Identity);
}
else
{
length = new FileInfo(compressedAsset.Identity).Length;
}

return Math.Round(1.0 / (length + 1), 12).ToString("F12", CultureInfo.InvariantCulture);
}

private static bool IsCompatible(StaticWebAssetEndpoint compressedEndpoint, StaticWebAssetEndpoint relatedEndpointCandidate)
{
var compressedFingerprint = compressedEndpoint.EndpointProperties.FirstOrDefault(ep => ep.Name == "fingerprint");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.NET.Sdk.StaticWebAssets.Tasks;

namespace Microsoft.AspNetCore.StaticWebAssets.Tasks;

[JsonSerializable(typeof(StaticWebAssetsManifest))]
[JsonSerializable(typeof(GenerateStaticWebAssetsDevelopmentManifest.StaticWebAssetsDevelopmentManifest))]
[JsonSerializable(typeof(StaticWebAssetEndpointsManifest))]
public partial class StaticWebAssetsJsonSerializerContext : JsonSerializerContext
{
// Since the manifest is only used at development time, it's ok for it to use the relaxed
// json escaping (which is also what MVC uses by default)
private static readonly JsonSerializerOptions ManifestSerializationOptions = new()
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};

public static readonly StaticWebAssetsJsonSerializerContext RelaxedEscaping = new(ManifestSerializationOptions);
}
2 changes: 1 addition & 1 deletion src/StaticWebAssetsSdk/Tasks/Data/StaticAssetsManifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ private string ComputeManifestHash()

public static StaticWebAssetsManifest FromJsonBytes(byte[] jsonBytes)
{
var manifest = JsonSerializer.Deserialize<StaticWebAssetsManifest>(jsonBytes);
var manifest = JsonSerializer.Deserialize(jsonBytes, StaticWebAssetsJsonSerializerContext.RelaxedEscaping.StaticWebAssetsManifest);
if (manifest.Version != 1)
{
throw new InvalidOperationException($"Invalid manifest version. Expected manifest version '1' and found version '{manifest.Version}'.");
Expand Down
44 changes: 34 additions & 10 deletions src/StaticWebAssetsSdk/Tasks/Data/StaticWebAsset.cs
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.Diagnostics;
using System.IO;
using System.Security.Cryptography;
using System.Security.Principal;
using Microsoft.Build.Framework;
Expand Down Expand Up @@ -232,11 +233,13 @@ public void ApplyDefaults()

internal static (string fingerprint, string integrity) ComputeFingerprintAndIntegrity(string identity, string originalItemSpec)
{
using var file = File.Exists(identity) ?
File.OpenRead(identity) :
(File.Exists(originalItemSpec) ?
File.OpenRead(originalItemSpec) :
throw new InvalidOperationException($"No file exists for the asset at either location '{identity}' or '{originalItemSpec}'."));
var fileInfo = ResolveFile(identity, originalItemSpec);
return ComputeFingerprintAndIntegrity(fileInfo);
}

internal static (string fingerprint, string integrity) ComputeFingerprintAndIntegrity(FileInfo fileInfo)
{
using var file = fileInfo.OpenRead();

#if NET6_0_OR_GREATER
var hash = SHA256.HashData(file);
Expand All @@ -249,11 +252,14 @@ internal static (string fingerprint, string integrity) ComputeFingerprintAndInte

internal static string ComputeIntegrity(string identity, string originalItemSpec)
{
using var file = File.Exists(identity) ?
File.OpenRead(identity) :
(File.Exists(originalItemSpec) ?
File.OpenRead(originalItemSpec) :
throw new InvalidOperationException($"No file exists for the asset at either location '{identity}' or '{originalItemSpec}'."));
var fileInfo = ResolveFile(identity, originalItemSpec);
return ComputeIntegrity(fileInfo);
}

internal static string ComputeIntegrity(FileInfo fileInfo)
{
using var file = fileInfo.OpenRead();

#if NET6_0_OR_GREATER
var hash = SHA256.HashData(file);
#else
Expand Down Expand Up @@ -916,6 +922,24 @@ internal string EmbedTokens(string relativePath)
return pattern.RawPattern;
}

internal FileInfo ResolveFile() => ResolveFile(Identity, OriginalItemSpec);

internal static FileInfo ResolveFile(string identity, string originalItemSpec)
{
var fileInfo = new FileInfo(identity);
if (fileInfo.Exists)
{
return fileInfo;
}
fileInfo = new FileInfo(originalItemSpec);
if (fileInfo.Exists)
{
return fileInfo;
}

throw new InvalidOperationException($"No file exists for the asset at either location '{identity}' or '{originalItemSpec}'.");
}

[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
internal class StaticWebAssetResolvedRoute(string pathLabel, string path, Dictionary<string, string> tokens)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.


using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using Microsoft.AspNetCore.StaticWebAssets.Tasks;

namespace Microsoft.NET.Sdk.StaticWebAssets.Tasks;

[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
public class StaticWebAssetEndpointProperty : IComparable<StaticWebAssetEndpointProperty>, IEquatable<StaticWebAssetEndpointProperty>
{
private static readonly JsonTypeInfo<StaticWebAssetEndpointProperty[]> _jsonTypeInfo =
StaticWebAssetsJsonSerializerContext.Default.StaticWebAssetEndpointPropertyArray;

public string Name { get; set; }

public string Value { get; set; }

internal static StaticWebAssetEndpointProperty[] FromMetadataValue(string value)
{
return string.IsNullOrEmpty(value) ? [] : JsonSerializer.Deserialize<StaticWebAssetEndpointProperty[]>(value);
return string.IsNullOrEmpty(value) ? [] : JsonSerializer.Deserialize(value, _jsonTypeInfo);
}

internal static string ToMetadataValue(StaticWebAssetEndpointProperty[] responseHeaders)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.


using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using Microsoft.AspNetCore.StaticWebAssets.Tasks;

namespace Microsoft.NET.Sdk.StaticWebAssets.Tasks;

[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
public class StaticWebAssetEndpointResponseHeader : IEquatable<StaticWebAssetEndpointResponseHeader>, IComparable<StaticWebAssetEndpointResponseHeader>
{
private static readonly JsonTypeInfo<StaticWebAssetEndpointResponseHeader[]> _jsonTypeInfo =
StaticWebAssetsJsonSerializerContext.Default.StaticWebAssetEndpointResponseHeaderArray;

public string Name { get; set; }

public string Value { get; set; }

internal static StaticWebAssetEndpointResponseHeader[] FromMetadataValue(string value)
{
return string.IsNullOrEmpty(value) ? [] : JsonSerializer.Deserialize<StaticWebAssetEndpointResponseHeader[]>(value);
return string.IsNullOrEmpty(value) ? [] : JsonSerializer.Deserialize(value, _jsonTypeInfo);
}

internal static string ToMetadataValue(StaticWebAssetEndpointResponseHeader[] responseHeaders)
Expand Down
Loading