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());
        }
Example #2
0
        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);
        }
Example #3
0
        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);
        }
Example #4
0
        /// <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);
                }
            }
        }
Example #5
0
        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);
        }
Example #6
0
        /// <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()));
        }
Example #7
0
        /// <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()));
        }
Example #8
0
 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);
 }
Example #9
0
 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);
            }
        }
Example #12
0
        /// <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));
    }
Example #14
0
        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);
        }
Example #15
0
        /// <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);
        }
Example #16
0
 /// <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));
 }
Example #17
0
 /// <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));
 }
Example #18
0
 /// <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));
 }