Skip to content

Instantly share code, notes, and snippets.

@jpurcell001
Created January 27, 2013 03:30
Show Gist options
  • Save jpurcell001/4646105 to your computer and use it in GitHub Desktop.
Save jpurcell001/4646105 to your computer and use it in GitHub Desktop.
tpl plugin for requirejs, for loading Underscore templates asynchronously
// 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");
}
}
};
});
@cardinal27513
Copy link

Following marionettejs/backbone.marionette#426 leads me to here..

Thanks..that is exactly what I need...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment