The link to the "speeding-up-ngcc-compilation" URL does not exist,
it was removed shortly after it was added, but the link in the ngcc
error message was not updated.
Fixes#39837
PR Close#40285
The `annotations` package in the compiler previously contained a registry
which tracks NgModule scopes for template type-checking, including unifying
all type-checking metadata across class inheritance lines.
This commit generalizes this utility and prepares it for use in the
`TemplateTypeChecker` as well, to back APIs used by the language service.
PR Close#40032
Prior to this change, the `setClassMetadata` call would be invoked
inside of an IIFE that was marked as pure. This allows the call to be
tree-shaken away in production builds, as the `setClassMetadata` call
is only present to make the original class metadata available to the
testing infrastructure. The pure marker is problematic, though, as the
`setClassMetadata` call does in fact have the side-effect of assigning
the metadata into class properties. This has worked under the assumption
that only build optimization tools perform tree-shaking, however modern
bundlers are also able to elide calls that have been marked pure so this
assumption does no longer hold. Instead, an `ngDevMode` guard is used
which still allows the call to be elided but only by tooling that is
configured to consider `ngDevMode` as constant `false` value.
PR Close#39987
Add a TaggedTemplateExpr to represent tagged template literals in
Angular's syntax tree (more specifically Expression in output_ast.ts).
Also update classes that implement ExpressionVisitor to add support for
tagged template literals in different contexts, such as JIT compilation
and conversion to JS.
Partial support for tagged template literals had already been
implemented to support the $localize tag used by Angular's i18n
framework. Where applicable, this code was refactored to support
arbitrary tags, although completely replacing the i18n-specific support
for the $localize tag with the new generic support for tagged template
literals may not be completely trivial, and is left as future work.
PR Close#39122
When the compiler is invoked via ngc or the Angular CLI, its APIs are used
under the assumption that Angular analysis/diagnostics are only requested if
the program has no TypeScript-level errors. A result of this assumption is
that the incremental engine has not needed to resolve changes via its
dependency graph when the program contained broken imports, since broken
imports are a TypeScript error.
The Angular Language Service for Ivy is using the compiler as a backend, and
exercising its incremental compilation APIs without enforcing this
assumption. As a result, the Language Service has run into issues where
broken imports cause incremental compilation to fail and produce incorrect
results.
This commit introduces a mechanism within the compiler to keep track of
files for which dependency analysis has failed, and to always treat such
files as potentially affected by future incremental steps. This is tested
via the Language Service infrastructure to ensure that the compiler is doing
the right thing in the case of invalid imports.
PR Close#39923
To avoid overwhelming a user with secondary diagnostics that derive from a
"root cause" error, the compiler has the notion of a "poisoned" NgModule.
An NgModule becomes poisoned when its declaration contains semantic errors:
declarations which are not components or pipes, imports which are not other
NgModules, etc. An NgModule also becomes poisoned if it imports or exports
another poisoned NgModule.
Previously, the compiler tracked this poisoned status as an alternate state
for each scope. Either a correct scope could be produced, or the entire
scope would be set to a sentinel error value. This meant that the compiler
would not track any information about a scope that was determined to be in
error.
This method presents several issues:
1. The compiler is unable to support the language service and return results
when a component or its module scope is poisoned.
This is fine for compilation, since diagnostics will be produced showing the
error(s), but the language service needs to still work for incorrect code.
2. `getComponentScopes()` does not return components with a poisoned scope,
which interferes with resource tracking of incremental builds.
If the component isn't included in that list, then the NgModule for it will
not have its dependencies properly tracked, and this can cause future
incremental build steps to produce incorrect results.
This commit changes the tracking of poisoned module scopes to use a flag on
the scope itself, rather than a sentinel value that replaces the scope. This
means that the scope itself will still be tracked, even if it contains
semantic errors. A test is added to the language service which verifies that
poisoned scopes can still be used in template type-checking.
PR Close#39923
Previously, if a trait's analysis step resulted in diagnostics, the trait
would be considered "errored" and no further operations, including register,
would be performed. Effectively, this meant that the compiler would pretend
the class in question was actually undecorated.
However, this behavior is problematic for several reasons:
1. It leads to inaccurate diagnostics being reported downstream.
For example, if a component is put into the error state, for example due to
a template error, the NgModule which declares the component would produce a
diagnostic claiming that the declaration is neither a directive nor a pipe.
This happened because the compiler wouldn't register() the component trait,
so the component would not be recorded as actually being a directive.
2. It can cause incorrect behavior on incremental builds.
This bug is more complex, but the general issue is that if the compiler
fails to associate a component and its module, then incremental builds will
not correctly re-analyze the module when the component's template changes.
Failing to register the component as such is one link in the larger chain of
issues that result in these kinds of issues.
3. It lumps together diagnostics produced during analysis and resolve steps.
This is not causing issues currently as the dependency graph ensures the
right classes are re-analyzed when needed, instead of showing stale
diagnostics. However, the dependency graph was not intended to serve this
role, and could potentially be optimized in ways that would break this
functionality.
This commit removes the concept of an "errored" trait entirely from the
trait system. Instead, analyzed and resolved traits have corresponding (and
separate) diagnostics, in addition to potentially `null` analysis results.
Analysis (but not resolution) diagnostics are carried forward during
incremental build operations. Compilation (emit) is only performed when
a trait reaches the resolved state with no diagnostics.
This change is functionally different than before as the `register` step is
now performed even in the presence of analysis errors, as long as analysis
results are also produced. This fixes problem 1 above, and is part of the
larger solution to problem 2.
PR Close#39923
ngtsc has a robust suite of testing utilities, designed for in-memory
testing of a TypeScript compiler. Previously these utilities lived in the
`test` directory for the compiler-cli package.
This commit moves those utilities to an `ngtsc/testing` package, enabling
them to be depended on separately and opening the door for using them from
the upcoming language server testing infrastructure.
As part of this refactoring, the `fake_core` package (a lightweight API
replacement for @angular/core) is expanded to include functionality needed
for Language Service test use cases.
PR Close#39594
In addition to the template mapping that already existed, we want to also track the mapping for external
style files. We also store the `ts.Expression` in the registry so external tools can look up a resource
on a component by expression and avoid reading the value.
PR Close#39373
This reverts commit 561c0f81a0.
The original commit provided a quick escape from an already terminal
situation by killing the process if the PID in the lockfile was not
found in the list of processes running on the current machine.
But this broke use-cases where the node_modules was being shared between
multiple machines (or more commonly Docker containers on the same actual
machine).
Fixes#38875
PR Close#39435
Previously, UMD/CommonJS class inline declarations of the form:
```ts
exports.Foo = (function() { function Foo(); return Foo; })();
```
were capturing the whole IIFE as the implementation, rather than
the inner class (i.e. `function Foo() {}` in this case). This caused
the interpreter to break when it was trying to access such an export,
since it would try to evaluate the IIFE rather than treating it as a class
declaration.
PR Close#39346
Previously, inline exports of the form `exports.foo = <implementation>;` were
being interpreted (by the ngtsc `PartialInterpeter`) as `Reference` objects.
This is not what is desired since it prevents the value of the export
from being unpacked, such as when analyzing `NgModule` declarations:
```
exports.directives = [Directive1, Directive2];
@NgImport({declarations: [exports.directives]})
class AppModule {}
```
In this example the interpreter would think that `exports.directives`
was a reference rather than an array that needs to be unpacked.
This bug was picked up by the ngcc-validation repository. See
https://github.com/angular/ngcc-validation/pull/1990 and
https://circleci.com/gh/angular/ngcc-validation/17130
PR Close#39267
When ngcc is configured to run with the `--use-program-dependencies`
flag, as is the case in the CLI's asynchronous processing, it will scan
all source files in the program, starting from the program's root files
as configured in the tsconfig. Each individual root file could
potentially rescan files that had already been scanned for an earlier
root file, causing a severe performance penalty if the number of root
files is large. This would be the case if glob patterns are used in the
"include" specification of a tsconfig file.
This commit avoids the performance penalty by keeping track of the files
that have been scanned across all root files, such that no source file
is scanned multiple times.
Fixes#39240
PR Close#39254
Previously, any declarations that were defined "inline" were not
recognised by the `UmdReflectionHost`.
For example, the following syntax was completely unrecognized:
```ts
var Foo_1;
exports.Foo = Foo_1 = (function() {
function Foo() {}
return Foo;
})();
exports.Foo = Foo_1 = __decorate(SomeDecorator, Foo);
```
Such inline classes were ignored and not processed by ngcc.
This lack of processing led to failures in Ivy applications that relied
on UMD formats of libraries such as `syncfusion/ej2-angular-ui-components`.
Now all known inline UMD exports are recognized and processed accordingly.
Fixes#38947
PR Close#38959
Previously these tests were checking multiple specific expression
types. The new helper function is more general and will also support
`PropertyAccessExpression` nodes for `InlineDeclaration` types.
PR Close#38959
Previously the `ConcreteDeclaration` and `InlineDeclaration` had
different properties for the underlying node type. And the `InlineDeclaration`
did not store a value that represented its declaration.
It turns out that a natural declaration node for an inline type is the
expression. For example in UMD/CommonJS this would be the `exports.<name>`
property access node.
So this expression is now used for the `node` of `InlineDeclaration` types
and the `expression` property is dropped.
To support this the codebase has been refactored to use a new `DeclarationNode`
type which is a union of `ts.Declaration|ts.Expression` instead of `ts.Declaration`
throughout.
PR Close#38959
This makes these tests more resilient to changes in the test code
structure. For example switching from
```
var SomeClass = <implementation>;
exports.SomeClass = SomeClass;
```
to
```
exports.SomeClass = <implementation>;
```
PR Close#38959
Previously `getDeclaration()` would only return the first node that matched
the name passed in and then assert the predicate on this single node.
It also only considered a subset of possible declaration types that we might
care about.
Now the function will parse the whole tree collecting an array of all the
nodes that match the name. It then filters this array based on the predicate
and only errors if the filtered array is empty.
This makes this function much more resilient to more esoteric code formats
such as UMD.
PR Close#38959
The new function does not try to restrict the kind of AST node that it
finds, leaving that to the caller. This will make it more resuable in the
UMD reflection host.
PR Close#38959
Sometimes UMD exports appear in the following form:
```
exports.MyClass = alias1 = alias2 = <<declaration>>
```
Previously the declaration of the export would have been captured
as `alias1 = alias2 = <<declaration>>`, which the `PartialInterpreter`
would have failed on, since it cannot handle assignments.
Now we skip over these aliases capturing only the `<<declaration>>`
expression.
Fixes#38947
PR Close#38959
UMD files export values by assigning them to an `exports` variable.
When evaluating expressions ngcc was failing to cope with expressions
like `exports.MyComponent`.
This commit fixes the `UmdReflectionHost.getDeclarationOfIdentifier()`
method to map the `exports` variable to the current source file.
PR Close#38959
The `SIMPLE_CLASS_FILE` contained a `ChildClass` that had an
internal aliases implementation and extended a `SuperClass` base
class. The call to `__extends` was using the wrong argument for
the child class.
PR Close#38959
This clarifies that this is specifically about statements of the form
`exports.<name> = <declaration>`, rather than a general export
statement such as `export class <ClassName> { ... }`.
PR Close#38959
This is a precursor to introducing the Angular linker. As an initial
step, a compiler option to configure the compilation mode is introduced.
This option is initially internal until the linker is considered ready.
PR Close#38938
This commit adds an API to `NgCompiler`, a method called
`getComponentsWithTemplateFile`. Given a filesystem path to an external
template file, it retrieves a `Set` (actually a `ReadonlySet`) of component
declarations which are using this template. In most cases, this will only be
a single component.
This information is easily determined by the compiler during analysis, but
is hard for a lot of Angular tooling (e.g. the language service) to infer
independently. Therefore, it makes sense to expose this as a compiler API.
PR Close#39002
This commit refactors the `ExpressionTranslatorVisitor` so that it
is not tied directly to the TypeScript AST. Instead it uses generic
`TExpression` and `TStatement` types that are then converted
to concrete types by the `TypeScriptAstFactory`.
This paves the way for a `BabelAstFactory` that can be used to
generate Babel AST nodes instead of TypeScript, which will be
part of the new linker tool.
PR Close#38775
When the target of the compiler is ES2015 or newer then we should
be generating `let` and `const` variable declarations rather than `var`.
PR Close#38775
In #38666 we changed how ngcc deals with type expressions, where it
would now always emit the original type expression into the generated
code as a "local" type value reference instead of synthesizing new
imports using an "imported" type value reference. This was done as a fix
to properly deal with renamed symbols, however it turns out that the
compiler has special handling for certain imported symbols, e.g.
`ChangeDetectorRef` from `@angular/core`. The "local" type value
reference prevented this special logic from being hit, resulting in
incorrect compilation of pipe factories.
This commit fixes the issue by manually inspecting the import of the
type expression, in order to return an "imported" type value reference.
By manually inspecting the import we continue to handle renamed symbols.
Fixes#38883
PR Close#38892
Common AST formats such as TS and Babel do not use a separate
node for comments, but instead attach comments to other AST nodes.
Previously this was worked around in TS by creating a `NotEmittedStatement`
AST node to attach the comment to. But Babel does not have this facility,
so it will not be a viable approach for the linker.
This commit refactors the output AST, to remove the `CommentStmt` and
`JSDocCommentStmt` nodes. Instead statements have a collection of
`leadingComments` that are rendered/attached to the final AST nodes
when being translated or printed.
PR Close#38811
In the integration test suite of ngcc, we load a set of files from
`node_modules` into memory. This includes the `typescript` package and
`@angular` scoped packages, which account for a large number of large
files that needs to be loaded from disk. This commit moves this work
to the top-level, such that it doesn't have to be repeated in all tests.
PR Close#38840
Recent optimizations to ngcc have significantly reduced the total time
it takes to process `node_modules`, to such extend that sharding across
multiple processes has become less effective. Previously, running
ngcc asynchronously would allow for up to 8 workers to be allocated,
however these workers have to repeat work that could otherwise be shared.
Because ngcc is now able to reuse more shared computations, the overhead
of multiple workers is increased and therefore becomes less effective.
As an additional benefit, having fewer workers requires less memory and
less startup time.
To give an idea, using the following test setup:
```bash
npx @angular/cli new perf-test
cd perf-test
yarn ng add @angular/material
./node_modules/.bin/ngcc --properties es2015 module main \
--first-only --create-ivy-entry-points
```
We observe the following figures on CI:
| | 10.1.1 | PR #38840 |
| ----------------- | --------- | --------- |
| Sync | 85s | 25s |
| Async (8 workers) | 22s | 16s |
| Async (4 workers) | - | 11s |
In addition to changing the default number of workers, ngcc will now
use the environment variable `NGCC_MAX_WORKERS` that may be configured
to either reduce or increase the number of workers.
PR Close#38840
ngcc creates typically two `ts.Program` instances for each entry-point,
one for processing sources and another one for processing the typings.
The creation of these programs is somewhat expensive, as it concerns
module resolution and parsing of source files.
This commit implements several layers of caching to optimize the
creation of programs:
1. A shared module resolution cache across all entry-points within a
single invocation of ngcc. Both the sources and typings program
benefit from this cache.
2. Sharing the parsed `ts.SourceFile` for a single entry-point between
the sources and typings program.
3. Sharing parsed `ts.SourceFile`s of TypeScript's default libraries
across all entry-points within a single invocation. Some of these
default library typings are large and therefore expensive to parse,
so sharing the parsed source files across all entry-points offers
a significant performance improvement.
Using a bare CLI app created using `ng new` + `ng add @angular/material`,
the above changes offer a 3-4x improvement in ngcc's processing time
when running synchronously and ~2x improvement for asynchronous runs.
PR Close#38840
If a type has been renamed when it was exported, we need to
reference the external public alias name rather than the internal
original name for the type. Otherwise we will try to import the
type by its internal name, which is not publicly accessible.
Fixes#38238
PR Close#38666
Similarly to the change we landed in the `@angular/core` reflection
capabilities, we need to make sure that ngcc can detect pass-through
delegate constructors for classes using downleveled ES2015 output.
More details can be found in the preceding commit, and in the issue
outlining the problem: #38453.
Fixes#38453.
PR Close#38463
In TypeScript 3.8 support was added for type-only imports, which only brings in
the symbol as a type, not their value. The Angular compiler did not yet take
the type-only keyword into account when representing symbols in type positions
as value expressions. The class metadata that the compiler emits would include
the value expression for its parameter types, generating actual imports as
necessary. For type-only imports this should not be done, as it introduces an
actual import of the module that was originally just a type-only import.
This commit lets the compiler deal with type-only imports specially, preventing
a value expression from being created.
Fixes#37900
PR Close#37912
This commit significantly refactors the 'typecheck' package to introduce a
new abstraction, the `TemplateTypeChecker`. To achieve this:
* a 'typecheck:api' package is introduced, containing common interfaces that
consumers of the template type-checking infrastructure can depend on
without incurring a dependency on the template type-checking machinery as
a whole.
* interfaces for `TemplateTypeChecker` and `TypeCheckContext` are introduced
which contain the abstract operations supported by the implementation
classes `TemplateTypeCheckerImpl` and `TypeCheckContextImpl` respectively.
* the `TemplateTypeChecker` interface supports diagnostics on a whole
program basis to start with, but the implementation is purposefully
designed to support incremental diagnostics at a per-file or per-component
level.
* `TemplateTypeChecker` supports direct access to the type check block of a
component.
* the testing utility is refactored to be a lot more useful, and new tests
are added for the new abstraction.
PR Close#38105
In CLI v10 there was a move to use the new solution-style tsconfig
which became available in TS 3.9.
The result of this is that the standard tsconfig.json no longer contains
important information such as "paths" mappings, which ngcc might need to
correctly compute dependencies.
ngcc (and ngc and tsc) infer the path to tsconfig.json if not given an
explicit tsconfig file-path. But now that means it infers the solution
tsconfig rather than one that contains the useful information it used to
get.
This commit logs a warning in this case to inform the developer
that they might not have meant to load this tsconfig and offer
alternative options.
Fixes#36386
PR Close#38003
The `fs.relative()` method assumed that the file-system is a single tree,
which is not the case in Windows, where you can have multiple drives,
e.g. `C:`, `D:` etc.
This commit changes `fs.relative()` so that it no longer forces the result
to be a `PathSegment` and then flows that refactoring through the rest of
the compiler-cli (and ngcc). The main difference is that now, in some cases,
we needed to check whether the result is "rooted", i.e an `AbsoluteFsPath`,
rather than a `PathSegment`, before using it.
Fixes#36777
PR Close#37959
When ngcc creates an entry-point program, the `allowJs` option is enabled
in order to operate on the JavaScript source files of the entry-point.
A side-effect of this approach is that external modules that don't ship
declaration files will also have their JavaScript source files loaded
into the program, as the `allowJs` flag allows for them to be imported.
This may pose an issue in certain edge cases, where ngcc would inadvertently
operate on these external modules. This can introduce all sorts of undesirable
behavior and incompatibilities, e.g. the reflection host that is selected for
the entry-point's format could be incompatible with that of the external
module's JavaScript bundles.
To avoid these kinds of issues, module resolution that would resolve to
a JavaScript file located outside of the package will instead be rejected,
as if the file would not exist. This would have been the behavior when
`allowJs` is set to false, which is the case in typical Angular compilations.
Fixes#37508
PR Close#37596
Changes `isWithinPackage` to take an `AbsoluteFsPath` instead of `ts.SourceFile`,
to allow for an upcoming change to use it when no `ts.SourceFile` is available,
but just a path.
PR Close#37596
Previously an error thrown in the `analyzeFn` would cause
the ngcc process to exit immediately without removing the
lockfile, and potentially before the unlocker process had been
successfully spawned resulting in the lockfile being orphaned
and left behind.
Now we catch these errors and remove the lockfile as needed.
PR Close#37739
Previously the `ProgramBasedEntryPointFinder` was parsing all the
entry-points referenced by the program for dependencies even if all the
entry-points had been processed already.
Now this entry-point finder will re-use the `EntryPointManifest` to load
the entry-point dependencies when possible which avoids having to parse
them all again, on every invocation of ngcc.
Previously the `EntryPointManifest` was only used in the
`DirectoryWalkerEntryPointFinder`, which also contained the logic for
computing the contents of the manifest. This logic has been factored out
into an `EntryPointCollector` class. Both the `ProgramBasedEntryPointFinder`
and `DirectoryWalkerEntryPointFinder` now use the `EntryPointManifest` and
the `EntryPointCollector`.
The result of this change is that there is a small cost on the first run of
ngcc to compute and store the manifest - the processing takes 102% of the
processing time before this PR. But on subsequent runs there is a
significant benefit on subsequent runs - the processing takes around 50%
of the processing time before this PR.
PR Close#37665
Webpack and other build tools sometimes inline the contents of the
source files in their generated source-maps, and at the same time
change the paths to be prefixed with a protocol, such as `webpack://`.
This can confuse tools that need to read these paths, so now it is
possible to provide a mapping to where these files originated.
PR Close#32912
The file-writing error in the this commit can also be the result
of the ngcc process dying in the middle of writing files.
This commit improves the error message to offer a resolution
in case this is the reason for the error.
Fixes#36393
PR Close#37672
The `SourceFile` and associated code is general and reusable in
other projects (such as `@angular/localize`). Moving it to `ngtsc`
makes it more easily shared.
PR Close#37114
The `Logger` interface and its related classes are general purpose
and could be used by other tooling. Moving it into ngtsc is a more
suitable place from which to share it - similar to the FileSystem stuff.
PR Close#37114