Permalink
Browse files

Reworked entire engine to generate a content source graph from rules.…

… Rules are created using the fluent interface and can be replayed to recreate the object graph at any time. Physical file sources (css, js files) read files in and then pass the content through a file transformation pipeline, allowing for CSS transformations, coffee script compilation, etc. The pipeline is configurable externally. Added resource type of 'Image', allowing images to be minified. Converted client-side placeholders to use timestamps rather than GUIDs, so that client-side caching works with multiple instances and app pool restarts.

CSS transforms can include references to client-side cached images, without these images having been previously configured in Rejuicer rules. All in all, a lot of powerful changes.
  • Loading branch information...
1 parent a39e5ca commit fcb99c8d751e28a76b06713515776486c81382f1 @skroonenburg skroonenburg committed Sep 18, 2011
Showing with 843 additions and 380 deletions.
  1. +1 −1 Demos/Mvc/MvcDemo/MvcDemo/Content/Site.css
  2. BIN Demos/Mvc/MvcDemo/MvcDemo/Content/transformedUrlImage.png
  3. +6 −1 Demos/Mvc/MvcDemo/MvcDemo/Global.asax.cs
  4. BIN Lib/Rejuicer.dll
  5. +5 −0 Source/Rejuicer/Rejuicer-test/DefaultCssTransformerTest.cs
  6. +4 −42 Source/Rejuicer/Rejuicer-test/FluentConfigurerTests.cs
  7. +0 −6 Source/Rejuicer/Rejuicer/Configuration/Configuration.cs
  8. +55 −0 Source/Rejuicer/Rejuicer/Engine/BaseStringMinificationProvider.cs
  9. +23 −0 Source/Rejuicer/Rejuicer/Engine/CssMinificationProvider.cs
  10. +41 −4 Source/Rejuicer/Rejuicer/Engine/DefaultCssTransformer.cs
  11. +3 −50 Source/Rejuicer/Rejuicer/Engine/FileResolver.cs
  12. +11 −3 Source/Rejuicer/Rejuicer/Engine/FileTransformationPipeline.cs
  13. +2 −1 Source/Rejuicer/Rejuicer/Engine/IFileTransformer.cs
  14. +16 −0 Source/Rejuicer/Rejuicer/Engine/IMinificationProvider.cs
  15. +54 −0 Source/Rejuicer/Rejuicer/Engine/ImageMinificationProvider.cs
  16. +23 −0 Source/Rejuicer/Rejuicer/Engine/JsMinificationProvider.cs
  17. +30 −0 Source/Rejuicer/Rejuicer/Engine/MinificationRegistry.cs
  18. +5 −11 Source/Rejuicer/Rejuicer/Engine/OutputContent.cs
  19. +13 −115 Source/Rejuicer/Rejuicer/Engine/RejuicerEngine.cs
  20. +11 −2 Source/Rejuicer/Rejuicer/Engine/RejuicerModule.cs
  21. +51 −0 Source/Rejuicer/Rejuicer/Engine/StreamUtilities.cs
  22. +15 −14 Source/Rejuicer/Rejuicer/Fluent/CompactorConfigurer.cs
  23. +9 −5 Source/Rejuicer/Rejuicer/Fluent/DirectoryFileMatchConfigurer.cs
  24. +3 −1 Source/Rejuicer/Rejuicer/Fluent/ICompactorConfigurer.cs
  25. +1 −0 Source/Rejuicer/Rejuicer/Fluent/IDirectoryFileMatchConfigurer.cs
  26. +15 −4 Source/Rejuicer/Rejuicer/Fluent/OnRequest.cs
  27. +2 −2 Source/Rejuicer/Rejuicer/HtmlHelpers/IncludesCacheModel.cs
  28. +13 −11 Source/Rejuicer/Rejuicer/HtmlHelpers/Rejuicer.cs
  29. +0 −13 Source/Rejuicer/Rejuicer/Model/FileMatchModel.cs
  30. +16 −0 Source/Rejuicer/Rejuicer/Model/IContentSource.cs
  31. +13 −0 Source/Rejuicer/Rejuicer/Model/IPhysicalFileDependency.cs
  32. +2 −2 Source/Rejuicer/Rejuicer/Model/Mode.cs
  33. +129 −0 Source/Rejuicer/Rejuicer/Model/PhysicalFileSource.cs
  34. +0 −74 Source/Rejuicer/Rejuicer/Model/RejuicedFileModel.cs
  35. +154 −0 Source/Rejuicer/Rejuicer/Model/RejuicerConfigurationSource.cs
  36. +2 −1 Source/Rejuicer/Rejuicer/Model/ResourceType.cs
  37. +1 −0 Source/Rejuicer/Rejuicer/PathUtilities/IVirtualPathResolver.cs
  38. +8 −2 Source/Rejuicer/Rejuicer/PathUtilities/VirtualPathResolver.cs
  39. +14 −2 Source/Rejuicer/Rejuicer/Rejuicer.csproj
  40. +13 −0 Source/Rejuicer/Rejuicer/Rules/IContentSourceRule.cs
  41. +32 −0 Source/Rejuicer/Rejuicer/Rules/SingleFileRule.cs
  42. +47 −0 Source/Rejuicer/Rejuicer/Rules/WildcardMatchFileRule.cs
  43. +0 −13 Source/Rejuicer/RejuicerConfiguration-test/ConfigurationTest.cs
@@ -345,7 +345,7 @@ div#title
.testImageCssTransform
{
- background-image: url(~/Content/transformedUrlImage.png);
+ background-image: url(~/Content/transformedUrlImage{0}.png);
width: 400px;
height: 72px;
}
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -30,13 +30,18 @@ public static void ConfigureRejuicer()
OnRequest.ForJs("~/Combined-{0}.js")
.Compact
.FilesIn("~/Scripts/")
- .Matching("*.js")
+ .Matching("jquery-*.js")
.Configure();
OnRequest.ForCss("~/Combined.css")
.Compact
.File("~/Content/Site.css")
.Configure();
+
+ OnRequest.ForImage("~/CachedImage-{0}.png")
+ .Combine
+ .File("~/Content/transformedUrlImage.png")
+ .Configure();
}
protected void Application_Start()
View
Binary file not shown.
@@ -87,6 +87,11 @@ public string GetRelativeUrl(string virtualPath)
{
return _replacement(virtualPath);
}
+
+ public string GetVirtualPathFor(FileInfo file)
+ {
+ throw new NotImplementedException();
+ }
}
}
}
@@ -16,7 +16,7 @@ public void Setup()
RejuicerEngine._configurations.Clear();
}
- private RejuicedFileModel GetModelFor(object configurer)
+ private RejuicerConfigurationSource GetModelFor(object configurer)
{
return ((CompactorConfigurer)configurer)._config;
}
@@ -50,23 +50,7 @@ public void FluentConfiguration_ConfigureCompact_ConfigModeIsCompact()
{
var config = GetModelFor(OnRequest.ForJs("~/Scripts/Combined-Test.js").Compact);
- Assert.AreEqual(Mode.Compact, config.Mode);
- }
-
- [Test]
- public void FluentConfiguration_ConfigureCaching_CachingConfigIsTrue()
- {
- var config = GetModelFor(OnRequest.ForJs("~/Scripts/Combined-Test.js").Combine);
-
- Assert.IsTrue(config.Cache);
- }
-
- [Test]
- public void FluentConfiguration_DoNotConfigureCaching_CachingConfigIsFalse()
- {
- var config = GetModelFor(OnRequest.ForJs("~/Scripts/Combined-Test.js").Combine.DoNotCache);
-
- Assert.IsFalse(config.Cache);
+ Assert.AreEqual(Mode.Minify, config.Mode);
}
[Test]
@@ -75,30 +59,8 @@ public void FluentConfiguration_File_AddsFileToConfig()
var config = GetModelFor(OnRequest.ForJs("~/Scripts/Combined-Test.js").Combine
.File("~/Scripts/myfile.js"));
- Assert.AreEqual(1, config.OrderedFiles.Count);
- Assert.AreEqual("~/Scripts/myfile.js", config.OrderedFiles[0]);
- }
-
- [Test]
- public void FluentConfiguration_AllFilesInPath_AddsFilesMatchingToConfig()
- {
- var config = GetModelFor(OnRequest.ForJs("~/Scripts/Combined-Test.js").Combine
- .FilesIn("~/Scripts/").All);
-
- Assert.AreEqual(1, config.OrderedFiles.Count);
- Assert.AreEqual("~/Scripts/", ((FileMatchModel) config.OrderedFiles[0]).Path);
- Assert.IsNull(((FileMatchModel)config.OrderedFiles[0]).WildCard);
- }
-
- [Test]
- public void FluentConfiguration_AllFilesInPathMatchingWildcard_AddsFilesMatchingWildcardToConfig()
- {
- var config = GetModelFor(OnRequest.ForJs("~/Scripts/Combined-Test.js").Combine
- .FilesIn("~/Scripts/").Matching("*.js"));
-
- Assert.AreEqual(1, config.OrderedFiles.Count);
- Assert.AreEqual("~/Scripts/", ((FileMatchModel)config.OrderedFiles[0]).Path);
- Assert.AreEqual("*.js", ((FileMatchModel)config.OrderedFiles[0]).WildCard);
+ Assert.AreEqual(1, config.Count);
+ Assert.AreEqual("~/Scripts/myfile.js", ((PhysicalFileSource)config[0]).VirtualPath);
}
[Test]
@@ -23,12 +23,6 @@ public static RejuicerConfiguration Current
}
}
- [ConfigurationProperty("Cache", IsRequired = false)]
- public bool? Cache
- {
- get { return (bool?)this["Cache"]; }
- }
-
[ConfigurationProperty("PreventPassThroughOnDebug", IsRequired = false)]
public bool? PreventPassThroughOnDebug
{
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+
+namespace Rejuicer.Engine
+{
+ public abstract class BaseStringMinificationProvider : IMinificationProvider
+ {
+ public abstract string MinifyStringValue(string data);
+
+ public Stream Minify(Stream data)
+ {
+ // Read the data into a string
+ var stringValue = data.ReadString();
+ var culture = Thread.CurrentThread.CurrentCulture;
+
+ try
+ {
+ Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
+
+ var minifiedValue = MinifyStringValue(stringValue);
+
+ return minifiedValue.AsStream();
+ }
+ finally
+ {
+ Thread.CurrentThread.CurrentCulture = culture;
+ }
+ }
+
+ public Stream Combine(IEnumerable<Stream> data)
+ {
+ var combinedStream = new MemoryStream();
+
+ var streamWriter = new StreamWriter(combinedStream);
+
+ foreach (var value in data)
+ {
+ streamWriter.Write(value.ReadString());
+ streamWriter.WriteLine();
+ }
+
+ streamWriter.Flush();
+
+ return combinedStream;
+ }
+
+
+ public abstract string GetContentType(string filename);
+ }
+}
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+
+namespace Rejuicer.Engine
+{
+ public class CssMinificationProvider : BaseStringMinificationProvider
+ {
+ public override string MinifyStringValue(string data)
+ {
+ return Yahoo.Yui.Compressor.CssCompressor.Compress(data);
+ }
+
+ public override string GetContentType(string filename)
+ {
+ return "text/css";
+ }
+ }
+}
@@ -5,40 +5,77 @@
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
+using Rejuicer.Model;
namespace Rejuicer.Engine
{
public class DefaultCssTransformer : IFileTransformer
{
private readonly IVirtualPathResolver _virtualPathResolver;
+ private ICacheProvider _cacheProvider;
public DefaultCssTransformer()
+ : this(new VirtualPathResolver(), new CacheProvider())
{
_virtualPathResolver = new VirtualPathResolver();
+ _cacheProvider = new CacheProvider();
}
public DefaultCssTransformer(IVirtualPathResolver virtualPathResolver)
+ : this(virtualPathResolver, new CacheProvider())
+ {
+ }
+
+ public DefaultCssTransformer(IVirtualPathResolver virtualPathResolver, ICacheProvider cacheProvider)
{
_virtualPathResolver = virtualPathResolver;
+ _cacheProvider = cacheProvider;
}
- public string TransformFile(string inputContent)
+ public Stream TransformFile(PhysicalFileSource source, Stream inputContent)
{
- inputContent = Regex.Replace(inputContent, "url\\((?<quotation>['\"]?)(?<capturedUrl>[^\\)]*)", x =>
+ string stringValue = inputContent.ReadString();
+
+ stringValue = Regex.Replace(stringValue, "url\\((?<quotation>['\"]?)(?<capturedUrl>[^\\)]*)", x =>
{
var url = x.Groups["capturedUrl"];
if (url != null)
{
var quotation = x.Groups["quotation"];
var quoteWrapper = quotation != null ? quotation.Value : "";
- var urlValue = _virtualPathResolver.GetRelativeUrl(url.Value);
+ var virtualPath = url.Value;
+
+ // look for a placeholder in the virtual path
+ if (virtualPath.Contains(RejuicerConfigurationSource.FilenameUniquePlaceholder))
+ {
+ // Look for a configuration
+ if (!RejuicerEngine.HasConfigurationFor(virtualPath))
+ {
+ // Create a configuration for this file
+ OnRequest.ForImage(virtualPath)
+ .Combine
+ .File(virtualPath.Replace(RejuicerConfigurationSource.FilenameUniquePlaceholder, ""))
+ .Configure();
+ }
+
+ // get the timestamp and write it out into the URL...
+ virtualPath = RejuicerEngine.GetConfigFor(virtualPath).GetTimestampedUrl(_cacheProvider);
+
+ // Need to add a dependency of this CSS file to the now linked image.
+ source.AddDependency(RejuicerEngine.GetConfigFor(url.Value));
+ }
+
+ var urlValue = _virtualPathResolver.GetRelativeUrl(virtualPath);
return string.Format("url({1}{0}", urlValue, quoteWrapper);
}
return x.Value;
});
- return inputContent;
+
+
+
+ return stringValue.AsStream();
}
}
}
@@ -16,57 +16,10 @@ static FileResolver()
}
public static IVirtualPathResolver VirtualPathResolver { get; set; }
- public static IEnumerable<FileInfo> Resolve(RejuicedFileModel config)
+
+ public static IEnumerable<string> VirtualPathsFor(RejuicerConfigurationSource config)
{
- List<FileInfo> allFiles = new List<FileInfo>();
-
- if (config == null)
- {
- return allFiles;
- }
-
- var orderedFiles = config.OrderedFiles;
-
- foreach (var fileSpec in orderedFiles)
- {
- if(fileSpec is string)
- {
- var fileName = (string) fileSpec;
- allFiles.Add(VirtualPathResolver.ResolveVirtualPathToFile(fileName));
- }
-
- else if(fileSpec is FileMatchModel)
- {
- var model = (FileMatchModel)fileSpec;
- var physicalPath = VirtualPathResolver.ResolveVirtualPathToDirectory(model.Path);
-
- if (physicalPath == null)
- continue;
-
- allFiles.AddRange(Directory.EnumerateFiles(physicalPath.FullName, model.WildCard, SearchOption.TopDirectoryOnly)
- .Select(file => new FileInfo(file)));
- }
- }
-
- return allFiles.Distinct(new FileComparer());
- }
-
- public static IEnumerable<string> VirtualPathsFor(RejuicedFileModel config)
- {
- var allFiles = new List<string>();
-
- if (config == null)
- return allFiles;
-
- allFiles.AddRange(Resolve(config).Where(f=> f != null).Select(f => GetVirtualRelativeFor(f.FullName)));
-
- return allFiles;
- }
-
- internal static string GetVirtualRelativeFor(string filename)
- {
- var root = HttpContext.Current.Request.MapPath("~");
- return string.Format("~/{0}", filename.Substring(root.Length).Replace('\\', '/'));
+ return config == null ? Enumerable.Empty<string>() : config.GetDependencies().Select(f => VirtualPathResolver.GetVirtualPathFor(f));
}
private class FileComparer : IEqualityComparer<FileInfo>
@@ -9,11 +9,19 @@ namespace Rejuicer.Engine
{
public static class FileTransformationPipeline
{
- public static string TransformInputFile(string inputContent, ResourceType resourceType)
+ public static Stream TransformInputFile(PhysicalFileSource source, Stream inputContent)
{
- foreach (var transformation in FileTransformerRegistry.GetTransformationsFor(resourceType))
+ var transformations = FileTransformerRegistry.GetTransformationsFor(source.ResourceType);
+
+ if (transformations.Count() == 0)
+ {
+ return inputContent.Clone();
+ }
+
+ foreach (var transformation in transformations)
{
- inputContent = transformation.TransformFile(inputContent);
+ inputContent = transformation.TransformFile(source, inputContent);
+ inputContent.Seek(0, SeekOrigin.Begin);
}
return inputContent;
@@ -3,11 +3,12 @@
using System.IO;
using System.Linq;
using System.Text;
+using Rejuicer.Model;
namespace Rejuicer.Engine
{
public interface IFileTransformer
{
- string TransformFile(string file);
+ Stream TransformFile(PhysicalFileSource source, Stream file);
}
}
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace Rejuicer.Engine
+{
+ // Provides minification of a stream of data
+ public interface IMinificationProvider
+ {
+ Stream Minify(Stream data);
+ Stream Combine(IEnumerable<Stream> data);
+ string GetContentType(string filename);
+ }
+}
Oops, something went wrong.

0 comments on commit fcb99c8

Please sign in to comment.