Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 5ebd46c

Browse files
committed
perf($compile): Lazily compile the transclude function
For transcluded directives, the transclude function can be lazily compiled most of the time since the contents will not be needed until the `transclude` function was actually invoked. For example, the `transclude` function that is passed to `ng-if` or `ng-switch-when` does not need to be invoked until the condition that it's bound to has been matched. For complex trees or switch statements, this can represent significant performance gains since compilation of branches is deferred, and that compilation may never actually happen if it isn't needed. There are two instances where compilation will not be lazy; when we scan ahead in the array of directives to be processed and find at least two of the following: * A directive that is transcluded and does not allow multiple transclusion * A directive that has templateUrl and replace: true In both of those cases, we will need to continue eager compilation in order to generate the multiple transclusion exception at the correct time.
1 parent ffac747 commit 5ebd46c

File tree

1 file changed

+56
-2
lines changed

1 file changed

+56
-2
lines changed

src/ng/compile.js

+56-2
Original file line numberDiff line numberDiff line change
@@ -1625,6 +1625,37 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
16251625
};
16261626
}
16271627

1628+
/**
1629+
* A function generator that is used to support both eager and lazy compilation
1630+
* linking function.
1631+
* @param eager
1632+
* @param $compileNodes
1633+
* @param transcludeFn
1634+
* @param maxPriority
1635+
* @param ignoreDirective
1636+
* @param previousCompileContext
1637+
* @returns {Function}
1638+
*/
1639+
function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) {
1640+
if (eager) {
1641+
return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext);
1642+
}
1643+
1644+
var compiled;
1645+
1646+
return function() {
1647+
if (!compiled) {
1648+
compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext);
1649+
1650+
// Null out all of these references in order to make them eligible for garbage collection
1651+
// since this is a potentially long lived closure
1652+
$compileNodes = transcludeFn = previousCompileContext = null;
1653+
}
1654+
1655+
return compiled.apply(this, arguments);
1656+
};
1657+
}
1658+
16281659
/**
16291660
* Once the directives have been collected, their compile functions are executed. This method
16301661
* is responsible for inlining directive templates as well as terminating the application
@@ -1669,6 +1700,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
16691700
replaceDirective = originalReplaceDirective,
16701701
childTranscludeFn = transcludeFn,
16711702
linkFn,
1703+
didScanForMultipleTransclusion = false,
1704+
mightHaveMultipleTransclusionError = false,
16721705
directiveValue;
16731706

16741707
// executes all directives on the current element
@@ -1711,6 +1744,27 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
17111744

17121745
directiveName = directive.name;
17131746

1747+
// If we encounter a condition that can result in transclusion on the directive,
1748+
// then scan ahead in the remaining directives for others that may cause a multiple
1749+
// transclusion error to be thrown during the compilation process. If a matching directive
1750+
// is found, then we know that when we encounter a transcluded directive, we need to eagerly
1751+
// compile the `transclude` function rather than doing it lazily in order to throw
1752+
// exceptions at the correct time
1753+
if (!didScanForMultipleTransclusion && (directive.templateUrl && directive.replace)
1754+
|| (directive.transclude && !directive.$$tlb)) {
1755+
var candidateDirective;
1756+
1757+
for (var scanningIndex = i + 1; candidateDirective = directives[scanningIndex++];) {
1758+
if ((candidateDirective.transclude && !candidateDirective.$$tlb)
1759+
|| (candidateDirective.templateUrl && candidateDirective.replace)) {
1760+
mightHaveMultipleTransclusionError = true;
1761+
break;
1762+
}
1763+
}
1764+
1765+
didScanForMultipleTransclusion = true;
1766+
}
1767+
17141768
if (!directive.templateUrl && directive.controller) {
17151769
directiveValue = directive.controller;
17161770
controllerDirectives = controllerDirectives || createMap();
@@ -1740,7 +1794,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
17401794
compileNode = $compileNode[0];
17411795
replaceWith(jqCollection, sliceArgs($template), compileNode);
17421796

1743-
childTranscludeFn = compile($template, transcludeFn, terminalPriority,
1797+
childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority,
17441798
replaceDirective && replaceDirective.name, {
17451799
// Don't pass in:
17461800
// - controllerDirectives - otherwise we'll create duplicates controllers
@@ -1754,7 +1808,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
17541808
} else {
17551809
$template = jqLite(jqLiteClone(compileNode)).contents();
17561810
$compileNode.empty(); // clear contents
1757-
childTranscludeFn = compile($template, transcludeFn);
1811+
childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn);
17581812
}
17591813
}
17601814

0 commit comments

Comments
 (0)