@@ -20,9 +20,10 @@ public class ApplyCompressionNegotiation : Task
20
20
[ Output ]
21
21
public ITaskItem [ ] UpdatedEndpoints { get ; set ; }
22
22
23
- // Reusable collections and contexts for optimization
24
23
private readonly List < StaticWebAssetEndpointSelector > _selectorsList = new ( ) ;
25
24
private readonly List < StaticWebAssetEndpointResponseHeader > _headersList = new ( ) ;
25
+ private readonly List < StaticWebAssetEndpointProperty > _propertiesList = new ( ) ;
26
+ private const int ExpectedCompressionHeadersCount = 2 ;
26
27
27
28
public override bool Execute ( )
28
29
{
@@ -32,9 +33,20 @@ public override bool Execute()
32
33
33
34
var updatedEndpoints = new HashSet < StaticWebAssetEndpoint > ( CandidateEndpoints . Length , StaticWebAssetEndpoint . RouteAndAssetComparer ) ;
34
35
35
- var compressionHeadersByEncoding = new Dictionary < string , StaticWebAssetEndpointResponseHeader [ ] > ( 2 ) ;
36
+ var compressionHeadersByEncoding = new Dictionary < string , StaticWebAssetEndpointResponseHeader [ ] > ( ExpectedCompressionHeadersCount ) ;
36
37
37
- // Add response headers to compressed endpoints
38
+ ProcessCompressedAssets ( assetsById , endpointsByAsset , updatedEndpoints , compressionHeadersByEncoding ) ;
39
+ AddRemainingEndpoints ( endpointsByAsset , updatedEndpoints ) ;
40
+ UpdatedEndpoints = StaticWebAssetEndpoint . ToTaskItems ( updatedEndpoints ) ;
41
+ return true ;
42
+ }
43
+
44
+ private void ProcessCompressedAssets (
45
+ Dictionary < string , StaticWebAsset > assetsById ,
46
+ IDictionary < string , List < StaticWebAssetEndpoint > > endpointsByAsset ,
47
+ HashSet < StaticWebAssetEndpoint > updatedEndpoints ,
48
+ Dictionary < string , StaticWebAssetEndpointResponseHeader [ ] > compressionHeadersByEncoding )
49
+ {
38
50
foreach ( var compressedAsset in assetsById . Values )
39
51
{
40
52
if ( ! string . Equals ( compressedAsset . AssetTraitName , "Content-Encoding" , StringComparison . Ordinal ) )
@@ -58,14 +70,9 @@ public override bool Execute()
58
70
59
71
if ( ! HasContentEncodingResponseHeader ( compressedEndpoint ) )
60
72
{
61
- // Add the Content-Encoding and Vary headers using reusable list
62
- _headersList . Clear ( ) ;
63
- // Parse existing headers from string to avoid accessing array property
64
73
StaticWebAssetEndpointResponseHeader . PopulateFromMetadataValue ( compressedEndpoint . ResponseHeadersString , _headersList ) ;
65
- // Add compression headers
66
74
var currentCompressionHeaders = GetOrCreateCompressionHeaders ( compressionHeadersByEncoding , compressedAsset ) ;
67
75
_headersList . AddRange ( currentCompressionHeaders ) ;
68
- // Serialize back to string
69
76
using var headerContext = new JsonWriterContext ( ) ;
70
77
var headersString = StaticWebAssetEndpointResponseHeader . ToMetadataValue ( _headersList , headerContext ) ;
71
78
compressedEndpoint . SetResponseHeadersString ( headersString ) ;
@@ -85,27 +92,16 @@ public override bool Execute()
85
92
86
93
var endpointCopy = CreateUpdatedEndpoint ( compressedAsset , quality , compressedEndpoint , compressedHeaders , relatedEndpointCandidate ) ;
87
94
updatedEndpoints . Add ( endpointCopy ) ;
88
- // Since we are going to remove the endpoints from the associated item group and the route is
89
- // the ItemSpec, we want to add the original as well so that it gets re-added.
90
- // The endpoint pointing to the uncompressed asset doesn't have a Content-Encoding selector and
91
- // will use the default "identity" encoding during content negotiation.
92
95
updatedEndpoints . Add ( relatedEndpointCandidate ) ;
93
96
}
94
97
}
95
98
}
99
+ }
96
100
97
- // Before we return the updated endpoints we need to capture any other endpoint whose asset is not associated
98
- // with the compressed asset. This is because we are going to remove the endpoints from the associated item group
99
- // and the route is the ItemSpec, so it will cause those endpoints to be removed.
100
- // For example, we have css/app.css and Link/css/app.css where Link=css/app.css and the first asset is a build asset
101
- // and the second asset is a publish asset.
102
- // If we are processing build assets, we'll mistakenly remove the endpoints associated with the publish asset.
103
-
104
- // Iterate over the endpoints and find those endpoints whose route is in the set of updated endpoints but whose asset
105
- // is not, and add them to the updated endpoints.
106
-
107
- // Reuse the map we created at the beginning.
108
- // Remove all the endpoints that were updated to avoid adding them again.
101
+ private void AddRemainingEndpoints (
102
+ IDictionary < string , List < StaticWebAssetEndpoint > > endpointsByAsset ,
103
+ HashSet < StaticWebAssetEndpoint > updatedEndpoints )
104
+ {
109
105
foreach ( var endpoint in updatedEndpoints )
110
106
{
111
107
if ( endpointsByAsset . TryGetValue ( endpoint . AssetFile , out var endpointsToSkip ) )
@@ -118,9 +114,6 @@ public override bool Execute()
118
114
endpointsByAsset . Remove ( endpoint . AssetFile ) ;
119
115
}
120
116
121
- // We now have only endpoints that might have the same route but point to different assets
122
- // and we want to include them in the updated endpoints so that we don't incorrectly remove
123
- // them from the associated item group when we update the endpoints.
124
117
var endpointsByRoute = GetEndpointsByRoute ( endpointsByAsset ) ;
125
118
var additionalUpdatedEndpoints = new HashSet < StaticWebAssetEndpoint > ( updatedEndpoints . Count , StaticWebAssetEndpoint . RouteAndAssetComparer ) ;
126
119
foreach ( var updatedEndpoint in updatedEndpoints )
@@ -142,16 +135,10 @@ public override bool Execute()
142
135
}
143
136
144
137
updatedEndpoints . UnionWith ( additionalUpdatedEndpoints ) ;
145
-
146
- UpdatedEndpoints = StaticWebAssetEndpoint . ToTaskItems ( updatedEndpoints ) ;
147
-
148
- return true ;
149
138
}
150
139
151
140
private HashSet < string > GetCompressedHeaders ( StaticWebAssetEndpoint compressedEndpoint )
152
141
{
153
- // Parse headers from string to avoid accessing array property
154
- _headersList . Clear ( ) ;
155
142
StaticWebAssetEndpointResponseHeader . PopulateFromMetadataValue ( compressedEndpoint . ResponseHeadersString , _headersList ) ;
156
143
157
144
var result = new HashSet < string > ( _headersList . Count , StringComparer . Ordinal ) ;
@@ -227,7 +214,6 @@ private StaticWebAssetEndpoint CreateUpdatedEndpoint(
227
214
Log . LogMessage ( MessageImportance . Low , " Created Content-Encoding selector for compressed asset '{0}' with size '{1}' is '{2}'" , encodingSelector . Value , encodingSelector . Quality , relatedEndpointCandidate . Route ) ;
228
215
229
216
// Build selectors using reusable list to avoid array allocation
230
- _selectorsList . Clear ( ) ;
231
217
StaticWebAssetEndpointSelector . PopulateFromMetadataValue ( relatedEndpointCandidate . SelectorsString , _selectorsList ) ;
232
218
_selectorsList . Add ( encodingSelector ) ;
233
219
using var selectorContext = new JsonWriterContext ( ) ;
@@ -258,8 +244,6 @@ private StaticWebAssetEndpoint CreateUpdatedEndpoint(
258
244
259
245
private bool HasContentEncodingResponseHeader ( StaticWebAssetEndpoint compressedEndpoint )
260
246
{
261
- // Parse headers from string to avoid accessing array property
262
- _headersList . Clear ( ) ;
263
247
StaticWebAssetEndpointResponseHeader . PopulateFromMetadataValue ( compressedEndpoint . ResponseHeadersString , _headersList ) ;
264
248
265
249
for ( var i = 0 ; i < _headersList . Count ; i ++ )
@@ -276,8 +260,6 @@ private bool HasContentEncodingResponseHeader(StaticWebAssetEndpoint compressedE
276
260
277
261
private bool HasContentEncodingSelector ( StaticWebAssetEndpoint compressedEndpoint )
278
262
{
279
- // Parse selectors from string to avoid accessing array property
280
- _selectorsList . Clear ( ) ;
281
263
StaticWebAssetEndpointSelector . PopulateFromMetadataValue ( compressedEndpoint . SelectorsString , _selectorsList ) ;
282
264
283
265
for ( var i = 0 ; i < _selectorsList . Count ; i ++ )
@@ -321,18 +303,15 @@ private bool HasContentEncodingSelector(StaticWebAssetEndpoint compressedEndpoin
321
303
private static string ResolveQuality ( StaticWebAsset compressedAsset ) =>
322
304
Math . Round ( 1.0 / ( compressedAsset . FileLength + 1 ) , 12 ) . ToString ( "F12" , CultureInfo . InvariantCulture ) ;
323
305
324
- private static bool IsCompatible ( StaticWebAssetEndpoint compressedEndpoint , StaticWebAssetEndpoint relatedEndpointCandidate )
306
+ private bool IsCompatible ( StaticWebAssetEndpoint compressedEndpoint , StaticWebAssetEndpoint relatedEndpointCandidate )
325
307
{
326
- var tempPropertiesList = new List < StaticWebAssetEndpointProperty > ( ) ;
327
- var compressedFingerprint = ResolveFingerprint ( compressedEndpoint , tempPropertiesList ) ;
328
- var relatedFingerprint = ResolveFingerprint ( relatedEndpointCandidate , tempPropertiesList ) ;
308
+ var compressedFingerprint = ResolveFingerprint ( compressedEndpoint , _propertiesList ) ;
309
+ var relatedFingerprint = ResolveFingerprint ( relatedEndpointCandidate , _propertiesList ) ;
329
310
return string . Equals ( compressedFingerprint . Value , relatedFingerprint . Value , StringComparison . Ordinal ) ;
330
311
}
331
312
332
313
private static StaticWebAssetEndpointProperty ResolveFingerprint ( StaticWebAssetEndpoint compressedEndpoint , List < StaticWebAssetEndpointProperty > tempList )
333
314
{
334
- // Parse properties from string to avoid accessing array property
335
- tempList . Clear ( ) ;
336
315
StaticWebAssetEndpointProperty . PopulateFromMetadataValue ( compressedEndpoint . EndpointPropertiesString , tempList ) ;
337
316
338
317
foreach ( var property in tempList )
@@ -347,11 +326,9 @@ private static StaticWebAssetEndpointProperty ResolveFingerprint(StaticWebAssetE
347
326
348
327
private void ApplyCompressedEndpointHeaders ( List < StaticWebAssetEndpointResponseHeader > headers , StaticWebAssetEndpoint compressedEndpoint , string relatedEndpointCandidateRoute )
349
328
{
350
- // Parse headers from string to avoid accessing array property
351
- var tempHeadersList = new List < StaticWebAssetEndpointResponseHeader > ( ) ;
352
- StaticWebAssetEndpointResponseHeader . PopulateFromMetadataValue ( compressedEndpoint . ResponseHeadersString , tempHeadersList ) ;
329
+ StaticWebAssetEndpointResponseHeader . PopulateFromMetadataValue ( compressedEndpoint . ResponseHeadersString , _headersList ) ;
353
330
354
- foreach ( var header in tempHeadersList )
331
+ foreach ( var header in _headersList )
355
332
{
356
333
if ( string . Equals ( header . Name , "Content-Type" , StringComparison . Ordinal ) )
357
334
{
@@ -369,11 +346,9 @@ private void ApplyCompressedEndpointHeaders(List<StaticWebAssetEndpointResponseH
369
346
370
347
private void ApplyRelatedEndpointCandidateHeaders ( List < StaticWebAssetEndpointResponseHeader > headers , StaticWebAssetEndpoint relatedEndpointCandidate , HashSet < string > compressedHeaders )
371
348
{
372
- // Parse headers from string to avoid accessing array property
373
- var tempHeadersList = new List < StaticWebAssetEndpointResponseHeader > ( ) ;
374
- StaticWebAssetEndpointResponseHeader . PopulateFromMetadataValue ( relatedEndpointCandidate . ResponseHeadersString , tempHeadersList ) ;
349
+ StaticWebAssetEndpointResponseHeader . PopulateFromMetadataValue ( relatedEndpointCandidate . ResponseHeadersString , _headersList ) ;
375
350
376
- foreach ( var header in tempHeadersList )
351
+ foreach ( var header in _headersList )
377
352
{
378
353
// We need to keep the headers that are specific to the compressed asset like Content-Length,
379
354
// Last-Modified and ETag. Any other header we should add it.
0 commit comments