Currently if an Angular library has multiple unnamed module re-exports, NGC will
generate incorrect metdata if the project is using the flat-module bundle option.
e.g.
_public-api.ts_
```ts
export * from '@mypkg/secondary1';
export * from '@mypkg/secondary2';
```
There are clearly two unnamed re-exports in the `public-api.ts` file. NGC right now
accidentally overwrites all previous re-exports with the last one. Resulting in the
generated metadata only containing a reference to `@mypkg/secondary2`.
This is problematic as it is common for primary library entry-points to have
multiple re-exports (e.g. Material re-exporting all public symbols; or flex-layout
exporting all public symbols from their secondary entry-points).
Currently Angular Material works around this issue by manually creating
a metadata file that declares the re-exports from all unnamed re-exports.
(see: https://github.com/angular/material2/blob/master/tools/package-tools/build-release.ts#L78-L85)
This workaround works fine currently, but is no longer easily integrated when
building the package output with Bazel. In order to be able to build such
libraries with Bazel (Material/flex-layout), we need to make sure that NGC
generates the proper flat-module metadata bundle.
PR Close#29360
Previously, ngtsc would resolve forward references while evaluating the
bootstrap, declaration, imports, and exports fields of NgModule types.
However, when generating the resulting ngModuleDef, the forward nature of
these references was not taken into consideration, and so the generated JS
code would incorrectly reference types not yet declared.
This commit fixes this issue by introducing function closures in the
NgModuleDef type, similarly to how NgComponentDef uses them for forward
declarations of its directives and pipes arrays. ngtsc will then generate
closures when required, and the runtime will unwrap them if present.
PR Close#29198
This fixes an issue with commit b6f6b117. In this commit, default imports
processed in a type-to-value conversion were recorded as non-local imports
with a '*' name, and the ImportManager generated a new default import for
them. When transpiled to ES2015 modules, this resulted in the following
correct code:
import i3 from './module';
// somewhere in the file, a value reference of i3:
{type: i3}
However, when the AST with this synthetic import and reference was
transpiled to non-ES2015 modules (for example, to commonjs) an issue
appeared:
var module_1 = require('./module');
{type: i3}
TypeScript renames the imported identifier from i3 to module_1, but doesn't
substitute later references to i3. This is because the import and reference
are both synthetic, and never went through the TypeScript AST step of
"binding" which associates the reference to its import. This association is
important during emit when the identifiers might change.
Synthetic (transformer-added) imports will never be bound properly. The only
possible solution is to reuse the user's original import and the identifier
from it, which will be properly downleveled. The issue with this approach
(which prompted the fix in b6f6b117) is that if the import is only used in a
type position, TypeScript will mark it for deletion in the generated JS,
even though additional non-type usages are added in the transformer. This
again would leave a dangling import.
To work around this, it's necessary for the compiler to keep track of
identifiers that it emits which came from default imports, and tell TS not
to remove those imports during transpilation. A `DefaultImportTracker` class
is implemented to perform this tracking. It implements a
`DefaultImportRecorder` interface, which is used to record two significant
pieces of information:
* when a WrappedNodeExpr is generated which refers to a default imported
value, the ts.Identifier is associated to the ts.ImportDeclaration via
the recorder.
* when that WrappedNodeExpr is later emitted as part of the statement /
expression translators, the fact that the ts.Identifier was used is
also recorded.
Combined, this tracking gives the `DefaultImportTracker` enough information
to implement another TS transformer, which can recognize default imports
which were used in the output of the Ivy transform and can prevent them
from being elided. This is done by creating a new ts.ImportDeclaration for
the imports with the same ts.ImportClause. A test verifies that this works.
PR Close#29266
Prior to this change default selector for Components was not applied in case selector is missing or defined as an empty string. This update aligns this behavior between Ivy and VE: now default selector is used for Components when it's needed. Directives with empty selector are not allowed and trigger a compile-time error in both Ivy and VE.
PR Close#29239
Prior to this change the code didn't take into account the fact that decorators can be aliases while importing into a script. As a result, these decorators were not recognized by Angular and various failures happened because of that. Now we take aliases into account and resolve decorator name properly.
PR Close#29195
ngsummary files were generated with an export for each class declaration.
However, some Angular code declares classes (class Foo) and exports them
(export {Foo}) separately, which was causing incomplete summary files.
This commit expands the set of symbol names for which summary exports will
be generated, fixing this issue.
PR Close#29193
Previously, when the NgModule scope resolver discovered semantic errors
within a users NgModules, it would throw assertion errors. TODOs in the
codebase indicated these should become ts.Diagnostics eventually.
Besides producing better-looking errors, there is another reason to make
this change asap: these assertions were shadowing actual errors, via an
interesting mechanism:
1) a component would produce a ts.Diagnostic during its analyze() step
2) as a result, it wouldn't register component metadata with the scope
resolver
3) the NgModule for the component references it in exports, which was
detected as an invalid export (no metadata registering it as a
component).
4) the resulting assertion error would crash the compiler, hiding the
real cause of the problem (an invalid component).
This commit should mitigate this problem by converting scoping errors to
proper ts.Diagnostics. Additionally, we should consider registering some
marker indicating a class is a directive/component/pipe without actually
requiring full metadata to be produced for it, which would allow suppression
of errors like "invalid export" for such invalid types.
PR Close#29191
At the moment, certain tests relies on resolving the module with an index.d.ts, this root cause might be some implementations are missing from the mocks.
Similar to: 58b4045359
PR Close#28884
This commit refactors and expands ngtsc's support for generating imports of
values from imports of types (this is used for example when importing a
class referenced in a type annotation in a constructor).
Previously, this logic handled "import {Foo} from" and "import * as foo
from" style imports, but failed on imports of default values ("import
Foo from"). This commit moves the type-to-value logic to a separate file and
expands it to cover the default import case. Doing this also required
augmenting the ImportManager to track default as well as non-default import
generation. The APIs were made a little cleaner at the same time.
PR Close#29146
Prior to this commit, i18n instructions (i18n, i18nStart) were generated before listener instructions. As a result, event listeners were attached to the wrong element (text node, not the parent element). This change updates the order of instructions and puts i18n ones after listeners, to make sure listeners are attached to the right elements.
PR Close#29173
The content projection mechanism is static, in that it only looks at the static
template nodes before directives are matched and change detection is run.
When you have a selector-based content projection the selection is based
on nodes that are available in the template.
For example:
```
<ng-content selector="[some-attr]"></ng-content>
```
would match
```
<div some-attr="..."></div>
```
If you have an inline-template in your projected nodes. For example:
```
<div *ngIf="..." some-attr="..."></div>
```
This gets pre-parsed and converted to a canonical form.
For example:
```
<ng-template [ngIf]="...">
<div some-attr=".."></div>
</ng-template>
```
Note that only structural attributes (e.g. `*ngIf`) stay with the `<ng-template>`
node. The other attributes move to the contained element inside the template.
When this happens in ivy, the ng-template content is removed
from the component template function and is compiled into its own
template function. But this means that the information about the
attributes that were on the content are lost and the projection
selection mechanism is unable to match the original
`<div *ngIf="..." some-attr>`.
This commit adds support for this in ivy. Attributes are separated into three
groups (Bindings, Templates and "other"). For inline-templates the Bindings
and "other" types are hoisted back from the contained node to the `template()`
instruction, so that they can be used in content projection matching.
PR Close#29041
Prior to this change, the RegExp that was used to check for dashes in field names used "g" (global) flag that retains lastIndex, which might result in skipping some fields that should be wrapped in quotes (since lastIndex advanced beyond the next "-" location). This commit removes this flag and updates the test to make sure there are no regressions.
PR Close#29126
ngtsc occasionally converts a type reference (such as the type of a
parameter in a constructor) to a value reference (argument to a
directiveInject call). TypeScript has a bad habit of sometimes removing
the import statement associated with this type reference, because it's a
type only import when it initially looks at the file.
A solution to this is to always add an import to refer to a type position
value that's imported, and not rely on the existing import.
PR Close#29111
When ngtsc generates a .ngfactory shim, it does so based on the contents of
an original file in the program. Occasionally these original files have
comments at the top which are load-bearing (e.g. they contain jsdoc
annotations which are significant to downstream bundling tools). The
generated factory shims should preserve this comment.
This commit adds a step to the ngfactory generator to preserve the top-level
comment from the original source file.
FW-1006 #resolve
FW-1095 #resolve
PR Close#29065
Prior to this change, keys in "inputs" and "outputs" objects generated by compiler were not checked against unsafe characters. As a result, in some cases the generated code was throwing JS error. Now we check whether a given key contains any unsafe chars and wrap it in quotes if needed.
PR Close#28919
Currently, ngtsc has a bug where if you alias the name of a decorator when
importing it, it won't be detected properly. This is because the compiler
uses the aliased name and not the original, declared name of the decorator
for detection.
This commit fixes the compiler to compare against the declared name of
decorators when available, and adds a test to prevent regression.
PR Close#29061
ngtsc has cyclic import detection, to determine when adding an import to a
directive or pipe would create a cycle. However, this detection must also
account for already inserted imports, as it's possible for both directions
of a circular import to be inserted by Ivy (as opposed to at least one of
those edges existing in the user's program).
This commit fixes the circular import detection for components to take into
consideration already added edges. This is difficult for one critical
reason: only edges to files which will *actually* be imported should be
considered. However, that depends on which directives & pipes are used in
a given template, which is currently only known by running the
TemplateDefinitionBuilder during the 'compile' phase. This is too late; the
decision whether to use remote scoping (which consults the import graph) is
made during the 'resolve' phase, before any compilation has taken place.
Thus, the only way to correctly consider synthetic edges is for the compiler
to know exactly which directives & pipes are used in a template during
'resolve'. There are two ways to achieve this:
1) refactor `TemplateDefinitionBuilder` to do its work in two phases, with
directive matching occurring as a separate step which can be performed
earlier.
2) use the `R3TargetBinder` in the 'resolve' phase to independently bind the
template and get information about used directives.
Option 1 is ideal, but option 2 is currently used for practical reasons. The
cost of binding the template can be shared with template-typechecking.
PR Close#29040
In the @Component decorator, the 'host' field is an object which represents
host bindings. The type of this field is complex, but is generally of the
form {[key: string]: string}. Several different kinds of bindings can be
specified, depending on the structure of the key.
For example:
```
@Component({
host: {'[prop]': 'someExpr'}
})
```
will bind an expression 'someExpr' to the property 'prop'. This is known to
be a property binding because of the square brackets in the binding key.
If the binding key is a plain string (no brackets or parentheses), then it
is known as an attribute binding. In this case, the right-hand side is not
interpreted as an expression, but is instead a constant string.
There is no actual requirement that at build time, these constant strings
are known to the compiler, but this was previously enforced as a side effect
of requiring the binding expressions for property and event bindings to be
statically known (as they need to be parsed). This commit breaks that
relationship and allows the attribute bindings to be dynamic. In the case
that they are dynamic, the references to the dynamic values are reflected
into the Ivy instructions for attribute bindings.
PR Close#29033
Prior to this change, TypeScript stripped out some imports in case we reference a type that can be represented as a value (for ex. classes). This fix ensures that we use correct symbol identifier, which makes TypeScript retain the necessary import statements.
PR Close#28941
Angular supports using <style> and <link> tags inline in component
templates, but previously such tags were not implemented within the ngtsc
compiler. This commit introduces that support.
FW-1069 #resolve
PR Close#28997
Prior to this change i18n block bindings were converted to Expressions right away (once we first access them), when in non-i18n cases we processed them differently: the actual conversion happens at instructions generation. Because of this discrepancy, the output for bindings in i18n blocks was generated incorrectly (with invalid indicies in pipeBindN fns and invalid references to non-existent local variables). Now the bindings processing is unified and i18nExp instructions should contain right bind expressions.
PR Close#28969
Prior to this change, the logic that outputs i18n consts (like `const MSG_XXX = goog.getMsg(...)`) didn't have a check whether a given const that represent a certain i18n message was already included into the generated output. This commit adds the logic to mark corresponding i18n contexts after translation was generated, to avoid duplicate consts in the output.
PR Close#28967
In certain configurations (such as the g3 repository) which have lots of
small compilation units as well as strict dependency checking on generated
code, ngtsc's default strategy of directly importing directives/pipes into
components will not work. To handle these cases, an additional mode is
introduced, and is enabled when using the FileToModuleHost provided by such
compilation environments.
In this mode, when ngtsc encounters an NgModule which re-exports another
from a different file, it will re-export all the directives it contains at
the ES2015 level. The exports will have a predictable name based on the
FileToModuleHost. For example, if the host says that a directive Foo is
from the 'root/external/foo' module, ngtsc will add:
```
export {Foo as ɵng$root$external$foo$$Foo} from 'root/external/foo';
```
Consumers of the re-exported directive will then import it via this path
instead of directly from root/external/foo, preserving strict dependency
semantics.
PR Close#28852
This commit splits apart selector_scope.ts in ngtsc and extracts the logic
into two separate classes, the LocalModuleScopeRegistry and the
DtsModuleScopeResolver. The logic is cleaned up significantly and new tests
are added to verify behavior.
LocalModuleScopeRegistry implements the NgModule semantics for compilation
scopes, and handles NgModules declared in the current compilation unit.
DtsModuleScopeResolver implements simpler logic for export scopes and
handles NgModules declared in .d.ts files.
This is done in preparation for the addition of re-export logic to solve
StrictDeps issues.
PR Close#28852
Prior to this change presence of HTML comments inside <ng-content> caused compiler to throw an error that <ng-content> is not empty. Now HTML comments are not considered as a meaningful content, thus no error is thrown. This behavior is now aligned in Ivy/VE.
PR Close#28849
Prior to this change absolute file paths (like `/a/b/c/style.css`) were calculated taking current component file location into account. As a result, absolute file paths were calculated using current file as a root. This change updates this logic to ignore current file path in case of absolute paths.
PR Close#28789
Prior to this change, Ivy and VE CSS resource resolution was different: in addition to specified styleUrl (with .scss, .less and .styl extensions), VE also makes an attempt to resolve resource with .css extension. This change introduces similar logic for Ivy to make sure Ivy behavior is backwards compatible.
PR Close#28770
Prior to this change, the @fileoverview annotations added by users in source files or by tsickle during compilation might have change a location due to the fact that Ngtsc may prepend extra imports or constants. As a result, the output file is considered invalid by Closure (misplaced @fileoverview annotation). In order to resolve the problem we relocate @fileoverview annotation if we detect that its host node shifted.
PR Close#28723
This change is kind of similar to #27466, but instead of ensuring that
these shims can be generated, we also need to make sure that developers
are able to also use the factory shims like with `ngc`.
This issue is now surfacing because we have various old examples which
are now also built with `ngtsc` (due to the bazel migration). On case insensitive
platforms (e.g. windows) these examples cannot be built because ngtsc fails
the app imports a generated shim file (such as the factory shim files).
This is because the `GeneratedShimsHostWrapper` TypeScript host uses
the `getCanonicalFileName` method in order to check whether a given
file/module exists in the generator file maps. e.g.
```
// Generator Map:
'C:/users/paul/_bazel_paul/lm3s4mgv/execroot/angular/packages/core/index.ngfactory.ts' =>
'C:/users/paul/_bazel_paul/lm3s4mgv/execroot/angular/packages/core/index.ts',
// Path passed into `fileExists`
C:/users/paul/_bazel_paul/lm3s4mgv/execroot/angular/packages/core/index.ngfactory.ts
// After getCanonicalFileName (notice the **lower-case drive name**)
c:/users/paul/_bazel_paul/lm3s4mgv/execroot/angular/packages/core/index.ngfactory.ts
```
As seen above, the generator map does not use the canonical file names, as well as
TypeScript internally does not pass around canonical file names. We can fix this by removing
the manual call to `getCanonicalFileName` and just following TypeScript internal-semantics.
PR Close#28831
Fixes a minor typo in the `listLazyRoutes` method for `ngtsc`. Also in
addition fixes that a newly introduced test for `listLazyRoutes` broke the
tests in Windows. It's clear that we still don't run tests against
Windows, but we also made all other tests pass (without CI verification),
and it's not a big deal fixing this while being at it.
PR Close#28831
Prior to this fix, using the compiler's ivy_switch mechanism was
only available to core packages. This patch allows for this variable
switching mechanism to work across all other angular packages.
PR Close#28711
This commit adds support for the `static: true` flag in `ContentChild`
queries. Prior to this commit, all `ContentChild` queries were resolved
after change detection ran. This is a problem for backwards
compatibility because View Engine also supported "static" queries which
would resolve before change detection.
Now if users add a `static: true` option, the query will be resolved in
creation mode (before change detection runs). For example:
```ts
@ContentChild(TemplateRef, {static: true}) template !: TemplateRef;
```
This feature will come in handy for components that need
to create components dynamically.
PR Close#28811
This commit adds support for the `static: true` flag in
`ViewChild` queries. Prior to this commit, all `ViewChild`
queries were resolved after change detection ran. This is
a problem for backwards compatibility because View Engine
also supported "static" queries which would resolve before
change detection.
Now if users add a `static: true` option, the query will be
resolved in creation mode (before change detection runs).
For example:
```ts
@ViewChild(TemplateRef, {static: true}) template !: TemplateRef;
```
This feature will come in handy for components that need
to create components dynamically.
PR Close#28811
Currently if developers use call expressions in their static
class members ([like we do in Angular](https://github.com/angular/angular/blob/master/packages/core/src/change_detection/differs/keyvalue_differs.ts#L121)),
the metadata that is generated for flat modules is invalid. This
is because the metadata bundler logic currently does not handle
call expressions in static class members and the symbol references
are not rewritten to avoid relative paths in the bundle.
Static class members using a call expression are not relevant for
the ViewEngine AOT compilation, but it is problematic that the
bundled metadata references modules using their original relative
path. This means that the bundled metadata is no longer encapsulated
and depends on other emitted files to be emitted in the proper place.
These incorrect relative paths can now cause issues where NGC
looks for the referenced symbols in the incorrect path. e.g.
```
src/
| lib/
| index.ts -> References the call expression using `../../di`
```
Now the metadata looks like that:
```
node_modules/
| @angular/
-- | core/
-- -- | core.metadata.json -> Says that the call expr. is in `../../di`.
| di/
```
Now if NGC tries to use the metadata files and create the summary files,
NGC resolves the call expression to the `node_modules/di` module. Since
the "unexpected" module does not contain the desired symbol, NGC will
error out.
We should fix this by ensuring that we don't ship corrupted metadata
to NPM which contains relative references that can cause such
failures (other imports can be affected as well; it depends on what
modules the developer has installed and how we import our call
expressions).
Fixes#28741.
PR Close#28762
Currently setting `enableIvy` to true runs a hybrid mode of `ngc` and `ngtsc`. This is counterintuitive given the name of the flag itself.
This PR makes the `true` value equivalent to the previous `ngtsc`, and `ngtsc` becomes an alias for `true`. Effectively this removes the hybrid mode as well since there's no other way to enable it.
PR Close#28616
Previously, ngtsc would throw an error if two decorators were matched on
the same class simultaneously. However, @Injectable is a special case, and
it appears frequently on component, directive, and pipe classes. For pipes
in particular, it's a common pattern to treat the pipe class also as an
injectable service.
ngtsc actually lacked the capability to compile multiple matching
decorators on a class, so this commit adds support for that. Decorator
handlers (and thus the decorators they match) are classified into three
categories: PRIMARY, SHARED, and WEAK.
PRIMARY handlers compile decorators that cannot coexist with other primary
decorators. The handlers for Component, Directive, Pipe, and NgModule are
marked as PRIMARY. A class may only have one decorator from this group.
SHARED handlers compile decorators that can coexist with others. Injectable
is the only decorator in this category, meaning it's valid to put an
@Injectable decorator on a previously decorated class.
WEAK handlers behave like SHARED, but are dropped if any non-WEAK handler
matches a class. The handler which compiles ngBaseDef is WEAK, since
ngBaseDef is only needed if a class doesn't otherwise have a decorator.
Tests are added to validate that @Injectable can coexist with the other
decorators and that an error is generated when mixing the primaries.
PR Close#28523
In the past, @Injectable had no side effects and existing Angular code is
therefore littered with @Injectable usage on classes which are not intended
to be injected.
A common example is:
@Injectable()
class Foo {
constructor(private notInjectable: string) {}
}
and somewhere else:
providers: [{provide: Foo, useFactory: ...})
Here, there is no need for Foo to be injectable - indeed, it's impossible
for the DI system to create an instance of it, as it has a non-injectable
constructor. The provider configures a factory for the DI system to be
able to create instances of Foo.
Adding @Injectable in Ivy signifies that the class's own constructor, and
not a provider, determines how the class will be created.
This commit adds logic to compile classes which are marked with @Injectable
but are otherwise not injectable, and create an ngInjectableDef field with
a factory function that throws an error. This way, existing code in the wild
continues to compile, but if someone attempts to use the injectable it will
fail with a useful error message.
In the case where strictInjectionParameters is set to true, a compile-time
error is thrown instead of the runtime error, as ngtsc has enough
information to determine when injection couldn't possibly be valid.
PR Close#28523