Skip to content

Instantly share code, notes, and snippets.

@brendankowitz
Created February 4, 2015 03:48
Show Gist options
  • Save brendankowitz/eeb5f22a4e056970f16f to your computer and use it in GitHub Desktop.
Save brendankowitz/eeb5f22a4e056970f16f to your computer and use it in GitHub Desktop.
Js Bundle Orderer using comment references. Using QuickGraph nuget package.
public class JsReferenceBundleOrderer : IBundleOrderer
{
readonly Regex _referenceRegex = new Regex(@"///\s*<reference\s+path\s*=\s*""(?<filename>.*?)""\s*(?:/>|>)", RegexOptions.Compiled);
public IEnumerable<BundleFile> OrderFiles(BundleContext context, IEnumerable<BundleFile> files)
{
var bundleFiles = files as List<BundleFile> ?? files.ToList();
var adjacencyGraph = new AdjacencyGraph<string, Edge<string>>();
var topSort = new TopologicalSortAlgorithm<string, Edge<string>>(adjacencyGraph);
foreach (var file in MapDependencies(bundleFiles))
{
adjacencyGraph.AddVertex(file.FileName);
foreach (var dependecy in file.Dependencies)
{
adjacencyGraph.AddEdge(new Edge<string>(file.FileName, dependecy.FileName));
}
}
topSort.Compute();
var order = topSort.SortedVertices.Reverse()
.Select((x,i) => new { Order = i, Path = x })
.ToDictionary(x => x.Path, x => x.Order);
bundleFiles.Sort(new MappedFileComparer(order));
return bundleFiles;
}
public static string ExpandFileName(string relativeFileName, string baseDir = null)
{
var basePath = baseDir ?? HostingEnvironment.ApplicationPhysicalPath;
if (relativeFileName.StartsWith("~/"))
{
return HostingEnvironment.MapPath(relativeFileName);
}
var substringIndex = 0;
if (relativeFileName.StartsWith("/") || relativeFileName.StartsWith("\\"))
substringIndex = 1;
return Path.GetFullPath(Path.Combine(basePath, relativeFileName.Substring(substringIndex)));
}
public IEnumerable<ScriptFile> MapDependencies(IEnumerable<BundleFile> files)
{
var mappedList = new List<ScriptFile>();
IEnumerable<ScriptFile> scriptFiles = files
.Select(x => new ScriptFile(ExpandFileName(x.VirtualFile.VirtualPath)))
.Distinct()
.ToList();
var typeScriptFiles = scriptFiles.ToDictionary(x => x.FileName);
Action<List<ScriptFile>, ScriptFile> map = null;
map = (alreadyMapped, file) =>
{
if (alreadyMapped.Contains(file))
{
return;
}
alreadyMapped.Add(file);
string code;
try
{
code = File.ReadAllText(file.FileName);
}
catch (Exception ex)
{
Log.Error(ex, "Failed to read script file: {scriptFile}", file.FileName);
throw;
}
var match = _referenceRegex.Match(code);
while (match != Match.Empty)
{
var dependencyFileName = match.Groups["filename"].Value;
dependencyFileName = ExpandFileName(dependencyFileName, Path.GetDirectoryName(file.FileName));
var dependencyJsFile = dependencyFileName.Replace(".ts", ".js");
if (!File.Exists(dependencyJsFile))
{
Log.Error("Script file not found: {scriptFile}", dependencyJsFile);
}
else
{
ScriptFile dependency;
if (typeScriptFiles.TryGetValue(dependencyJsFile, out dependency))
{
dependency.AddDependent(file);
map(alreadyMapped, dependency);
}
}
match = match.NextMatch();
}
};
foreach (var file in typeScriptFiles)
{
map(mappedList, file.Value);
}
return mappedList;
}
public class MappedFileComparer : IComparer<BundleFile>
{
private readonly Dictionary<string, int> _mapped;
public MappedFileComparer(Dictionary<string, int> files)
{
_mapped = files;
}
public int Compare(BundleFile x, BundleFile y)
{
var xPath = JsReferenceBundleOrderer.ExpandFileName(x.VirtualFile.VirtualPath);
var yPath = JsReferenceBundleOrderer.ExpandFileName(y.VirtualFile.VirtualPath);
if (_mapped.ContainsKey(xPath) && _mapped.ContainsKey(yPath))
{
var xFile = _mapped[xPath];
var yFile = _mapped[yPath];
return xFile.CompareTo(yFile);
}
return 0;
}
}
public class ScriptFile : IEquatable<ScriptFile>
{
public ScriptFile(string fileName)
{
FileName = fileName;
}
public string FileName { get; private set; }
private List<ScriptFile> _dependencies = new List<ScriptFile>();
public List<ScriptFile> Dependencies { get { return _dependencies; } set { _dependencies = value.ToList(); } }
private readonly List<ScriptFile> _dependents = new List<ScriptFile>();
public IEnumerable<ScriptFile> Dependents { get { return _dependents; } }
public void AddDependency(ScriptFile dependency)
{
if (!_dependencies.Contains(dependency))
{
_dependencies.Add(dependency);
}
if (!dependency._dependents.Contains(this))
{
dependency._dependents.Add(this);
}
}
public void AddDependent(ScriptFile dependent)
{
if (!_dependents.Contains(dependent))
{
_dependents.Add(dependent);
}
if (!dependent._dependencies.Contains(this))
{
dependent._dependencies.Add(this);
}
}
public bool Equals(ScriptFile other)
{
if (other == null) return false;
return FileName.Equals(other.FileName);
}
public override bool Equals(object obj)
{
return Equals((ScriptFile)obj);
}
public override int GetHashCode()
{
return FileName.GetHashCode();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment