From b16bf2f58379feac0ba7b13cefd9ad18190dc315 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 15 Jul 2021 14:06:33 -0600 Subject: [PATCH] Adds BundlingOptions for better bundling support and moves all logic to the bundler (don't manually process urls). --- src/Umbraco.Core/WebAssets/BundlingOptions.cs | 44 ++++++++++ .../WebAssets/IRuntimeMinifier.cs | 4 +- .../WebAssets/BackOfficeWebAssets.cs | 54 ++++++++++-- .../Controllers/BackOfficeController.cs | 3 +- .../Extensions/RuntimeMinifierExtensions.cs | 28 ++---- .../SmidgeRuntimeMinifier.cs | 88 ++++++------------- 6 files changed, 128 insertions(+), 93 deletions(-) create mode 100644 src/Umbraco.Core/WebAssets/BundlingOptions.cs diff --git a/src/Umbraco.Core/WebAssets/BundlingOptions.cs b/src/Umbraco.Core/WebAssets/BundlingOptions.cs new file mode 100644 index 0000000000..e9234b06a4 --- /dev/null +++ b/src/Umbraco.Core/WebAssets/BundlingOptions.cs @@ -0,0 +1,44 @@ +using System; + +namespace Umbraco.Cms.Core.WebAssets +{ + public struct BundlingOptions : IEquatable + { + public static BundlingOptions OptimizedAndComposite => new BundlingOptions(true, true); + public static BundlingOptions OptimizedNotComposite => new BundlingOptions(true, false); + public static BundlingOptions NotOptimizedNotComposite => new BundlingOptions(false, false); + public static BundlingOptions NotOptimizedAndComposite => new BundlingOptions(false, false); + + public BundlingOptions(bool optimizeOutput = true, bool enabledCompositeFiles = true) + { + OptimizeOutput = optimizeOutput; + EnabledCompositeFiles = enabledCompositeFiles; + } + + /// + /// If true, the files in the bundle will be minified + /// + public bool OptimizeOutput { get; } + + /// + /// If true, the files in the bundle will be combined, if false the files + /// will be served as individual files. + /// + public bool EnabledCompositeFiles { get; } + + public override bool Equals(object obj) => obj is BundlingOptions options && Equals(options); + public bool Equals(BundlingOptions other) => OptimizeOutput == other.OptimizeOutput && EnabledCompositeFiles == other.EnabledCompositeFiles; + + public override int GetHashCode() + { + int hashCode = 2130304063; + hashCode = hashCode * -1521134295 + OptimizeOutput.GetHashCode(); + hashCode = hashCode * -1521134295 + EnabledCompositeFiles.GetHashCode(); + return hashCode; + } + + public static bool operator ==(BundlingOptions left, BundlingOptions right) => left.Equals(right); + + public static bool operator !=(BundlingOptions left, BundlingOptions right) => !(left == right); + } +} diff --git a/src/Umbraco.Core/WebAssets/IRuntimeMinifier.cs b/src/Umbraco.Core/WebAssets/IRuntimeMinifier.cs index f287c722ad..7ca1cd883b 100644 --- a/src/Umbraco.Core/WebAssets/IRuntimeMinifier.cs +++ b/src/Umbraco.Core/WebAssets/IRuntimeMinifier.cs @@ -25,7 +25,7 @@ namespace Umbraco.Cms.Core.WebAssets /// /// Thrown if any of the paths specified are not absolute /// - void CreateCssBundle(string bundleName, bool optimizeOutput, params string[] filePaths); + void CreateCssBundle(string bundleName, BundlingOptions bundleOptions, params string[] filePaths); /// /// Renders the html link tag for the bundle @@ -48,7 +48,7 @@ namespace Umbraco.Cms.Core.WebAssets /// /// Thrown if any of the paths specified are not absolute /// - void CreateJsBundle(string bundleName, bool optimizeOutput, params string[] filePaths); + void CreateJsBundle(string bundleName, BundlingOptions bundleOptions, params string[] filePaths); /// /// Renders the html script tag for the bundle diff --git a/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs b/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs index 07de655fb8..8944691404 100644 --- a/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs +++ b/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs @@ -21,6 +21,8 @@ namespace Umbraco.Cms.Infrastructure.WebAssets public const string UmbracoInitCssBundleName = "umbraco-backoffice-init-css"; public const string UmbracoCoreJsBundleName = "umbraco-backoffice-js"; public const string UmbracoExtensionsJsBundleName = "umbraco-backoffice-extensions-js"; + public const string UmbracoNonOptimizedPackageJsBundleName = "umbraco-backoffice-non-optimized-js"; + public const string UmbracoNonOptimizedPackageCssBundleName = "umbraco-backoffice-non-optimized-css"; public const string UmbracoTinyMceJsBundleName = "umbraco-tinymce-js"; public const string UmbracoUpgradeCssBundleName = "umbraco-authorize-upgrade-css"; @@ -51,26 +53,32 @@ namespace Umbraco.Cms.Infrastructure.WebAssets { // Create bundles - _runtimeMinifier.CreateCssBundle(UmbracoInitCssBundleName, false, + _runtimeMinifier.CreateCssBundle(UmbracoInitCssBundleName, + BundlingOptions.NotOptimizedAndComposite, FormatPaths("lib/bootstrap-social/bootstrap-social.css", "assets/css/umbraco.min.css", "lib/font-awesome/css/font-awesome.min.css")); - _runtimeMinifier.CreateCssBundle(UmbracoUpgradeCssBundleName, false, + _runtimeMinifier.CreateCssBundle(UmbracoUpgradeCssBundleName, + BundlingOptions.NotOptimizedAndComposite, FormatPaths("assets/css/umbraco.min.css", "lib/bootstrap-social/bootstrap-social.css", "lib/font-awesome/css/font-awesome.min.css")); - _runtimeMinifier.CreateCssBundle(UmbracoPreviewCssBundleName, false, + _runtimeMinifier.CreateCssBundle(UmbracoPreviewCssBundleName, + BundlingOptions.NotOptimizedAndComposite, FormatPaths("assets/css/canvasdesigner.min.css")); - _runtimeMinifier.CreateJsBundle(UmbracoPreviewJsBundleName, false, + _runtimeMinifier.CreateJsBundle(UmbracoPreviewJsBundleName, + BundlingOptions.NotOptimizedAndComposite, FormatPaths(GetScriptsForPreview())); - _runtimeMinifier.CreateJsBundle(UmbracoTinyMceJsBundleName, false, + _runtimeMinifier.CreateJsBundle(UmbracoTinyMceJsBundleName, + BundlingOptions.NotOptimizedAndComposite, FormatPaths(GetScriptsForTinyMce())); - _runtimeMinifier.CreateJsBundle(UmbracoCoreJsBundleName, false, + _runtimeMinifier.CreateJsBundle(UmbracoCoreJsBundleName, + BundlingOptions.NotOptimizedAndComposite, FormatPaths(GetScriptsForBackOfficeCore())); @@ -91,13 +99,16 @@ namespace Umbraco.Cms.Infrastructure.WebAssets _runtimeMinifier.CreateJsBundle( UmbracoExtensionsJsBundleName, - true, + BundlingOptions.OptimizedAndComposite, FormatPaths( GetScriptsForBackOfficeExtensions(jsAssets))); // Create a bundle per package manifest that is declaring an Independent bundle type RegisterPackageBundlesForIndependentOptions(_parser.CombinedManifest.Scripts, AssetType.Javascript); + // Create a single non-optimized (no file processing) bundle for all manifests declaring None as a bundle option + RegisterPackageBundlesForNoneOption(_parser.CombinedManifest.Scripts, UmbracoNonOptimizedPackageJsBundleName); + // This bundle includes all CSS from property editor assets, // custom back office assets, and any CSS found in package manifests // that have the default bundle options. @@ -107,17 +118,42 @@ namespace Umbraco.Cms.Infrastructure.WebAssets _runtimeMinifier.CreateCssBundle( UmbracoCssBundleName, - true, + BundlingOptions.OptimizedAndComposite, FormatPaths( GetStylesheetsForBackOffice(cssAssets))); // Create a bundle per package manifest that is declaring an Independent bundle type RegisterPackageBundlesForIndependentOptions(_parser.CombinedManifest.Stylesheets, AssetType.Css); + + // Create a single non-optimized (no file processing) bundle for all manifests declaring None as a bundle option + RegisterPackageBundlesForNoneOption(_parser.CombinedManifest.Stylesheets, UmbracoNonOptimizedPackageCssBundleName); } public static string GetIndependentPackageBundleName(ManifestAssets manifestAssets, AssetType assetType) => $"{manifestAssets.PackageName.ToLowerInvariant()}-{(assetType == AssetType.Css ? "css" : "js")}"; + private void RegisterPackageBundlesForNoneOption( + IReadOnlyDictionary> combinedPackageManifestAssets, + string bundleName) + { + var assets = new HashSet(StringComparer.InvariantCultureIgnoreCase); + + // Create a bundle per package manifest that is declaring the matching BundleOptions + if (combinedPackageManifestAssets.TryGetValue(BundleOptions.None, out IReadOnlyList manifestAssetList)) + { + foreach(var asset in manifestAssetList.SelectMany(x => x.Assets)) + { + assets.Add(asset); + } + } + + _runtimeMinifier.CreateJsBundle( + bundleName, + // no optimization, no composite files, just render individual files + BundlingOptions.NotOptimizedNotComposite, + FormatPaths(assets.ToArray())); + } + private void RegisterPackageBundlesForIndependentOptions( IReadOnlyDictionary> combinedPackageManifestAssets, AssetType assetType) @@ -129,7 +165,7 @@ namespace Umbraco.Cms.Infrastructure.WebAssets { _runtimeMinifier.CreateJsBundle( GetIndependentPackageBundleName(manifestAssets, assetType), - true, + BundlingOptions.OptimizedAndComposite, FormatPaths(manifestAssets.Assets.ToArray())); } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index ca1aea69a7..67dfaa29fe 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -220,8 +220,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var result = await _runtimeMinifier.GetScriptForLoadingBackOfficeAsync( _globalSettings, _hostingEnvironment, - _manifestParser, - Url); + _manifestParser); return new JavaScriptResult(result); } diff --git a/src/Umbraco.Web.BackOffice/Extensions/RuntimeMinifierExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/RuntimeMinifierExtensions.cs index e436a57d83..0be88c9d2a 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/RuntimeMinifierExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/RuntimeMinifierExtensions.cs @@ -22,8 +22,7 @@ namespace Umbraco.Extensions this IRuntimeMinifier minifier, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, - IManifestParser manifestParser, - IUrlHelper urlHelper) + IManifestParser manifestParser) { var files = new HashSet(StringComparer.InvariantCultureIgnoreCase); foreach(var file in await minifier.GetJsAssetPathsAsync(BackOfficeWebAssets.UmbracoCoreJsBundleName)) @@ -50,15 +49,9 @@ namespace Umbraco.Extensions } // process the "None" bundles, meaning we'll just render the script as-is - if (manifestParser.CombinedManifest.Scripts.TryGetValue(BundleOptions.None, out IReadOnlyList noneManifestAssetsList)) + foreach (var asset in await minifier.GetJsAssetPathsAsync(BackOfficeWebAssets.UmbracoNonOptimizedPackageJsBundleName)) { - foreach (ManifestAssets manifestAssets in noneManifestAssetsList) - { - foreach(var asset in manifestAssets.Assets) - { - files.Add(urlHelper.Content(asset)); - } - } + files.Add(asset); } var result = BackOfficeJavaScriptInitializer.GetJavascriptInitialization( @@ -67,7 +60,7 @@ namespace Umbraco.Extensions globalSettings, hostingEnvironment); - result += await GetStylesheetInitializationAsync(minifier, manifestParser, urlHelper); + result += await GetStylesheetInitializationAsync(minifier, manifestParser); return result; } @@ -77,8 +70,7 @@ namespace Umbraco.Extensions /// private static async Task GetStylesheetInitializationAsync( IRuntimeMinifier minifier, - IManifestParser manifestParser, - IUrlHelper urlHelper) + IManifestParser manifestParser) { var files = new HashSet(StringComparer.InvariantCultureIgnoreCase); foreach(var file in await minifier.GetCssAssetPathsAsync(BackOfficeWebAssets.UmbracoCssBundleName)) @@ -100,15 +92,9 @@ namespace Umbraco.Extensions } // process the "None" bundles, meaning we'll just render the script as-is - if (manifestParser.CombinedManifest.Stylesheets.TryGetValue(BundleOptions.None, out IReadOnlyList noneManifestAssetsList)) + foreach (var asset in await minifier.GetCssAssetPathsAsync(BackOfficeWebAssets.UmbracoNonOptimizedPackageCssBundleName)) { - foreach (ManifestAssets manifestAssets in noneManifestAssetsList) - { - foreach (var asset in manifestAssets.Assets) - { - files.Add(urlHelper.Content(asset)); - } - } + files.Add(asset); } var sb = new StringBuilder(); diff --git a/src/Umbraco.Web.Common/RuntimeMinification/SmidgeRuntimeMinifier.cs b/src/Umbraco.Web.Common/RuntimeMinification/SmidgeRuntimeMinifier.cs index c202c49980..96188ba08c 100644 --- a/src/Umbraco.Web.Common/RuntimeMinification/SmidgeRuntimeMinifier.cs +++ b/src/Umbraco.Web.Common/RuntimeMinification/SmidgeRuntimeMinifier.cs @@ -90,7 +90,7 @@ namespace Umbraco.Cms.Web.Common.RuntimeMinification 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, bool optimizeOutput, params string[] filePaths) + public void CreateCssBundle(string bundleName, BundlingOptions bundleOptions, params string[] filePaths) { if (filePaths.Any(f => !f.StartsWith("/") && !f.StartsWith("~/"))) { @@ -102,39 +102,17 @@ namespace Umbraco.Cms.Web.Common.RuntimeMinification throw new InvalidOperationException($"The bundle name {bundleName} already exists"); } - 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() - // 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()); - } + PreProcessPipeline pipeline = bundleOptions.OptimizeOutput + ? _cssOptimizedPipeline.Value + : _cssNonOptimizedPipeline.Value; + + Bundle bundle = _bundles.Create(bundleName, pipeline, WebFileType.Css, filePaths); + bundle.WithEnvironmentOptions(ConfigureBundleEnvironmentOptions(bundleOptions)); } public async Task RenderCssHereAsync(string bundleName) => (await _smidge.SmidgeHelper.CssHereAsync(bundleName, _hostingEnvironment.IsDebugMode)).ToString(); - public void CreateJsBundle(string bundleName, bool optimizeOutput, params string[] filePaths) + public void CreateJsBundle(string bundleName, BundlingOptions bundleOptions, params string[] filePaths) { if (filePaths.Any(f => !f.StartsWith("/") && !f.StartsWith("~/"))) { @@ -146,40 +124,32 @@ namespace Umbraco.Cms.Web.Common.RuntimeMinification throw new InvalidOperationException($"The bundle name {bundleName} already exists"); } - 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() - // 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()); - } + PreProcessPipeline pipeline = bundleOptions.OptimizeOutput + ? _jsOptimizedPipeline.Value + : _jsNonOptimizedPipeline.Value; + + Bundle bundle = _bundles.Create(bundleName, pipeline, WebFileType.Js, filePaths); + bundle.WithEnvironmentOptions(ConfigureBundleEnvironmentOptions(bundleOptions)); + } + + private BundleEnvironmentOptions ConfigureBundleEnvironmentOptions(BundlingOptions bundleOptions) + { + var bundleEnvironmentOptions = new BundleEnvironmentOptions(); + // auto-invalidate bundle if files change in debug + bundleEnvironmentOptions.DebugOptions.FileWatchOptions.Enabled = true; + // set cache busters + bundleEnvironmentOptions.DebugOptions.SetCacheBusterType(_cacheBusterType); + bundleEnvironmentOptions.ProductionOptions.SetCacheBusterType(_cacheBusterType); + // config if the files should be combined + bundleEnvironmentOptions.ProductionOptions.ProcessAsCompositeFile = bundleOptions.EnabledCompositeFiles; + + return bundleEnvironmentOptions; } public async Task RenderJsHereAsync(string bundleName) => (await _smidge.SmidgeHelper.JsHereAsync(bundleName, _hostingEnvironment.IsDebugMode)).ToString(); - public async Task> GetJsAssetPathsAsync(string bundleName) => await _smidge.SmidgeHelper.GenerateJsUrlsAsync(bundleName, _hostingEnvironment.IsDebugMode); + public async Task> GetCssAssetPathsAsync(string bundleName) => await _smidge.SmidgeHelper.GenerateCssUrlsAsync(bundleName, _hostingEnvironment.IsDebugMode); ///