Created
January 27, 2013 03:30
-
-
Save jpurcell001/4646105 to your computer and use it in GitHub Desktop.
tpl plugin for requirejs, for loading Underscore templates asynchronously
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
// RequireJS plugin that loads template files and converts them into | |
// Underscore-compiled template functions. Inspired by the tpl! plugin at | |
// https://github.com/abersager/requirejs-tpl. | |
// Usage: | |
// - Specify template files as a resource to load with the `tpl!` prefix e.g. | |
// `tpl!products/productView.html`. If the template has (non-nested) <script> | |
// or <template> tags, then each script tag will be independently parsed as a template, and | |
// the set of templates will be returned by the module in a hash, keyed by each | |
// script's `id` attribute. Otherwise, a single function will be returned by | |
// the module. This allows multiple small templates to be combined into a single | |
// file, if desired. | |
// - Each template function has additional metadata added to it. At a minimum, each | |
// function will have a `file` property that specifies which resource file the | |
// template was loaded from. If the template function came from an embedded `<script>` | |
// tag, then it will also have an `id` property, and any `data-*` attributes on the tag | |
// will be added as properties; for example, if a script tag has `data-tagname="tr"`, then | |
// the generated function will have a property called `tagname` with value of `tr`. | |
// These attributes can be accessed as properties on the function, and allow you to | |
// consolidate HTML & CSS data into one place. | |
// - When in non-build mode, the generated functions have a try/catch block around | |
// them to catch script execution and print out debugging information in the event of a problem. | |
define(["underscore", "text"], function(_, text) { | |
// Returns a compiled template that is a wrapped version of the | |
// underscore-compiled template function, with metadata | |
// (including the original underscore source) added as object properties | |
// to the function. In 'build' mode, the original source is used and the | |
// try/catch block is ignored. | |
var getTemplate = function(template, metadata) { | |
var compiledTemplate; | |
try { | |
compiledTemplate = _.template(template); | |
} catch (err) { | |
throw new Error("unable to compile template: " + err); | |
} | |
return _.extend( | |
function() { | |
try { | |
return compiledTemplate.apply(this, arguments); | |
} catch ( err ) { | |
DEBUG && console.log("Error executing template (" + JSON.stringify(metadata) + "): " + err); | |
} | |
}, | |
{ | |
source: compiledTemplate.source | |
}, | |
metadata); | |
return compiledTemplate; | |
}; | |
var writeOutFunction = function(templateFunction) { | |
var str = "(function() {\n"; | |
str = str + "var templateFunction = " + templateFunction.source + ";\n" | |
for ( var key in templateFunction ) { | |
if ( templateFunction.hasOwnProperty(key) && key !== "source" ) { | |
str = str + "templateFunction[\"" + key + "\"] = \"" + text.jsEscape(templateFunction[key]) + "\";\n"; | |
} | |
} | |
str = str + "return templateFunction;\n})();\n"; | |
return str; | |
}; | |
var writeOutFunctions = function(templates) { | |
var str = "var templates = {};\n"; | |
_.each(templates, function(value, key) { | |
str = str + "templates[\"" + key + "\"] = " + writeOutFunction(value); | |
}); | |
str = str + "return templates;\n"; | |
return str; | |
}; | |
// Regex's used to extract data from the script tags, if present. | |
var scriptRegex = /<(script|template)\b([^>]*)>([\s\S]*?)<\/\1>/gm; | |
var idRegex = /id=(['"]?)([^\s]+)\1/mi; | |
var dataRegex = /data-([^=]+)=(['"]?)(.+?)\2/gmi; | |
// Cache the compiled modules as they're loaded, for potential | |
// use by the r.js optimizer. | |
var buildMap = []; | |
return { | |
load: function(resourceName, parentRequire, onLoad, config) { | |
// delegate to the `text` plugin to handle loading the specified resource as | |
// a text file. `resource` will then contain the contents of the html file | |
// as a string. | |
text.load(resourceName, parentRequire, function(content) { | |
var resource = {}; | |
var scriptTag, scriptBody, scriptId, match, metadata; | |
while ( match = scriptRegex.exec(content) ) { | |
metadata = { file: resourceName }; | |
scriptTag = match[2]; | |
scriptBody = match[3]; | |
match = idRegex.exec(scriptTag); | |
scriptId = match[2]; | |
if ( ! scriptId ) { | |
DEBUG && console.log("[" + resourceName + "] Script/template tags " + | |
"within templates without an `id` attribute are skipped."); | |
} | |
while ( match = dataRegex.exec(scriptTag) ) { | |
metadata[match[1].toLowerCase()] = match[3]; | |
} | |
metadata["id"] = scriptId; | |
resource[scriptId] = getTemplate(scriptBody.trim(), metadata); | |
}; | |
if ( _.isEmpty(resource) ) { | |
// no script tags present - just send back the compiled template | |
resource = getTemplate(content, { file: resourceName }); | |
} | |
onLoad(buildMap[resourceName] = resource); | |
}, config); | |
}, | |
// Write out the pre-compiled versions when requested by the r.js optimizer, | |
// so that optimized versions don't need to invoke the _.template() | |
// method at run-time. | |
write: function (pluginName, resourceName, write) { | |
if (buildMap.hasOwnProperty(resourceName)) { | |
var resource = buildMap[resourceName]; | |
var content = _.isFunction(resource) ? ( "return " + writeOutFunction(resource) ) : writeOutFunctions(resource); | |
write.asModule(pluginName + "!" + resourceName, | |
"define(function () {\n" + content + "\n});\n"); | |
} | |
} | |
}; | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Following marionettejs/backbone.marionette#426 leads me to here..
Thanks..that is exactly what I need...