diff --git a/src/Umbraco.Core/Configuration/Models/RuntimeMinificationCacheBuster.cs b/src/Umbraco.Core/Configuration/Models/RuntimeMinificationCacheBuster.cs new file mode 100644 index 0000000000..db1e1526e5 --- /dev/null +++ b/src/Umbraco.Core/Configuration/Models/RuntimeMinificationCacheBuster.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Cms.Core.Configuration.Models +{ + public enum RuntimeMinificationCacheBuster + { + Version, + AppDomain, + Timestamp + } +} diff --git a/src/Umbraco.Core/Configuration/Models/RuntimeMinificationSettings.cs b/src/Umbraco.Core/Configuration/Models/RuntimeMinificationSettings.cs new file mode 100644 index 0000000000..fa3519059a --- /dev/null +++ b/src/Umbraco.Core/Configuration/Models/RuntimeMinificationSettings.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Umbraco.Cms.Core.Configuration.Models +{ + public class RuntimeMinificationSettings + { + public bool UseInMemoryCache { get; set; } = false; + + /// + /// The cache buster type to use + /// + public RuntimeMinificationCacheBuster CacheBuster { get; set; } = RuntimeMinificationCacheBuster.Version; + } +} diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs index c37f8fb760..a9e112efc4 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -13,11 +13,10 @@ namespace Umbraco.Cms.Core.DependencyInjection private static OptionsBuilder AddOptions(IUmbracoBuilder builder, string key) where TOptions : class - { - return builder.Services.AddOptions() + => builder.Services.AddOptions() .Bind(builder.Config.GetSection(key)) .ValidateDataAnnotations(); - } + /// /// Add Umbraco configuration services and options /// @@ -57,6 +56,7 @@ namespace Umbraco.Cms.Core.DependencyInjection AddOptions(builder, Constants.Configuration.ConfigPlugins); AddOptions(builder, Constants.Configuration.ConfigUnattended); AddOptions(builder, Constants.Configuration.ConfigRichTextEditor); + AddOptions(builder, Constants.Configuration.ConfigRuntimeMinification); return builder; } diff --git a/src/Umbraco.Core/WebAssets/IRuntimeMinifier.cs b/src/Umbraco.Core/WebAssets/IRuntimeMinifier.cs index cb8a012205..ac26da104f 100644 --- a/src/Umbraco.Core/WebAssets/IRuntimeMinifier.cs +++ b/src/Umbraco.Core/WebAssets/IRuntimeMinifier.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -25,7 +25,7 @@ namespace Umbraco.Cms.Core.WebAssets /// /// Thrown if any of the paths specified are not absolute /// - void CreateCssBundle(string bundleName, params string[] filePaths); + void CreateCssBundle(string bundleName, bool optimizeOutput, params string[] filePaths); /// /// Renders the html link tag for the bundle diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index e3b327ffaa..6eb08bd4d5 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -66,7 +66,8 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection .AddRepositories() .AddServices() .AddCoreMappingProfiles() - .AddFileSystems(); + .AddFileSystems() + .AddWebAssets(); // register persistence mappers - required by database factory so needs to be done here // means the only place the collection can be modified is in a runtime - afterwards it diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.WebAssets.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.WebAssets.cs new file mode 100644 index 0000000000..53e638997b --- /dev/null +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.WebAssets.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Infrastructure.WebAssets; + +namespace Umbraco.Cms.Infrastructure.DependencyInjection +{ + public static partial class UmbracoBuilderExtensions + { + internal static IUmbracoBuilder AddWebAssets(this IUmbracoBuilder builder) + { + builder.Services.AddSingleton(); + return builder; + } + } +} diff --git a/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs b/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs index ef598e6150..05171988cf 100644 --- a/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs +++ b/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs @@ -48,17 +48,18 @@ namespace Umbraco.Cms.Infrastructure.WebAssets { // Create bundles - _runtimeMinifier.CreateCssBundle(UmbracoInitCssBundleName, + // TODO: I think we don't want to optimize these css if/when we get gulp to do that all for us + _runtimeMinifier.CreateCssBundle(UmbracoInitCssBundleName, true, FormatPaths("lib/bootstrap-social/bootstrap-social.css", "assets/css/umbraco.css", "lib/font-awesome/css/font-awesome.min.css")); - _runtimeMinifier.CreateCssBundle(UmbracoUpgradeCssBundleName, + _runtimeMinifier.CreateCssBundle(UmbracoUpgradeCssBundleName, true, FormatPaths("assets/css/umbraco.css", "lib/bootstrap-social/bootstrap-social.css", "lib/font-awesome/css/font-awesome.min.css")); - _runtimeMinifier.CreateCssBundle(UmbracoPreviewCssBundleName, + _runtimeMinifier.CreateCssBundle(UmbracoPreviewCssBundleName, true, FormatPaths("assets/css/canvasdesigner.css")); _runtimeMinifier.CreateJsBundle(UmbracoPreviewJsBundleName, false, @@ -81,7 +82,7 @@ namespace Umbraco.Cms.Infrastructure.WebAssets propertyEditorAssets.TryGetValue(AssetType.Javascript, out var scripts) ? scripts : Enumerable.Empty()))); _runtimeMinifier.CreateCssBundle( - UmbracoCssBundleName, + UmbracoCssBundleName, true, FormatPaths( GetStylesheetsForBackOffice( propertyEditorAssets.TryGetValue(AssetType.Css, out var styles) ? styles : Enumerable.Empty()))); diff --git a/src/Umbraco.Infrastructure/WebAssets/WebAssetsComponent.cs b/src/Umbraco.Infrastructure/WebAssets/WebAssetsComponent.cs deleted file mode 100644 index 2eba85a3c3..0000000000 --- a/src/Umbraco.Infrastructure/WebAssets/WebAssetsComponent.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Infrastructure.WebAssets -{ - public sealed class WebAssetsComponent : IComponent - { - private readonly BackOfficeWebAssets _backOfficeWebAssets; - - public WebAssetsComponent(BackOfficeWebAssets backOfficeWebAssets) - { - _backOfficeWebAssets = backOfficeWebAssets; - } - - public void Initialize() - { - // TODO: This will eagerly scan types but we don't really want that, however it works for now. - // We don't actually have to change Smidge or anything, all we have to do is postpone this call for when the first request on the website arrives. - _backOfficeWebAssets.CreateBundles(); - } - - public void Terminate() - { - } - } -} diff --git a/src/Umbraco.Infrastructure/WebAssets/WebAssetsComposer.cs b/src/Umbraco.Infrastructure/WebAssets/WebAssetsComposer.cs deleted file mode 100644 index 1298340a73..0000000000 --- a/src/Umbraco.Infrastructure/WebAssets/WebAssetsComposer.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Infrastructure.WebAssets -{ - public sealed class WebAssetsComposer : ComponentComposer - { - public override void Compose(IUmbracoBuilder builder) - { - base.Compose(builder); - builder.Services.AddUnique(); - } - } -} diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index d4d9ee5047..b08acdb5ef 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -18,6 +18,8 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Serilog; using Smidge; +using Smidge.FileProcessors; +using Smidge.InMemory; using Smidge.Nuglify; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; @@ -34,6 +36,7 @@ using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Templates; using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Core.WebAssets; using Umbraco.Cms.Infrastructure.DependencyInjection; using Umbraco.Cms.Infrastructure.HostedServices; using Umbraco.Cms.Infrastructure.HostedServices.ServerRegistration; @@ -53,6 +56,7 @@ using Umbraco.Cms.Web.Common.ModelBinders; using Umbraco.Cms.Web.Common.Mvc; using Umbraco.Cms.Web.Common.Profiler; using Umbraco.Cms.Web.Common.Routing; +using Umbraco.Cms.Web.Common.RuntimeMinification; using Umbraco.Cms.Web.Common.Security; using Umbraco.Cms.Web.Common.Templates; using Umbraco.Cms.Web.Common.UmbracoContext; @@ -233,10 +237,17 @@ namespace Umbraco.Extensions new GlobPatternFilterFileProvider( hostEnv.ContentRootFileProvider, // only include js or css files within App_Plugins - new[] { "App_Plugins/**/*.js", "App_Plugins/**/*.css" })); + new[] { "/App_Plugins/**/*.js", "/App_Plugins/**/*.css" })); }); + builder.Services.AddSmidge(builder.Config.GetSection(Constants.Configuration.ConfigRuntimeMinification)); builder.Services.AddSmidgeNuglify(); + builder.Services.AddSmidgeInMemory(false); // it will be enabled based on config/cachebuster + + builder.Services.AddUnique(); + builder.Services.AddUnique(); + builder.Services.AddTransient(); + builder.Services.ConfigureOptions(); return builder; } @@ -426,7 +437,7 @@ namespace Umbraco.Extensions var wrappedHostingSettings = new OptionsMonitorAdapter(hostingSettings); var wrappedWebRoutingSettings = new OptionsMonitorAdapter(webRoutingSettings); - return new AspNetCoreHostingEnvironment(wrappedHostingSettings,wrappedWebRoutingSettings, webHostEnvironment); + return new AspNetCoreHostingEnvironment(wrappedHostingSettings, wrappedWebRoutingSettings, webHostEnvironment); } } diff --git a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs index 02976f9e1d..78b8ede69e 100644 --- a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs +++ b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs @@ -6,15 +6,19 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Smidge.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Web; using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Infrastructure.WebAssets; using Umbraco.Cms.Web.Common.Profiler; using Umbraco.Extensions; @@ -41,11 +45,21 @@ namespace Umbraco.Cms.Web.Common.Middleware private readonly PublishedSnapshotServiceEventHandler _publishedSnapshotServiceEventHandler; private readonly IEventAggregator _eventAggregator; private readonly IHostingEnvironment _hostingEnvironment; + private readonly UmbracoRequestPaths _umbracoRequestPaths; + private readonly BackOfficeWebAssets _backOfficeWebAssets; + private readonly SmidgeOptions _smidgeOptions; private readonly WebProfiler _profiler; - private static bool s_cacheInitialized = false; + + private static bool s_cacheInitialized; private static bool s_cacheInitializedFlag = false; private static object s_cacheInitializedLock = new object(); +#pragma warning disable IDE0044 // Add readonly modifier + private static bool s_firstBackOfficeRequest; + private static bool s_firstBackOfficeReqestFlag; + private static object s_firstBackOfficeRequestLocker = new object(); +#pragma warning restore IDE0044 // Add readonly modifier + /// /// Initializes a new instance of the class. /// @@ -56,7 +70,10 @@ namespace Umbraco.Cms.Web.Common.Middleware PublishedSnapshotServiceEventHandler publishedSnapshotServiceEventHandler, IEventAggregator eventAggregator, IProfiler profiler, - IHostingEnvironment hostingEnvironment) + IHostingEnvironment hostingEnvironment, + UmbracoRequestPaths umbracoRequestPaths, + BackOfficeWebAssets backOfficeWebAssets, + IOptions smidgeOptions) { _logger = logger; _umbracoContextFactory = umbracoContextFactory; @@ -64,6 +81,9 @@ namespace Umbraco.Cms.Web.Common.Middleware _publishedSnapshotServiceEventHandler = publishedSnapshotServiceEventHandler; _eventAggregator = eventAggregator; _hostingEnvironment = hostingEnvironment; + _umbracoRequestPaths = umbracoRequestPaths; + _backOfficeWebAssets = backOfficeWebAssets; + _smidgeOptions = smidgeOptions.Value; _profiler = profiler as WebProfiler; // Ignore if not a WebProfiler } @@ -73,6 +93,8 @@ namespace Umbraco.Cms.Web.Common.Middleware // do not process if client-side request if (context.Request.IsClientSideRequest()) { + // we need this here because for bundle requests, these are 'client side' requests that we need to handle + LazyInitializeBackOfficeServices(context.Request.Path); await next(context); return; } @@ -97,7 +119,8 @@ namespace Umbraco.Cms.Web.Common.Middleware _logger.LogTrace("Begin request [{HttpRequestId}]: {RequestUrl}", httpRequestId, pathAndQuery); try - { + { + LazyInitializeBackOfficeServices(context.Request.Path); await _eventAggregator.PublishAsync(new UmbracoRequestBegin(umbracoContextReference.UmbracoContext)); } catch (Exception ex) @@ -141,6 +164,30 @@ namespace Umbraco.Cms.Web.Common.Middleware _profiler?.UmbracoApplicationEndRequest(context); } + /// + /// Used to lazily initialize any back office services when the first request to the back office is made + /// + /// + /// + private void LazyInitializeBackOfficeServices(PathString absPath) + { + if (s_firstBackOfficeRequest) + { + return; + } + + if (_umbracoRequestPaths.IsBackOfficeRequest(absPath) + || absPath.Value.InvariantStartsWith($"/{_smidgeOptions.UrlOptions.CompositeFilePath}") + || absPath.Value.InvariantStartsWith($"/{_smidgeOptions.UrlOptions.BundleFilePath}")) + { + LazyInitializer.EnsureInitialized(ref s_firstBackOfficeRequest, ref s_firstBackOfficeReqestFlag, ref s_firstBackOfficeRequestLocker, () => + { + _backOfficeWebAssets.CreateBundles(); + return true; + }); + } + } + private Uri GetApplicationUrlFromCurrentRequest(HttpRequest request) { // We only consider GET and POST. diff --git a/src/Umbraco.Web.Common/RuntimeMinification/SmidgeComposer.cs b/src/Umbraco.Web.Common/RuntimeMinification/SmidgeComposer.cs deleted file mode 100644 index c4869a9cf9..0000000000 --- a/src/Umbraco.Web.Common/RuntimeMinification/SmidgeComposer.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Smidge.FileProcessors; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Core.WebAssets; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Web.Common.RuntimeMinification -{ - public sealed class SmidgeComposer : IComposer - { - public void Compose(IUmbracoBuilder builder) - { - // TODO: For this to work we need to have services.AddSmidge() based on the Smidge APIs but our composer APIs don't really let us do that - // This makes it a bit awkward to boot the runtime since that call would need to be made outside of the composer... .hrm... - - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddTransient(); - } - } -} diff --git a/src/Umbraco.Web.Common/RuntimeMinification/SmidgeOptionsSetup.cs b/src/Umbraco.Web.Common/RuntimeMinification/SmidgeOptionsSetup.cs new file mode 100644 index 0000000000..d054a0c036 --- /dev/null +++ b/src/Umbraco.Web.Common/RuntimeMinification/SmidgeOptionsSetup.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.Options; +using Smidge.Options; +using Umbraco.Cms.Core.Configuration.Models; + +namespace Umbraco.Cms.Web.Common.RuntimeMinification +{ + public class SmidgeOptionsSetup : IConfigureOptions + { + private readonly IOptions _runtimeMinificatinoSettings; + + public SmidgeOptionsSetup(IOptions runtimeMinificatinoSettings) + => _runtimeMinificatinoSettings = runtimeMinificatinoSettings; + + /// + /// Configures Smidge to use in-memory caching if configured that way or if certain cache busters are used + /// + /// + public void Configure(SmidgeOptions options) + => options.CacheOptions.UseInMemoryCache = _runtimeMinificatinoSettings.Value.UseInMemoryCache || _runtimeMinificatinoSettings.Value.CacheBuster == RuntimeMinificationCacheBuster.Timestamp; + } +} diff --git a/src/Umbraco.Web.Common/RuntimeMinification/SmidgeRuntimeMinifier.cs b/src/Umbraco.Web.Common/RuntimeMinification/SmidgeRuntimeMinifier.cs index 11ed3680c1..85f0398ea6 100644 --- a/src/Umbraco.Web.Common/RuntimeMinification/SmidgeRuntimeMinifier.cs +++ b/src/Umbraco.Web.Common/RuntimeMinification/SmidgeRuntimeMinifier.cs @@ -3,13 +3,16 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Threading.Tasks; +using Microsoft.Extensions.Options; using Smidge; using Smidge.Cache; using Smidge.CompositeFiles; using Smidge.FileProcessors; using Smidge.Models; using Smidge.Nuglify; +using Smidge.Options; using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.WebAssets; using CssFile = Smidge.Models.CssFile; @@ -17,24 +20,12 @@ using JavaScriptFile = Smidge.Models.JavaScriptFile; namespace Umbraco.Cms.Web.Common.RuntimeMinification { -// public class UmbracoCacheBuster : ICacheBuster -// { -// /// -// /// Gets the cache buster value -// /// -// /// -// public string GetValue() => throw new NotImplementedException(); - -// // This doesn't do anything in Smidge (is removed in v4 unreleased version) -// [EditorBrowsable(EditorBrowsableState.Never)] -// public bool PersistProcessedFiles => throw new NotImplementedException(); -// } - public class SmidgeRuntimeMinifier : IRuntimeMinifier { private readonly IHostingEnvironment _hostingEnvironment; - private readonly ISmidgeConfig _smidgeConfig; private readonly IConfigManipulator _configManipulator; + private readonly CacheBusterResolver _cacheBusterResolver; + private readonly RuntimeMinificationSettings _runtimeMinificationSettings; private readonly IBundleManager _bundles; private readonly SmidgeHelperAccessor _smidge; @@ -43,47 +34,93 @@ namespace Umbraco.Cms.Web.Common.RuntimeMinification private readonly Lazy _cssMinPipeline; // default pipelines for processing js/css files for the back office - private readonly Lazy _jsPipeline; - private readonly Lazy _cssPipeline; + private readonly Lazy _jsOptimizedPipeline; + private readonly Lazy _jsNonOptimizedPipeline; + private readonly Lazy _cssOptimizedPipeline; + private readonly Lazy _cssNonOptimizedPipeline; + private ICacheBuster _cacheBuster; + private readonly Type _cacheBusterType; public SmidgeRuntimeMinifier( IBundleManager bundles, SmidgeHelperAccessor smidge, IHostingEnvironment hostingEnvironment, - ISmidgeConfig smidgeConfig, - IConfigManipulator configManipulator) + IConfigManipulator configManipulator, + IOptions runtimeMinificationSettings, + CacheBusterResolver cacheBusterResolver) { _bundles = bundles; _smidge = smidge; _hostingEnvironment = hostingEnvironment; - _smidgeConfig = smidgeConfig; _configManipulator = configManipulator; - + _cacheBusterResolver = cacheBusterResolver; + _runtimeMinificationSettings = runtimeMinificationSettings.Value; _jsMinPipeline = new Lazy(() => _bundles.PipelineFactory.Create(typeof(JsMinifier))); _cssMinPipeline = new Lazy(() => _bundles.PipelineFactory.Create(typeof(NuglifyCss))); // replace the default JsMinifier with NuglifyJs and CssMinifier with NuglifyCss in the default pipelines // for use with our bundles only (not modifying global options) - _jsPipeline = new Lazy(() => bundles.PipelineFactory.DefaultJs().Replace(_bundles.PipelineFactory)); - _cssPipeline = new Lazy(() => bundles.PipelineFactory.DefaultCss().Replace(_bundles.PipelineFactory)); + _jsOptimizedPipeline = new Lazy(() => bundles.PipelineFactory.DefaultJs().Replace(_bundles.PipelineFactory)); + _jsNonOptimizedPipeline = new Lazy(() => bundles.PipelineFactory.DefaultJs()); + _cssOptimizedPipeline = new Lazy(() => bundles.PipelineFactory.DefaultCss().Replace(_bundles.PipelineFactory)); + _cssNonOptimizedPipeline = new Lazy(() => bundles.PipelineFactory.DefaultCss()); + Type cacheBusterType = _runtimeMinificationSettings.CacheBuster switch + { + RuntimeMinificationCacheBuster.AppDomain => typeof(AppDomainLifetimeCacheBuster), + RuntimeMinificationCacheBuster.Version => typeof(ConfigCacheBuster), + RuntimeMinificationCacheBuster.Timestamp => typeof(TimestampCacheBuster), + _ => throw new NotImplementedException() + }; + + _cacheBusterType = cacheBusterType; } - public string CacheBuster => _smidgeConfig.Version; + public string CacheBuster => (_cacheBuster ??= _cacheBusterResolver.GetCacheBuster(_cacheBusterType)).GetValue(); // only issue with creating bundles like this is that we don't have full control over the bundle options, though that could - public void CreateCssBundle(string bundleName, params string[] filePaths) + public void CreateCssBundle(string bundleName, bool optimizeOutput, params string[] filePaths) { if (filePaths.Any(f => !f.StartsWith("/") && !f.StartsWith("~/"))) + { throw new InvalidOperationException("All file paths must be absolute"); + } if (_bundles.Exists(bundleName)) + { throw new InvalidOperationException($"The bundle name {bundleName} already exists"); + } - // TODO: Here we could configure bundle options instead of using smidge's global defaults. - // For example we can use our own custom cache buster for this bundle without having the global one - // affect this or vice versa. - var bundle = _bundles.Create(bundleName, _cssPipeline.Value, WebFileType.Css, filePaths); + if (optimizeOutput) + { + var bundle = _bundles.Create(bundleName, _cssOptimizedPipeline.Value, WebFileType.Css, filePaths) + .WithEnvironmentOptions( + BundleEnvironmentOptions.Create() + .ForDebug(builder => builder + // auto-invalidate bundle if files change in debug + .EnableFileWatcher() + // keep using composite files in debug, not raw static files + .EnableCompositeProcessing() + // use the cache buster defined in config + .SetCacheBusterType(_cacheBusterType)) + .ForProduction(builder => builder + // use the cache buster defined in config + .SetCacheBusterType(_cacheBusterType)) + .Build()); + } + else + { + var bundle = _bundles.Create(bundleName, _cssNonOptimizedPipeline.Value, WebFileType.Css, filePaths) + .WithEnvironmentOptions( + BundleEnvironmentOptions.Create() + .ForDebug(builder => builder + // use the cache buster defined in config + .SetCacheBusterType(_cacheBusterType)) + .ForProduction(builder => builder + // use the cache buster defined in config + .SetCacheBusterType(_cacheBusterType)) + .Build()); + } } public async Task RenderCssHereAsync(string bundleName) => (await _smidge.SmidgeHelper.CssHereAsync(bundleName, _hostingEnvironment.IsDebugMode)).ToString(); @@ -91,16 +128,45 @@ namespace Umbraco.Cms.Web.Common.RuntimeMinification public void CreateJsBundle(string bundleName, bool optimizeOutput, params string[] filePaths) { if (filePaths.Any(f => !f.StartsWith("/") && !f.StartsWith("~/"))) + { throw new InvalidOperationException("All file paths must be absolute"); + } if (_bundles.Exists(bundleName)) + { throw new InvalidOperationException($"The bundle name {bundleName} already exists"); + } - // TODO: Here we could configure bundle options instead of using smidge's global defaults. - // For example we can use our own custom cache buster for this bundle without having the global one - // affect this or vice versa. - var pipeline = optimizeOutput ? _jsPipeline.Value : _bundles.PipelineFactory.Create(); - var bundle = _bundles.Create(bundleName, pipeline, WebFileType.Js, filePaths); + if (optimizeOutput) + { + var bundle = _bundles.Create(bundleName, _jsOptimizedPipeline.Value, WebFileType.Js, filePaths) + .WithEnvironmentOptions( + BundleEnvironmentOptions.Create() + .ForDebug(builder => builder + // auto-invalidate bundle if files change in debug + .EnableFileWatcher() + // keep using composite files in debug, not raw static files + .EnableCompositeProcessing() + // use the cache buster defined in config + .SetCacheBusterType(_cacheBusterType)) + .ForProduction(builder => builder + // use the cache buster defined in config + .SetCacheBusterType(_cacheBusterType)) + .Build()); + } + else + { + var bundle = _bundles.Create(bundleName, _jsNonOptimizedPipeline.Value, WebFileType.Js, filePaths) + .WithEnvironmentOptions( + BundleEnvironmentOptions.Create() + .ForDebug(builder => builder + // use the cache buster defined in config + .SetCacheBusterType(_cacheBusterType)) + .ForProduction(builder => builder + // use the cache buster defined in config + .SetCacheBusterType(_cacheBusterType)) + .Build()); + } } public async Task RenderJsHereAsync(string bundleName) => (await _smidge.SmidgeHelper.JsHereAsync(bundleName, _hostingEnvironment.IsDebugMode)).ToString(); diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index 03fcca3c90..bb2ecbc346 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -1,4 +1,4 @@ - + net5.0 @@ -30,10 +30,9 @@ - - - - + + + diff --git a/src/Umbraco.Web.UI.NetCore/appsettings.Development.json b/src/Umbraco.Web.UI.NetCore/appsettings.Development.json index 06afcf2a7a..edeb4946d6 100644 --- a/src/Umbraco.Web.UI.NetCore/appsettings.Development.json +++ b/src/Umbraco.Web.UI.NetCore/appsettings.Development.json @@ -17,30 +17,34 @@ ] }, "Umbraco": { - "CMS": { - "Global": { - "Smtp": { - //"From": "your@email.here", - //"Host": "localhost", - // "Port": "25" - } - }, - "Hosting": { - "Debug": true - }, - "RichTextEditor": { - "Commands" : [ - { - "Alias": "fullscreen", - "Name": "Full Screen", - "Mode": "All" - } - ], - "Plugins": [ - "fullscreen" - ] + "CMS": { + "Global": { + "Smtp": { + //"From": "your@email.here", + //"Host": "localhost", + // "Port": "25" } + }, + "Hosting": { + "Debug": true + }, + "RuntimeMinification": { + "useInMemoryCache": true, + "cacheBuster": "Timestamp" + }, + "RichTextEditor": { + "Commands": [ + { + "Alias": "fullscreen", + "Name": "Full Screen", + "Mode": "All" + } + ], + "Plugins": [ + "fullscreen" + ] } + } } }