Created
July 19, 2013 15:12
-
-
Save tealtail/6039828 to your computer and use it in GitHub Desktop.
Handlebars Bundle Transformer class for compiling Handlebars with ASP.NET MVC4 Bundles. Works well, so I thought I'd save it for reuse.
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
using System; | |
using System.Collections.Generic; | |
using System.Web; | |
using System.Web.Caching; | |
using System.Web.Script.Serialization; | |
using BundleTransformer.Core; | |
using System.Linq; | |
using BundleTransformer.Core.Assets; | |
using BundleTransformer.Core.Configuration; | |
using BundleTransformer.Core.FileSystem; | |
using BundleTransformer.Core.HttpHandlers; | |
using BundleTransformer.Core.Translators; | |
using BundleTransformer.Core.Web; | |
using MsieJavaScriptEngine; | |
using MsieJavaScriptEngine.ActiveScript; | |
using dotless.Core.Input; | |
using CoreStrings = BundleTransformer.Core.Resources.Strings; | |
namespace CollexAdmin.Infrastructure.Optimization | |
{ | |
public class HandlebarsAssetHandler : AssetHandlerBase | |
{ | |
public override string ContentType | |
{ | |
get { return BundleTransformer.Core.Constants.ContentType.Js; } | |
} | |
/// <summary> | |
/// Constructs instance of Handlebars asset handler | |
/// </summary> | |
public HandlebarsAssetHandler() | |
: this(HttpContext.Current.Cache, | |
BundleTransformerContext.Current.GetFileSystemWrapper(), | |
BundleTransformerContext.Current.GetCoreConfiguration().AssetHandler, | |
BundleTransformerContext.Current.GetApplicationInfo()) | |
{ } | |
/// <summary> | |
/// Constructs instance of Handlebars asset handler | |
/// </summary> | |
/// <param name="cache">Server cache</param> | |
/// <param name="fileSystemWrapper">File system wrapper</param> | |
/// <param name="assetHandlerConfig">Configuration settings of HTTP-handler that responsible | |
/// for text output of processed asset</param> | |
/// <param name="applicationInfo">Information about web application</param> | |
private HandlebarsAssetHandler(Cache cache, IFileSystemWrapper fileSystemWrapper, | |
AssetHandlerSettings assetHandlerConfig, IHttpApplicationInfo applicationInfo) | |
: base(cache, fileSystemWrapper, assetHandlerConfig, applicationInfo) | |
{ } | |
/// <summary> | |
/// Translates code of asset written on Handlebars to JS-code | |
/// </summary> | |
/// <param name="asset">Asset with code written on Handlebars</param> | |
/// <returns>Asset with translated code</returns> | |
protected override IAsset ProcessAsset(IAsset asset) | |
{ | |
ITranslator handlebarsTranslator = BundleTransformerContext.Current.GetJsTranslatorInstance("HandlebarsTranslator"); | |
handlebarsTranslator.IsDebugMode = _applicationInfo.IsDebugMode; | |
IAsset processedAsset = handlebarsTranslator.Translate(asset); | |
return processedAsset; | |
} | |
} | |
public sealed class HandlebarsTranslator : ITranslator | |
{ | |
private readonly MsieJsEngine _jsEngine = new MsieJsEngine(); | |
/// <summary> | |
/// Name of input code type | |
/// </summary> | |
const string INPUT_CODE_TYPE = "Handlebars"; | |
/// <summary> | |
/// Name of output code type | |
/// </summary> | |
const string OUTPUT_CODE_TYPE = "JS"; | |
/// <summary> | |
/// Gets or sets a flag that web application is in debug mode | |
/// </summary> | |
public bool IsDebugMode | |
{ | |
get; | |
set; | |
} | |
/// <summary> | |
/// Translates code of asset written on Handlebars to JS-code | |
/// </summary> | |
/// <param name="asset">Asset with code written on Handlebars</param> | |
/// <returns>Asset with translated code</returns> | |
public IAsset Translate(IAsset asset) | |
{ | |
if (asset == null) | |
{ | |
throw new ArgumentException(CoreStrings.Common_ValueIsEmpty, "asset"); | |
} | |
using (var handlebarsCompiler = new HandlebarsCompiler()) | |
{ | |
InnerTranslate(asset, handlebarsCompiler); | |
} | |
return asset; | |
} | |
/// <summary> | |
/// Translates code of assets written on Handlebars to JS-code | |
/// </summary> | |
/// <param name="assets">Set of assets with code written on Handlebars</param> | |
/// <returns>Set of assets with translated code</returns> | |
public IList<IAsset> Translate(IList<IAsset> assets) | |
{ | |
if (assets == null) | |
{ | |
throw new ArgumentException(CoreStrings.Common_ValueIsEmpty, "assets"); | |
} | |
if (assets.Count == 0) | |
{ | |
return assets; | |
} | |
var assetsToProcessing = assets.Where(a => a.AssetType == AssetType.Handlebars).ToList(); | |
if (assetsToProcessing.Count == 0) | |
{ | |
return assets; | |
} | |
using (var handlebarsCompiler = new HandlebarsCompiler()) | |
{ | |
foreach (var asset in assetsToProcessing) | |
{ | |
InnerTranslate(asset, handlebarsCompiler); | |
} | |
} | |
return assets; | |
} | |
private void InnerTranslate(IAsset asset, HandlebarsCompiler handlebarsCompiler) | |
{ | |
string newContent; | |
try | |
{ | |
newContent = handlebarsCompiler.Compile(asset.Content, asset.Url); | |
} | |
catch (Exception e) | |
{ | |
throw new AssetTranslationException( | |
string.Format(CoreStrings.Translators_TranslationFailed, | |
INPUT_CODE_TYPE, OUTPUT_CODE_TYPE, asset.Url, e.Message)); | |
} | |
asset.Content = newContent; | |
} | |
} | |
internal sealed class HandlebarsCompiler : IDisposable | |
{ | |
/// <summary> | |
/// Name of resource, which contains a Handlebars-library | |
/// </summary> | |
const string HandlebarsLibraryVirtualPath = "~/scripts/handlebars.js"; | |
/// <summary> | |
/// Template of function call, which is responsible for compilation | |
/// </summary> | |
const string CompilationFunctionCallTemplate = "Handlebars.precompile({0}, {{ knownHelpers : ['t', 'eachkeys', 'ifCond'], knownHelpersOnly: false }});"; | |
/// <summary> | |
/// Window-level namespace name for storing each Handlebars template | |
/// </summary> | |
private const string HandlebarsTemplateNamespace = "if (typeof {0}==='undefined'){{var {0}={{}};}} {0}[\"{1}\"] = Handlebars.template({2})\n"; | |
/// <summary> | |
/// MSIE JS engine | |
/// </summary> | |
private MsieJsEngine _jsEngine; | |
/// <summary> | |
/// Synchronizer of compilation | |
/// </summary> | |
private readonly object _compilationSynchronizer = new object(); | |
/// <summary> | |
/// JS-serializer | |
/// </summary> | |
private readonly JavaScriptSerializer _jsSerializer; | |
/// <summary> | |
/// Flag that compiler is initialized | |
/// </summary> | |
private bool _initialized; | |
/// <summary> | |
/// Flag that object is destroyed | |
/// </summary> | |
private bool _disposed; | |
/// <summary> | |
/// Constructs instance of Handlebars-compiler | |
/// </summary> | |
public HandlebarsCompiler() | |
{ | |
_jsSerializer = new JavaScriptSerializer(); | |
} | |
/// <summary> | |
/// Destructs instance of Handlebars-compiler | |
/// </summary> | |
~HandlebarsCompiler() | |
{ | |
Dispose(false /* disposing */); | |
} | |
/// <summary> | |
/// Initializes compiler | |
/// </summary> | |
private void Initialize() | |
{ | |
if (!_initialized) | |
{ | |
var server = new AspServerPathResolver(); | |
_jsEngine = new MsieJsEngine(true); | |
_jsEngine.ExecuteFile(server.GetFullPath(HandlebarsLibraryVirtualPath)); | |
_initialized = true; | |
} | |
} | |
/// <summary> | |
/// "Compiles" Handlebars-code to JS-code | |
/// </summary> | |
/// <param name="content">Text content written on Handlebars</param> | |
/// <param name="assetPath"></param> | |
/// <param name="handlebarsNamespace"></param> | |
/// <returns>Translated Handlebars-code</returns> | |
public string Compile(string content, string assetPath, string handlebarsNamespace = "JST") | |
{ | |
string newContent = null; | |
var options = new | |
{ | |
JSTNamespace = handlebarsNamespace, | |
PathReplace = "/js/templates/" | |
}; | |
lock (_compilationSynchronizer) | |
{ | |
Initialize(); | |
try | |
{ | |
var compileString = string.Format(CompilationFunctionCallTemplate, _jsSerializer.Serialize(content)); | |
var compiledTemplate = _jsEngine.Evaluate<string>(compileString); | |
newContent = string.Format(HandlebarsTemplateNamespace, options.JSTNamespace, FormatAssetSourcePath(options.PathReplace, assetPath, true), compiledTemplate); | |
} | |
catch (ActiveScriptException e) | |
{ | |
//throw new HandlebarsCompilingException(ActiveScriptErrorFormatter.Format(e)); | |
} | |
} | |
return newContent; | |
} | |
/// <summary> | |
/// Cleans up the asset path so that referencing JST's is relative to the "views" path where | |
/// all the client's JST's are stored | |
/// </summary> | |
/// <param name="pathReplace"></param> | |
/// <param name="assetPath"></param> | |
/// <param name="removeExtension"></param> | |
/// <returns></returns> | |
private string FormatAssetSourcePath(string pathReplace, string assetPath, bool removeExtension) | |
{ | |
string output = assetPath.Replace(pathReplace, string.Empty); | |
if (removeExtension) | |
{ | |
var index = output.IndexOf('.'); | |
if (index > 0) output = output.Substring(0, index); | |
} | |
return output.TrimStart('/'); | |
} | |
/// <summary> | |
/// Destroys object | |
/// </summary> | |
public void Dispose() | |
{ | |
Dispose(true /* disposing */); | |
GC.SuppressFinalize(this); | |
} | |
/// <summary> | |
/// Destroys object | |
/// </summary> | |
/// <param name="disposing">Flag, allowing destruction of | |
/// managed objects contained in fields of class</param> | |
private void Dispose(bool disposing) | |
{ | |
if (!_disposed) | |
{ | |
_disposed = true; | |
if (_jsEngine != null) | |
{ | |
_jsEngine.Dispose(); | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment