refactor(compiler): add @nocollapse annotation using a synthetic comment (#35932)

In Ivy, Angular decorators are compiled into static fields that are
inserted into a class declaration in a TypeScript transform. When
targeting Closure compiler such fields need to be annotated with
`@nocollapse` to prevent them from being lifted from a static field into
a variable, as that would prevent the Ivy runtime from being able to
find the compiled definitions.

Previously, there was a bug in TypeScript where synthetic comments added
in a transform would not be emitted at all, so as a workaround a global
regex-replace was done in the emit's `writeFile` callback that would add
the `@nocollapse` annotation to all static Ivy definition fields. This
approach is no longer possible when ngtsc is running as TypeScript
plugin, as a plugin cannot control emit behavior.

The workaround is no longer necessary, as synthetic comments are now
properly emitted, likely as of
https://github.com/microsoft/TypeScript/pull/22141 which has been
released with TypeScript 2.8.

This change is required for running ngtsc as TypeScript plugin in
Bazel's `ts_library` rule, to move away from the custom `ngc_wrapped`
approach.

Resolves FW-1952

PR Close #35932
This commit is contained in:
JoostK
2020-03-07 17:14:25 +01:00
committed by Kara Erickson
parent a5eb0e56b6
commit 75afd80ae8
10 changed files with 65 additions and 136 deletions

View File

@ -10,7 +10,6 @@ import {GeneratedFile} from '@angular/compiler';
import * as ts from 'typescript';
import * as api from '../transformers/api';
import {nocollapseHack} from '../transformers/nocollapse_hack';
import {verifySupportedTypeScriptVersion} from '../typescript_support';
import {NgCompilerHost} from './core';
@ -193,16 +192,6 @@ export class NgtscProgram implements api.Program {
this.compiler.incrementalDriver.recordSuccessfulEmit(writtenSf);
}
}
// If Closure annotations are being produced, tsickle should be adding `@nocollapse` to
// any static fields present. However, tsickle doesn't yet handle synthetic fields added
// during other transformations, so this hack is in place to ensure Ivy definitions get
// properly annotated, pending an upstream fix in tsickle.
//
// TODO(alxhub): remove when tsickle properly annotates synthetic fields.
if (this.closureCompilerEnabled && fileName.endsWith('.js')) {
data = nocollapseHack(data);
}
this.host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
};

View File

@ -47,7 +47,8 @@ class IvyVisitor extends Visitor {
constructor(
private compilation: TraitCompiler, private reflector: ReflectionHost,
private importManager: ImportManager, private defaultImportRecorder: DefaultImportRecorder,
private isCore: boolean, private constantPool: ConstantPool) {
private isClosureCompilerEnabled: boolean, private isCore: boolean,
private constantPool: ConstantPool) {
super();
}
@ -73,6 +74,16 @@ class IvyVisitor extends Visitor {
undefined, [ts.createToken(ts.SyntaxKind.StaticKeyword)], field.name, undefined,
undefined, exprNode);
if (this.isClosureCompilerEnabled) {
// Closure compiler transforms the form `Service.ɵprov = X` into `Service$ɵprov = X`. To
// prevent this transformation, such assignments need to be annotated with @nocollapse.
// Note that tsickle is typically responsible for adding such annotations, however it
// doesn't yet handle synthetic fields added during other transformations.
ts.addSyntheticLeadingComment(
property, ts.SyntaxKind.MultiLineCommentTrivia, '* @nocollapse ',
/* hasTrailingNewLine */ false);
}
field.statements
.map(
stmt => translateStatement(
@ -215,7 +226,8 @@ function transformIvySourceFile(
// Recursively scan through the AST and perform any updates requested by the IvyCompilation.
const visitor = new IvyVisitor(
compilation, reflector, importManager, defaultImportRecorder, isCore, constantPool);
compilation, reflector, importManager, defaultImportRecorder, isClosureCompilerEnabled,
isCore, constantPool);
let sf = visit(file, visitor, context);
// Generate the constant statements first, as they may involve adding additional imports