(function () { /* Imports */ var Meteor = Package.meteor.Meteor; var HTML = Package.htmljs.HTML; var HTMLTools = Package['html-tools'].HTMLTools; var BlazeTools = Package['blaze-tools'].BlazeTools; var _ = Package.underscore._; /* Package-scope variables */ var SpacebarsCompiler, TemplateTag; (function () { //////////////////////////////////////////////////////////////////////////////////////////// // // // packages/spacebars-compiler/templatetag.js // // // //////////////////////////////////////////////////////////////////////////////////////////// // SpacebarsCompiler = {}; // 1 // 2 // A TemplateTag is the result of parsing a single `{{...}}` tag. // 3 // // 4 // The `.type` of a TemplateTag is one of: // 5 // // 6 // - `"DOUBLE"` - `{{foo}}` // 7 // - `"TRIPLE"` - `{{{foo}}}` // 8 // - `"COMMENT"` - `{{! foo}}` // 9 // - `"BLOCKCOMMENT" - `{{!-- foo--}}` // 10 // - `"INCLUSION"` - `{{> foo}}` // 11 // - `"BLOCKOPEN"` - `{{#foo}}` // 12 // - `"BLOCKCLOSE"` - `{{/foo}}` // 13 // - `"ELSE"` - `{{else}}` // 14 // - `"ESCAPE"` - `{{|`, `{{{|`, `{{{{|` and so on // 15 // // 16 // Besides `type`, the mandatory properties of a TemplateTag are: // 17 // // 18 // - `path` - An array of one or more strings. The path of `{{foo.bar}}` // 19 // is `["foo", "bar"]`. Applies to DOUBLE, TRIPLE, INCLUSION, BLOCKOPEN, // 20 // and BLOCKCLOSE. // 21 // // 22 // - `args` - An array of zero or more argument specs. An argument spec // 23 // is a two or three element array, consisting of a type, value, and // 24 // optional keyword name. For example, the `args` of `{{foo "bar" x=3}}` // 25 // are `[["STRING", "bar"], ["NUMBER", 3, "x"]]`. Applies to DOUBLE, // 26 // TRIPLE, INCLUSION, and BLOCKOPEN. // 27 // // 28 // - `value` - A string of the comment's text. Applies to COMMENT and // 29 // BLOCKCOMMENT. // 30 // // 31 // These additional are typically set during parsing: // 32 // // 33 // - `position` - The HTMLTools.TEMPLATE_TAG_POSITION specifying at what sort // 34 // of site the TemplateTag was encountered (e.g. at element level or as // 35 // part of an attribute value). Its absence implies // 36 // TEMPLATE_TAG_POSITION.ELEMENT. // 37 // // 38 // - `content` and `elseContent` - When a BLOCKOPEN tag's contents are // 39 // parsed, they are put here. `elseContent` will only be present if // 40 // an `{{else}}` was found. // 41 // 42 var TEMPLATE_TAG_POSITION = HTMLTools.TEMPLATE_TAG_POSITION; // 43 // 44 TemplateTag = SpacebarsCompiler.TemplateTag = function () { // 45 HTMLTools.TemplateTag.apply(this, arguments); // 46 }; // 47 TemplateTag.prototype = new HTMLTools.TemplateTag; // 48 TemplateTag.prototype.constructorName = 'SpacebarsCompiler.TemplateTag'; // 49 // 50 var makeStacheTagStartRegex = function (r) { // 51 return new RegExp(r.source + /(?![{>!#/])/.source, // 52 r.ignoreCase ? 'i' : ''); // 53 }; // 54 // 55 // "starts" regexes are used to see what type of template // 56 // tag the parser is looking at. They must match a non-empty // 57 // result, but not the interesting part of the tag. // 58 var starts = { // 59 ESCAPE: /^\{\{(?=\{*\|)/, // 60 ELSE: makeStacheTagStartRegex(/^\{\{\s*else(?=[\s}])/i), // 61 DOUBLE: makeStacheTagStartRegex(/^\{\{\s*(?!\s)/), // 62 TRIPLE: makeStacheTagStartRegex(/^\{\{\{\s*(?!\s)/), // 63 BLOCKCOMMENT: makeStacheTagStartRegex(/^\{\{\s*!--/), // 64 COMMENT: makeStacheTagStartRegex(/^\{\{\s*!/), // 65 INCLUSION: makeStacheTagStartRegex(/^\{\{\s*>\s*(?!\s)/), // 66 BLOCKOPEN: makeStacheTagStartRegex(/^\{\{\s*#\s*(?!\s)/), // 67 BLOCKCLOSE: makeStacheTagStartRegex(/^\{\{\s*\/\s*(?!\s)/) // 68 }; // 69 // 70 var ends = { // 71 DOUBLE: /^\s*\}\}/, // 72 TRIPLE: /^\s*\}\}\}/ // 73 }; // 74 // 75 // Parse a tag from the provided scanner or string. If the input // 76 // doesn't start with `{{`, returns null. Otherwise, either succeeds // 77 // and returns a SpacebarsCompiler.TemplateTag, or throws an error (using // 78 // `scanner.fatal` if a scanner is provided). // 79 TemplateTag.parse = function (scannerOrString) { // 80 var scanner = scannerOrString; // 81 if (typeof scanner === 'string') // 82 scanner = new HTMLTools.Scanner(scannerOrString); // 83 // 84 if (! (scanner.peek() === '{' && // 85 (scanner.rest()).slice(0, 2) === '{{')) // 86 return null; // 87 // 88 var run = function (regex) { // 89 // regex is assumed to start with `^` // 90 var result = regex.exec(scanner.rest()); // 91 if (! result) // 92 return null; // 93 var ret = result[0]; // 94 scanner.pos += ret.length; // 95 return ret; // 96 }; // 97 // 98 var advance = function (amount) { // 99 scanner.pos += amount; // 100 }; // 101 // 102 var scanIdentifier = function (isFirstInPath) { // 103 var id = BlazeTools.parseIdentifierName(scanner); // 104 if (! id) // 105 expected('IDENTIFIER'); // 106 if (isFirstInPath && // 107 (id === 'null' || id === 'true' || id === 'false')) // 108 scanner.fatal("Can't use null, true, or false, as an identifier at start of path"); // 109 // 110 return id; // 111 }; // 112 // 113 var scanPath = function () { // 114 var segments = []; // 115 // 116 // handle initial `.`, `..`, `./`, `../`, `../..`, `../../`, etc // 117 var dots; // 118 if ((dots = run(/^[\.\/]+/))) { // 119 var ancestorStr = '.'; // eg `../../..` maps to `....` // 120 var endsWithSlash = /\/$/.test(dots); // 121 // 122 if (endsWithSlash) // 123 dots = dots.slice(0, -1); // 124 // 125 _.each(dots.split('/'), function(dotClause, index) { // 126 if (index === 0) { // 127 if (dotClause !== '.' && dotClause !== '..') // 128 expected("`.`, `..`, `./` or `../`"); // 129 } else { // 130 if (dotClause !== '..') // 131 expected("`..` or `../`"); // 132 } // 133 // 134 if (dotClause === '..') // 135 ancestorStr += '.'; // 136 }); // 137 // 138 segments.push(ancestorStr); // 139 // 140 if (!endsWithSlash) // 141 return segments; // 142 } // 143 // 144 while (true) { // 145 // scan a path segment // 146 // 147 if (run(/^\[/)) { // 148 var seg = run(/^[\s\S]*?\]/); // 149 if (! seg) // 150 error("Unterminated path segment"); // 151 seg = seg.slice(0, -1); // 152 if (! seg && ! segments.length) // 153 error("Path can't start with empty string"); // 154 segments.push(seg); // 155 } else { // 156 var id = scanIdentifier(! segments.length); // 157 if (id === 'this') { // 158 if (! segments.length) { // 159 // initial `this` // 160 segments.push('.'); // 161 } else { // 162 error("Can only use `this` at the beginning of a path.\nInstead of `foo.this` or `../this`, just write `foo` or `..`."); } // 164 } else { // 165 segments.push(id); // 166 } // 167 } // 168 // 169 var sep = run(/^[\.\/]/); // 170 if (! sep) // 171 break; // 172 } // 173 // 174 return segments; // 175 }; // 176 // 177 // scan the keyword portion of a keyword argument // 178 // (the "foo" portion in "foo=bar"). // 179 // Result is either the keyword matched, or null // 180 // if we're not at a keyword argument position. // 181 var scanArgKeyword = function () { // 182 var match = /^([^\{\}\(\)\>#=\s"'\[\]]+)\s*=\s*/.exec(scanner.rest()); // 183 if (match) { // 184 scanner.pos += match[0].length; // 185 return match[1]; // 186 } else { // 187 return null; // 188 } // 189 }; // 190 // 191 // scan an argument; succeeds or errors. // 192 // Result is an array of two or three items: // 193 // type , value, and (indicating a keyword argument) // 194 // keyword name. // 195 var scanArg = function () { // 196 var keyword = scanArgKeyword(); // null if not parsing a kwarg // 197 var value = scanArgValue(); // 198 return keyword ? value.concat(keyword) : value; // 199 }; // 200 // 201 // scan an argument value (for keyword or positional arguments); // 202 // succeeds or errors. Result is an array of type, value. // 203 var scanArgValue = function () { // 204 var startPos = scanner.pos; // 205 var result; // 206 if ((result = BlazeTools.parseNumber(scanner))) { // 207 return ['NUMBER', result.value]; // 208 } else if ((result = BlazeTools.parseStringLiteral(scanner))) { // 209 return ['STRING', result.value]; // 210 } else if (/^[\.\[]/.test(scanner.peek())) { // 211 return ['PATH', scanPath()]; // 212 } else if ((result = BlazeTools.parseIdentifierName(scanner))) { // 213 var id = result; // 214 if (id === 'null') { // 215 return ['NULL', null]; // 216 } else if (id === 'true' || id === 'false') { // 217 return ['BOOLEAN', id === 'true']; // 218 } else { // 219 scanner.pos = startPos; // unconsume `id` // 220 return ['PATH', scanPath()]; // 221 } // 222 } else { // 223 expected('identifier, number, string, boolean, or null'); // 224 } // 225 }; // 226 // 227 var type; // 228 // 229 var error = function (msg) { // 230 scanner.fatal(msg); // 231 }; // 232 // 233 var expected = function (what) { // 234 error('Expected ' + what); // 235 }; // 236 // 237 // must do ESCAPE first, immediately followed by ELSE // 238 // order of others doesn't matter // 239 if (run(starts.ESCAPE)) type = 'ESCAPE'; // 240 else if (run(starts.ELSE)) type = 'ELSE'; // 241 else if (run(starts.DOUBLE)) type = 'DOUBLE'; // 242 else if (run(starts.TRIPLE)) type = 'TRIPLE'; // 243 else if (run(starts.BLOCKCOMMENT)) type = 'BLOCKCOMMENT'; // 244 else if (run(starts.COMMENT)) type = 'COMMENT'; // 245 else if (run(starts.INCLUSION)) type = 'INCLUSION'; // 246 else if (run(starts.BLOCKOPEN)) type = 'BLOCKOPEN'; // 247 else if (run(starts.BLOCKCLOSE)) type = 'BLOCKCLOSE'; // 248 else // 249 error('Unknown stache tag'); // 250 // 251 var tag = new TemplateTag; // 252 tag.type = type; // 253 // 254 if (type === 'BLOCKCOMMENT') { // 255 var result = run(/^[\s\S]*?--\s*?\}\}/); // 256 if (! result) // 257 error("Unclosed block comment"); // 258 tag.value = result.slice(0, result.lastIndexOf('--')); // 259 } else if (type === 'COMMENT') { // 260 var result = run(/^[\s\S]*?\}\}/); // 261 if (! result) // 262 error("Unclosed comment"); // 263 tag.value = result.slice(0, -2); // 264 } else if (type === 'BLOCKCLOSE') { // 265 tag.path = scanPath(); // 266 if (! run(ends.DOUBLE)) // 267 expected('`}}`'); // 268 } else if (type === 'ELSE') { // 269 if (! run(ends.DOUBLE)) // 270 expected('`}}`'); // 271 } else if (type === 'ESCAPE') { // 272 var result = run(/^\{*\|/); // 273 tag.value = '{{' + result.slice(0, -1); // 274 } else { // 275 // DOUBLE, TRIPLE, BLOCKOPEN, INCLUSION // 276 tag.path = scanPath(); // 277 tag.args = []; // 278 var foundKwArg = false; // 279 while (true) { // 280 run(/^\s*/); // 281 if (type === 'TRIPLE') { // 282 if (run(ends.TRIPLE)) // 283 break; // 284 else if (scanner.peek() === '}') // 285 expected('`}}}`'); // 286 } else { // 287 if (run(ends.DOUBLE)) // 288 break; // 289 else if (scanner.peek() === '}') // 290 expected('`}}`'); // 291 } // 292 var newArg = scanArg(); // 293 if (newArg.length === 3) { // 294 foundKwArg = true; // 295 } else { // 296 if (foundKwArg) // 297 error("Can't have a non-keyword argument after a keyword argument"); // 298 } // 299 tag.args.push(newArg); // 300 // 301 if (run(/^(?=[\s}])/) !== '') // 302 expected('space'); // 303 } // 304 } // 305 // 306 return tag; // 307 }; // 308 // 309 // Returns a SpacebarsCompiler.TemplateTag parsed from `scanner`, leaving scanner // 310 // at its original position. // 311 // // 312 // An error will still be thrown if there is not a valid template tag at // 313 // the current position. // 314 TemplateTag.peek = function (scanner) { // 315 var startPos = scanner.pos; // 316 var result = TemplateTag.parse(scanner); // 317 scanner.pos = startPos; // 318 return result; // 319 }; // 320 // 321 // Like `TemplateTag.parse`, but in the case of blocks, parse the complete // 322 // `{{#foo}}...{{/foo}}` with `content` and possible `elseContent`, rather // 323 // than just the BLOCKOPEN tag. // 324 // // 325 // In addition: // 326 // // 327 // - Throws an error if `{{else}}` or `{{/foo}}` tag is encountered. // 328 // // 329 // - Returns `null` for a COMMENT. (This case is distinguishable from // 330 // parsing no tag by the fact that the scanner is advanced.) // 331 // // 332 // - Takes an HTMLTools.TEMPLATE_TAG_POSITION `position` and sets it as the // 333 // TemplateTag's `.position` property. // 334 // // 335 // - Validates the tag's well-formedness and legality at in its position. // 336 TemplateTag.parseCompleteTag = function (scannerOrString, position) { // 337 var scanner = scannerOrString; // 338 if (typeof scanner === 'string') // 339 scanner = new HTMLTools.Scanner(scannerOrString); // 340 // 341 var startPos = scanner.pos; // for error messages // 342 var result = TemplateTag.parse(scannerOrString); // 343 if (! result) // 344 return result; // 345 // 346 if (result.type === 'BLOCKCOMMENT') // 347 return null; // 348 // 349 if (result.type === 'COMMENT') // 350 return null; // 351 // 352 if (result.type === 'ELSE') // 353 scanner.fatal("Unexpected {{else}}"); // 354 // 355 if (result.type === 'BLOCKCLOSE') // 356 scanner.fatal("Unexpected closing template tag"); // 357 // 358 position = (position || TEMPLATE_TAG_POSITION.ELEMENT); // 359 if (position !== TEMPLATE_TAG_POSITION.ELEMENT) // 360 result.position = position; // 361 // 362 if (result.type === 'BLOCKOPEN') { // 363 // parse block contents // 364 // 365 // Construct a string version of `.path` for comparing start and // 366 // end tags. For example, `foo/[0]` was parsed into `["foo", "0"]` // 367 // and now becomes `foo,0`. This form may also show up in error // 368 // messages. // 369 var blockName = result.path.join(','); // 370 // 371 var textMode = null; // 372 if (blockName === 'markdown' || // 373 position === TEMPLATE_TAG_POSITION.IN_RAWTEXT) { // 374 textMode = HTML.TEXTMODE.STRING; // 375 } else if (position === TEMPLATE_TAG_POSITION.IN_RCDATA || // 376 position === TEMPLATE_TAG_POSITION.IN_ATTRIBUTE) { // 377 textMode = HTML.TEXTMODE.RCDATA; // 378 } // 379 var parserOptions = { // 380 getTemplateTag: TemplateTag.parseCompleteTag, // 381 shouldStop: isAtBlockCloseOrElse, // 382 textMode: textMode // 383 }; // 384 result.content = HTMLTools.parseFragment(scanner, parserOptions); // 385 // 386 if (scanner.rest().slice(0, 2) !== '{{') // 387 scanner.fatal("Expected {{else}} or block close for " + blockName); // 388 // 389 var lastPos = scanner.pos; // save for error messages // 390 var tmplTag = TemplateTag.parse(scanner); // {{else}} or {{/foo}} // 391 // 392 if (tmplTag.type === 'ELSE') { // 393 // parse {{else}} and content up to close tag // 394 result.elseContent = HTMLTools.parseFragment(scanner, parserOptions); // 395 // 396 if (scanner.rest().slice(0, 2) !== '{{') // 397 scanner.fatal("Expected block close for " + blockName); // 398 // 399 lastPos = scanner.pos; // 400 tmplTag = TemplateTag.parse(scanner); // 401 } // 402 // 403 if (tmplTag.type === 'BLOCKCLOSE') { // 404 var blockName2 = tmplTag.path.join(','); // 405 if (blockName !== blockName2) { // 406 scanner.pos = lastPos; // 407 scanner.fatal('Expected tag to close ' + blockName + ', found ' + // 408 blockName2); // 409 } // 410 } else { // 411 scanner.pos = lastPos; // 412 scanner.fatal('Expected tag to close ' + blockName + ', found ' + // 413 tmplTag.type); // 414 } // 415 } // 416 // 417 var finalPos = scanner.pos; // 418 scanner.pos = startPos; // 419 validateTag(result, scanner); // 420 scanner.pos = finalPos; // 421 // 422 return result; // 423 }; // 424 // 425 var isAtBlockCloseOrElse = function (scanner) { // 426 // Detect `{{else}}` or `{{/foo}}`. // 427 // // 428 // We do as much work ourselves before deferring to `TemplateTag.peek`, // 429 // for efficiency (we're called for every input token) and to be // 430 // less obtrusive, because `TemplateTag.peek` will throw an error if it // 431 // sees `{{` followed by a malformed tag. // 432 var rest, type; // 433 return (scanner.peek() === '{' && // 434 (rest = scanner.rest()).slice(0, 2) === '{{' && // 435 /^\{\{\s*(\/|else\b)/.test(rest) && // 436 (type = TemplateTag.peek(scanner).type) && // 437 (type === 'BLOCKCLOSE' || type === 'ELSE')); // 438 }; // 439 // 440 // Validate that `templateTag` is correctly formed and legal for its // 441 // HTML position. Use `scanner` to report errors. On success, does // 442 // nothing. // 443 var validateTag = function (ttag, scanner) { // 444 // 445 if (ttag.type === 'INCLUSION' || ttag.type === 'BLOCKOPEN') { // 446 var args = ttag.args; // 447 if (args.length > 1 && args[0].length === 2 && args[0][0] !== 'PATH') { // 448 // we have a positional argument that is not a PATH followed by // 449 // other arguments // 450 scanner.fatal("First argument must be a function, to be called on the rest of the arguments; found " + args[0][0]); } // 452 } // 453 // 454 var position = ttag.position || TEMPLATE_TAG_POSITION.ELEMENT; // 455 if (position === TEMPLATE_TAG_POSITION.IN_ATTRIBUTE) { // 456 if (ttag.type === 'DOUBLE' || ttag.type === 'ESCAPE') { // 457 return; // 458 } else if (ttag.type === 'BLOCKOPEN') { // 459 var path = ttag.path; // 460 var path0 = path[0]; // 461 if (! (path.length === 1 && (path0 === 'if' || // 462 path0 === 'unless' || // 463 path0 === 'with' || // 464 path0 === 'each'))) { // 465 scanner.fatal("Custom block helpers are not allowed in an HTML attribute, only built-in ones like #each and #if"); } // 467 } else { // 468 scanner.fatal(ttag.type + " template tag is not allowed in an HTML attribute"); // 469 } // 470 } else if (position === TEMPLATE_TAG_POSITION.IN_START_TAG) { // 471 if (! (ttag.type === 'DOUBLE')) { // 472 scanner.fatal("Reactive HTML attributes must either have a constant name or consist of a single {{helper}} providing a dictionary of names and values. A template tag of type " + ttag.type + " is not allowed here."); } // 474 if (scanner.peek() === '=') { // 475 scanner.fatal("Template tags are not allowed in attribute names, only in attribute values or in the form of a single {{helper}} that evaluates to a dictionary of name=value pairs."); } // 477 } // 478 // 479 }; // 480 // 481 //////////////////////////////////////////////////////////////////////////////////////////// }).call(this); (function () { //////////////////////////////////////////////////////////////////////////////////////////// // // // packages/spacebars-compiler/optimizer.js // // // //////////////////////////////////////////////////////////////////////////////////////////// // // Optimize parts of an HTMLjs tree into raw HTML strings when they don't // 1 // contain template tags. // 2 // 3 var constant = function (value) { // 4 return function () { return value; }; // 5 }; // 6 // 7 var OPTIMIZABLE = { // 8 NONE: 0, // 9 PARTS: 1, // 10 FULL: 2 // 11 }; // 12 // 13 // We can only turn content into an HTML string if it contains no template // 14 // tags and no "tricky" HTML tags. If we can optimize the entire content // 15 // into a string, we return OPTIMIZABLE.FULL. If the we are given an // 16 // unoptimizable node, we return OPTIMIZABLE.NONE. If we are given a tree // 17 // that contains an unoptimizable node somewhere, we return OPTIMIZABLE.PARTS. // 18 // // 19 // For example, we always create SVG elements programmatically, since SVG // 20 // doesn't have innerHTML. If we are given an SVG element, we return NONE. // 21 // However, if we are given a big tree that contains SVG somewhere, we // 22 // return PARTS so that the optimizer can descend into the tree and optimize // 23 // other parts of it. // 24 var CanOptimizeVisitor = HTML.Visitor.extend(); // 25 CanOptimizeVisitor.def({ // 26 visitNull: constant(OPTIMIZABLE.FULL), // 27 visitPrimitive: constant(OPTIMIZABLE.FULL), // 28 visitComment: constant(OPTIMIZABLE.FULL), // 29 visitCharRef: constant(OPTIMIZABLE.FULL), // 30 visitRaw: constant(OPTIMIZABLE.FULL), // 31 visitObject: constant(OPTIMIZABLE.NONE), // 32 visitFunction: constant(OPTIMIZABLE.NONE), // 33 visitArray: function (x) { // 34 for (var i = 0; i < x.length; i++) // 35 if (this.visit(x[i]) !== OPTIMIZABLE.FULL) // 36 return OPTIMIZABLE.PARTS; // 37 return OPTIMIZABLE.FULL; // 38 }, // 39 visitTag: function (tag) { // 40 var tagName = tag.tagName; // 41 if (tagName === 'textarea') { // 42 // optimizing into a TEXTAREA's RCDATA would require being a little // 43 // more clever. // 44 return OPTIMIZABLE.NONE; // 45 } else if (! (HTML.isKnownElement(tagName) && // 46 ! HTML.isKnownSVGElement(tagName))) { // 47 // foreign elements like SVG can't be stringified for innerHTML. // 48 return OPTIMIZABLE.NONE; // 49 } else if (tagName === 'table') { // 50 // Avoid ever producing HTML containing `...`, because the // 51 // browser will insert a TBODY. If we just `createElement("table")` and // 52 // `createElement("tr")`, on the other hand, no TBODY is necessary // 53 // (assuming IE 8+). // 54 return OPTIMIZABLE.NONE; // 55 } // 56 // 57 var children = tag.children; // 58 for (var i = 0; i < children.length; i++) // 59 if (this.visit(children[i]) !== OPTIMIZABLE.FULL) // 60 return OPTIMIZABLE.PARTS; // 61 // 62 if (this.visitAttributes(tag.attrs) !== OPTIMIZABLE.FULL) // 63 return OPTIMIZABLE.PARTS; // 64 // 65 return OPTIMIZABLE.FULL; // 66 }, // 67 visitAttributes: function (attrs) { // 68 if (attrs) { // 69 var isArray = HTML.isArray(attrs); // 70 for (var i = 0; i < (isArray ? attrs.length : 1); i++) { // 71 var a = (isArray ? attrs[i] : attrs); // 72 if ((typeof a !== 'object') || (a instanceof HTMLTools.TemplateTag)) // 73 return OPTIMIZABLE.PARTS; // 74 for (var k in a) // 75 if (this.visit(a[k]) !== OPTIMIZABLE.FULL) // 76 return OPTIMIZABLE.PARTS; // 77 } // 78 } // 79 return OPTIMIZABLE.FULL; // 80 } // 81 }); // 82 // 83 var getOptimizability = function (content) { // 84 return (new CanOptimizeVisitor).visit(content); // 85 }; // 86 // 87 var toRaw = function (x) { // 88 return HTML.Raw(HTML.toHTML(x)); // 89 }; // 90 // 91 var TreeTransformer = HTML.TransformingVisitor.extend(); // 92 TreeTransformer.def({ // 93 visitAttributes: function (attrs/*, ...*/) { // 94 // pass template tags through by default // 95 if (attrs instanceof HTMLTools.TemplateTag) // 96 return attrs; // 97 // 98 return HTML.TransformingVisitor.prototype.visitAttributes.apply( // 99 this, arguments); // 100 } // 101 }); // 102 // 103 // Replace parts of the HTMLjs tree that have no template tags (or // 104 // tricky HTML tags) with HTML.Raw objects containing raw HTML. // 105 var OptimizingVisitor = TreeTransformer.extend(); // 106 OptimizingVisitor.def({ // 107 visitNull: toRaw, // 108 visitPrimitive: toRaw, // 109 visitComment: toRaw, // 110 visitCharRef: toRaw, // 111 visitArray: function (array) { // 112 var optimizability = getOptimizability(array); // 113 if (optimizability === OPTIMIZABLE.FULL) { // 114 return toRaw(array); // 115 } else if (optimizability === OPTIMIZABLE.PARTS) { // 116 return TreeTransformer.prototype.visitArray.call(this, array); // 117 } else { // 118 return array; // 119 } // 120 }, // 121 visitTag: function (tag) { // 122 var optimizability = getOptimizability(tag); // 123 if (optimizability === OPTIMIZABLE.FULL) { // 124 return toRaw(tag); // 125 } else if (optimizability === OPTIMIZABLE.PARTS) { // 126 return TreeTransformer.prototype.visitTag.call(this, tag); // 127 } else { // 128 return tag; // 129 } // 130 }, // 131 visitChildren: function (children) { // 132 // don't optimize the children array into a Raw object! // 133 return TreeTransformer.prototype.visitArray.call(this, children); // 134 }, // 135 visitAttributes: function (attrs) { // 136 return attrs; // 137 } // 138 }); // 139 // 140 // Combine consecutive HTML.Raws. Remove empty ones. // 141 var RawCompactingVisitor = TreeTransformer.extend(); // 142 RawCompactingVisitor.def({ // 143 visitArray: function (array) { // 144 var result = []; // 145 for (var i = 0; i < array.length; i++) { // 146 var item = array[i]; // 147 if ((item instanceof HTML.Raw) && // 148 ((! item.value) || // 149 (result.length && // 150 (result[result.length - 1] instanceof HTML.Raw)))) { // 151 // two cases: item is an empty Raw, or previous item is // 152 // a Raw as well. In the latter case, replace the previous // 153 // Raw with a longer one that includes the new Raw. // 154 if (item.value) { // 155 result[result.length - 1] = HTML.Raw( // 156 result[result.length - 1].value + item.value); // 157 } // 158 } else { // 159 result.push(item); // 160 } // 161 } // 162 return result; // 163 } // 164 }); // 165 // 166 // Replace pointless Raws like `HTMl.Raw('foo')` that contain no special // 167 // characters with simple strings. // 168 var RawReplacingVisitor = TreeTransformer.extend(); // 169 RawReplacingVisitor.def({ // 170 visitRaw: function (raw) { // 171 var html = raw.value; // 172 if (html.indexOf('&') < 0 && html.indexOf('<') < 0) { // 173 return html; // 174 } else { // 175 return raw; // 176 } // 177 } // 178 }); // 179 // 180 SpacebarsCompiler.optimize = function (tree) { // 181 tree = (new OptimizingVisitor).visit(tree); // 182 tree = (new RawCompactingVisitor).visit(tree); // 183 tree = (new RawReplacingVisitor).visit(tree); // 184 return tree; // 185 }; // 186 // 187 //////////////////////////////////////////////////////////////////////////////////////////// }).call(this); (function () { //////////////////////////////////////////////////////////////////////////////////////////// // // // packages/spacebars-compiler/codegen.js // // // //////////////////////////////////////////////////////////////////////////////////////////// // // ============================================================ // 1 // Code-generation of template tags // 2 // 3 // The `CodeGen` class currently has no instance state, but in theory // 4 // it could be useful to track per-function state, like whether we // 5 // need to emit `var self = this` or not. // 6 var CodeGen = SpacebarsCompiler.CodeGen = function () {}; // 7 // 8 var builtInBlockHelpers = SpacebarsCompiler._builtInBlockHelpers = { // 9 'if': 'Blaze.If', // 10 'unless': 'Blaze.Unless', // 11 'with': 'Spacebars.With', // 12 'each': 'Blaze.Each' // 13 }; // 14 // 15 // 16 // Mapping of "macros" which, when preceded by `Template.`, expand // 17 // to special code rather than following the lookup rules for dotted // 18 // symbols. // 19 var builtInTemplateMacros = { // 20 // `view` is a local variable defined in the generated render // 21 // function for the template in which `Template.contentBlock` or // 22 // `Template.elseBlock` is invoked. // 23 'contentBlock': 'view.templateContentBlock', // 24 'elseBlock': 'view.templateElseBlock', // 25 // 26 // Confusingly, this makes `{{> Template.dynamic}}` an alias // 27 // for `{{> __dynamic}}`, where "__dynamic" is the template that // 28 // implements the dynamic template feature. // 29 'dynamic': 'Template.__dynamic', // 30 // 31 'subscriptionsReady': 'view.templateInstance().subscriptionsReady()' // 32 }; // 33 // 34 // A "reserved name" can't be used as a