public void MinificationExclusionWorks() { _resourceRepository.FillWithTestStyles(); var settings = new CombinatorSettings { MinificationExcludeFilter = new Regex("test\\.css"), MinifyResources = true }; var resource = _resourceRepository.GetResource("~/Modules/Piedone.Combinator/Styles/test.css"); _resourceProcessingService.ProcessResource(resource, new StringBuilder(), settings); Assert.That(resource.Content.StartsWith("minified:") && resource.Content.Contains("/Modules/Piedone.Combinator/Styles/test.css"), Is.False); resource = _resourceRepository.GetResource("~/Modules/Piedone.Combinator/Styles/test2.css"); _resourceProcessingService.ProcessResource(resource, new StringBuilder(), settings); Assert.That(resource.Content.StartsWith("minified:") && resource.Content.Contains("/Modules/Piedone.Combinator/Styles/test2.css"), Is.True); }
/// <summary> /// Combines (and minifies) the content of resources and saves the combinations. /// </summary> /// <param name="resources">Resources to combine.</param> /// <param name="fingerprint">Just so it shouldn't be recalculated.</param> /// <param name="resourceType">Type of the resources.</param> /// <param name="settings">Combination setting.s</param> /// <exception cref="ApplicationException">Thrown if there was a problem with a resource file (e.g. it was missing or could not be opened).</exception> private void Combine(IList <ResourceRequiredContext> resources, string fingerprint, ResourceType resourceType, ICombinatorSettings settings) { if (resources.Count == 0) { return; } var combinatorResources = new List <CombinatorResource>(resources.Count); foreach (var resource in resources) { var combinatorResource = _combinatorResourceManager.ResourceFactory(resourceType); // Copying the context so the original one won't be touched. combinatorResource.FillRequiredContext( resource.Resource.Name, resource.Resource.GetFullPath(), resource.Settings.Culture, resource.Settings.Condition, resource.Settings.Attributes, resource.Resource.TagBuilder.Attributes); combinatorResource.IsRemoteStorageResource = settings.RemoteStorageUrlPattern != null && settings.RemoteStorageUrlPattern.IsMatch(combinatorResource.AbsoluteUrl.ToString()); combinatorResources.Add(combinatorResource); } var combinedContent = new StringBuilder(1000); var resourceBaseUri = settings.ResourceBaseUri != null ? settings.ResourceBaseUri : new Uri(_hca.Current().Request.Url, "/"); Action <CombinatorResource, List <CombinatorResource> > saveCombination = (combinedResource, containedResources) => { if (combinedResource == null) { return; } // Don't save emtpy resources. if (combinedContent.Length == 0 && !combinedResource.IsOriginal) { return; } if (!containedResources.Any()) { containedResources = new List <CombinatorResource> { combinedResource } } ; var bundleFingerprint = containedResources.GetCombinatorResourceListFingerprint(settings); var localSettings = new CombinatorSettings(settings); if (localSettings.EnableResourceSharing && settings.ResourceSharingExcludeFilter != null) { foreach (var resource in containedResources) { if (localSettings.EnableResourceSharing) { localSettings.EnableResourceSharing = !settings.ResourceSharingExcludeFilter.IsMatch(resource.AbsoluteUrl.ToString()); } } } if (!combinedResource.IsOriginal) { combinedResource.Content = combinedContent.ToString(); if (combinedResource.Type == ResourceType.Style && !string.IsNullOrEmpty(combinedResource.Content) && settings.GenerateImageSprites) { _resourceProcessingService.ReplaceCssImagesWithSprite(combinedResource); } combinedResource.Content = "/*" + Environment.NewLine + "Resource bundle created by Combinator (http://combinator.codeplex.com/)" + Environment.NewLine + Environment.NewLine + "Resources in this bundle:" + Environment.NewLine + string.Join(Environment.NewLine, containedResources.Select(resource => { var url = resource.AbsoluteUrl.ToString(); if (localSettings.EnableResourceSharing && !resource.IsCdnResource && !resource.IsRemoteStorageResource) { var uriBuilder = new UriBuilder(url); uriBuilder.Host = "DefaultTenant"; url = uriBuilder.Uri.ToStringWithoutScheme(); } return("- " + url); })) + Environment.NewLine + "*/" + Environment.NewLine + Environment.NewLine + Environment.NewLine + combinedResource.Content; } // We save a bundle now. First the bundle should be saved separately under its unique name, then for this resource list. if (bundleFingerprint != fingerprint && containedResources.Count > 1) { if (!_cacheFileService.Exists(bundleFingerprint, localSettings)) { _cacheFileService.Save(bundleFingerprint, combinedResource, localSettings); } // Overriding the url for the resource in this resource list with the url of the set. combinedResource.IsOriginal = true; // The following should fetch one result theoretically but can more if the above Exists()-Save() happens // in multiple requests at the same time. var set = _cacheFileService.GetCombinedResources(bundleFingerprint, localSettings).First(); combinedResource.LastUpdatedUtc = set.LastUpdatedUtc; if (IsOwnedResource(set)) { if (settings.ResourceBaseUri != null && !set.IsRemoteStorageResource) { combinedResource.RequiredContext.Resource.SetUrl(UriHelper.Combine(resourceBaseUri.ToStringWithoutScheme(), set.AbsoluteUrl.PathAndQuery)); } else if (set.IsRemoteStorageResource) { combinedResource.IsRemoteStorageResource = true; combinedResource.RequiredContext.Resource.SetUrl(set.AbsoluteUrl.ToStringWithoutScheme()); } else { combinedResource.RequiredContext.Resource.SetUrl(set.RelativeUrl.ToString()); } AddTimestampToUrlIfNecessary(combinedResource); } else { combinedResource.RequiredContext.Resource.SetUrl(set.AbsoluteUrl.ToStringWithoutScheme()); } } _cacheFileService.Save(fingerprint, combinedResource, localSettings); combinedContent.Clear(); containedResources.Clear(); }; Regex currentSetRegex = null; var resourcesInCombination = new List <CombinatorResource>(); for (int i = 0; i < combinatorResources.Count; i++) { var resource = combinatorResources[i]; var previousResource = (i != 0) ? combinatorResources[i - 1] : null; var absoluteUrlString = ""; var saveOriginalResource = false; try { absoluteUrlString = resource.AbsoluteUrl.ToString(); if (settings.CombinationExcludeFilter == null || !settings.CombinationExcludeFilter.IsMatch(absoluteUrlString)) { // If this resource differs from the previous one in terms of settings or CDN they can't be combined if (previousResource != null && (!previousResource.SettingsEqual(resource) || (previousResource.IsCdnResource != resource.IsCdnResource && !settings.CombineCdnResources))) { saveCombination(previousResource, resourcesInCombination); previousResource = null; // So it doesn't get combined again in if (saveOriginalResource) below. } // If this resource is in a different set than the previous, they can't be combined if (currentSetRegex != null && !currentSetRegex.IsMatch(absoluteUrlString)) { currentSetRegex = null; saveCombination(previousResource, resourcesInCombination); } // Calculate if this resource is in a set if (currentSetRegex == null && settings.ResourceSetFilters != null && settings.ResourceSetFilters.Length > 0) { int r = 0; while (currentSetRegex == null && r < settings.ResourceSetFilters.Length) { if (settings.ResourceSetFilters[r].IsMatch(absoluteUrlString)) { currentSetRegex = settings.ResourceSetFilters[r]; } r++; } // The previous resource is in a different set or in no set so it can't be combined with this resource if (currentSetRegex != null && previousResource != null && resourcesInCombination.Any()) { saveCombination(previousResource, resourcesInCombination); } } // Nesting resources in such blocks is needed because some syntax is valid if in its own file but not anymore // when bundled. if (resourceType == ResourceType.JavaScript) { combinedContent.Append("{"); } combinedContent.Append(Environment.NewLine); _resourceProcessingService.ProcessResource(resource, combinedContent, settings); // This can be because e.g. it's a CDN resource and CDN combination is disabled. if (resource.IsOriginal) { saveOriginalResource = true; } combinedContent.Append(Environment.NewLine); if (resourceType == ResourceType.JavaScript) { combinedContent.Append("}"); } combinedContent.Append(Environment.NewLine); resourcesInCombination.Add(resource); } else { saveOriginalResource = true; } if (saveOriginalResource) { // This is a fully excluded resource if (previousResource != null) { saveCombination(previousResource, resourcesInCombination); } resource.IsOriginal = true; saveCombination(resource, resourcesInCombination); combinatorResources[i] = null; // So previous resource detection works correctly } } catch (Exception ex) { if (ex.IsFatal()) { throw; } throw new OrchardException(T("Processing of resource {0} failed.", absoluteUrlString), ex); } } saveCombination(combinatorResources[combinatorResources.Count - 1], resourcesInCombination); }
/// <summary> /// Combines (and minifies) the content of resources and saves the combinations /// </summary> /// <param name="resources">Resources to combine</param> /// <param name="hashCode">Just so it shouldn't be recalculated</param> /// <param name="resourceType">Type of the resources</param> /// <param name="settings">Combination settings</param> /// <exception cref="ApplicationException">Thrown if there was a problem with a resource file (e.g. it was missing or could not be opened)</exception> private void Combine(IList <ResourceRequiredContext> resources, int hashCode, ResourceType resourceType, ICombinatorSettings settings) { if (resources.Count == 0) { return; } var combinatorResources = new List <CombinatorResource>(resources.Count); foreach (var resource in resources) { var combinatorResource = _combinatorResourceManager.ResourceFactory(resourceType); // Copying the context so the original one won't be touched combinatorResource.FillRequiredContext( resource.Resource.Name, resource.Resource.GetFullPath(), resource.Settings.Culture, resource.Settings.Condition, resource.Settings.Attributes); //var requiredContext = new ResourceRequiredContext(); //var resourceManifest = new ResourceManifest(); //requiredContext.Resource = resourceManifest.DefineResource(ResourceTypeHelper.EnumToStringType(resourceType), resource.Resource.Name); //requiredContext.Resource.SetUrl(resource.Resource.GetFullPath()); //requiredContext.Settings = new RequireSettings(); //requiredContext.Settings.Culture = resource.Settings.Culture; //requiredContext.Settings.Condition = resource.Settings.Condition; //requiredContext.Settings.Attributes = new Dictionary<string, string>(resource.Settings.Attributes); //combinatorResource.RequiredContext = requiredContext; combinatorResources.Add(combinatorResource); } var combinedContent = new StringBuilder(1000); Action <CombinatorResource> saveCombination = (combinedResource) => { if (combinedResource == null) { return; } // Don't save emtpy resources if (combinedContent.Length == 0 && !combinedResource.IsOriginal) { return; } combinedResource.Content = combinedContent.ToString(); _cacheFileService.Save(hashCode, combinedResource); combinedContent.Clear(); }; Regex currentSetRegex = null; for (int i = 0; i < combinatorResources.Count; i++) { var resource = combinatorResources[i]; var previousResource = (i != 0) ? combinatorResources[i - 1] : null; var absoluteUrlString = ""; try { absoluteUrlString = resource.AbsoluteUrl.ToString(); if (settings.CombinationExcludeFilter == null || !settings.CombinationExcludeFilter.IsMatch(absoluteUrlString)) { // If this resource differs from the previous one in terms of settings or CDN they can't be combined if (previousResource != null && (!previousResource.SettingsEqual(resource) || (previousResource.IsCdnResource != resource.IsCdnResource && !settings.CombineCDNResources))) { saveCombination(previousResource); } // If this resource is in a different set than the previous, they can't be combined if (currentSetRegex != null && !currentSetRegex.IsMatch(absoluteUrlString)) { currentSetRegex = null; saveCombination(previousResource); } // Calculate if this resource is in a set if (currentSetRegex == null && settings.ResourceSetFilters != null && settings.ResourceSetFilters.Length > 0) { int r = 0; while (currentSetRegex == null && r < settings.ResourceSetFilters.Length) { if (settings.ResourceSetFilters[r].IsMatch(absoluteUrlString)) { currentSetRegex = settings.ResourceSetFilters[r]; } r++; } // The previous resource is in a different set or in no set so it can't be combined with this resource if (currentSetRegex != null && previousResource != null) { saveCombination(previousResource); } } _resourceProcessingService.ProcessResource(resource, combinedContent, settings); } else { // This is a fully excluded resource if (previousResource != null) { saveCombination(previousResource); } resource.IsOriginal = true; saveCombination(resource); combinatorResources[i] = null; // So previous resource detection works correctly } } catch (Exception ex) { if (ex.IsFatal()) { throw; } throw new OrchardException(T("Processing of resource {0} failed.", absoluteUrlString), ex); } } saveCombination(combinatorResources[combinatorResources.Count - 1]); }