What Does CssRewriteUrlTransform Do, Exactly?
It seems there is something up with CssRewriteUrlTransform class. From MSDN, we can read the following about it: 'Rewrites urls to be absolute so assets will still be found after bundling.' We can find out about it from the Process() method page: Example: bundle.Include(“~/content/some.css”)
will transform url(images/1.jpg) => url(/content/images/1.jpg)
.
If all CSS is under our control, then we should be good to go with CssRewriteUrlTransform class. As I found out, it doesn’t work in all cases. Especially if you have to bundle a big number of CSS files provided by some agency.
I have some web applications where tens of CSS files are bundled and minified. The files come in as they are: some paths are relative to CSS files and some paths are already absolute, meaning that after publishing the web application to some subfolder (some customers need it) some of the files in the design are not showing up anymore. It is unthinkable that after every CSS update I would have to manually go through all these files and format paths, like the CssRewriteUrlTransform class expects.
Custom Bundle Transform for CSS Paths
After some digging around on the internet, I found some code samples and I ended up with the following path transforming class:
public class StyleRelativePathTransform : IBundleTransform
{
private static Regex pattern = new Regex(@"url\s*\(\s*([""']?)([^:)]+)\1\s*\)", RegexOptions.IgnoreCase); public void Process(BundleContext context, BundleResponse response)
{
response.Content = string.Empty; foreach (BundleFile file in response.Files)
{
using (var reader = new StreamReader(file.VirtualFile.Open()))
{
var contents = reader.ReadToEnd();
var matches = pattern.Matches(contents);if(matches.Count == 0)
{
continue;
} var directoryPath = VirtualPathUtility.GetDirectory(file.VirtualFile.VirtualPath);
foreach (Match match in matches)
{
var fileRelativePath = match.Groups[2].Value;
var fileVirtualPath = VirtualPathUtility.Combine(directoryPath, fileRelativePath);
var quote = match.Groups[1].Value;
var replace = String.Format("url({0}{1}{0})", quote, VirtualPathUtility.ToAbsolute(fileVirtualPath)); contents = contents.Replace(match.Groups[0].Value, replace);
} response.Content = String.Format("{0}\r\n{1}", response.Content, contents);
}
}
}
}
It is able to handle URLs in CSS files perfectly in my case. An additional change is needed for bundling and minification. Let’s open BundleConfig class in the App_Start folder and the make styles section of the RegisterBundles method look similar to the following code:
var styles = new StyleBundle("~/styles")
.Include("~/content/pages/meetings.min.css", new CssRewriteUrlTransform())
.Include("~/content/pages/login.min.css", new CssRewriteUrlTransform())
.Include("~/content/pages/error.min.css", new CssRewriteUrlTransform())
.Include("~/content/Site.css", new CssRewriteUrlTransform());
styles.Transforms.Insert(0, new StyleRelativePathTransform());
styles.Orderer = new AsIsBundleOrderer();
bundles.Add(styles);
AsIsBundeOrderer
is another class I found on the web and it keeps CSS files in order, so I added these to bundle. By default, they are reordered for reasons unknown to me.
public class AsIsBundleOrderer : IBundleOrderer
{
public virtual IEnumerable<FileInfo> OrderFiles(BundleContext context, IEnumerable<FileInfo> files)
{
return files;
}public IEnumerable<BundleFile> OrderFiles(BundleContext context, IEnumerable<BundleFile> files)
{
return files;
}
}
After applying the transform class and orderer given in this blog post, my issues with the long list of externally provided CSS files were solved.
{{ parent.title || parent.header.title}}
{{ parent.tldr }}
{{ parent.linkDescription }}
{{ parent.urlSource.name }}