public void Get_Ordered_File_Set_No_Duplicates() { var websiteInfo = new Mock <IWebsiteInfo>(); websiteInfo.Setup(x => x.GetBasePath()).Returns("/"); websiteInfo.Setup(x => x.GetBaseUrl()).Returns(new Uri("http://test.com")); var urlHelper = new RequestHelper(websiteInfo.Object); var fileProvider = new Mock <IFileProvider>(); var config = Mock.Of <ISmidgeConfig>(); var hasher = Mock.Of <IHasher>(); var hostingEnv = Mock.Of <IHostingEnvironment>(); var fileSystemHelper = new FileSystemHelper(hostingEnv, config, fileProvider.Object, hasher); var pipeline = new PreProcessPipeline(Enumerable.Empty <IPreProcessor>()); var smidgeOptions = new Mock <IOptions <SmidgeOptions> >(); smidgeOptions.Setup(opt => opt.Value).Returns(new SmidgeOptions()); var generator = new BundleFileSetGenerator(fileSystemHelper, urlHelper, new FileProcessingConventions(smidgeOptions.Object, Enumerable.Empty <IFileProcessingConvention>())); var result = generator.GetOrderedFileSet(new IWebFile[] { Mock.Of <IWebFile>(f => f.FilePath == "~/test/test.js"), Mock.Of <IWebFile>(f => f.FilePath == "~/test/test.js"), Mock.Of <IWebFile>(f => f.FilePath == "hello/world.js") }, pipeline); Assert.Equal(2, result.Count()); }
public Bundle Create(string bundleName, PreProcessPipeline pipeline, WebFileType type, params string[] paths) { if (string.IsNullOrWhiteSpace(bundleName)) { throw new ArgumentException("Value cannot be null or whitespace.", nameof(bundleName)); } if (bundleName.Contains('.')) { throw new ArgumentException("A bundle name cannot contain a '.' character"); } _logger.LogDebug($"Creating {type} bundle '{bundleName}' with {paths.Length} files and a custom pipeline"); var collection = type == WebFileType.Css ? new Bundle(paths.Select(x => (IWebFile) new CssFile(x) { Pipeline = pipeline }).ToList()) : new Bundle(paths.Select(x => (IWebFile) new JavaScriptFile(x) { Pipeline = pipeline }).ToList()); _bundles.TryAdd(bundleName, collection); return(collection); }
public Bundle Create(string bundleName, PreProcessPipeline pipeline, params JavaScriptFile[] jsFiles) { if (string.IsNullOrWhiteSpace(bundleName)) { throw new ArgumentException("Value cannot be null or whitespace.", nameof(bundleName)); } if (bundleName.Contains('.')) { throw new ArgumentException("A bundle name cannot contain a '.' character"); } _logger.LogDebug($"Creating {WebFileType.Js} bundle '{bundleName}' with {jsFiles.Length} files and a custom pipeline"); foreach (var file in jsFiles) { if (file.Pipeline == null) { file.Pipeline = pipeline; } } var collection = new Bundle(new List <IWebFile>(jsFiles)); _bundles.TryAdd(bundleName, collection); return(collection); }
/// <summary> /// Minifies (and performs any other operation defined in the pipeline) for each file /// </summary> /// <param name="files"></param> /// <returns></returns> private async Task ProcessWebFilesAsync(IEnumerable <IWebFile> files, PreProcessPipeline pipeline) { //we need to do the minify on the original files foreach (var file in files) { //if the pipeline on the file is null, assign the default one passed in if (file.Pipeline == null) { file.Pipeline = pipeline; } //We need to check if this path is a folder, then iterate the files if (_fileSystemHelper.IsFolder(file.FilePath)) { var filePaths = _fileSystemHelper.GetPathsForFilesInFolder(file.FilePath); foreach (var f in filePaths) { await _fileManager.ProcessAndCacheFileAsync(new WebFile { FilePath = _fileSystemHelper.NormalizeWebPath(f, _request), DependencyType = file.DependencyType, Pipeline = file.Pipeline }); } } else { await _fileManager.ProcessAndCacheFileAsync(file); } } }
private async Task <IEnumerable <string> > GenerateUrlsAsync( IEnumerable <IWebFile> files, WebFileType fileType, PreProcessPipeline pipeline = null) { var result = new List <string>(); if (_config.IsDebug) { return(GenerateUrlsDebug(files)); } else { if (pipeline == null) { pipeline = _processorFactory.GetDefault(fileType); } var compression = _request.GetClientCompression(); //Get the file collection used to create the composite URLs and the external requests var fileBatches = _fileBatcher.GetCompositeFileCollectionForUrlGeneration(files); foreach (var batch in fileBatches) { //if it's external, the rule is that a WebFileBatch can only contain a single external file // it's path will be normalized as an external url so we just use it if (batch.IsExternal) { result.Add(batch.Single().Original.FilePath); } else { //Get the URLs for the batch, this could be more than one resulting URL depending on how many // files are in the batch and the max url length var compositeUrls = _context.UrlCreator.GetUrls(batch.Select(x => x.Hashed), fileType == WebFileType.Css ? ".css" : ".js"); foreach (var u in compositeUrls) { //now we need to determine if these files have already been minified var compositeFilePath = _fileSystemHelper.GetCurrentCompositeFilePath(compression, u.Key); if (!File.Exists(compositeFilePath)) { //need to process/minify these files - need to use their original paths of course await ProcessWebFilesAsync(batch.Select(x => x.Original), pipeline); } result.Add(u.Url); } } } } return(result); }
/// <summary> /// Renders the JS tags /// </summary> /// <returns></returns> /// <remarks> /// TODO: Once the tags are rendered the collection on the context is cleared. Therefore if this method is called multiple times it will /// render anything that has been registered as 'pending' but has not been rendered. /// </remarks> public async Task <HtmlString> JsHereAsync(PreProcessPipeline pipeline = null) { var result = new StringBuilder(); var urls = await GenerateJsUrlsAsync(pipeline); foreach (var url in urls) { result.AppendFormat("<script src='{0}' type='text/javascript'></script>", url); } return(new HtmlString(result.ToString())); }
/// <summary> /// Renders the CSS tags /// </summary> /// <returns></returns> /// <remarks> /// TODO: Once the tags are rendered the collection on the context is cleared. Therefore if this method is called multiple times it will /// render anything that has been registered as 'pending' but has not been rendered. /// </remarks> public async Task <HtmlString> CssHereAsync(PreProcessPipeline pipeline = null) { var result = new StringBuilder(); var urls = await GenerateCssUrlsAsync(pipeline); foreach (var url in urls) { result.AppendFormat("<link href='{0}' rel='stylesheet' type='text/css'/>", url); } return(new HtmlString(result.ToString())); }
public void Create(string bundleName, PreProcessPipeline pipeline, params CssFile[] cssFiles) { foreach (var file in cssFiles) { if (file.Pipeline == null) { file.Pipeline = pipeline; } } _bundles.TryAdd(bundleName, cssFiles); }
public void Create(string bundleName, PreProcessPipeline pipeline, WebFileType type, params string[] paths) { _bundles.TryAdd( bundleName, type == WebFileType.Css ? paths.Select(x => (IWebFile) new CssFile(x) { Pipeline = pipeline }) : paths.Select(x => (IWebFile) new JavaScriptFile(x) { Pipeline = pipeline })); }
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; _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.Value.CacheBuster switch { RuntimeMinificationCacheBuster.AppDomain => typeof(AppDomainLifetimeCacheBuster), RuntimeMinificationCacheBuster.Version => typeof(UmbracoSmidgeConfigCacheBuster), RuntimeMinificationCacheBuster.Timestamp => typeof(TimestampCacheBuster), _ => throw new NotImplementedException(), }; _cacheBusterType = cacheBusterType; }
public async Task Can_Process_Pipeline() { var pipeline = new PreProcessPipeline(new IPreProcessor[] { new ProcessorHeaderAndFooter(), new ProcessorHeader(), new ProcessorFooter() }); using (var bc = BundleContext.CreateEmpty()) { var result = await pipeline.ProcessAsync(new FileProcessContext("This is some content", Mock.Of <IWebFile>(), bc)); Assert.Equal("WrappedHeader\nHeader\nThis is some content\nFooter\nWrappedFooter", result); } }
/// <summary> /// Returns the ordered file set for a bundle and ensures that all pre-processor pipelines are applied correctly /// </summary> /// <param name="bundle"></param> /// <param name="pipeline"></param> /// <returns></returns> public IEnumerable <IWebFile> GetOrderedFileSet(Bundle bundle, PreProcessPipeline pipeline) { if (bundle == null) { throw new ArgumentNullException(nameof(bundle)); } if (pipeline == null) { throw new ArgumentNullException(nameof(pipeline)); } var ordered = GetOrderedFileSet(bundle.Files, pipeline); //call the registered callback if any is set return(bundle.OrderingCallback == null ? ordered : bundle.OrderingCallback(ordered)); }
// 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("~/")) ?? false) { 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 Bundle Create(string bundleName, PreProcessPipeline pipeline, params CssFile[] cssFiles) { if (string.IsNullOrWhiteSpace(bundleName)) { throw new ArgumentException("Value cannot be null or whitespace.", nameof(bundleName)); } if (bundleName.Contains('.')) { throw new ArgumentException("A bundle name cannot contain a '.' character"); } foreach (var file in cssFiles) { if (file.Pipeline == null) { file.Pipeline = pipeline; } } var collection = new Bundle(new List <IWebFile>(cssFiles)); _bundles.TryAdd(bundleName, collection); return(collection); }
/// <summary> /// Generates the URLs for a dynamically registered set of files (non pre-defined bundle) /// </summary> /// <param name="files"></param> /// <param name="fileType"></param> /// <param name="pipeline"></param> /// <param name="debug"></param> /// <returns></returns> private async Task <IEnumerable <string> > GenerateUrlsAsync( IEnumerable <IWebFile> files, WebFileType fileType, PreProcessPipeline pipeline = null, bool debug = false) { var result = new List <string>(); var orderedFiles = _fileSetGenerator.GetOrderedFileSet(files, pipeline ?? _processorFactory.CreateDefault(fileType)); if (debug) { return(orderedFiles.Select(x => x.FilePath)); } var compression = _requestHelper.GetClientCompression(_httpContextAccessor.HttpContext.Request.Headers); //Get the file collection used to create the composite URLs and the external requests var fileBatches = _fileBatcher.GetCompositeFileCollectionForUrlGeneration(orderedFiles); var cacheBuster = _cacheBusterResolver.GetCacheBuster(_bundleManager.GetDefaultBundleOptions(debug).GetCacheBusterType()); foreach (var batch in fileBatches) { //if it's external, the rule is that a WebFileBatch can only contain a single external file // it's path will be normalized as an external url so we just use it if (batch.IsExternal) { result.Add(batch.Single().Original.FilePath); } else { //Get the URLs for the batch, this could be more than one resulting URL depending on how many // files are in the batch and the max url length var compositeUrls = _urlManager.GetUrls( batch.Select(x => x.Hashed), fileType == WebFileType.Css ? ".css" : ".js", cacheBuster); foreach (var u in compositeUrls) { //now we need to determine if these files have already been minified var compositeFilePath = _fileSystemHelper.GetCurrentCompositeFilePath(cacheBuster, compression, u.Key); if (!File.Exists(compositeFilePath)) { using (var bundleContext = BundleContext.CreateEmpty()) { //need to process/minify these files - need to use their original paths of course foreach (var file in batch.Select(x => x.Original)) { await _preProcessManager.ProcessAndCacheFileAsync(file, null, bundleContext); } } } result.Add(u.Url); } } } return(result); }
/// <summary> /// Create a CSS bundle /// </summary> /// <param name="bundleManager"></param> /// <param name="bundleName"></param> /// <param name="pipeline"></param> /// <param name="paths"></param> /// <returns></returns> public static Bundle CreateCss(this IBundleManager bundleManager, string bundleName, PreProcessPipeline pipeline, params string[] paths) { return(bundleManager.Create(bundleName, pipeline, WebFileType.Css, paths)); }
/// <summary> /// Generates the list of URLs to render based on what is dynamically registered /// </summary> /// <returns></returns> public async Task <IEnumerable <string> > GenerateCssUrlsAsync(PreProcessPipeline pipeline = null, bool debug = false) { return(await GenerateUrlsAsync(_dynamicallyRegisteredWebFiles.CssFiles, WebFileType.Css, pipeline, debug)); }
/// <summary> /// Generates the list of URLs to render based on what is registered /// </summary> /// <returns></returns> public async Task <IEnumerable <string> > GenerateCssUrlsAsync(PreProcessPipeline pipeline = null) { return(await GenerateUrlsAsync(_context.CssFiles, WebFileType.Css, pipeline)); }