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