187 lines
8.9 KiB
C#
187 lines
8.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
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;
|
|
using JavaScriptFile = Smidge.Models.JavaScriptFile;
|
|
|
|
namespace Umbraco.Cms.Web.Common.RuntimeMinification
|
|
{
|
|
public class SmidgeRuntimeMinifier : IRuntimeMinifier
|
|
{
|
|
private readonly IHostingEnvironment _hostingEnvironment;
|
|
private readonly IConfigManipulator _configManipulator;
|
|
private readonly CacheBusterResolver _cacheBusterResolver;
|
|
private readonly RuntimeMinificationSettings _runtimeMinificationSettings;
|
|
private readonly IBundleManager _bundles;
|
|
private readonly SmidgeHelperAccessor _smidge;
|
|
|
|
// used only for minifying in MinifyAsync not for an actual pipeline
|
|
private readonly Lazy<PreProcessPipeline> _jsMinPipeline;
|
|
private readonly Lazy<PreProcessPipeline> _cssMinPipeline;
|
|
|
|
// default pipelines for processing js/css files for the back office
|
|
private readonly Lazy<PreProcessPipeline> _jsOptimizedPipeline;
|
|
private readonly Lazy<PreProcessPipeline> _jsNonOptimizedPipeline;
|
|
private readonly Lazy<PreProcessPipeline> _cssOptimizedPipeline;
|
|
private readonly Lazy<PreProcessPipeline> _cssNonOptimizedPipeline;
|
|
private ICacheBuster _cacheBuster;
|
|
private readonly Type _cacheBusterType;
|
|
|
|
public SmidgeRuntimeMinifier(
|
|
IBundleManager bundles,
|
|
SmidgeHelperAccessor smidge,
|
|
IHostingEnvironment hostingEnvironment,
|
|
IConfigManipulator configManipulator,
|
|
IOptions<RuntimeMinificationSettings> runtimeMinificationSettings,
|
|
CacheBusterResolver cacheBusterResolver)
|
|
{
|
|
_bundles = bundles;
|
|
_smidge = smidge;
|
|
_hostingEnvironment = hostingEnvironment;
|
|
_configManipulator = configManipulator;
|
|
_cacheBusterResolver = cacheBusterResolver;
|
|
_runtimeMinificationSettings = runtimeMinificationSettings.Value;
|
|
_jsMinPipeline = new Lazy<PreProcessPipeline>(() => _bundles.PipelineFactory.Create(typeof(JsMinifier)));
|
|
_cssMinPipeline = new Lazy<PreProcessPipeline>(() => _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)
|
|
_jsOptimizedPipeline = new Lazy<PreProcessPipeline>(() => bundles.PipelineFactory.DefaultJs().Replace<JsMinifier, SmidgeNuglifyJs>(_bundles.PipelineFactory));
|
|
_jsNonOptimizedPipeline = new Lazy<PreProcessPipeline>(() =>
|
|
{
|
|
PreProcessPipeline defaultJs = bundles.PipelineFactory.DefaultJs();
|
|
// remove minification from this pipeline
|
|
defaultJs.Processors.RemoveAll(x => x is JsMinifier);
|
|
return defaultJs;
|
|
});
|
|
_cssOptimizedPipeline = new Lazy<PreProcessPipeline>(() => bundles.PipelineFactory.DefaultCss().Replace<CssMinifier, NuglifyCss>(_bundles.PipelineFactory));
|
|
_cssNonOptimizedPipeline = new Lazy<PreProcessPipeline>(() =>
|
|
{
|
|
PreProcessPipeline defaultCss = bundles.PipelineFactory.DefaultCss();
|
|
// remove minification from this pipeline
|
|
defaultCss.Processors.RemoveAll(x => x is CssMinifier);
|
|
return 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 => (_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, BundlingOptions bundleOptions, 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");
|
|
}
|
|
|
|
PreProcessPipeline pipeline = bundleOptions.OptimizeOutput
|
|
? _cssOptimizedPipeline.Value
|
|
: _cssNonOptimizedPipeline.Value;
|
|
|
|
Bundle bundle = _bundles.Create(bundleName, pipeline, WebFileType.Css, filePaths);
|
|
bundle.WithEnvironmentOptions(ConfigureBundleEnvironmentOptions(bundleOptions));
|
|
}
|
|
|
|
public async Task<string> RenderCssHereAsync(string bundleName) => (await _smidge.SmidgeHelper.CssHereAsync(bundleName, _hostingEnvironment.IsDebugMode)).ToString();
|
|
|
|
public void CreateJsBundle(string bundleName, BundlingOptions bundleOptions, 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");
|
|
}
|
|
|
|
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<string> RenderJsHereAsync(string bundleName) => (await _smidge.SmidgeHelper.JsHereAsync(bundleName, _hostingEnvironment.IsDebugMode)).ToString();
|
|
|
|
public async Task<IEnumerable<string>> GetJsAssetPathsAsync(string bundleName) => await _smidge.SmidgeHelper.GenerateJsUrlsAsync(bundleName, _hostingEnvironment.IsDebugMode);
|
|
|
|
public async Task<IEnumerable<string>> GetCssAssetPathsAsync(string bundleName) => await _smidge.SmidgeHelper.GenerateCssUrlsAsync(bundleName, _hostingEnvironment.IsDebugMode);
|
|
|
|
/// <inheritdoc />
|
|
public async Task<string> MinifyAsync(string fileContent, AssetType assetType)
|
|
{
|
|
switch (assetType)
|
|
{
|
|
case AssetType.Javascript:
|
|
return await _jsMinPipeline.Value
|
|
.ProcessAsync(
|
|
new FileProcessContext(fileContent, new JavaScriptFile(), BundleContext.CreateEmpty(CacheBuster)));
|
|
case AssetType.Css:
|
|
return await _cssMinPipeline.Value
|
|
.ProcessAsync(new FileProcessContext(fileContent, new CssFile(), BundleContext.CreateEmpty(CacheBuster)));
|
|
default:
|
|
throw new NotSupportedException("Unexpected AssetType");
|
|
}
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
/// <remarks>
|
|
/// Smidge uses the version number as cache buster (configurable).
|
|
/// We therefore can reset, by updating the version number in config
|
|
/// </remarks>
|
|
public void Reset()
|
|
{
|
|
var version = DateTime.UtcNow.Ticks.ToString();
|
|
_configManipulator.SaveConfigValue(Cms.Core.Constants.Configuration.ConfigRuntimeMinificationVersion, version.ToString());
|
|
}
|
|
|
|
|
|
}
|
|
}
|