Skip to content

Commit

Permalink
Reworked entire engine to generate a content source graph from rules.…
Browse files Browse the repository at this point in the history
… 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
skroonenburg committed Sep 18, 2011
1 parent a39e5ca commit fcb99c8
Show file tree
Hide file tree
Showing 43 changed files with 843 additions and 380 deletions.
2 changes: 1 addition & 1 deletion Demos/Mvc/MvcDemo/MvcDemo/Content/Site.css
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ div#title

.testImageCssTransform
{
background-image: url(~/Content/transformedUrlImage.png);
background-image: url(~/Content/transformedUrlImage{0}.png);
width: 400px;
height: 72px;
}
Expand Down
Binary file modified Demos/Mvc/MvcDemo/MvcDemo/Content/transformedUrlImage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 6 additions & 1 deletion Demos/Mvc/MvcDemo/MvcDemo/Global.asax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Binary file modified Lib/Rejuicer.dll
Binary file not shown.
5 changes: 5 additions & 0 deletions Source/Rejuicer/Rejuicer-test/DefaultCssTransformerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ public string GetRelativeUrl(string virtualPath)
{
return _replacement(virtualPath);
}

public string GetVirtualPathFor(FileInfo file)
{
throw new NotImplementedException();
}
}
}
}
46 changes: 4 additions & 42 deletions Source/Rejuicer/Rejuicer-test/FluentConfigurerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public void Setup()
RejuicerEngine._configurations.Clear();
}

private RejuicedFileModel GetModelFor(object configurer)
private RejuicerConfigurationSource GetModelFor(object configurer)
{
return ((CompactorConfigurer)configurer)._config;
}
Expand Down Expand Up @@ -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]
Expand All @@ -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]
Expand Down
6 changes: 0 additions & 6 deletions Source/Rejuicer/Rejuicer/Configuration/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
55 changes: 55 additions & 0 deletions Source/Rejuicer/Rejuicer/Engine/BaseStringMinificationProvider.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
23 changes: 23 additions & 0 deletions Source/Rejuicer/Rejuicer/Engine/CssMinificationProvider.cs
Original file line number Diff line number Diff line change
@@ -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";
}
}
}
45 changes: 41 additions & 4 deletions Source/Rejuicer/Rejuicer/Engine/DefaultCssTransformer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}
53 changes: 3 additions & 50 deletions Source/Rejuicer/Rejuicer/Engine/FileResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
14 changes: 11 additions & 3 deletions Source/Rejuicer/Rejuicer/Engine/FileTransformationPipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion Source/Rejuicer/Rejuicer/Engine/IFileTransformer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
16 changes: 16 additions & 0 deletions Source/Rejuicer/Rejuicer/Engine/IMinificationProvider.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading

0 comments on commit fcb99c8

Please sign in to comment.