mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
187 lines
6.0 KiB
JavaScript
187 lines
6.0 KiB
JavaScript
// Optimize parts of an HTMLjs tree into raw HTML strings when they don't
|
|
// contain template tags.
|
|
|
|
var constant = function (value) {
|
|
return function () { return value; };
|
|
};
|
|
|
|
var OPTIMIZABLE = {
|
|
NONE: 0,
|
|
PARTS: 1,
|
|
FULL: 2
|
|
};
|
|
|
|
// We can only turn content into an HTML string if it contains no template
|
|
// tags and no "tricky" HTML tags. If we can optimize the entire content
|
|
// into a string, we return OPTIMIZABLE.FULL. If the we are given an
|
|
// unoptimizable node, we return OPTIMIZABLE.NONE. If we are given a tree
|
|
// that contains an unoptimizable node somewhere, we return OPTIMIZABLE.PARTS.
|
|
//
|
|
// For example, we always create SVG elements programmatically, since SVG
|
|
// doesn't have innerHTML. If we are given an SVG element, we return NONE.
|
|
// However, if we are given a big tree that contains SVG somewhere, we
|
|
// return PARTS so that the optimizer can descend into the tree and optimize
|
|
// other parts of it.
|
|
var CanOptimizeVisitor = HTML.Visitor.extend();
|
|
CanOptimizeVisitor.def({
|
|
visitNull: constant(OPTIMIZABLE.FULL),
|
|
visitPrimitive: constant(OPTIMIZABLE.FULL),
|
|
visitComment: constant(OPTIMIZABLE.FULL),
|
|
visitCharRef: constant(OPTIMIZABLE.FULL),
|
|
visitRaw: constant(OPTIMIZABLE.FULL),
|
|
visitObject: constant(OPTIMIZABLE.NONE),
|
|
visitFunction: constant(OPTIMIZABLE.NONE),
|
|
visitArray: function (x) {
|
|
for (var i = 0; i < x.length; i++)
|
|
if (this.visit(x[i]) !== OPTIMIZABLE.FULL)
|
|
return OPTIMIZABLE.PARTS;
|
|
return OPTIMIZABLE.FULL;
|
|
},
|
|
visitTag: function (tag) {
|
|
var tagName = tag.tagName;
|
|
if (tagName === 'textarea') {
|
|
// optimizing into a TEXTAREA's RCDATA would require being a little
|
|
// more clever.
|
|
return OPTIMIZABLE.NONE;
|
|
} else if (! (HTML.isKnownElement(tagName) &&
|
|
! HTML.isKnownSVGElement(tagName))) {
|
|
// foreign elements like SVG can't be stringified for innerHTML.
|
|
return OPTIMIZABLE.NONE;
|
|
} else if (tagName === 'table') {
|
|
// Avoid ever producing HTML containing `<table><tr>...`, because the
|
|
// browser will insert a TBODY. If we just `createElement("table")` and
|
|
// `createElement("tr")`, on the other hand, no TBODY is necessary
|
|
// (assuming IE 8+).
|
|
return OPTIMIZABLE.NONE;
|
|
}
|
|
|
|
var children = tag.children;
|
|
for (var i = 0; i < children.length; i++)
|
|
if (this.visit(children[i]) !== OPTIMIZABLE.FULL)
|
|
return OPTIMIZABLE.PARTS;
|
|
|
|
if (this.visitAttributes(tag.attrs) !== OPTIMIZABLE.FULL)
|
|
return OPTIMIZABLE.PARTS;
|
|
|
|
return OPTIMIZABLE.FULL;
|
|
},
|
|
visitAttributes: function (attrs) {
|
|
if (attrs) {
|
|
var isArray = HTML.isArray(attrs);
|
|
for (var i = 0; i < (isArray ? attrs.length : 1); i++) {
|
|
var a = (isArray ? attrs[i] : attrs);
|
|
if ((typeof a !== 'object') || (a instanceof HTMLTools.TemplateTag))
|
|
return OPTIMIZABLE.PARTS;
|
|
for (var k in a)
|
|
if (this.visit(a[k]) !== OPTIMIZABLE.FULL)
|
|
return OPTIMIZABLE.PARTS;
|
|
}
|
|
}
|
|
return OPTIMIZABLE.FULL;
|
|
}
|
|
});
|
|
|
|
var getOptimizability = function (content) {
|
|
return (new CanOptimizeVisitor).visit(content);
|
|
};
|
|
|
|
var toRaw = function (x) {
|
|
return HTML.Raw(HTML.toHTML(x));
|
|
};
|
|
|
|
var TreeTransformer = HTML.TransformingVisitor.extend();
|
|
TreeTransformer.def({
|
|
visitAttributes: function (attrs/*, ...*/) {
|
|
// pass template tags through by default
|
|
if (attrs instanceof HTMLTools.TemplateTag)
|
|
return attrs;
|
|
|
|
return HTML.TransformingVisitor.prototype.visitAttributes.apply(
|
|
this, arguments);
|
|
}
|
|
});
|
|
|
|
// Replace parts of the HTMLjs tree that have no template tags (or
|
|
// tricky HTML tags) with HTML.Raw objects containing raw HTML.
|
|
var OptimizingVisitor = TreeTransformer.extend();
|
|
OptimizingVisitor.def({
|
|
visitNull: toRaw,
|
|
visitPrimitive: toRaw,
|
|
visitComment: toRaw,
|
|
visitCharRef: toRaw,
|
|
visitArray: function (array) {
|
|
var optimizability = getOptimizability(array);
|
|
if (optimizability === OPTIMIZABLE.FULL) {
|
|
return toRaw(array);
|
|
} else if (optimizability === OPTIMIZABLE.PARTS) {
|
|
return TreeTransformer.prototype.visitArray.call(this, array);
|
|
} else {
|
|
return array;
|
|
}
|
|
},
|
|
visitTag: function (tag) {
|
|
var optimizability = getOptimizability(tag);
|
|
if (optimizability === OPTIMIZABLE.FULL) {
|
|
return toRaw(tag);
|
|
} else if (optimizability === OPTIMIZABLE.PARTS) {
|
|
return TreeTransformer.prototype.visitTag.call(this, tag);
|
|
} else {
|
|
return tag;
|
|
}
|
|
},
|
|
visitChildren: function (children) {
|
|
// don't optimize the children array into a Raw object!
|
|
return TreeTransformer.prototype.visitArray.call(this, children);
|
|
},
|
|
visitAttributes: function (attrs) {
|
|
return attrs;
|
|
}
|
|
});
|
|
|
|
// Combine consecutive HTML.Raws. Remove empty ones.
|
|
var RawCompactingVisitor = TreeTransformer.extend();
|
|
RawCompactingVisitor.def({
|
|
visitArray: function (array) {
|
|
var result = [];
|
|
for (var i = 0; i < array.length; i++) {
|
|
var item = array[i];
|
|
if ((item instanceof HTML.Raw) &&
|
|
((! item.value) ||
|
|
(result.length &&
|
|
(result[result.length - 1] instanceof HTML.Raw)))) {
|
|
// two cases: item is an empty Raw, or previous item is
|
|
// a Raw as well. In the latter case, replace the previous
|
|
// Raw with a longer one that includes the new Raw.
|
|
if (item.value) {
|
|
result[result.length - 1] = HTML.Raw(
|
|
result[result.length - 1].value + item.value);
|
|
}
|
|
} else {
|
|
result.push(item);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
});
|
|
|
|
// Replace pointless Raws like `HTMl.Raw('foo')` that contain no special
|
|
// characters with simple strings.
|
|
var RawReplacingVisitor = TreeTransformer.extend();
|
|
RawReplacingVisitor.def({
|
|
visitRaw: function (raw) {
|
|
var html = raw.value;
|
|
if (html.indexOf('&') < 0 && html.indexOf('<') < 0) {
|
|
return html;
|
|
} else {
|
|
return raw;
|
|
}
|
|
}
|
|
});
|
|
|
|
SpacebarsCompiler.optimize = function (tree) {
|
|
tree = (new OptimizingVisitor).visit(tree);
|
|
tree = (new RawCompactingVisitor).visit(tree);
|
|
tree = (new RawReplacingVisitor).visit(tree);
|
|
return tree;
|
|
};
|