Created
April 11, 2013 18:01
-
-
Save jpsullivan/5365684 to your computer and use it in GitHub Desktop.
Precompile Handlebars templates using BundleTransformer
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.Collections.Generic; | |
using System.Web.Optimization; | |
using BundleTransformer.Core.Orderers; | |
using BundleTransformer.Core.Transformers; | |
using BundleTransformer.Core.Translators; | |
using Ember; | |
namespace MyApp.App_Start | |
{ | |
public class BundleConfig | |
{ | |
// For more information on Bundling, visit http://go.microsoft.com/fwlink/?LinkId=254725 | |
public static void RegisterBundles(BundleCollection bundles) | |
{ | |
var cssTransformer = new CssTransformer(); | |
var jsTransformer = new JsTransformer(); | |
var nullOrderer = new NullOrderer(); | |
... | |
// App JS | |
Bundle appJs = new ScriptBundle("~/bundles/js_app").IncludeDirectory("~/Content/js/app", "*.js", true) | |
appJs.Transforms.Add(jsTransformer); | |
appJs.Orderer = new NullOrderer(); | |
bundles.Add(appJs); | |
// JST's | |
Bundle jsTemplates = new ScriptBundle("~/bundles/templates").IncludeDirectory("~/Content/js/app/views/jst", "*.handlebars", true); | |
bundles.Add(jsTemplates); | |
... | |
} | |
} | |
} |
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 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; | |
namespace MyApp.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.GetVirtualFileSystemWrapper(), | |
BundleTransformerContext.Current.GetCoreConfiguration().AssetHandler, | |
BundleTransformerContext.Current.GetApplicationInfo()) | |
{ } | |
/// <summary> | |
/// Constructs instance of Handlebars asset handler | |
/// </summary> | |
/// <param name="cache">Server cache</param> | |
/// <param name="virtualFileSystemWrapper">Virtual 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, IVirtualFileSystemWrapper virtualFileSystemWrapper, | |
AssetHandlerSettings assetHandlerConfig, IHttpApplicationInfo applicationInfo) | |
: base(cache, virtualFileSystemWrapper, assetHandlerConfig, applicationInfo) | |
{ } | |
/// <summary> | |
/// Translates code of asset written on Handlebars to JS-code | |
/// </summary> | |
/// <param name="asset">Asset with code written on CoffeeScript</param> | |
/// <returns>Asset with translated code</returns> | |
protected override IAsset ProcessAsset(IAsset asset) | |
{ | |
ITranslator handlebarsTranslator = BundleTransformerContext.Current.GetJsTranslatorInstance("HandlebarsTranslator"); | |
handlebarsTranslator.IsDebugMode = _applicationInfo.IsDebugMode; | |
return handlebarsTranslator.Translate(asset); | |
} | |
} | |
public class HandlebarsTranslator : TranslatorWithNativeMinificationBase | |
{ | |
private readonly MsieJsEngine _jsEngine = new MsieJsEngine(); | |
/// <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 CoffeeScript to JS-code | |
/// </summary> | |
/// <param name="asset">Asset with code written on CoffeeScript</param> | |
/// <returns>Asset with translated code</returns> | |
public override IAsset Translate(IAsset asset) | |
{ | |
if (asset == null) { | |
throw new ArgumentException("Value cannot be empty.", "asset"); | |
} | |
using (var handlebarsCompiler = new HandlebarsCompiler()) { | |
InnerTranslate(asset, handlebarsCompiler); | |
} | |
return asset; | |
} | |
/// <summary> | |
/// Translates code of assets written on CoffeeScript to JS-code | |
/// </summary> | |
/// <param name="assets">Set of assets with code written on CoffeeScript</param> | |
/// <returns>Set of assets with translated code</returns> | |
public override IList<IAsset> Translate(IList<IAsset> assets) | |
{ | |
if (assets == null) { | |
throw new ArgumentException("Value cannot be empty.", "assets"); | |
} | |
if (assets.Count == 0) { | |
return assets; | |
} | |
using (var handlebarsCompiler = new HandlebarsCompiler()) { | |
foreach (var asset in assets) { | |
InnerTranslate(asset, handlebarsCompiler); | |
} | |
} | |
return assets; | |
} | |
private static void InnerTranslate(IAsset asset, HandlebarsCompiler handlebarsCompiler) | |
{ | |
string newContent; | |
try | |
{ | |
newContent = handlebarsCompiler.Compile(asset.Content, asset.VirtualPath); | |
} | |
catch (Exception e) | |
{ | |
// throw new AssetTranslationException( | |
// string.Format(CoreStrings.Translators_TranslationFailed, | |
// INPUT_CODE_TYPE, OUTPUT_CODE_TYPE, assetVirtualPath, e.Message)); | |
throw e; | |
} | |
asset.Content = newContent; | |
} | |
} | |
internal sealed class HandlebarsCompiler : IDisposable | |
{ | |
/// <summary> | |
/// Name of resource, which contains a CoffeeScript-library | |
/// </summary> | |
const string HandlebarsLibraryVirtualPath = "~/Content/js/lib/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}\"] = {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 CoffeeScript-compiler | |
/// </summary> | |
public HandlebarsCompiler() | |
{ | |
_jsSerializer = new JavaScriptSerializer(); | |
} | |
/// <summary> | |
/// Destructs instance of CoffeeScript-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" CoffeeScript-code to JS-code | |
/// </summary> | |
/// <param name="content">Text content written on CoffeeScript</param> | |
/// <param name="assetPath"></param> | |
/// <param name="handlebarsNamespace"></param> | |
/// <returns>Translated CoffeeScript-code</returns> | |
public string Compile(string content, string assetPath, string handlebarsNamespace = "JST") | |
{ | |
string newContent = null; | |
var options = new | |
{ | |
JSTNamespace = handlebarsNamespace, | |
PathReplace = "/Content/js/app/views/jst/" | |
}; | |
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(); | |
} | |
} | |
} | |
} | |
} |
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
<system.web> | |
... | |
<httpHandlers> | |
<add path="*.less" verb="GET" type="BundleTransformer.Less.HttpHandlers.LessAssetHandler, BundleTransformer.Less" /> | |
<add path="*.handlebars" verb="GET" type="MyApp.Infrastructure.Optimization.HandlebarsAssetHandler, MyApp" /> | |
</httpHandlers> | |
... | |
</system.web> | |
<system.webServer> | |
<validation validateIntegratedModeConfiguration="false" /> | |
<handlers> | |
... | |
<add name="LessAssetHandler" path="*.less" verb="GET" type="BundleTransformer.Less.HttpHandlers.LessAssetHandler, BundleTransformer.Less" resourceType="File" preCondition="" /> | |
<add name="HandlebarsAssetHandler" path="*.handlebars" verb="GET" type="MyApp.Infrastructure.Optimization.HandlebarsAssetHandler, MyApp" resourceType="File" preCondition="" /> | |
... | |
</handlers> | |
<staticContent> | |
<remove fileExtension=".svg" /> | |
<remove fileExtension=".eot" /> | |
<remove fileExtension=".woff" /> | |
<remove fileExtension=".hbs" /> | |
<mimeMap fileExtension=".svg" mimeType="image/svg+xml" /> | |
<mimeMap fileExtension=".eot" mimeType="application/vnd.ms-fontobject" /> | |
<mimeMap fileExtension=".woff" mimeType="application/x-woff" /> | |
<mimeMap fileExtension=".hbs" mimeType="text/javascript" /> | |
</staticContent> | |
</system.webServer> | |
... | |
<bundleTransformer xmlns="http://tempuri.org/BundleTransformer.Configuration.xsd"> | |
<core> | |
... | |
<js defaultMinifier="UglifyJsMinifier" usePreMinifiedFiles="false"> | |
... | |
<translators> | |
<add name="NullTranslator" type="BundleTransformer.Core.Translators.NullTranslator, BundleTransformer.Core" enabled="false" /> | |
<add name="HandlebarsTranslator" type="MyApp.Infrastructure.Optimization.HandlebarsTranslator, MyApp" enabled="true" /> | |
</translators> | |
</js> | |
<assetHandler clientCacheDurationInDays="365" enableCompression="true" serverCacheDurationInMinutes="15" useServerCacheSlidingExpiration="false" disableClientCacheInDebugMode="true" disableCompressionInDebugMode="true" /> | |
</core> | |
... | |
</bundleTransformer> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment