diff --git a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs index ab6a7e9396..f1a4f0643c 100644 --- a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs @@ -64,7 +64,7 @@ namespace Umbraco.Cms.Core.Configuration.Grid } // add manifest editors, skip duplicates - foreach (var gridEditor in _manifestParser.Manifest.GridEditors) + foreach (var gridEditor in _manifestParser.CombinedManifest.GridEditors) { if (editors.Contains(gridEditor) == false) editors.Add(gridEditor); } diff --git a/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs b/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs index ff1223d983..a80c79a3ef 100644 --- a/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs +++ b/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs @@ -33,7 +33,7 @@ namespace Umbraco.Cms.Core.ContentApps // its dependencies too, and that can create cycles or other oddities var manifestParser = factory.GetRequiredService(); var ioHelper = factory.GetRequiredService(); - return base.CreateItems(factory).Concat(manifestParser.Manifest.ContentApps.Select(x => new ManifestContentAppFactory(x, ioHelper))); + return base.CreateItems(factory).Concat(manifestParser.CombinedManifest.ContentApps.Select(x => new ManifestContentAppFactory(x, ioHelper))); } } } diff --git a/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs b/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs index 55e840ad8e..348e81e383 100644 --- a/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs +++ b/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.DependencyInjection; @@ -19,7 +19,7 @@ namespace Umbraco.Cms.Core.Dashboards // its dependencies too, and that can create cycles or other oddities var manifestParser = factory.GetRequiredService(); - var dashboardSections = Merge(base.CreateItems(factory), manifestParser.Manifest.Dashboards); + var dashboardSections = Merge(base.CreateItems(factory), manifestParser.CombinedManifest.Dashboards); return dashboardSections; } diff --git a/src/Umbraco.Core/Manifest/BundleOptions.cs b/src/Umbraco.Core/Manifest/BundleOptions.cs new file mode 100644 index 0000000000..810efb6c45 --- /dev/null +++ b/src/Umbraco.Core/Manifest/BundleOptions.cs @@ -0,0 +1,26 @@ +namespace Umbraco.Cms.Core.Manifest +{ + public enum BundleOptions + { + /// + /// The default bundling behavior for assets in the package folder. + /// + /// + /// The assets will be bundled with the typical packages bundle. + /// + Default = 0, + + /// + /// The assets in the package will not be processed at all and will all be requested as individual assets. + /// + /// + /// This will essentially be a bundle that has composite processing turned off for both debug and production. + /// + None = 1, + + /// + /// The packages assets will be processed as it's own separate bundle. (in debug, files will not be processed) + /// + Independent = 2 + } +} diff --git a/src/Umbraco.Core/Manifest/CompositePackageManifest.cs b/src/Umbraco.Core/Manifest/CompositePackageManifest.cs new file mode 100644 index 0000000000..939d635fc3 --- /dev/null +++ b/src/Umbraco.Core/Manifest/CompositePackageManifest.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using Umbraco.Cms.Core.PropertyEditors; + +namespace Umbraco.Cms.Core.Manifest +{ + + /// + /// A package manifest made up of all combined manifests + /// + public class CompositePackageManifest + { + public CompositePackageManifest( + IReadOnlyList propertyEditors, + IReadOnlyList parameterEditors, + IReadOnlyList gridEditors, + IReadOnlyList contentApps, + IReadOnlyList dashboards, + IReadOnlyList sections, + IReadOnlyDictionary> scripts, + IReadOnlyDictionary> stylesheets) + { + PropertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors)); + ParameterEditors = parameterEditors ?? throw new ArgumentNullException(nameof(parameterEditors)); + GridEditors = gridEditors ?? throw new ArgumentNullException(nameof(gridEditors)); + ContentApps = contentApps ?? throw new ArgumentNullException(nameof(contentApps)); + Dashboards = dashboards ?? throw new ArgumentNullException(nameof(dashboards)); + Sections = sections ?? throw new ArgumentNullException(nameof(sections)); + Scripts = scripts ?? throw new ArgumentNullException(nameof(scripts)); + Stylesheets = stylesheets ?? throw new ArgumentNullException(nameof(stylesheets)); + } + + /// + /// Gets or sets the property editors listed in the manifest. + /// + public IReadOnlyList PropertyEditors { get; } + + /// + /// Gets or sets the parameter editors listed in the manifest. + /// + public IReadOnlyList ParameterEditors { get; } + + /// + /// Gets or sets the grid editors listed in the manifest. + /// + public IReadOnlyList GridEditors { get; } + + /// + /// Gets or sets the content apps listed in the manifest. + /// + public IReadOnlyList ContentApps { get; } + + /// + /// Gets or sets the dashboards listed in the manifest. + /// + public IReadOnlyList Dashboards { get; } + + /// + /// Gets or sets the sections listed in the manifest. + /// + public IReadOnlyCollection Sections { get; } + + public IReadOnlyDictionary> Scripts { get; } + + public IReadOnlyDictionary> Stylesheets { get; } + } +} diff --git a/src/Umbraco.Core/Manifest/IManifestParser.cs b/src/Umbraco.Core/Manifest/IManifestParser.cs index dc3a19714e..9e56ed17fc 100644 --- a/src/Umbraco.Core/Manifest/IManifestParser.cs +++ b/src/Umbraco.Core/Manifest/IManifestParser.cs @@ -4,13 +4,13 @@ namespace Umbraco.Cms.Core.Manifest { public interface IManifestParser { - string Path { get; set; } + //string Path { get; set; } /// /// Gets all manifests, merged into a single manifest object. /// /// - PackageManifest Manifest { get; } + CompositePackageManifest CombinedManifest { get; } /// /// Parses a manifest. diff --git a/src/Umbraco.Core/Manifest/ManifestAssets.cs b/src/Umbraco.Core/Manifest/ManifestAssets.cs new file mode 100644 index 0000000000..ad5dfaa0f0 --- /dev/null +++ b/src/Umbraco.Core/Manifest/ManifestAssets.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Cms.Core.Manifest +{ + public class ManifestAssets + { + public ManifestAssets(string packageName, IReadOnlyList assets) + { + PackageName = packageName ?? throw new ArgumentNullException(nameof(packageName)); + Assets = assets ?? throw new ArgumentNullException(nameof(assets)); + } + + public string PackageName { get; } + public IReadOnlyList Assets { get; } + } +} diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs index 56c69ebb15..753ec0613a 100644 --- a/src/Umbraco.Core/Manifest/PackageManifest.cs +++ b/src/Umbraco.Core/Manifest/PackageManifest.cs @@ -6,6 +6,7 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Core.Manifest { + /// /// Represents the content of a package manifest. /// @@ -47,6 +48,8 @@ namespace Umbraco.Cms.Core.Manifest /// [IgnoreDataMember] public string Source { get; set; } + [DataMember(Name = "bundleOptions")] + public BundleOptions BundleOptions { get; set; } /// /// Gets or sets the scripts listed in the manifest. diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditorCollection.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditorCollection.cs index 039de8cb6a..39647bb753 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditorCollection.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditorCollection.cs @@ -9,7 +9,7 @@ namespace Umbraco.Cms.Core.PropertyEditors public ParameterEditorCollection(DataEditorCollection dataEditors, IManifestParser manifestParser) : base(() => dataEditors .Where(x => (x.Type & EditorType.MacroParameter) > 0) - .Union(manifestParser.Manifest.PropertyEditors)) + .Union(manifestParser.CombinedManifest.PropertyEditors)) { } // note: virtual so it can be mocked diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs index 4b551d3257..1ddf150f93 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs @@ -9,7 +9,7 @@ namespace Umbraco.Cms.Core.PropertyEditors public PropertyEditorCollection(DataEditorCollection dataEditors, IManifestParser manifestParser) : base(() => dataEditors .Where(x => (x.Type & EditorType.PropertyValue) > 0) - .Union(manifestParser.Manifest.PropertyEditors)) + .Union(manifestParser.CombinedManifest.PropertyEditors)) { } public PropertyEditorCollection(DataEditorCollection dataEditors) diff --git a/src/Umbraco.Core/Sections/SectionCollectionBuilder.cs b/src/Umbraco.Core/Sections/SectionCollectionBuilder.cs index 0c5b2d7ba9..219d634261 100644 --- a/src/Umbraco.Core/Sections/SectionCollectionBuilder.cs +++ b/src/Umbraco.Core/Sections/SectionCollectionBuilder.cs @@ -18,7 +18,7 @@ namespace Umbraco.Cms.Core.Sections // its dependencies too, and that can create cycles or other oddities var manifestParser = factory.GetRequiredService(); - return base.CreateItems(factory).Concat(manifestParser.Manifest.Sections); + return base.CreateItems(factory).Concat(manifestParser.CombinedManifest.Sections); } } } diff --git a/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs b/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs index 7d98a19091..529a148093 100644 --- a/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs +++ b/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs @@ -62,22 +62,22 @@ namespace Umbraco.Cms.Core.Manifest /// /// Initializes a new instance of the class. /// - private ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ManifestFilterCollection filters, string path, ILogger logger, IIOHelper ioHelper, IHostingEnvironment hostingEnvironment) + private ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ManifestFilterCollection filters, string appPluginsPath, ILogger logger, IIOHelper ioHelper, IHostingEnvironment hostingEnvironment) { if (appCaches == null) throw new ArgumentNullException(nameof(appCaches)); _cache = appCaches.RuntimeCache; _validators = validators ?? throw new ArgumentNullException(nameof(validators)); _filters = filters ?? throw new ArgumentNullException(nameof(filters)); - if (path == null) throw new ArgumentNullException(nameof(path)); - if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(path)); + if (appPluginsPath == null) throw new ArgumentNullException(nameof(appPluginsPath)); + if (string.IsNullOrWhiteSpace(appPluginsPath)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(appPluginsPath)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _ioHelper = ioHelper; _hostingEnvironment = hostingEnvironment; - Path = path; + AppPluginsPath = appPluginsPath; } - public string Path + public string AppPluginsPath { get => _path; set => _path = value.StartsWith("~/") ? _hostingEnvironment.MapPathContentRoot(value) : value; @@ -87,11 +87,12 @@ namespace Umbraco.Cms.Core.Manifest /// Gets all manifests, merged into a single manifest object. /// /// - public PackageManifest Manifest - => _cache.GetCacheItem("Umbraco.Core.Manifest.ManifestParser::Manifests", () => + public CompositePackageManifest CombinedManifest + => _cache.GetCacheItem("Umbraco.Core.Manifest.ManifestParser::Manifests", () => { - var manifests = GetManifests(); + IEnumerable manifests = GetManifests(); return MergeManifests(manifests); + }, new TimeSpan(0, 4, 0)); /// @@ -130,10 +131,10 @@ namespace Umbraco.Cms.Core.Manifest /// /// Merges all manifests into one. /// - private static PackageManifest MergeManifests(IEnumerable manifests) + private static CompositePackageManifest MergeManifests(IEnumerable manifests) { - var scripts = new HashSet(); - var stylesheets = new HashSet(); + var scripts = new Dictionary>(); + var stylesheets = new Dictionary>(); var propertyEditors = new List(); var parameterEditors = new List(); var gridEditors = new List(); @@ -141,10 +142,28 @@ namespace Umbraco.Cms.Core.Manifest var dashboards = new List(); var sections = new List(); - foreach (var manifest in manifests) + foreach (PackageManifest manifest in manifests) { - if (manifest.Scripts != null) foreach (var script in manifest.Scripts) scripts.Add(script); - if (manifest.Stylesheets != null) foreach (var stylesheet in manifest.Stylesheets) stylesheets.Add(stylesheet); + if (manifest.Scripts != null) + { + if (!scripts.TryGetValue(manifest.BundleOptions, out List scriptsPerBundleOption)) + { + scriptsPerBundleOption = new List(); + scripts[manifest.BundleOptions] = scriptsPerBundleOption; + } + scriptsPerBundleOption.Add(new ManifestAssets(manifest.PackageName, manifest.Scripts)); + } + + if (manifest.Stylesheets != null) + { + if (!stylesheets.TryGetValue(manifest.BundleOptions, out List stylesPerBundleOption)) + { + stylesPerBundleOption = new List(); + stylesheets[manifest.BundleOptions] = stylesPerBundleOption; + } + stylesPerBundleOption.Add(new ManifestAssets(manifest.PackageName, manifest.Stylesheets)); + } + if (manifest.PropertyEditors != null) propertyEditors.AddRange(manifest.PropertyEditors); if (manifest.ParameterEditors != null) parameterEditors.AddRange(manifest.ParameterEditors); if (manifest.GridEditors != null) gridEditors.AddRange(manifest.GridEditors); @@ -153,17 +172,15 @@ namespace Umbraco.Cms.Core.Manifest if (manifest.Sections != null) sections.AddRange(manifest.Sections.DistinctBy(x => x.Alias.ToLowerInvariant())); } - return new PackageManifest - { - Scripts = scripts.ToArray(), - Stylesheets = stylesheets.ToArray(), - PropertyEditors = propertyEditors.ToArray(), - ParameterEditors = parameterEditors.ToArray(), - GridEditors = gridEditors.ToArray(), - ContentApps = contentApps.ToArray(), - Dashboards = dashboards.ToArray(), - Sections = sections.ToArray() - }; + return new CompositePackageManifest( + propertyEditors, + parameterEditors, + gridEditors, + contentApps, + dashboards, + sections, + scripts.ToDictionary(x => x.Key, x => (IReadOnlyList)x.Value), + stylesheets.ToDictionary(x => x.Key, x => (IReadOnlyList)x.Value)); } // gets all manifest files (recursively) diff --git a/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs b/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs index 74668d3090..07de655fb8 100644 --- a/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs +++ b/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs @@ -73,25 +73,66 @@ namespace Umbraco.Cms.Infrastructure.WebAssets _runtimeMinifier.CreateJsBundle(UmbracoCoreJsBundleName, false, FormatPaths(GetScriptsForBackOfficeCore())); + + // get the property editor assets var propertyEditorAssets = ScanPropertyEditors() .GroupBy(x => x.AssetType) .ToDictionary(x => x.Key, x => x.Select(c => c.FilePath)); + // get the back office custom assets var customAssets = _customBackOfficeAssetsCollection.GroupBy(x => x.DependencyType).ToDictionary(x => x.Key, x => x.Select(c => c.FilePath)); - var jsAssets = (customAssets.TryGetValue(AssetType.Javascript, out var customScripts) ? customScripts : Enumerable.Empty()) - .Union(propertyEditorAssets.TryGetValue(AssetType.Javascript, out var scripts) ? scripts : Enumerable.Empty()); + // This bundle includes all scripts from property editor assets, + // custom back office assets, and any scripts found in package manifests + // that have the default bundle options. + + IEnumerable jsAssets = (customAssets.TryGetValue(AssetType.Javascript, out IEnumerable customScripts) ? customScripts : Enumerable.Empty()) + .Union(propertyEditorAssets.TryGetValue(AssetType.Javascript, out IEnumerable scripts) ? scripts : Enumerable.Empty()); + _runtimeMinifier.CreateJsBundle( - UmbracoExtensionsJsBundleName, true, + UmbracoExtensionsJsBundleName, + true, FormatPaths( GetScriptsForBackOfficeExtensions(jsAssets))); - var cssAssets = (customAssets.TryGetValue(AssetType.Css, out var customStyles) ? customStyles : Enumerable.Empty()) - .Union(propertyEditorAssets.TryGetValue(AssetType.Css, out var styles) ? styles : Enumerable.Empty()); + // Create a bundle per package manifest that is declaring an Independent bundle type + RegisterPackageBundlesForIndependentOptions(_parser.CombinedManifest.Scripts, AssetType.Javascript); + + // 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. + + IEnumerable cssAssets = (customAssets.TryGetValue(AssetType.Css, out IEnumerable customStyles) ? customStyles : Enumerable.Empty()) + .Union(propertyEditorAssets.TryGetValue(AssetType.Css, out IEnumerable styles) ? styles : Enumerable.Empty()); + _runtimeMinifier.CreateCssBundle( - UmbracoCssBundleName, true, + UmbracoCssBundleName, + true, FormatPaths( GetStylesheetsForBackOffice(cssAssets))); + + // Create a bundle per package manifest that is declaring an Independent bundle type + RegisterPackageBundlesForIndependentOptions(_parser.CombinedManifest.Stylesheets, AssetType.Css); + } + + public static string GetIndependentPackageBundleName(ManifestAssets manifestAssets, AssetType assetType) + => $"{manifestAssets.PackageName.ToLowerInvariant()}-{(assetType == AssetType.Css ? "css" : "js")}"; + + private void RegisterPackageBundlesForIndependentOptions( + IReadOnlyDictionary> combinedPackageManifestAssets, + AssetType assetType) + { + // Create a bundle per package manifest that is declaring the matching BundleOptions + if (combinedPackageManifestAssets.TryGetValue(BundleOptions.Independent, out IReadOnlyList manifestAssetList)) + { + foreach (ManifestAssets manifestAssets in manifestAssetList) + { + _runtimeMinifier.CreateJsBundle( + GetIndependentPackageBundleName(manifestAssets, assetType), + true, + FormatPaths(manifestAssets.Assets.ToArray())); + } + } } /// @@ -100,10 +141,15 @@ namespace Umbraco.Cms.Infrastructure.WebAssets /// private string[] GetScriptsForBackOfficeExtensions(IEnumerable propertyEditorScripts) { - var scripts = new HashSet(); - foreach (string script in _parser.Manifest.Scripts) + var scripts = new HashSet(StringComparer.InvariantCultureIgnoreCase); + + // only include scripts with the default bundle options here + if (_parser.CombinedManifest.Scripts.TryGetValue(BundleOptions.Default, out IReadOnlyList manifestAssets)) { - scripts.Add(script); + foreach (string script in manifestAssets.SelectMany(x => x.Assets)) + { + scripts.Add(script); + } } foreach (string script in propertyEditorScripts) @@ -130,11 +176,15 @@ namespace Umbraco.Cms.Infrastructure.WebAssets /// private string[] GetStylesheetsForBackOffice(IEnumerable propertyEditorStyles) { - var stylesheets = new HashSet(); + var stylesheets = new HashSet(StringComparer.InvariantCultureIgnoreCase); - foreach (string script in _parser.Manifest.Stylesheets) + // only include css with the default bundle options here + if (_parser.CombinedManifest.Stylesheets.TryGetValue(BundleOptions.Default, out IReadOnlyList manifestAssets)) { - stylesheets.Add(script); + foreach (string script in manifestAssets.SelectMany(x => x.Assets)) + { + stylesheets.Add(script); + } } foreach (string stylesheet in propertyEditorStyles) diff --git a/src/Umbraco.Infrastructure/WebAssets/RuntimeMinifierExtensions.cs b/src/Umbraco.Infrastructure/WebAssets/RuntimeMinifierExtensions.cs deleted file mode 100644 index f33d48d8fd..0000000000 --- a/src/Umbraco.Infrastructure/WebAssets/RuntimeMinifierExtensions.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.WebAssets; -using Umbraco.Cms.Infrastructure.WebAssets; - -namespace Umbraco.Extensions -{ - public static class RuntimeMinifierExtensions - { - /// - /// Returns the JavaScript to load the back office's assets - /// - /// - public static async Task GetScriptForLoadingBackOfficeAsync(this IRuntimeMinifier minifier, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) - { - var coreScripts = await minifier.GetJsAssetPathsAsync(BackOfficeWebAssets.UmbracoCoreJsBundleName); - var extensionsScripts = await minifier.GetJsAssetPathsAsync(BackOfficeWebAssets.UmbracoExtensionsJsBundleName); - var result = BackOfficeJavaScriptInitializer.GetJavascriptInitialization(coreScripts.Union(extensionsScripts), "umbraco", globalSettings, hostingEnvironment); - result += await GetStylesheetInitializationAsync(minifier); - - return result; - } - - /// - /// Gets the back office css bundle paths and formats a JS call to lazy load them - /// - private static async Task GetStylesheetInitializationAsync(IRuntimeMinifier minifier) - { - var files = await minifier.GetCssAssetPathsAsync(BackOfficeWebAssets.UmbracoCssBundleName); - var sb = new StringBuilder(); - foreach (var file in files) - sb.AppendFormat("{0}LazyLoad.css('{1}');", Environment.NewLine, file); - return sb.ToString(); - } - - } -} diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index 161896628b..ca1aea69a7 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -17,6 +17,7 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Grid; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Manifest; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Serialization; @@ -63,6 +64,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers private readonly IBackOfficeExternalLoginProviders _externalLogins; private readonly IHttpContextAccessor _httpContextAccessor; private readonly IBackOfficeTwoFactorOptions _backOfficeTwoFactorOptions; + private readonly IManifestParser _manifestParser; private readonly ServerVariablesParser _serverVariables; public BackOfficeController( @@ -81,6 +83,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers IBackOfficeExternalLoginProviders externalLogins, IHttpContextAccessor httpContextAccessor, IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions, + IManifestParser manifestParser, ServerVariablesParser serverVariables) { _userManager = userManager; @@ -98,6 +101,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers _externalLogins = externalLogins; _httpContextAccessor = httpContextAccessor; _backOfficeTwoFactorOptions = backOfficeTwoFactorOptions; + _manifestParser = manifestParser; _serverVariables = serverVariables; } @@ -213,7 +217,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers [AllowAnonymous] public async Task Application() { - var result = await _runtimeMinifier.GetScriptForLoadingBackOfficeAsync(_globalSettings, _hostingEnvironment); + var result = await _runtimeMinifier.GetScriptForLoadingBackOfficeAsync( + _globalSettings, + _hostingEnvironment, + _manifestParser, + Url); return new JavaScriptResult(result); } diff --git a/src/Umbraco.Web.BackOffice/Extensions/RuntimeMinifierExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/RuntimeMinifierExtensions.cs new file mode 100644 index 0000000000..e436a57d83 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Extensions/RuntimeMinifierExtensions.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Manifest; +using Umbraco.Cms.Core.WebAssets; +using Umbraco.Cms.Infrastructure.WebAssets; + +namespace Umbraco.Extensions +{ + public static class RuntimeMinifierExtensions + { + /// + /// Returns the JavaScript to load the back office's assets + /// + /// + public static async Task GetScriptForLoadingBackOfficeAsync( + this IRuntimeMinifier minifier, + GlobalSettings globalSettings, + IHostingEnvironment hostingEnvironment, + IManifestParser manifestParser, + IUrlHelper urlHelper) + { + var files = new HashSet(StringComparer.InvariantCultureIgnoreCase); + foreach(var file in await minifier.GetJsAssetPathsAsync(BackOfficeWebAssets.UmbracoCoreJsBundleName)) + { + files.Add(file); + } + + foreach (var file in await minifier.GetJsAssetPathsAsync(BackOfficeWebAssets.UmbracoExtensionsJsBundleName)) + { + files.Add(file); + } + + // process the independent bundles + if (manifestParser.CombinedManifest.Scripts.TryGetValue(BundleOptions.Independent, out IReadOnlyList independentManifestAssetsList)) + { + foreach (ManifestAssets manifestAssets in independentManifestAssetsList) + { + var bundleName = BackOfficeWebAssets.GetIndependentPackageBundleName(manifestAssets, AssetType.Javascript); + foreach(var asset in await minifier.GetJsAssetPathsAsync(bundleName)) + { + files.Add(asset); + } + } + } + + // process the "None" bundles, meaning we'll just render the script as-is + if (manifestParser.CombinedManifest.Scripts.TryGetValue(BundleOptions.None, out IReadOnlyList noneManifestAssetsList)) + { + foreach (ManifestAssets manifestAssets in noneManifestAssetsList) + { + foreach(var asset in manifestAssets.Assets) + { + files.Add(urlHelper.Content(asset)); + } + } + } + + var result = BackOfficeJavaScriptInitializer.GetJavascriptInitialization( + files, + "umbraco", + globalSettings, + hostingEnvironment); + + result += await GetStylesheetInitializationAsync(minifier, manifestParser, urlHelper); + + return result; + } + + /// + /// Gets the back office css bundle paths and formats a JS call to lazy load them + /// + private static async Task GetStylesheetInitializationAsync( + IRuntimeMinifier minifier, + IManifestParser manifestParser, + IUrlHelper urlHelper) + { + var files = new HashSet(StringComparer.InvariantCultureIgnoreCase); + foreach(var file in await minifier.GetCssAssetPathsAsync(BackOfficeWebAssets.UmbracoCssBundleName)) + { + files.Add(file); + } + + // process the independent bundles + if (manifestParser.CombinedManifest.Stylesheets.TryGetValue(BundleOptions.Independent, out IReadOnlyList independentManifestAssetsList)) + { + foreach (ManifestAssets manifestAssets in independentManifestAssetsList) + { + var bundleName = BackOfficeWebAssets.GetIndependentPackageBundleName(manifestAssets, AssetType.Css); + foreach (var asset in await minifier.GetCssAssetPathsAsync(bundleName)) + { + files.Add(asset); + } + } + } + + // process the "None" bundles, meaning we'll just render the script as-is + if (manifestParser.CombinedManifest.Stylesheets.TryGetValue(BundleOptions.None, out IReadOnlyList noneManifestAssetsList)) + { + foreach (ManifestAssets manifestAssets in noneManifestAssetsList) + { + foreach (var asset in manifestAssets.Assets) + { + files.Add(urlHelper.Content(asset)); + } + } + } + + var sb = new StringBuilder(); + foreach (string file in files) + { + sb.AppendFormat("{0}LazyLoad.css('{1}');", Environment.NewLine, file); + } + + return sb.ToString(); + } + + } +} diff --git a/src/Umbraco.Web.Common/RuntimeMinification/SmidgeRuntimeMinifier.cs b/src/Umbraco.Web.Common/RuntimeMinification/SmidgeRuntimeMinifier.cs index 4353b2ee1a..c202c49980 100644 --- a/src/Umbraco.Web.Common/RuntimeMinification/SmidgeRuntimeMinifier.cs +++ b/src/Umbraco.Web.Common/RuntimeMinification/SmidgeRuntimeMinifier.cs @@ -60,9 +60,21 @@ namespace Umbraco.Cms.Web.Common.RuntimeMinification // replace the default JsMinifier with NuglifyJs and CssMinifier with NuglifyCss in the default pipelines // for use with our bundles only (not modifying global options) _jsOptimizedPipeline = new Lazy(() => bundles.PipelineFactory.DefaultJs().Replace(_bundles.PipelineFactory)); - _jsNonOptimizedPipeline = new Lazy(() => bundles.PipelineFactory.DefaultJs()); + _jsNonOptimizedPipeline = new Lazy(() => + { + PreProcessPipeline defaultJs = bundles.PipelineFactory.DefaultJs(); + // remove minification from this pipeline + defaultJs.Processors.RemoveAll(x => x is JsMinifier); + return defaultJs; + }); _cssOptimizedPipeline = new Lazy(() => bundles.PipelineFactory.DefaultCss().Replace(_bundles.PipelineFactory)); - _cssNonOptimizedPipeline = new Lazy(() => bundles.PipelineFactory.DefaultCss()); + _cssNonOptimizedPipeline = new Lazy(() => + { + PreProcessPipeline defaultCss = bundles.PipelineFactory.DefaultCss(); + // remove minification from this pipeline + defaultCss.Processors.RemoveAll(x => x is CssMinifier); + return defaultCss; + }); Type cacheBusterType = _runtimeMinificationSettings.CacheBuster switch {