Skip to content

Commit

Permalink
- Fix bug when inline JS contains // comments and that the AjaxMin dl…
Browse files Browse the repository at this point in the history
…l is not available

- Implement the minification of inline CSS
  • Loading branch information
meleze committed Apr 24, 2012
1 parent d53cdc1 commit 8fd8f06
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 15 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Expand Up @@ -28,4 +28,7 @@ obj/
[Tt]est[Rr]esult*
tmp*/
*.swp
Meleze.Web.*.nupkg
Meleze.Web.*.nupkg
MVC3*/
MVC4*/
packages*/
4 changes: 2 additions & 2 deletions Meleze.Web/Properties/AssemblyInfo.cs
Expand Up @@ -31,5 +31,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.3.0.1")]
[assembly: AssemblyFileVersion("1.3.0.1")]
[assembly: AssemblyVersion("1.3.0.3")]
[assembly: AssemblyFileVersion("1.3.0.3")]
119 changes: 119 additions & 0 deletions Meleze.Web/Razor/MinifyHtmlMinifier.cs
Expand Up @@ -36,14 +36,18 @@ static MinifyHtmlMinifier()
}

private static Func<string, string> _minifyJS;
private static Func<string, string> _minifyCSS;
private bool _comments = true;
private bool _aggressive = true;
private bool _javascript = true;
private bool _css = true;

public Func<string, string> MinifyJS { set { _minifyJS = value; } }
public Func<string, string> MinifyCSS { set { _minifyCSS = value; } }
public bool Comments { set { _comments = value; } }
public bool Aggressive { set { _aggressive = value; } }
public bool Javascript { set { _javascript = value; } }
public bool CSS { set { _css = value; } }

public void AnalyseContent(string content, ref bool previousIsWhiteSpace, ref bool previousTokenEndsWithBlockElement)
{
Expand Down Expand Up @@ -76,6 +80,15 @@ public string Minify(string content, bool previousIsWhiteSpace, bool previousTok
// when analysing the JS (which is needed to take // comments into account correctly)
content = MinifyJavascript(content, builder);
}
else
{
content = MinifyJavascriptComments(content, builder);
}

if (_css && (_minifyCSS != null))
{
content = MinifyInlineCSS(content, builder);
}

if (_aggressive)
{
Expand Down Expand Up @@ -285,5 +298,111 @@ private static string MinifyJavascript(string content, StringBuilder builder)
}
return content;
}

/// <summary>
/// Removes the single line comments from the Javascript code.
/// (because when line returns are minimized, these comments commant the whole script).
/// </summary>
/// <param name="content"></param>
/// <param name="builder"></param>
/// <returns></returns>
private static string MinifyJavascriptComments(string content, StringBuilder builder)
{
builder.Clear();
var iscriptstart = content.IndexOf("<script");
while (iscriptstart >= 0)
{
var iscriptautoend = content.IndexOf("/>", iscriptstart + 7);
var iscriptend = content.IndexOf("</script>", iscriptstart + 7);
if ((iscriptend < 0) || ((iscriptautoend > 0) && (iscriptautoend < iscriptend)))
{
break;
}

// We have some javascript code inside the tag
// => we can ask a JS minifier to simplify it
var istartcode = content.IndexOf('>', iscriptstart) + 1;
var iendcode = iscriptend;
var code = content.Substring(istartcode, iendcode - istartcode);
builder.Append(content, 0, istartcode);

if (!string.IsNullOrWhiteSpace(code))
{
// We remove all // comments that cause problems when minifying the HTML later
var lines = code.Split('\n');
foreach (var line in lines)
{
var minifiedLine = line;
int icomment = line.IndexOf("//");
if (icomment >= 0)
{
minifiedLine = line.Substring(0, icomment);
}
builder.AppendLine(minifiedLine);
}
}

iscriptstart = builder.Length;

builder.Append(content, iscriptend, content.Length - iscriptend);
content = builder.ToString();
builder.Clear();

iscriptstart = content.IndexOf("<script", iscriptstart);
}
return content;
}

/// <summary>
/// Uses an external CSS minifier to minimize inline CSS code.
/// </summary>
/// <param name="content"></param>
/// <param name="builder"></param>
/// <returns></returns>
private static string MinifyInlineCSS(string content, StringBuilder builder)
{
builder.Clear();
var iscriptstart = content.IndexOf("<style");
while (iscriptstart >= 0)
{
var iscriptautoend = content.IndexOf("/>", iscriptstart + 6);
var iscriptend = content.IndexOf("</style>", iscriptstart + 6);
if ((iscriptend < 0) || ((iscriptautoend > 0) && (iscriptautoend < iscriptend)))
{
break;
}

// We have some CSS code inside the tag
// => we can ask a CSS minifier to simplify it
var istartcode = content.IndexOf('>', iscriptstart) + 1;
var iendcode = iscriptend;
var code = content.Substring(istartcode, iendcode - istartcode);
builder.Append(content, 0, istartcode);

if (!string.IsNullOrWhiteSpace(code))
{
// We call the Microsoft JS minifier by reflexion to cut the dependency.
var minifiedCode = code;
try
{
minifiedCode = _minifyCSS(code);
}
catch
{
}
builder.Append(minifiedCode);
}

iscriptstart = builder.Length;

builder.Append(content, iscriptend, content.Length - iscriptend);
content = builder.ToString();
builder.Clear();

iscriptstart = content.IndexOf("<style", iscriptstart);
}
return content;
}

}
}
29 changes: 28 additions & 1 deletion Meleze.Web/Razor/MinifyHtmlWebRazorHostFactory.cs
Expand Up @@ -20,21 +20,31 @@ public MinifyHtmlWebRazorHostFactory()
// Javascript minification

Func<string, string> minifyJS = null;
Func<string, string> minifyCSS = null;
//minifyJS = delegate(string code)
//{
// var sc = new Microsoft.Ajax.Utilities.ScriptCruncher();
// var scsettings = new Microsoft.Ajax.Utilities.CodeSettings() { LocalRenaming = Microsoft.Ajax.Utilities.LocalRenaming.KeepLocalizationVars };
// var minifiedCode = sc.Crunch(code, scsettings);
// return minifiedCode;
//};
//minifyCSS = delegate(string code)
//{
// var sc = new Microsoft.Ajax.Utilities.ScriptCruncher();
// var scsettings = new Microsoft.Ajax.Utilities.CssSettings() { CommentMode = Microsoft.Ajax.Utilities.CssComment.Hacks };
// var minifiedCode = sc.MinifyStyleSheet(code, scsettings);
// return minifiedCode;
//};

// We initialize the JS minification by reflexion to remove a DLL dependency
// We initialize the JS and CSS minification by reflexion to remove a DLL dependency
try
{
var ajaxmin = Assembly.Load("ajaxmin");
if (ajaxmin != null)
{
var scriptCruncherType = ajaxmin.GetType("Microsoft.Ajax.Utilities.ScriptCruncher");

// JS
var codeSettingsType = ajaxmin.GetType("Microsoft.Ajax.Utilities.CodeSettings");
var localRenamingProperty = codeSettingsType.GetProperty("LocalRenaming");
var crunchMethod = scriptCruncherType.GetMethod("Crunch", new Type[] { typeof(string), codeSettingsType });
Expand All @@ -48,6 +58,20 @@ public MinifyHtmlWebRazorHostFactory()
var minifiedCode = (string)crunchMethod.Invoke(sc, new object[] { code, scsettings });
return minifiedCode;
};

// CSS
var cssSettingsType = ajaxmin.GetType("Microsoft.Ajax.Utilities.CssSettings");
var commentModeProperty = cssSettingsType.GetProperty("CommentMode");
var minifyStyleSheetMethod = scriptCruncherType.GetMethod("MinifyStyleSheet", new Type[] { typeof(string), cssSettingsType });

var scsettings2 = cssSettingsType.GetConstructor(Type.EmptyTypes).Invoke(null);
commentModeProperty.SetValue(scsettings2, 2, null);

minifyCSS = delegate(string code)
{
var minifiedCode = (string)minifyStyleSheetMethod.Invoke(sc, new object[] { code, scsettings2 });
return minifiedCode;
};
}
}
catch
Expand All @@ -57,12 +81,15 @@ public MinifyHtmlWebRazorHostFactory()
var confAggressive = ConfigurationManager.AppSettings["meleze-minifier:Aggressive"];
var confComments = ConfigurationManager.AppSettings["meleze-minifier:Comments"];
var confJavascript = ConfigurationManager.AppSettings["meleze-minifier:Javascript"];
var confCSS = ConfigurationManager.AppSettings["meleze-minifier:CSS"];

_minifier = new MinifyHtmlMinifier();
_minifier.MinifyJS = minifyJS;
_minifier.MinifyCSS = minifyCSS;
_minifier.Aggressive = confAggressive == null || confAggressive.ToLower() == "true";
_minifier.Comments = confComments == null || confComments.ToLower() == "true";
_minifier.Javascript = confJavascript == null || confJavascript.ToLower() == "true";
_minifier.CSS = confCSS == null || confCSS.ToLower() == "true";
}
}
}
4 changes: 2 additions & 2 deletions Meleze.Web4/Properties/AssemblyInfo.cs
Expand Up @@ -31,5 +31,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.4.0.1")]
[assembly: AssemblyFileVersion("1.4.0.1")]
[assembly: AssemblyVersion("1.4.0.3")]
[assembly: AssemblyFileVersion("1.4.0.3")]
15 changes: 12 additions & 3 deletions README
Expand Up @@ -2,30 +2,39 @@ To use the HTML Minification for Razor, you have to change your Views/Web.config

<configuration>
<system.web.webPages.razor>
<host factoryType="Meleze.Web.Razor.MinifyHtmlWebRazorHostFactory, Meleze.Web, Version=1.0.0.2, Culture=neutral, PublicKeyToken=0a868b5321967eda" />
<host factoryType="Meleze.Web.Razor.MinifyHtmlWebRazorHostFactory, Meleze.Web, Version=1.4.0.3, Culture=neutral, PublicKeyToken=0a868b5321967eda" />
</system.web.webPages.razor>
</configuration>

For ASP.NET MVC 3.0, use the 1.3 version of Meleze:
<host factoryType="Meleze.Web.Razor.MinifyHtmlWebRazorHostFactory, Meleze.Web, Version=1.3.0.3, Culture=neutral, PublicKeyToken=0a868b5321967eda" />

There are two versions of the DLL: one for ASP.NET MVC3 and another for ASP.NET MVC4. Make sur to use the correct version (it is displayed in the DLL description).

Making this change in the Web.config may break Intelliscense in Visual Studio if it does not find the Meleze.Web dll.
There are two solutions:
1) You can setup the factory in Web.config.Release instead of the Web.config. The minification will be enabled only in release build.
2) Otherwize, you can register the Meleze.Web dll in the GAC using "gacutil.exe -i Meleze.Web". The minification will work even in design mode.

There are two versions of the DLL: one for ASP.NET MVC3 and another for ASP.NET MVC4. Make sur to use the correct version (it is displayed in the DLL description).

The Minifier behavior can be configured in the application's root Web.config with some appSettings keys:

<appSettings>
<add key="meleze-minifier:Aggressive" value="true"/>
<add key="meleze-minifier:Comments" value="true"/>
<add key="meleze-minifier:Javascript" value="true"/>
<add key="meleze-minifier:CSS" value="true"/>
</appSettings>

meleze-minifier:Aggressive removes as much whitespace as possible. In some cases, you may have to change yours CSS to fix the page layout (as whitespace is not interpreted in a compatible way by all browsers).
meleze-minifier:Comments removes all the HTML comments that are neither Javascript code or conditional comments.
meleze-minifier:Javascript calls the Microsoft Ajax Minifier. The AjaxMin.dll must be in the application references for this option to work (otherwise, JS is kept as is).
meleze-minifier:CSS also calls the Microsoft Ajax Minifier to minimize the inline styles.

There is also a NuGet package to setup the Minifier automatically in your applications.

You can find more details in http://cestdumeleze.net/blog/2011/minifying-the-html-with-asp-net-mvc-and-razor/

RELEASE NOTES:
- 1.4.3 and 1.3.3 (4/24/2012)
- Fix bug when inline Javascript contains // comments and that the Javascript minifier is not enabled.
- Minify the inline CSS with the Microsoft Ajax Minifier (like for the inline Javascript).
6 changes: 3 additions & 3 deletions cestdumeleze3.nuspec
Expand Up @@ -2,16 +2,16 @@
<package>
<metadata>
<id>Meleze.Web</id>
<version>1.3.2</version>
<version>1.3.3</version>
<authors>Nicolas Moyère</authors>
<description>
Meleze.Web is a toolbox to optimize ASP.NET MVC 3.0 and MVC 4.0 applications.
It provides HTML and JS minification of Razor views and caching of the returned pages.
It provides HTML, JS and CSS minification of Razor views and caching of the returned pages.

The package version 1.4.x targets ASP.NET MVC 4.0. Use version 1.3.x for MVC 3.0.
</description>
<projectUrl>https://github.com/meleze/Meleze.Web</projectUrl>
<licenseUrl>http://cestdumeleze.net/license.txt</licenseUrl>
<tags>ASP.NET-MVC MVC MVC3 MVC4 Razor HTML compression minification minify javascript js</tags>
<tags>ASP.NET-MVC MVC MVC3 MVC4 Razor HTML compression minification minify javascript js css</tags>
</metadata>
</package>
6 changes: 3 additions & 3 deletions cestdumeleze4.nuspec
Expand Up @@ -2,16 +2,16 @@
<package>
<metadata>
<id>Meleze.Web</id>
<version>1.4.2</version>
<version>1.4.3</version>
<authors>Nicolas Moyère</authors>
<description>
Meleze.Web is a toolbox to optimize ASP.NET MVC 3.0 and MVC 4.0 applications.
It provides HTML and JS minification of Razor views and caching of the returned pages.
It provides HTML, JS and CSS minification of Razor views and caching of the returned pages.

The package version 1.4.x targets ASP.NET MVC 4.0. Use version 1.3.x for MVC 3.0.
</description>
<projectUrl>https://github.com/meleze/Meleze.Web</projectUrl>
<licenseUrl>http://cestdumeleze.net/license.txt</licenseUrl>
<tags>ASP.NET-MVC MVC MVC3 MVC4 Razor HTML compression minification minify javascript js</tags>
<tags>ASP.NET-MVC MVC MVC3 MVC4 Razor HTML compression minification minify javascript js css</tags>
</metadata>
</package>
1 change: 1 addition & 0 deletions web.config.transform
Expand Up @@ -4,5 +4,6 @@
<add key="meleze-minifier:Aggressive" value="true"/>
<add key="meleze-minifier:Comments" value="true"/>
<add key="meleze-minifier:Javascript" value="true"/>
<add key="meleze-minifier:CSS" value="true"/>
</appSettings>
</configuration>

0 comments on commit 8fd8f06

Please sign in to comment.