292 lines
15 KiB
Markdown
Raw Normal View History

perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
# Incremental Compilation
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
The `incremental` package contains logic related to incremental compilation in ngtsc. Its goal is to
ensure that the compiler's incremental performance is largely O(number of files changed in that
iteration) instead of O(size of the program as a whole), by allowing the compiler to optimize away
as much work as possible without sacrificing the correctness of its output.
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
An incremental compilation receives information about the prior compilation, including
its `ts.Program` and the result of ngtsc's analyses of each class in that program. Depending on the
nature of any changes made to files in the program between its prior and current versions, and on
the semantic effect of those changes, ngtsc may perform 3 different optimizations as it processes
the new build:
* It can reuse analysis work performed in the previous program
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
ngtsc receives the analyses of all decorated classes performed as part of the previous compilation,
and can reuse that work for a class if it can prove that the results are not stale.
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
* It can skip emitting a file
perf(ivy): reuse prior analysis work during incremental builds (#34288) Previously, the compiler performed an incremental build by analyzing and resolving all classes in the program (even unchanged ones) and then using the dependency graph information to determine which .js files were stale and needed to be re-emitted. This algorithm produced "correct" rebuilds, but the cost of re-analyzing the entire program turned out to be higher than anticipated, especially for component-heavy compilations. To achieve performant rebuilds, it is necessary to reuse previous analysis results if possible. Doing this safely requires knowing when prior work is viable and when it is stale and needs to be re-done. The new algorithm implemented by this commit is such: 1) Each incremental build starts with knowledge of the last known good dependency graph and analysis results from the last successful build, plus of course information about the set of files changed. 2) The previous dependency graph's information is used to determine the set of source files which have "logically" changed. A source file is considered logically changed if it or any of its dependencies have physically changed (on disk) since the last successful compilation. Any logically unchanged dependencies have their dependency information copied over to the new dependency graph. 3) During the `TraitCompiler`'s loop to consider all source files in the program, if a source file is logically unchanged then its previous analyses are "adopted" (and their 'register' steps are run). If the file is logically changed, then it is re-analyzed as usual. 4) Then, incremental build proceeds as before, with the new dependency graph being used to determine the set of files which require re-emitting. This analysis reuse avoids template parsing operations in many circumstances and significantly reduces the time it takes ngtsc to rebuild a large application. Future work will increase performance even more, by tackling a variety of other opportunities to reuse or avoid work. PR Close #34288
2019-12-05 16:03:17 -08:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
Emitting a file is a very expensive operation in TypeScript, involving the execution of many
internal TS transforms (downleveling, module system, etc) as well as the synthesis of a large text
buffer for the final JS output. Skipping emit of a file is the most effective optimizations ngtsc
can do. It's also one of the most challenging. Even if ngtsc's _analysis_ of a specific file is not
stale, that file may still need to be re-emitted if other changes in the program impact its
semantics. For example, a change to a component selector affects other components which use that
selector in their templates, even though no direct dependency exists between them.
perf(ivy): reuse prior analysis work during incremental builds (#34288) Previously, the compiler performed an incremental build by analyzing and resolving all classes in the program (even unchanged ones) and then using the dependency graph information to determine which .js files were stale and needed to be re-emitted. This algorithm produced "correct" rebuilds, but the cost of re-analyzing the entire program turned out to be higher than anticipated, especially for component-heavy compilations. To achieve performant rebuilds, it is necessary to reuse previous analysis results if possible. Doing this safely requires knowing when prior work is viable and when it is stale and needs to be re-done. The new algorithm implemented by this commit is such: 1) Each incremental build starts with knowledge of the last known good dependency graph and analysis results from the last successful build, plus of course information about the set of files changed. 2) The previous dependency graph's information is used to determine the set of source files which have "logically" changed. A source file is considered logically changed if it or any of its dependencies have physically changed (on disk) since the last successful compilation. Any logically unchanged dependencies have their dependency information copied over to the new dependency graph. 3) During the `TraitCompiler`'s loop to consider all source files in the program, if a source file is logically unchanged then its previous analyses are "adopted" (and their 'register' steps are run). If the file is logically changed, then it is re-analyzed as usual. 4) Then, incremental build proceeds as before, with the new dependency graph being used to determine the set of files which require re-emitting. This analysis reuse avoids template parsing operations in many circumstances and significantly reduces the time it takes ngtsc to rebuild a large application. Future work will increase performance even more, by tackling a variety of other opportunities to reuse or avoid work. PR Close #34288
2019-12-05 16:03:17 -08:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
* It can reuse template type-checking code
perf(ivy): reuse prior analysis work during incremental builds (#34288) Previously, the compiler performed an incremental build by analyzing and resolving all classes in the program (even unchanged ones) and then using the dependency graph information to determine which .js files were stale and needed to be re-emitted. This algorithm produced "correct" rebuilds, but the cost of re-analyzing the entire program turned out to be higher than anticipated, especially for component-heavy compilations. To achieve performant rebuilds, it is necessary to reuse previous analysis results if possible. Doing this safely requires knowing when prior work is viable and when it is stale and needs to be re-done. The new algorithm implemented by this commit is such: 1) Each incremental build starts with knowledge of the last known good dependency graph and analysis results from the last successful build, plus of course information about the set of files changed. 2) The previous dependency graph's information is used to determine the set of source files which have "logically" changed. A source file is considered logically changed if it or any of its dependencies have physically changed (on disk) since the last successful compilation. Any logically unchanged dependencies have their dependency information copied over to the new dependency graph. 3) During the `TraitCompiler`'s loop to consider all source files in the program, if a source file is logically unchanged then its previous analyses are "adopted" (and their 'register' steps are run). If the file is logically changed, then it is re-analyzed as usual. 4) Then, incremental build proceeds as before, with the new dependency graph being used to determine the set of files which require re-emitting. This analysis reuse avoids template parsing operations in many circumstances and significantly reduces the time it takes ngtsc to rebuild a large application. Future work will increase performance even more, by tackling a variety of other opportunities to reuse or avoid work. PR Close #34288
2019-12-05 16:03:17 -08:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
Template type-checking code is generated using semantic information extracted from the user's
program. This generation can be expensive, and ngtsc attempts to reuse previous results as much as
possible. This optimization can be thought of as a special case of the above re-emit optimization,
since template type-checking code is a particular flavor of "emit" for a component.
perf(ivy): reuse prior analysis work during incremental builds (#34288) Previously, the compiler performed an incremental build by analyzing and resolving all classes in the program (even unchanged ones) and then using the dependency graph information to determine which .js files were stale and needed to be re-emitted. This algorithm produced "correct" rebuilds, but the cost of re-analyzing the entire program turned out to be higher than anticipated, especially for component-heavy compilations. To achieve performant rebuilds, it is necessary to reuse previous analysis results if possible. Doing this safely requires knowing when prior work is viable and when it is stale and needs to be re-done. The new algorithm implemented by this commit is such: 1) Each incremental build starts with knowledge of the last known good dependency graph and analysis results from the last successful build, plus of course information about the set of files changed. 2) The previous dependency graph's information is used to determine the set of source files which have "logically" changed. A source file is considered logically changed if it or any of its dependencies have physically changed (on disk) since the last successful compilation. Any logically unchanged dependencies have their dependency information copied over to the new dependency graph. 3) During the `TraitCompiler`'s loop to consider all source files in the program, if a source file is logically unchanged then its previous analyses are "adopted" (and their 'register' steps are run). If the file is logically changed, then it is re-analyzed as usual. 4) Then, incremental build proceeds as before, with the new dependency graph being used to determine the set of files which require re-emitting. This analysis reuse avoids template parsing operations in many circumstances and significantly reduces the time it takes ngtsc to rebuild a large application. Future work will increase performance even more, by tackling a variety of other opportunities to reuse or avoid work. PR Close #34288
2019-12-05 16:03:17 -08:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
Due to the way that template type-checking works (creation of a second `ts.Program`
with `.ngtypecheck` files containing template type-checking blocks, or TCBs), reuse of template
type-checking code is critical for good performance. Not only is generation of these TCBs expensive,
but forcing TypeScript to re-parse and re-analyze every `.ngtypecheck` file on each incremental
change would be costly as well.
perf(ivy): reuse prior analysis work during incremental builds (#34288) Previously, the compiler performed an incremental build by analyzing and resolving all classes in the program (even unchanged ones) and then using the dependency graph information to determine which .js files were stale and needed to be re-emitted. This algorithm produced "correct" rebuilds, but the cost of re-analyzing the entire program turned out to be higher than anticipated, especially for component-heavy compilations. To achieve performant rebuilds, it is necessary to reuse previous analysis results if possible. Doing this safely requires knowing when prior work is viable and when it is stale and needs to be re-done. The new algorithm implemented by this commit is such: 1) Each incremental build starts with knowledge of the last known good dependency graph and analysis results from the last successful build, plus of course information about the set of files changed. 2) The previous dependency graph's information is used to determine the set of source files which have "logically" changed. A source file is considered logically changed if it or any of its dependencies have physically changed (on disk) since the last successful compilation. Any logically unchanged dependencies have their dependency information copied over to the new dependency graph. 3) During the `TraitCompiler`'s loop to consider all source files in the program, if a source file is logically unchanged then its previous analyses are "adopted" (and their 'register' steps are run). If the file is logically changed, then it is re-analyzed as usual. 4) Then, incremental build proceeds as before, with the new dependency graph being used to determine the set of files which require re-emitting. This analysis reuse avoids template parsing operations in many circumstances and significantly reduces the time it takes ngtsc to rebuild a large application. Future work will increase performance even more, by tackling a variety of other opportunities to reuse or avoid work. PR Close #34288
2019-12-05 16:03:17 -08:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
The `incremental` package is dedicated to allowing ngtsc to make these important optimizations
safely.
During an incremental compilation, the compiler begins with a process called "reconciliation",
focused on understanding the differences between the incoming, new `ts.Program` and the
last `ts.Program`. In TypeScript, an unchanged file will have its `ts.SourceFile` AST completely
reused. Reconciliation therefore examines the `ts.SourceFile`s of both the old and new programs, and
identifies files which have been added, removed, or changed. This information feeds in to the rest
of the incremental compilation process.
## Reuse of analysis results
Angular's process of understanding an individual component, directive, or other decorated class is
known as "analysis". Analysis is always performed on a class-by-class basis, so the analysis of a
component only takes into consideration information present in the `@Component` decorator, and not
for example in the `@NgModule` which declares the component.
However, analysis _can_ depend on information outside of the decorated class's file. This can happen
in two ways:
* External resources, such as templates or stylesheets, are covered by analysis.
* The partial evaluation of expressions within a class's metadata may descend into symbols imported
from other files.
For example, a directive's selector may be determined via an imported constant:
perf(ivy): reuse prior analysis work during incremental builds (#34288) Previously, the compiler performed an incremental build by analyzing and resolving all classes in the program (even unchanged ones) and then using the dependency graph information to determine which .js files were stale and needed to be re-emitted. This algorithm produced "correct" rebuilds, but the cost of re-analyzing the entire program turned out to be higher than anticipated, especially for component-heavy compilations. To achieve performant rebuilds, it is necessary to reuse previous analysis results if possible. Doing this safely requires knowing when prior work is viable and when it is stale and needs to be re-done. The new algorithm implemented by this commit is such: 1) Each incremental build starts with knowledge of the last known good dependency graph and analysis results from the last successful build, plus of course information about the set of files changed. 2) The previous dependency graph's information is used to determine the set of source files which have "logically" changed. A source file is considered logically changed if it or any of its dependencies have physically changed (on disk) since the last successful compilation. Any logically unchanged dependencies have their dependency information copied over to the new dependency graph. 3) During the `TraitCompiler`'s loop to consider all source files in the program, if a source file is logically unchanged then its previous analyses are "adopted" (and their 'register' steps are run). If the file is logically changed, then it is re-analyzed as usual. 4) Then, incremental build proceeds as before, with the new dependency graph being used to determine the set of files which require re-emitting. This analysis reuse avoids template parsing operations in many circumstances and significantly reduces the time it takes ngtsc to rebuild a large application. Future work will increase performance even more, by tackling a variety of other opportunities to reuse or avoid work. PR Close #34288
2019-12-05 16:03:17 -08:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
```typescript=
import {Directive} from '@angular/core';
import {DIR_SELECTOR} from './selectors';
@Directive({
selector: DIR_SELECTOR,
})
export class Dir {}
```
The analysis of this directive _depends on_ the value of `DIR_SELECTOR` from `selectors.ts`.
Consequently, if `selectors.ts` changes, `Dir` needs to be re-analyzed, even if `dir.ts` has not
changed.
The `incremental` system provides a mechanism which tracks such dependencies at the file level. The
partial evaluation system records dependencies for any given evaluation operation when an import
boundary is crossed, building up a file-to-file dependency graph. This graph is then transmitted to
the next incremental compilation, where it can be used to determine, based on the set of files
physically changed on disk, which files have _logically_ changed and need to be re-analyzed.
perf(ivy): reuse prior analysis work during incremental builds (#34288) Previously, the compiler performed an incremental build by analyzing and resolving all classes in the program (even unchanged ones) and then using the dependency graph information to determine which .js files were stale and needed to be re-emitted. This algorithm produced "correct" rebuilds, but the cost of re-analyzing the entire program turned out to be higher than anticipated, especially for component-heavy compilations. To achieve performant rebuilds, it is necessary to reuse previous analysis results if possible. Doing this safely requires knowing when prior work is viable and when it is stale and needs to be re-done. The new algorithm implemented by this commit is such: 1) Each incremental build starts with knowledge of the last known good dependency graph and analysis results from the last successful build, plus of course information about the set of files changed. 2) The previous dependency graph's information is used to determine the set of source files which have "logically" changed. A source file is considered logically changed if it or any of its dependencies have physically changed (on disk) since the last successful compilation. Any logically unchanged dependencies have their dependency information copied over to the new dependency graph. 3) During the `TraitCompiler`'s loop to consider all source files in the program, if a source file is logically unchanged then its previous analyses are "adopted" (and their 'register' steps are run). If the file is logically changed, then it is re-analyzed as usual. 4) Then, incremental build proceeds as before, with the new dependency graph being used to determine the set of files which require re-emitting. This analysis reuse avoids template parsing operations in many circumstances and significantly reduces the time it takes ngtsc to rebuild a large application. Future work will increase performance even more, by tackling a variety of other opportunities to reuse or avoid work. PR Close #34288
2019-12-05 16:03:17 -08:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
## Reuse of emit results
In plain TypeScript programs, the compiled JavaScript code for any given input file (e.g. `foo.ts`)
depends only on the code within that input file. That is, only the contents of `foo.ts` can affect
the generated contents written to `foo.js`. The TypeScript compiler can therefore perform a very
simple optimization, and avoid generating and emitting code for any input files which do not change.
This is important for good incremental build performance, as emitting a file is a very expensive
operation.
perf(ivy): reuse prior analysis work during incremental builds (#34288) Previously, the compiler performed an incremental build by analyzing and resolving all classes in the program (even unchanged ones) and then using the dependency graph information to determine which .js files were stale and needed to be re-emitted. This algorithm produced "correct" rebuilds, but the cost of re-analyzing the entire program turned out to be higher than anticipated, especially for component-heavy compilations. To achieve performant rebuilds, it is necessary to reuse previous analysis results if possible. Doing this safely requires knowing when prior work is viable and when it is stale and needs to be re-done. The new algorithm implemented by this commit is such: 1) Each incremental build starts with knowledge of the last known good dependency graph and analysis results from the last successful build, plus of course information about the set of files changed. 2) The previous dependency graph's information is used to determine the set of source files which have "logically" changed. A source file is considered logically changed if it or any of its dependencies have physically changed (on disk) since the last successful compilation. Any logically unchanged dependencies have their dependency information copied over to the new dependency graph. 3) During the `TraitCompiler`'s loop to consider all source files in the program, if a source file is logically unchanged then its previous analyses are "adopted" (and their 'register' steps are run). If the file is logically changed, then it is re-analyzed as usual. 4) Then, incremental build proceeds as before, with the new dependency graph being used to determine the set of files which require re-emitting. This analysis reuse avoids template parsing operations in many circumstances and significantly reduces the time it takes ngtsc to rebuild a large application. Future work will increase performance even more, by tackling a variety of other opportunities to reuse or avoid work. PR Close #34288
2019-12-05 16:03:17 -08:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
(in practice, the TypeScript feature of `const enum` declarations breaks this overly simple model)
perf(ivy): reuse prior analysis work during incremental builds (#34288) Previously, the compiler performed an incremental build by analyzing and resolving all classes in the program (even unchanged ones) and then using the dependency graph information to determine which .js files were stale and needed to be re-emitted. This algorithm produced "correct" rebuilds, but the cost of re-analyzing the entire program turned out to be higher than anticipated, especially for component-heavy compilations. To achieve performant rebuilds, it is necessary to reuse previous analysis results if possible. Doing this safely requires knowing when prior work is viable and when it is stale and needs to be re-done. The new algorithm implemented by this commit is such: 1) Each incremental build starts with knowledge of the last known good dependency graph and analysis results from the last successful build, plus of course information about the set of files changed. 2) The previous dependency graph's information is used to determine the set of source files which have "logically" changed. A source file is considered logically changed if it or any of its dependencies have physically changed (on disk) since the last successful compilation. Any logically unchanged dependencies have their dependency information copied over to the new dependency graph. 3) During the `TraitCompiler`'s loop to consider all source files in the program, if a source file is logically unchanged then its previous analyses are "adopted" (and their 'register' steps are run). If the file is logically changed, then it is re-analyzed as usual. 4) Then, incremental build proceeds as before, with the new dependency graph being used to determine the set of files which require re-emitting. This analysis reuse avoids template parsing operations in many circumstances and significantly reduces the time it takes ngtsc to rebuild a large application. Future work will increase performance even more, by tackling a variety of other opportunities to reuse or avoid work. PR Close #34288
2019-12-05 16:03:17 -08:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
In Angular applications, however, this optimization is not nearly so simple. The emit of a `.js`
file in Angular is affected in four main ways:
perf(ivy): reuse prior analysis work during incremental builds (#34288) Previously, the compiler performed an incremental build by analyzing and resolving all classes in the program (even unchanged ones) and then using the dependency graph information to determine which .js files were stale and needed to be re-emitted. This algorithm produced "correct" rebuilds, but the cost of re-analyzing the entire program turned out to be higher than anticipated, especially for component-heavy compilations. To achieve performant rebuilds, it is necessary to reuse previous analysis results if possible. Doing this safely requires knowing when prior work is viable and when it is stale and needs to be re-done. The new algorithm implemented by this commit is such: 1) Each incremental build starts with knowledge of the last known good dependency graph and analysis results from the last successful build, plus of course information about the set of files changed. 2) The previous dependency graph's information is used to determine the set of source files which have "logically" changed. A source file is considered logically changed if it or any of its dependencies have physically changed (on disk) since the last successful compilation. Any logically unchanged dependencies have their dependency information copied over to the new dependency graph. 3) During the `TraitCompiler`'s loop to consider all source files in the program, if a source file is logically unchanged then its previous analyses are "adopted" (and their 'register' steps are run). If the file is logically changed, then it is re-analyzed as usual. 4) Then, incremental build proceeds as before, with the new dependency graph being used to determine the set of files which require re-emitting. This analysis reuse avoids template parsing operations in many circumstances and significantly reduces the time it takes ngtsc to rebuild a large application. Future work will increase performance even more, by tackling a variety of other opportunities to reuse or avoid work. PR Close #34288
2019-12-05 16:03:17 -08:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
* Just as in plain TS, it depends on the contents of the input `.ts` file.
* It can be affected by expressions that were statically evaluated during analysis of any decorated
classes in the input, and these expressions can depend on other files.
perf(ivy): reuse prior analysis work during incremental builds (#34288) Previously, the compiler performed an incremental build by analyzing and resolving all classes in the program (even unchanged ones) and then using the dependency graph information to determine which .js files were stale and needed to be re-emitted. This algorithm produced "correct" rebuilds, but the cost of re-analyzing the entire program turned out to be higher than anticipated, especially for component-heavy compilations. To achieve performant rebuilds, it is necessary to reuse previous analysis results if possible. Doing this safely requires knowing when prior work is viable and when it is stale and needs to be re-done. The new algorithm implemented by this commit is such: 1) Each incremental build starts with knowledge of the last known good dependency graph and analysis results from the last successful build, plus of course information about the set of files changed. 2) The previous dependency graph's information is used to determine the set of source files which have "logically" changed. A source file is considered logically changed if it or any of its dependencies have physically changed (on disk) since the last successful compilation. Any logically unchanged dependencies have their dependency information copied over to the new dependency graph. 3) During the `TraitCompiler`'s loop to consider all source files in the program, if a source file is logically unchanged then its previous analyses are "adopted" (and their 'register' steps are run). If the file is logically changed, then it is re-analyzed as usual. 4) Then, incremental build proceeds as before, with the new dependency graph being used to determine the set of files which require re-emitting. This analysis reuse avoids template parsing operations in many circumstances and significantly reduces the time it takes ngtsc to rebuild a large application. Future work will increase performance even more, by tackling a variety of other opportunities to reuse or avoid work. PR Close #34288
2019-12-05 16:03:17 -08:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
For example, the directive with its selector specified via the imported `DIR_SELECTOR` constant
above has compilation output which depends on the value of `DIR_SELECTOR`. Therefore, the `dir.js`
file needs to be emitted whenever the value of the selector constant in `selectors.ts` changes, even
if `dir.ts` itself is unchanged. The compiler therefore will re-emit `dir.js` if the `dir.ts` file
is determined to have _logically_ changed, using the same dependency graph that powers analysis
reuse.
perf(ivy): reuse prior analysis work during incremental builds (#34288) Previously, the compiler performed an incremental build by analyzing and resolving all classes in the program (even unchanged ones) and then using the dependency graph information to determine which .js files were stale and needed to be re-emitted. This algorithm produced "correct" rebuilds, but the cost of re-analyzing the entire program turned out to be higher than anticipated, especially for component-heavy compilations. To achieve performant rebuilds, it is necessary to reuse previous analysis results if possible. Doing this safely requires knowing when prior work is viable and when it is stale and needs to be re-done. The new algorithm implemented by this commit is such: 1) Each incremental build starts with knowledge of the last known good dependency graph and analysis results from the last successful build, plus of course information about the set of files changed. 2) The previous dependency graph's information is used to determine the set of source files which have "logically" changed. A source file is considered logically changed if it or any of its dependencies have physically changed (on disk) since the last successful compilation. Any logically unchanged dependencies have their dependency information copied over to the new dependency graph. 3) During the `TraitCompiler`'s loop to consider all source files in the program, if a source file is logically unchanged then its previous analyses are "adopted" (and their 'register' steps are run). If the file is logically changed, then it is re-analyzed as usual. 4) Then, incremental build proceeds as before, with the new dependency graph being used to determine the set of files which require re-emitting. This analysis reuse avoids template parsing operations in many circumstances and significantly reduces the time it takes ngtsc to rebuild a large application. Future work will increase performance even more, by tackling a variety of other opportunities to reuse or avoid work. PR Close #34288
2019-12-05 16:03:17 -08:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
* Components can have external templates and CSS stylesheets which influence their compilation.
perf(ivy): reuse prior analysis work during incremental builds (#34288) Previously, the compiler performed an incremental build by analyzing and resolving all classes in the program (even unchanged ones) and then using the dependency graph information to determine which .js files were stale and needed to be re-emitted. This algorithm produced "correct" rebuilds, but the cost of re-analyzing the entire program turned out to be higher than anticipated, especially for component-heavy compilations. To achieve performant rebuilds, it is necessary to reuse previous analysis results if possible. Doing this safely requires knowing when prior work is viable and when it is stale and needs to be re-done. The new algorithm implemented by this commit is such: 1) Each incremental build starts with knowledge of the last known good dependency graph and analysis results from the last successful build, plus of course information about the set of files changed. 2) The previous dependency graph's information is used to determine the set of source files which have "logically" changed. A source file is considered logically changed if it or any of its dependencies have physically changed (on disk) since the last successful compilation. Any logically unchanged dependencies have their dependency information copied over to the new dependency graph. 3) During the `TraitCompiler`'s loop to consider all source files in the program, if a source file is logically unchanged then its previous analyses are "adopted" (and their 'register' steps are run). If the file is logically changed, then it is re-analyzed as usual. 4) Then, incremental build proceeds as before, with the new dependency graph being used to determine the set of files which require re-emitting. This analysis reuse avoids template parsing operations in many circumstances and significantly reduces the time it takes ngtsc to rebuild a large application. Future work will increase performance even more, by tackling a variety of other opportunities to reuse or avoid work. PR Close #34288
2019-12-05 16:03:17 -08:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
These are incorporated into a component's analysis dependencies.
perf(compiler-cli): perform template type-checking incrementally (#36211) This optimization builds on a lot of prior work to finally make type- checking of templates incremental. Incrementality requires two main components: - the ability to reuse work from a prior compilation. - the ability to know when changes in the current program invalidate that prior work. Prior to this commit, on every type-checking pass the compiler would generate new .ngtypecheck files for each original input file in the program. 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked. 3. (Build #2 main program): throw away old .ngtypecheck files and generate new empty ones. 4. (Build #2 type-check program): same as step 2. With this commit, the `IncrementalDriver` now tracks template type-checking _metadata_ for each input file. The metadata contains information about source mappings for generated type-checking code, as well as some diagnostics which were discovered at type-check analysis time. The actual type-checking code is stored in the TypeScript AST for type-checking files, which is now re-used between programs as follows: 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked, and the metadata registered in the `IncrementalDriver`. 3. (Build #2 main program): The `TypeCheckShimGenerator` now reuses _all_ .ngtypecheck `ts.SourceFile` shims from build #1's type-check program in the construction of build #2's main program. Some of the contents of these files might be stale (if a component's template changed, for example), but wholesale reuse here prevents unnecessary changes in the contents of the program at this point and makes TypeScript's job a lot easier. 4. (Build #2 type-check program): For those input files which have not "logically changed" (meaning components within are semantically the same as they were before), the compiler will re-use the type-check file metadata from build #1, and _not_ generate a new .ngtypecheck shim. For components which have logically changed or where the previous .ngtypecheck contents cannot otherwise be reused, code generation happens as before. PR Close #36211
2020-03-19 11:29:58 -07:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
* Components (and NgModules) are influenced by the NgModule graph, which controls which directives
and pipes are "in scope" for each component's template.
perf(compiler-cli): perform template type-checking incrementally (#36211) This optimization builds on a lot of prior work to finally make type- checking of templates incremental. Incrementality requires two main components: - the ability to reuse work from a prior compilation. - the ability to know when changes in the current program invalidate that prior work. Prior to this commit, on every type-checking pass the compiler would generate new .ngtypecheck files for each original input file in the program. 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked. 3. (Build #2 main program): throw away old .ngtypecheck files and generate new empty ones. 4. (Build #2 type-check program): same as step 2. With this commit, the `IncrementalDriver` now tracks template type-checking _metadata_ for each input file. The metadata contains information about source mappings for generated type-checking code, as well as some diagnostics which were discovered at type-check analysis time. The actual type-checking code is stored in the TypeScript AST for type-checking files, which is now re-used between programs as follows: 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked, and the metadata registered in the `IncrementalDriver`. 3. (Build #2 main program): The `TypeCheckShimGenerator` now reuses _all_ .ngtypecheck `ts.SourceFile` shims from build #1's type-check program in the construction of build #2's main program. Some of the contents of these files might be stale (if a component's template changed, for example), but wholesale reuse here prevents unnecessary changes in the contents of the program at this point and makes TypeScript's job a lot easier. 4. (Build #2 type-check program): For those input files which have not "logically changed" (meaning components within are semantically the same as they were before), the compiler will re-use the type-check file metadata from build #1, and _not_ generate a new .ngtypecheck shim. For components which have logically changed or where the previous .ngtypecheck contents cannot otherwise be reused, code generation happens as before. PR Close #36211
2020-03-19 11:29:58 -07:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
This last relationship is the most difficult, as there is no import relationship between a component
and the directives and pipes it uses in its template. That means that a component file can be
logically unchanged, but still require re-emit if one of its dependencies has been updated in a way
that influences the compilation of the component.
perf(compiler-cli): perform template type-checking incrementally (#36211) This optimization builds on a lot of prior work to finally make type- checking of templates incremental. Incrementality requires two main components: - the ability to reuse work from a prior compilation. - the ability to know when changes in the current program invalidate that prior work. Prior to this commit, on every type-checking pass the compiler would generate new .ngtypecheck files for each original input file in the program. 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked. 3. (Build #2 main program): throw away old .ngtypecheck files and generate new empty ones. 4. (Build #2 type-check program): same as step 2. With this commit, the `IncrementalDriver` now tracks template type-checking _metadata_ for each input file. The metadata contains information about source mappings for generated type-checking code, as well as some diagnostics which were discovered at type-check analysis time. The actual type-checking code is stored in the TypeScript AST for type-checking files, which is now re-used between programs as follows: 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked, and the metadata registered in the `IncrementalDriver`. 3. (Build #2 main program): The `TypeCheckShimGenerator` now reuses _all_ .ngtypecheck `ts.SourceFile` shims from build #1's type-check program in the construction of build #2's main program. Some of the contents of these files might be stale (if a component's template changed, for example), but wholesale reuse here prevents unnecessary changes in the contents of the program at this point and makes TypeScript's job a lot easier. 4. (Build #2 type-check program): For those input files which have not "logically changed" (meaning components within are semantically the same as they were before), the compiler will re-use the type-check file metadata from build #1, and _not_ generate a new .ngtypecheck shim. For components which have logically changed or where the previous .ngtypecheck contents cannot otherwise be reused, code generation happens as before. PR Close #36211
2020-03-19 11:29:58 -07:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
### Example
perf(compiler-cli): perform template type-checking incrementally (#36211) This optimization builds on a lot of prior work to finally make type- checking of templates incremental. Incrementality requires two main components: - the ability to reuse work from a prior compilation. - the ability to know when changes in the current program invalidate that prior work. Prior to this commit, on every type-checking pass the compiler would generate new .ngtypecheck files for each original input file in the program. 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked. 3. (Build #2 main program): throw away old .ngtypecheck files and generate new empty ones. 4. (Build #2 type-check program): same as step 2. With this commit, the `IncrementalDriver` now tracks template type-checking _metadata_ for each input file. The metadata contains information about source mappings for generated type-checking code, as well as some diagnostics which were discovered at type-check analysis time. The actual type-checking code is stored in the TypeScript AST for type-checking files, which is now re-used between programs as follows: 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked, and the metadata registered in the `IncrementalDriver`. 3. (Build #2 main program): The `TypeCheckShimGenerator` now reuses _all_ .ngtypecheck `ts.SourceFile` shims from build #1's type-check program in the construction of build #2's main program. Some of the contents of these files might be stale (if a component's template changed, for example), but wholesale reuse here prevents unnecessary changes in the contents of the program at this point and makes TypeScript's job a lot easier. 4. (Build #2 type-check program): For those input files which have not "logically changed" (meaning components within are semantically the same as they were before), the compiler will re-use the type-check file metadata from build #1, and _not_ generate a new .ngtypecheck shim. For components which have logically changed or where the previous .ngtypecheck contents cannot otherwise be reused, code generation happens as before. PR Close #36211
2020-03-19 11:29:58 -07:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
For example, the output of a compiled component includes an array called `directiveDefs`, listing
all of the directives and components actually used within the component's template. This array is
built by combining the template (from analysis) with the "scope" of the component - the set of
directives and pipes which are available for use in its template. This scope is synthesized from the
analysis of not just the component's NgModule, but other NgModules which might be imported, and the
components/directives that those NgModules export, and their analysis data as well.
perf(compiler-cli): perform template type-checking incrementally (#36211) This optimization builds on a lot of prior work to finally make type- checking of templates incremental. Incrementality requires two main components: - the ability to reuse work from a prior compilation. - the ability to know when changes in the current program invalidate that prior work. Prior to this commit, on every type-checking pass the compiler would generate new .ngtypecheck files for each original input file in the program. 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked. 3. (Build #2 main program): throw away old .ngtypecheck files and generate new empty ones. 4. (Build #2 type-check program): same as step 2. With this commit, the `IncrementalDriver` now tracks template type-checking _metadata_ for each input file. The metadata contains information about source mappings for generated type-checking code, as well as some diagnostics which were discovered at type-check analysis time. The actual type-checking code is stored in the TypeScript AST for type-checking files, which is now re-used between programs as follows: 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked, and the metadata registered in the `IncrementalDriver`. 3. (Build #2 main program): The `TypeCheckShimGenerator` now reuses _all_ .ngtypecheck `ts.SourceFile` shims from build #1's type-check program in the construction of build #2's main program. Some of the contents of these files might be stale (if a component's template changed, for example), but wholesale reuse here prevents unnecessary changes in the contents of the program at this point and makes TypeScript's job a lot easier. 4. (Build #2 type-check program): For those input files which have not "logically changed" (meaning components within are semantically the same as they were before), the compiler will re-use the type-check file metadata from build #1, and _not_ generate a new .ngtypecheck shim. For components which have logically changed or where the previous .ngtypecheck contents cannot otherwise be reused, code generation happens as before. PR Close #36211
2020-03-19 11:29:58 -07:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
These dependencies of a component on the directives/pipes it consumes, and the NgModule structures
that made them visible, are not captured in the file-level dependency graph. This is due to the
peculiar nature of NgModule and component relationships: NgModules import components, so there is
never a reference from a component to its NgModule, or any of its directive or pipe dependencies.
perf(compiler-cli): perform template type-checking incrementally (#36211) This optimization builds on a lot of prior work to finally make type- checking of templates incremental. Incrementality requires two main components: - the ability to reuse work from a prior compilation. - the ability to know when changes in the current program invalidate that prior work. Prior to this commit, on every type-checking pass the compiler would generate new .ngtypecheck files for each original input file in the program. 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked. 3. (Build #2 main program): throw away old .ngtypecheck files and generate new empty ones. 4. (Build #2 type-check program): same as step 2. With this commit, the `IncrementalDriver` now tracks template type-checking _metadata_ for each input file. The metadata contains information about source mappings for generated type-checking code, as well as some diagnostics which were discovered at type-check analysis time. The actual type-checking code is stored in the TypeScript AST for type-checking files, which is now re-used between programs as follows: 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked, and the metadata registered in the `IncrementalDriver`. 3. (Build #2 main program): The `TypeCheckShimGenerator` now reuses _all_ .ngtypecheck `ts.SourceFile` shims from build #1's type-check program in the construction of build #2's main program. Some of the contents of these files might be stale (if a component's template changed, for example), but wholesale reuse here prevents unnecessary changes in the contents of the program at this point and makes TypeScript's job a lot easier. 4. (Build #2 type-check program): For those input files which have not "logically changed" (meaning components within are semantically the same as they were before), the compiler will re-use the type-check file metadata from build #1, and _not_ generate a new .ngtypecheck shim. For components which have logically changed or where the previous .ngtypecheck contents cannot otherwise be reused, code generation happens as before. PR Close #36211
2020-03-19 11:29:58 -07:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
In code, this looks like:
perf(compiler-cli): perform template type-checking incrementally (#36211) This optimization builds on a lot of prior work to finally make type- checking of templates incremental. Incrementality requires two main components: - the ability to reuse work from a prior compilation. - the ability to know when changes in the current program invalidate that prior work. Prior to this commit, on every type-checking pass the compiler would generate new .ngtypecheck files for each original input file in the program. 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked. 3. (Build #2 main program): throw away old .ngtypecheck files and generate new empty ones. 4. (Build #2 type-check program): same as step 2. With this commit, the `IncrementalDriver` now tracks template type-checking _metadata_ for each input file. The metadata contains information about source mappings for generated type-checking code, as well as some diagnostics which were discovered at type-check analysis time. The actual type-checking code is stored in the TypeScript AST for type-checking files, which is now re-used between programs as follows: 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked, and the metadata registered in the `IncrementalDriver`. 3. (Build #2 main program): The `TypeCheckShimGenerator` now reuses _all_ .ngtypecheck `ts.SourceFile` shims from build #1's type-check program in the construction of build #2's main program. Some of the contents of these files might be stale (if a component's template changed, for example), but wholesale reuse here prevents unnecessary changes in the contents of the program at this point and makes TypeScript's job a lot easier. 4. (Build #2 type-check program): For those input files which have not "logically changed" (meaning components within are semantically the same as they were before), the compiler will re-use the type-check file metadata from build #1, and _not_ generate a new .ngtypecheck shim. For components which have logically changed or where the previous .ngtypecheck contents cannot otherwise be reused, code generation happens as before. PR Close #36211
2020-03-19 11:29:58 -07:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
```typescript=
// dir.ts
@Directive({selector: '[dir]'})
export class Dir {}
perf(ivy): reuse prior analysis work during incremental builds (#34288) Previously, the compiler performed an incremental build by analyzing and resolving all classes in the program (even unchanged ones) and then using the dependency graph information to determine which .js files were stale and needed to be re-emitted. This algorithm produced "correct" rebuilds, but the cost of re-analyzing the entire program turned out to be higher than anticipated, especially for component-heavy compilations. To achieve performant rebuilds, it is necessary to reuse previous analysis results if possible. Doing this safely requires knowing when prior work is viable and when it is stale and needs to be re-done. The new algorithm implemented by this commit is such: 1) Each incremental build starts with knowledge of the last known good dependency graph and analysis results from the last successful build, plus of course information about the set of files changed. 2) The previous dependency graph's information is used to determine the set of source files which have "logically" changed. A source file is considered logically changed if it or any of its dependencies have physically changed (on disk) since the last successful compilation. Any logically unchanged dependencies have their dependency information copied over to the new dependency graph. 3) During the `TraitCompiler`'s loop to consider all source files in the program, if a source file is logically unchanged then its previous analyses are "adopted" (and their 'register' steps are run). If the file is logically changed, then it is re-analyzed as usual. 4) Then, incremental build proceeds as before, with the new dependency graph being used to determine the set of files which require re-emitting. This analysis reuse avoids template parsing operations in many circumstances and significantly reduces the time it takes ngtsc to rebuild a large application. Future work will increase performance even more, by tackling a variety of other opportunities to reuse or avoid work. PR Close #34288
2019-12-05 16:03:17 -08:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
// cmp.ts
@Component({
selector: 'cmp',
template: '<div dir></div>', // Matches the `[dir]` selector
})
export class Cmp {}
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
// mod.ts
import {Dir} from './dir';
import {Cmp} from './cmp';
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
@NgModule({declarations: [Dir, Cmp]})
export class Mod {}
```
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
Here, `Cmp` never directly imports or refers to `Dir`, but it _does_ consume the directive in its
template. During emit, `Cmp` would receive a `directiveDefs` array:
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
```typescript=
// cmp.js
import * as i1 from './dir';
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
export class Cmp {
static cmp = defineComponent({
...
directiveDefs: [i1.Dir],
});
}
```
perf(ivy): reuse prior analysis work during incremental builds (#34288) Previously, the compiler performed an incremental build by analyzing and resolving all classes in the program (even unchanged ones) and then using the dependency graph information to determine which .js files were stale and needed to be re-emitted. This algorithm produced "correct" rebuilds, but the cost of re-analyzing the entire program turned out to be higher than anticipated, especially for component-heavy compilations. To achieve performant rebuilds, it is necessary to reuse previous analysis results if possible. Doing this safely requires knowing when prior work is viable and when it is stale and needs to be re-done. The new algorithm implemented by this commit is such: 1) Each incremental build starts with knowledge of the last known good dependency graph and analysis results from the last successful build, plus of course information about the set of files changed. 2) The previous dependency graph's information is used to determine the set of source files which have "logically" changed. A source file is considered logically changed if it or any of its dependencies have physically changed (on disk) since the last successful compilation. Any logically unchanged dependencies have their dependency information copied over to the new dependency graph. 3) During the `TraitCompiler`'s loop to consider all source files in the program, if a source file is logically unchanged then its previous analyses are "adopted" (and their 'register' steps are run). If the file is logically changed, then it is re-analyzed as usual. 4) Then, incremental build proceeds as before, with the new dependency graph being used to determine the set of files which require re-emitting. This analysis reuse avoids template parsing operations in many circumstances and significantly reduces the time it takes ngtsc to rebuild a large application. Future work will increase performance even more, by tackling a variety of other opportunities to reuse or avoid work. PR Close #34288
2019-12-05 16:03:17 -08:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
If `Dir`'s selector were to change to `[other]` in an incremental step, it might no longer
match `Cmp`'s template, in which case `cmp.js` would need to be re-emitted.
perf(ivy): reuse prior analysis work during incremental builds (#34288) Previously, the compiler performed an incremental build by analyzing and resolving all classes in the program (even unchanged ones) and then using the dependency graph information to determine which .js files were stale and needed to be re-emitted. This algorithm produced "correct" rebuilds, but the cost of re-analyzing the entire program turned out to be higher than anticipated, especially for component-heavy compilations. To achieve performant rebuilds, it is necessary to reuse previous analysis results if possible. Doing this safely requires knowing when prior work is viable and when it is stale and needs to be re-done. The new algorithm implemented by this commit is such: 1) Each incremental build starts with knowledge of the last known good dependency graph and analysis results from the last successful build, plus of course information about the set of files changed. 2) The previous dependency graph's information is used to determine the set of source files which have "logically" changed. A source file is considered logically changed if it or any of its dependencies have physically changed (on disk) since the last successful compilation. Any logically unchanged dependencies have their dependency information copied over to the new dependency graph. 3) During the `TraitCompiler`'s loop to consider all source files in the program, if a source file is logically unchanged then its previous analyses are "adopted" (and their 'register' steps are run). If the file is logically changed, then it is re-analyzed as usual. 4) Then, incremental build proceeds as before, with the new dependency graph being used to determine the set of files which require re-emitting. This analysis reuse avoids template parsing operations in many circumstances and significantly reduces the time it takes ngtsc to rebuild a large application. Future work will increase performance even more, by tackling a variety of other opportunities to reuse or avoid work. PR Close #34288
2019-12-05 16:03:17 -08:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
### SemanticSymbols
perf(ivy): reuse prior analysis work during incremental builds (#34288) Previously, the compiler performed an incremental build by analyzing and resolving all classes in the program (even unchanged ones) and then using the dependency graph information to determine which .js files were stale and needed to be re-emitted. This algorithm produced "correct" rebuilds, but the cost of re-analyzing the entire program turned out to be higher than anticipated, especially for component-heavy compilations. To achieve performant rebuilds, it is necessary to reuse previous analysis results if possible. Doing this safely requires knowing when prior work is viable and when it is stale and needs to be re-done. The new algorithm implemented by this commit is such: 1) Each incremental build starts with knowledge of the last known good dependency graph and analysis results from the last successful build, plus of course information about the set of files changed. 2) The previous dependency graph's information is used to determine the set of source files which have "logically" changed. A source file is considered logically changed if it or any of its dependencies have physically changed (on disk) since the last successful compilation. Any logically unchanged dependencies have their dependency information copied over to the new dependency graph. 3) During the `TraitCompiler`'s loop to consider all source files in the program, if a source file is logically unchanged then its previous analyses are "adopted" (and their 'register' steps are run). If the file is logically changed, then it is re-analyzed as usual. 4) Then, incremental build proceeds as before, with the new dependency graph being used to determine the set of files which require re-emitting. This analysis reuse avoids template parsing operations in many circumstances and significantly reduces the time it takes ngtsc to rebuild a large application. Future work will increase performance even more, by tackling a variety of other opportunities to reuse or avoid work. PR Close #34288
2019-12-05 16:03:17 -08:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
For each decorated class being processed, the compiler creates a `SemanticSymbol` representing the
data regarding that class that's involved in these "indirect" relationships. During the
compiler's `resolve` phase, these `SemanticSymbol`s are connected together to form a "semantic
dependency graph". Two classes of data are recorded:
perf(ivy): reuse prior analysis work during incremental builds (#34288) Previously, the compiler performed an incremental build by analyzing and resolving all classes in the program (even unchanged ones) and then using the dependency graph information to determine which .js files were stale and needed to be re-emitted. This algorithm produced "correct" rebuilds, but the cost of re-analyzing the entire program turned out to be higher than anticipated, especially for component-heavy compilations. To achieve performant rebuilds, it is necessary to reuse previous analysis results if possible. Doing this safely requires knowing when prior work is viable and when it is stale and needs to be re-done. The new algorithm implemented by this commit is such: 1) Each incremental build starts with knowledge of the last known good dependency graph and analysis results from the last successful build, plus of course information about the set of files changed. 2) The previous dependency graph's information is used to determine the set of source files which have "logically" changed. A source file is considered logically changed if it or any of its dependencies have physically changed (on disk) since the last successful compilation. Any logically unchanged dependencies have their dependency information copied over to the new dependency graph. 3) During the `TraitCompiler`'s loop to consider all source files in the program, if a source file is logically unchanged then its previous analyses are "adopted" (and their 'register' steps are run). If the file is logically changed, then it is re-analyzed as usual. 4) Then, incremental build proceeds as before, with the new dependency graph being used to determine the set of files which require re-emitting. This analysis reuse avoids template parsing operations in many circumstances and significantly reduces the time it takes ngtsc to rebuild a large application. Future work will increase performance even more, by tackling a variety of other opportunities to reuse or avoid work. PR Close #34288
2019-12-05 16:03:17 -08:00
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
* Information about the public shape API of the class.
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
For example, directives have a public API which includes their selector, any inputs or outputs, and
their `exportAs` name if any.
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
* Information about the emit shape of the class, including any dependencies on
other `SemanticSymbol`s.
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
This information allows the compiler to determine which classes have been semantically affected by
other changes in the program (and therefore need to be re-emitted) according to a simple algorithm:
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
1. Determine the set of `SemanticSymbol`s which have had their public API changed.
2. For each `SemanticSymbol`, determine if its emit shape was affected by any of the public API
changes (that is, if it depends on a symbol with public API changes).
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
### Determination of public API changes
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
The first step of this algorithm is to determine, for each `SemanticSymbol`, if its public API has
been affected. Doing this requires knowing which `SemanticSymbol` in the previous program
corresponds to the current version of the symbol. There are two ways that symbols can be "matched":
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
* The old and new symbols share the same `ts.ClassDeclaration`.
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
This is true whenever the `ts.SourceFile` declaring the class has not changed between the old and
new programs. The public API of the symbol may still have changed (such as when a directive's
selector is determined by a constant imported from another file, like in one of the examples above).
But if the declaration file itself has not changed, then the previous symbol can be directly found
this way.
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
* By its unique path and name.
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
If the file _has_ changed, then symbols can be located by their declaration path plus their name, if
they have a name that's guaranteed to be unique. Currently, this means that the classes are declared
at the top level of the source file, so their names are in the module's scope. If this is the case,
then a symbol can be matched to its ancestor even if the declaration itself has changed in the
meantime. Note that there is no guarantee the symbol will be of the same type - an incremental step
may change a directive into a component, or even into a pipe or injectable.
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
Once a previous symbol is located, its public API can be compared against the current version of the
symbol. Symbols without a valid ancestor are assumed to have changed in their public API.
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
The compiler processes all `SemanticSymbol`s and determines the `Set` of them which have experienced
public API changes. In the example above, this `Set` would include the `DirectiveSymbol` for `Dir`,
since its selector would have changed.
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
### Determination of emit requirements
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
For each potential output file, the compiler then looks at all declared `SemanticSymbol`s and uses
their ancestor symbol (if present) as well as the `Set` of public API changes to make a
determination if that file needs be emitted.
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
In the case of a `ComponentSymbol`, for example, the symbol tracks the dependencies of the component
which will go into the `directiveDefs` array. If that array is different, the component needs to be
re-emitted. Even if the same directives are referenced, if one of those directives has changed in
its public API, the emitted output (especially when generating prelink library code) may be
affected, and the component needs to be re-emitted.
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
### `SemanticReference`s
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
`ComponentSymbol`s track their dependencies via an intermediate type, a `SemanticReference`. Such
references track not only the `SemanticSymbol` of the dependency, but also the name by which it was
imported previously. Even if a dependency's identity and public API remain the same, changes in how
it was exported can affect the import which needs to be emitted within the component consuming it,
and thus would require a re-emit.
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
## Reuse of template type-checking results
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
Since type-checking block (TCB) generation for template type-checking is a form of
emit, `SemanticSymbol`s also track the type-checking shape of decorated classes. This includes any
data which is not public API, but upon which the TCB generation for components might depend. Such
data includes:
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
* Type-checking API shape from any base classes, since TCB generation uses information from the full
inheritance chain of a directive/pipe.
* The generic signature shape of the class.
* Private field names for `@Input`s and `@Output`s.
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
Using a similar algorithm to the `emit` optimization, the compiler can determine which files need
their type-checking code regenerated, and which can continue to use TCB code from the previous
program, even if some dependencies have unrelated changes.
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
## Unsuccessful compilation attempts
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
Often, incremental compilations will fail. The user's input program may contain incomplete changes,
typos, semantic errors, or other problems which prevent the compiler from fully analyzing or
emitting it. Such errors create problems for incremental build correctness, as the compiler relies
on information extracted from the previous program to correctly optimize the next compilation. If
the previous compilation failed, such information may be unreliable.
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
In theory, the compiler could simply not perform incremental compilation on top of a broken build,
and assume that it must redo all analysis and re-emit all files, but this would result in
devestatingly poor performance for common developer workflows that rely on automatically running
builds and/or tests on every change. The compiler must deal with such scenarios more gracefully.
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
ngtsc solves this problem by always performing its incremental steps from a "last known good"
compilation. Thus, if compilation A succeeds, and a subsequent compilation B fails, compilation C
will begin using the state of compilation A as a starting point. This requires tracking of two
important pieces of state:
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
* Reusable information, such as analysis results, from the last known good compilation.
* The accumulated set of files which have physically changed since the last known good compilation.
perf(compiler-cli): detect semantic changes and their effect on an incremental rebuild (#40947) In Angular programs, changing a file may require other files to be emitted as well due to implicit NgModule dependencies. For example, if the selector of a directive is changed then all components that have that directive in their compilation scope need to be recompiled, as the change of selector may affect the directive matching results. Until now, the compiler solved this problem using a single dependency graph. The implicit NgModule dependencies were represented in this graph, such that a changed file would correctly also cause other files to be re-emitted. This approach is limited in a few ways: 1. The file dependency graph is used to determine whether it is safe to reuse the analysis data of an Angular decorated class. This analysis data is invariant to unrelated changes to the NgModule scope, but because the single dependency graph also tracked the implicit NgModule dependencies the compiler had to consider analysis data as stale far more often than necessary. 2. It is typical for a change to e.g. a directive to not affect its public API—its selector, inputs, outputs, or exportAs clause—in which case there is no need to re-emit all declarations in scope, as their compilation output wouldn't have changed. This commit implements a mechanism by which the compiler is able to determine the impact of a change by comparing it to the prior compilation. To achieve this, a new graph is maintained that tracks all public API information of all Angular decorated symbols. During an incremental compilation this information is compared to the information that was captured in the most recently succeeded compilation. This determines the exact impact of the changes to the public API, which is then used to determine which files need to be re-emitted. Note that the file dependency graph remains, as it is still used to track the dependencies of analysis data. This graph does no longer track the implicit NgModule dependencies, which allows for better reuse of analysis data. These changes also fix a bug where template type-checking would fail to incorporate changes made to a transitive base class of a directive/component. This used to be a problem because transitive base classes were not recorded as a transitive dependency in the file dependency graph, such that prior type-check blocks would erroneously be reused. This commit also fixes an incorrectness where a change to a declaration in NgModule `A` would not cause the declarations in NgModules that import from NgModule `A` to be re-emitted. This was intentionally incorrect as otherwise the performance of incremental rebuilds would have been far worse. This is no longer a concern, as the compiler is now able to only re-emit when actually necessary. Fixes #34867 Fixes #40635 Closes #40728 PR Close #40947
2020-11-20 21:18:46 +01:00
Using this information, ngtsc is able to "forget" about the intermediate failed attempts and begin
each new compilation as if it were a single step from the last successful build. It can then ensure
complete correctness of its reuse optimization, since it has reliable data extracted from the "
previous" successful build.