Skip to content

Commit 08f106a

Browse files
author
Paul Johnson
authored
Fix smidge bundles not always invalidated between backoffice versions. (#11700)
* Introduced new smidge CacheBuster with sensible defaults. * Bring back reset functionality in SmidgeRuntimeMinifier * Clearer obsolete messaging.
1 parent 6c5851f commit 08f106a

File tree

12 files changed

+257
-15
lines changed

12 files changed

+257
-15
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ src/Umbraco.Web.UI/wwwroot/[Uu]mbraco/js/*
8484
src/Umbraco.Web.UI/wwwroot/[Uu]mbraco/lib/*
8585
src/Umbraco.Web.UI/wwwroot/[Uu]mbraco/views/*
8686
src/Umbraco.Web.UI/wwwroot/Media/*
87+
src/Umbraco.Web.UI/Smidge/
8788

8889
# Tests
8990
cypress.env.json
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System.Reflection;
2+
3+
namespace Umbraco.Cms.Core.Configuration
4+
{
5+
internal class EntryAssemblyMetadata : IEntryAssemblyMetadata
6+
{
7+
public EntryAssemblyMetadata()
8+
{
9+
var entryAssembly = Assembly.GetEntryAssembly();
10+
11+
Name = entryAssembly
12+
?.GetName()
13+
?.Name ?? string.Empty;
14+
15+
InformationalVersion = entryAssembly
16+
?.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
17+
?.InformationalVersion ?? string.Empty;
18+
}
19+
20+
public string Name { get; }
21+
22+
public string InformationalVersion { get; }
23+
}
24+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace Umbraco.Cms.Core.Configuration
2+
{
3+
/// <summary>
4+
/// Provides metadata about the entry assembly.
5+
/// </summary>
6+
public interface IEntryAssemblyMetadata
7+
{
8+
/// <summary>
9+
/// Gets the Name of entry assembly.
10+
/// </summary>
11+
public string Name { get; }
12+
13+
/// <summary>
14+
/// Gets the InformationalVersion string for entry assembly.
15+
/// </summary>
16+
public string InformationalVersion { get; }
17+
}
18+
}

src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ private void AddCoreServices()
163163
Services.AddUnique(factory => factory.GetRequiredService<AppCaches>().RequestCache);
164164
Services.AddUnique<IProfilingLogger, ProfilingLogger>();
165165
Services.AddUnique<IUmbracoVersion, UmbracoVersion>();
166+
Services.AddUnique<IEntryAssemblyMetadata, EntryAssemblyMetadata>();
166167

167168
this.AddAllCoreCollectionBuilders();
168169
this.AddNotificationHandler<UmbracoApplicationStartingNotification, EssentialDirectoryCreator>();

src/Umbraco.Core/WebAssets/IRuntimeMinifier.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,18 @@ public interface IRuntimeMinifier
8888
/// <summary>
8989
/// Ensures that all runtime minifications are refreshed on next request. E.g. Clearing cache.
9090
/// </summary>
91+
/// <remarks>
92+
/// <para>
93+
/// No longer necessary, invalidation occurs automatically if any of the following occur.
94+
/// </para>
95+
/// <list type="bullet">
96+
/// <item>Your sites assembly information version changes.</item>
97+
/// <item>Umbraco.Cms.Core assembly information version changes.</item>
98+
/// <item>RuntimeMinificationSettings Version string changes.</item>
99+
/// </list>
100+
/// <see href="https://our.umbraco.com/documentation/Reference/V9-Config/RuntimeMinificationSettings/" /> for further details.
101+
/// </remarks>
102+
[Obsolete("Invalidation is handled automatically. Scheduled for removal V11.")]
91103
void Reset();
92-
93104
}
94105
}

src/Umbraco.Web.BackOffice/Install/InstallController.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,6 @@ public async Task<ActionResult> Index()
7272
// TODO: Update for package migrations
7373
if (_runtime.Level == RuntimeLevel.Upgrade)
7474
{
75-
// Update ClientDependency version and delete its temp directories to make sure we get fresh caches
76-
_runtimeMinifier.Reset();
77-
7875
var authResult = await this.AuthenticateBackOfficeAsync();
7976

8077
if (!authResult.Succeeded)

src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using Microsoft.Extensions.Logging;
1919
using Serilog;
2020
using Smidge;
21+
using Smidge.Cache;
2122
using Smidge.FileProcessors;
2223
using Smidge.InMemory;
2324
using Smidge.Nuglify;
@@ -274,6 +275,7 @@ public static IUmbracoBuilder AddRuntimeMinifier(this IUmbracoBuilder builder)
274275
new[] { "/App_Plugins/**/*.js", "/App_Plugins/**/*.css" }));
275276
});
276277

278+
builder.Services.AddUnique<ICacheBuster, UmbracoSmidgeConfigCacheBuster>();
277279
builder.Services.AddSmidge(builder.Config.GetSection(Constants.Configuration.ConfigRuntimeMinification));
278280
// Replace the Smidge request helper, in order to discourage the use of brotli since it's super slow
279281
builder.Services.AddUnique<IRequestHelper, SmidgeRequestHelper>();

src/Umbraco.Web.Common/RuntimeMinification/SmidgeRuntimeMinifier.cs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ public class SmidgeRuntimeMinifier : IRuntimeMinifier
2424
private readonly IHostingEnvironment _hostingEnvironment;
2525
private readonly IConfigManipulator _configManipulator;
2626
private readonly CacheBusterResolver _cacheBusterResolver;
27-
private readonly RuntimeMinificationSettings _runtimeMinificationSettings;
2827
private readonly IBundleManager _bundles;
2928
private readonly SmidgeHelperAccessor _smidge;
3029

@@ -53,7 +52,6 @@ public SmidgeRuntimeMinifier(
5352
_hostingEnvironment = hostingEnvironment;
5453
_configManipulator = configManipulator;
5554
_cacheBusterResolver = cacheBusterResolver;
56-
_runtimeMinificationSettings = runtimeMinificationSettings.Value;
5755
_jsMinPipeline = new Lazy<PreProcessPipeline>(() => _bundles.PipelineFactory.Create(typeof(JsMinifier)));
5856
_cssMinPipeline = new Lazy<PreProcessPipeline>(() => _bundles.PipelineFactory.Create(typeof(NuglifyCss)));
5957

@@ -76,10 +74,10 @@ public SmidgeRuntimeMinifier(
7674
return defaultCss;
7775
});
7876

79-
Type cacheBusterType = _runtimeMinificationSettings.CacheBuster switch
77+
Type cacheBusterType = runtimeMinificationSettings.Value.CacheBuster switch
8078
{
8179
RuntimeMinificationCacheBuster.AppDomain => typeof(AppDomainLifetimeCacheBuster),
82-
RuntimeMinificationCacheBuster.Version => typeof(ConfigCacheBuster),
80+
RuntimeMinificationCacheBuster.Version => typeof(UmbracoSmidgeConfigCacheBuster),
8381
RuntimeMinificationCacheBuster.Timestamp => typeof(TimestampCacheBuster),
8482
_ => throw new NotImplementedException()
8583
};
@@ -169,18 +167,12 @@ public async Task<string> MinifyAsync(string fileContent, AssetType assetType)
169167
}
170168
}
171169

172-
173170
/// <inheritdoc />
174-
/// <remarks>
175-
/// Smidge uses the version number as cache buster (configurable).
176-
/// We therefore can reset, by updating the version number in config
177-
/// </remarks>
171+
[Obsolete("Invalidation is handled automatically. Scheduled for removal V11.")]
178172
public void Reset()
179173
{
180174
var version = DateTime.UtcNow.Ticks.ToString();
181175
_configManipulator.SaveConfigValue(Cms.Core.Constants.Configuration.ConfigRuntimeMinificationVersion, version.ToString());
182176
}
183-
184-
185177
}
186178
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using System;
2+
using Microsoft.Extensions.Logging;
3+
using Microsoft.Extensions.Options;
4+
using Smidge;
5+
using Smidge.Cache;
6+
using Umbraco.Cms.Core.Configuration;
7+
using Umbraco.Cms.Core.Configuration.Models;
8+
using Umbraco.Extensions;
9+
10+
namespace Umbraco.Cms.Web.Common.RuntimeMinification
11+
{
12+
/// <summary>
13+
/// Constructs a cache buster string with sensible defaults.
14+
/// </summary>
15+
/// <remarks>
16+
/// <para>
17+
/// Had planned on handling all of this in SmidgeRuntimeMinifier, but that only handles some urls.
18+
/// </para>
19+
/// <para>
20+
/// A lot of the work is delegated e.g. to <see cref="SmidgeHelper.GenerateJsUrlsAsync(string, bool)"/>
21+
/// which doesn't care about the cache buster string in our classes only the value returned by the resolved ICacheBuster.
22+
/// </para>
23+
/// <para>
24+
/// Then I thought fine I'll just use a IConfigureOptions to tweak upstream <see cref="ConfigCacheBuster"/>, but that only cares about the <see cref="SmidgeConfig"/>
25+
/// class we instantiate and pass through in <see cref="Umbraco.Extensions.UmbracoBuilderExtensions.AddRuntimeMinifier"/>
26+
/// </para>
27+
/// <para>
28+
/// So here we are, create our own to ensure we cache bust in a reasonable fashion.
29+
/// </para>
30+
/// <br/><br/>
31+
/// <para>
32+
/// Note that this class makes some other bits of code pretty redundant e.g. <see cref="UrlHelperExtensions.GetUrlWithCacheBust"/> will
33+
/// concatenate version with CacheBuster value and hash again, but there's no real harm so can think about that later.
34+
/// </para>
35+
/// </remarks>
36+
internal class UmbracoSmidgeConfigCacheBuster : ICacheBuster
37+
{
38+
private readonly IOptions<RuntimeMinificationSettings> _runtimeMinificationSettings;
39+
private readonly IUmbracoVersion _umbracoVersion;
40+
private readonly IEntryAssemblyMetadata _entryAssemblyMetadata;
41+
42+
private string _cacheBusterValue;
43+
44+
public UmbracoSmidgeConfigCacheBuster(
45+
IOptions<RuntimeMinificationSettings> runtimeMinificationSettings,
46+
IUmbracoVersion umbracoVersion,
47+
IEntryAssemblyMetadata entryAssemblyMetadata)
48+
{
49+
_runtimeMinificationSettings = runtimeMinificationSettings ?? throw new ArgumentNullException(nameof(runtimeMinificationSettings));
50+
_umbracoVersion = umbracoVersion ?? throw new ArgumentNullException(nameof(umbracoVersion));
51+
_entryAssemblyMetadata = entryAssemblyMetadata ?? throw new ArgumentNullException(nameof(entryAssemblyMetadata));
52+
}
53+
54+
private string CacheBusterValue
55+
{
56+
get
57+
{
58+
if (_cacheBusterValue != null)
59+
{
60+
return _cacheBusterValue;
61+
}
62+
63+
// Assembly Name adds a bit of uniqueness across sites when version missing from config.
64+
// Adds a bit of security through obscurity that was asked for in standup.
65+
var prefix = _runtimeMinificationSettings.Value.Version ?? _entryAssemblyMetadata.Name ?? string.Empty;
66+
var umbracoVersion = _umbracoVersion.SemanticVersion.ToString();
67+
var downstreamVersion = _entryAssemblyMetadata.InformationalVersion;
68+
69+
_cacheBusterValue = $"{prefix}_{umbracoVersion}_{downstreamVersion}".GenerateHash();
70+
71+
return _cacheBusterValue;
72+
}
73+
}
74+
75+
public string GetValue() => CacheBusterValue;
76+
}
77+
}

tests/.editorconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
root = false
2+
3+
[*.cs]
4+
csharp_style_var_when_type_is_apparent = true:none
5+
csharp_style_var_elsewhere = true:none

0 commit comments

Comments
 (0)