Last active
January 10, 2020 02:16
-
-
Save zaus/7436601 to your computer and use it in GitHub Desktop.
How to create a custom BundleTransform in .NET MVC4, specifically for the purposes of not renaming variables. Since I could not find this after an hour of concentrated interweb searching...
This file contains hidden or 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
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; | |
} | |
} |
This file contains hidden or 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
// 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... |
This file contains hidden or 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
/// <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); | |
} | |
} |
This file contains hidden or 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
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