-
Notifications
You must be signed in to change notification settings - Fork 27.3k
perf($compile): Lazily compile the transclude function
#12078
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1625,6 +1625,37 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
| }; | ||
| } | ||
|
|
||
| /** | ||
| * A function generator that is used to support both eager and lazy compilation | ||
| * linking function. | ||
| * @param eager | ||
| * @param $compileNodes | ||
| * @param transcludeFn | ||
| * @param maxPriority | ||
| * @param ignoreDirective | ||
| * @param previousCompileContext | ||
| * @returns {Function} | ||
| */ | ||
| function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) { | ||
| if (eager) { | ||
| return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); | ||
| } | ||
|
|
||
| var compiled; | ||
|
|
||
| return function() { | ||
| if (!compiled) { | ||
| compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); | ||
|
|
||
| // Null out all of these references in order to make them eligible for garbage collection | ||
| // since this is a potentially long lived closure | ||
| $compileNodes = transcludeFn = previousCompileContext = null; | ||
| } | ||
|
|
||
| return compiled.apply(this, arguments); | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Once the directives have been collected, their compile functions are executed. This method | ||
| * is responsible for inlining directive templates as well as terminating the application | ||
|
|
@@ -1669,6 +1700,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
| replaceDirective = originalReplaceDirective, | ||
| childTranscludeFn = transcludeFn, | ||
| linkFn, | ||
| didScanForMultipleTransclusion = false, | ||
| mightHaveMultipleTransclusionError = false, | ||
| directiveValue; | ||
|
|
||
| // executes all directives on the current element | ||
|
|
@@ -1711,6 +1744,27 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
|
|
||
| directiveName = directive.name; | ||
|
|
||
| // If we encounter a condition that can result in transclusion on the directive, | ||
| // then scan ahead in the remaining directives for others that may cause a multiple | ||
| // transclusion error to be thrown during the compilation process. If a matching directive | ||
| // is found, then we know that when we encounter a transcluded directive, we need to eagerly | ||
| // compile the `transclude` function rather than doing it lazily in order to throw | ||
| // exceptions at the correct time | ||
| if (!didScanForMultipleTransclusion && ((directive.replace && (directive.templateUrl || directive.template)) | ||
| || (directive.transclude && !directive.$$tlb))) { | ||
| var candidateDirective; | ||
|
|
||
| for (var scanningIndex = i + 1; candidateDirective = directives[scanningIndex++];) { | ||
| if ((candidateDirective.transclude && !candidateDirective.$$tlb) | ||
| || (candidateDirective.replace && (candidateDirective.templateUrl || candidateDirective.template ))) { | ||
| mightHaveMultipleTransclusionError = true; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| didScanForMultipleTransclusion = true; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if there is some corner case and we should add at line 1859 This is, after a directive with a template and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than doing that, template and replace: true should just be another case that can cause an eager compile if a second one is found. I'm not overly concerned about that path compiling eagerly since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, sounds good |
||
| } | ||
|
|
||
| if (!directive.templateUrl && directive.controller) { | ||
| directiveValue = directive.controller; | ||
| controllerDirectives = controllerDirectives || createMap(); | ||
|
|
@@ -1740,7 +1794,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
| compileNode = $compileNode[0]; | ||
| replaceWith(jqCollection, sliceArgs($template), compileNode); | ||
|
|
||
| childTranscludeFn = compile($template, transcludeFn, terminalPriority, | ||
| childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority, | ||
| replaceDirective && replaceDirective.name, { | ||
| // Don't pass in: | ||
| // - controllerDirectives - otherwise we'll create duplicates controllers | ||
|
|
@@ -1754,7 +1808,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
| } else { | ||
| $template = jqLite(jqLiteClone(compileNode)).contents(); | ||
| $compileNode.empty(); // clear contents | ||
| childTranscludeFn = compile($template, transcludeFn); | ||
| childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn); | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Not a blocker to land this]
I am somehow torn on having all the parameters here or if we should use
arguments. i.e.It is somehow more code, but it should be more resilient to changes. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If someone makes a change that makes the arguments passed invalid, we should have sufficient unit tests in order to catch any resulting bugs. Since the compile stage is generally considered a hot path, before making this change I'd be curious to see what ( if any ) perf implications there might be from V8 being unable to optimize this function due to the use of
argumentslike that.We can also consider copying the arguments manually with a for loop which avoids the deopt if you think it really would make it more resilient.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM