A Visual Studio Extension that locates and removes duplicate script references in a web project.
Whilst developing a web project, you will add some script references (JavaScript\CSS) to your page (well, duh).
But then you forget which scripts you added, and add them again.
(happens a lot with MVC 3 where you don't have a visual designer and the page can become quite big)
so basically you end up with something like this:
Referencing a script twice will not only slow down the website, it may also lead to an unexpected behavior as a JavaScript function calling a controller can be executed twice.
Sometimes the scenario gets more complicated, when a reference to the same script is included in both the layout/master page-file as well as a partial view.
Redundant References Remover Extension solves these problems, it scans the views in the web-project looking for duplicated references and removes them.
Download Extension
Source Code
After the installation, the tool appears under Other Windows
There are 3 services that do the work
IVisualStudioService
handles Visual Studio Interaction
public interface IVisualStudioService
{
WebProject GetWebProject();
IList<Page> GetPages(ProjectItems item);
vsSaveStatus RemoveReference(ProjectItem projectItem, string reference, int offset);
}
IPagesService
handles processing of pages and scanning for repeated references
public interface IPagesService
{
void ProcessPages(IEnumerable<Page> pagesList);
IEnumerable<Page> GetDuplicatesInSameFile(IEnumerable<Page> pages);
IEnumerable<ParentChildDuplicates> GetDuplicatesInLayoutAndFile(IEnumerable<Page> pages);
}
IRegexService
Extracts different parts of info from the Page's html
public interface IRegexService
{
IEnumerable<string> GetReferences(string pageContent);
string GetDefaultLayoutPageName(string content);
IEnumerable<string> GetMultipleLayouts(string content);
IEnumerable<string> GetPartialViews(string content);
IEnumerable<string> GetWebControls(string content);
bool CheckPageHasNullLayout(string content);
string GetMasterPageName(string content);
}
There are 3 types of web projects, mvc2, mvc3 and web forms, each handle the master\child layout differently.
So we make the parent class
WebFormPagesService
with virtual functions.
namespace Services.Implementation
{
using System;
using System.Collections.Generic;
using System.Linq;
using Entities;
using Services.Contracts;
using EnvDTE;
public class WebFormPagesService : IPagesService
{
#region Member Variables
protected readonly IRegexService _regexService;
#endregion Member Variables
#region Constructor
public WebFormPagesService(IRegexService regexService)
{
this._regexService = regexService;
}
#endregion Constructor
#region IPagesService
void IPagesService.ProcessPages(IEnumerable<Page> pagesList)
{
foreach (Page page in pagesList)
{
page.References = GetReferences(page);
var parents = GetParents(page.Name, page.Content, pagesList);
if (page.Parents != null)
{
page.Parents.Concat(parents);
}
else
{
page.Parents = parents;
}
var children = GetChildren(page.Content, pagesList);
foreach (var child in children)
{
if (child.Parents == null)
{
child.Parents = new List<Page>();
}
child.Parents.Add(page);
}
}
LinkParents(pagesList);
}
IEnumerable<Page> IPagesService.GetDuplicatesInSameFile(IEnumerable<Page> pages)
{
foreach (var page in pages)
{
var duplicates = page.References.Where(p => p.Value != 1).ToDictionary(p => p.Key, p => p.Value);
if (duplicates.Count != 0)
{
yield return new Page
{
Content = page.Content,
Item = page.Item,
Name = page.Name,
Parents = page.Parents,
References = duplicates
};
}
}
}
IEnumerable<ParentChildDuplicates> IPagesService.GetDuplicatesInLayoutAndFile(IEnumerable<Page> pages)
{
var duplicatesList = new List<ParentChildDuplicates>();
foreach (var child in pages)
{
var childReferences = child.References.Keys;
foreach (var parent in child.Parents)
{
var parentsReferences = parent.References.Keys;
var commonReferences = parentsReferences.Intersect(childReferences);
if (commonReferences.Count() > 0)
{
var parentChildDuplicate = new ParentChildDuplicates { Parent = parent, Child = child, Duplicates = commonReferences };
duplicatesList.Add(parentChildDuplicate);
}
}
}
return duplicatesList;
}
#endregion
.
.
.
}
}
Conclusion
That's it, the code is pretty much straight forward.
If you have any comment\suggestion\question about the implementation\code topology drop me a line ;)
Improvement\Future Work
Currently the code doesn't support detecting the Inclusion of both a minified and unminified version of a script.
... src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.js">
... src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js">
But I'll get to it, when I have time.
Useful Resources
YSlow rule #13 – Remove Duplicate Scripts
Introduction to Visual Studio 2010 Extensibility
Manipulating Project Files in VS
Customizing Visual Studio Extension Icon in Visual Studio 2010