Skip to content

Instantly share code, notes, and snippets.

@Jeff-Tian
Forked from zaus/ConfigurableJsMinify.cs
Created March 31, 2014 12:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Jeff-Tian/9890831 to your computer and use it in GitHub Desktop.
Save Jeff-Tian/9890831 to your computer and use it in GitHub Desktop.
using System.IO;
using Microsoft.Ajax.Utilities;
/// <summary>
/// Minify javascript files with externally overridable configuration settings.
/// </summary>
public class ConfigurableJsMinify : StandardFileBundleTransform {
protected bool includeFilenamesInOutput;
public CodeSettings Configuration { get; set; }
/// <summary>
/// Create a new minifier, optionally specifying all configuration properties.
/// </summary>
/// <param name="configuration">Sets the public settings <see cref="Configuration"/>. If not provided, will use <see cref="GetStandardSettings"/> -- note that you can then override specific properties of <see cref="Configuration"/>.</param>
/// <param name="newlineBetweenFiles">optionally append multiple bundle files with spacing between</param>
/// <param name="includeFilenamesInOutput">optionally append multiple bundle files with the original file name, for added debugging</param>
public ConfigurableJsMinify(CodeSettings configuration = null, bool newlineBetweenFiles = false, bool includeFilenamesInOutput = false)
: base("text/javascript" /* would be awesome if MS exposed JsMinify.JsContentType */, newlineBetweenFiles) {
this.includeFilenamesInOutput = includeFilenamesInOutput;
this.Configuration = configuration ?? GetStandardSettings();
}
protected static Minifier compiler = new Minifier();
/// <summary>
/// Set up the basic minification settings, allowing you to override them later
/// </summary>
/// <returns></returns>
public static CodeSettings GetStandardSettings() {
// combining with JsMinify source code + http://outcastgeek.com/combine-minify-javascript-dotnet.html
// also, look at explanations for command-line switches http://ajaxmin.codeplex.com/wikipage?title=Command-Line%20Switches
return new CodeSettings {
// copied from JsMinify source code
EvalTreatment = EvalTreatment.MakeImmediateSafe,
PreserveImportantComments = false, // set this to true to preserve license stuff at the beginning of files -- see http://giddyrobot.com/preserving-important-comments-in-mvc-4-bundles/
// custom stuff
MinifyCode = true // the most important part...explicitly tell it to minify
// LocalRenaming = LocalRenaming.KeepAll,
// NoAutoRenameList = "document", // this is the main troublemaker, don't try to obfuscate it
};
}
protected override string compileContents(StreamReader source, string path, string fullpath) {
//return base.compileContents(source);
var compiled = includeFilenamesInOutput
? "/* " + path + " */" + (this.newlineBetweenFiles ? System.Environment.NewLine : null) + compiler.MinifyJavaScript(source.ReadToEnd(), this.Configuration)
: compiler.MinifyJavaScript(source.ReadToEnd(), this.Configuration);
return compiler.ErrorList.Count > 0
? BuildErrorResults(compiler.ErrorList)
: compiled;
}
}
// stuff above...
var bundleDt = new ScriptBundle("~/js/datatables").Include(
"~/assets/js/plugins/datatables/jquery.dataTables.js",
"~/assets/js/plugins/datatables/DT_bootstrap.js",
"~/assets/js/plugins/responsive-tables/responsive-tables.js");
bundleDt.Transforms.Clear(); // remove default JsMinify
bundleDt.Transforms.Add(new JsMinifyNoRename("document")); // preserve the troublesome variable that is broken on minification
bundles.Add(bundleDt);
// stuff below...
/// <summary>
/// Minify js but preserve indicated variable names
/// </summary>
public class JsMinifyNoRename : ConfigurableJsMinify {
/// <summary>
/// Minify js but preserve indicated variable names
/// </summary>
/// <param name="doNotRenameThese">list of variable names to preserve</param>
public JsMinifyNoRename(params string[] doNotRenameThese) : this(false, false, doNotRenameThese) {
}
/// <summary>
/// Minify js but preserve indicated variable names
/// </summary>
/// <param name="newlineBetweenFiles">optionally append multiple bundle files with spacing between</param>
/// <param name="includeFilenamesInOutput">optionally append multiple bundle files with the original file name, for added debugging</param>
/// <param name="doNotRenameThese">list of variable names to preserve</param>
public JsMinifyNoRename(bool newlineBetweenFiles, bool includeFilenamesInOutput, params string[] doNotRenameThese)
: base(null, newlineBetweenFiles, includeFilenamesInOutput) {
this.Configuration.SetNoAutoRenames(doNotRenameThese);
}
}
using System;
using System.IO;
using System.Text;
using System.Web.Optimization; // the NuGet package
/// <summary>
/// Base class for transforming a list of files. Override method <see cref="compileContents"/> to customize transform.
///
/// <list type="bullet">
/// <listheader>Additional reading:</listheader>
/// <item><description>http://stackoverflow.com/questions/13032721/system-web-optimization-making-function-argument-names-stay-the-same-for-certain</description></item>
/// <item><description>http://www.asp.net/mvc/tutorials/mvc-4/bundling-and-minification</description></item>
/// <item><description>http://www.dotnetexpertguide.com/2011/12/custom-transformtype-bundling.html</description></item>
/// <item><description>http://giddyrobot.com/preserving-important-comments-in-mvc-4-bundles/</description></item>
/// <item><description>http://daniel.wertheim.se/2012/11/16/customizing-the-minification-of-javascript-in-asp-net-mvc4-allowing-reserved-words/</description></item>
/// <item><description>but most importantly, ReSharper to download the source code of JsMinify</description></item>
/// </list>
/// </summary>
public abstract class StandardFileBundleTransform : /* JsMinify ,*/ IBundleTransform {
protected readonly bool newlineBetweenFiles;
protected readonly string contentType;
/// <summary>
/// Create a new transformer
/// </summary>
/// <param name="contentType">specify the output type -- can set this by default in derived types</param>
/// <param name="newlineBetweenFiles">optionally append multiple bundle files with spacing between</param>
protected StandardFileBundleTransform(string contentType, bool newlineBetweenFiles = false) {
this.contentType = contentType;
this.newlineBetweenFiles = newlineBetweenFiles;
}
/// <summary>
/// Loop through the files in the response and compile their contents with <see cref="compileContents"/>
/// </summary>
/// <param name="context"></param>
/// <param name="response"></param>
public virtual void Process(BundleContext context, BundleResponse response) {
//base.Process(context, response);
response.ContentType = this.contentType;
if (!this.isContextReady(context, response)) return;
var compiled = new StringBuilder();
foreach (var path in response.Files.Select(f => f.IncludedVirtualPath)) {
var fullpath = context.HttpContext.Server.MapPath(path);
using (var reader = new StreamReader(fullpath)) {
// might as well pass the stream by reference rather than the entire contents
// also so implementations could decide how to read the contents other than .ReadToEnd()
if (this.newlineBetweenFiles) compiled.AppendLine(compileContents(reader, path, fullpath));
else compiled.Append(compileContents(reader, path, fullpath));
reader.Close();
}
}
// overwrite original content with compiled version
response.Content = compiled.ToString();
// don't need to set caching...handled by framework automatically
}
/// <summary>
/// Basic argument checking and confirming that we should process the bundle.
/// </summary>
/// <param name="context"></param>
/// <param name="response"></param>
/// <returns>true if we should continue processing, false otherwise</returns>
protected virtual bool isContextReady(BundleContext context, BundleResponse response) {
if (context == null)
throw new ArgumentNullException("context");
if (response == null)
throw new ArgumentNullException("response");
// don't want to do anything if not enabled
return !context.EnableInstrumentation;
}
/// <summary>
/// Implements expected transformation.
/// </summary>
/// <param name="source"></param>
/// <param name="path">original relative source path</param>
/// <param name="fullpath">fully mapped path on server</param>
/// <remarks>If not overridden, will just return file contents</remarks>
/// <returns></returns>
protected virtual string compileContents(StreamReader source, string path, string fullpath) {
return source.ReadToEnd();
}
/// <summary>
/// Because the original MS version is static internal, which is dumb. Call this after minifying engine runs in <see cref="compileContents"/>.
/// </summary>
/// <param name="errors">list of compiler/minifier engine errors</param>
protected virtual string BuildErrorResults(IEnumerable<object> errors) {
var content = new StringBuilder();
content.Append("/* ");
// should use `OptimizationResources.MinifyError`, but again...internal
content.Append("MinifyError").Append(Environment.NewLine);
foreach (object current in errors) {
content.Append(current).Append(Environment.NewLine);
}
content.Append(" */").Append(Environment.NewLine);
return content.ToString();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment