Created
February 4, 2015 03:48
-
-
Save brendankowitz/eeb5f22a4e056970f16f to your computer and use it in GitHub Desktop.
Js Bundle Orderer using comment references. Using QuickGraph nuget package.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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