fix(compiler-cli): annotate Ivy fields as @nocollapse in closure mode (#22691)
Closure has a transformation which turns: Service.ngInjectableDef = ...; into: Service$ngInjectableDef = ...; This transformation obviously breaks Ivy in a major way. The solution is to annotate the fields as @nocollapse. However, Typescript appears to ignore synthetic comments added to a node during a transformation, so the "right" way to add these comments doesn't work. As an interim measure, a post-processing step just before the compiled JS is written to disk appends the correct comments with a regular expression. PR Close #22691
This commit is contained in:
parent
f95730b8e2
commit
6e00410e1c
|
@ -25,7 +25,36 @@ import {PartialModuleMetadataTransformer} from './r3_metadata_transform';
|
|||
import {getAngularClassTransformerFactory} from './r3_transform';
|
||||
import {DTS, GENERATED_FILES, StructureIsReused, TS, createMessageDiagnostic, isInRootDir, ngToTsDiagnostic, tsStructureIsReused, userError} from './util';
|
||||
|
||||
// Closure compiler transforms the form `Service.ngInjectableDef = X` into
|
||||
// `Service$ngInjectableDef = X`. To prevent this transformation, such assignments need to be
|
||||
// annotated with @nocollapse. Unfortunately, a bug in Typescript where comments aren't propagated
|
||||
// through the TS transformations precludes adding the comment via the AST. This workaround detects
|
||||
// the static assignments to R3 properties such as ngInjectableDef using a regex, as output files
|
||||
// are written, and applies the annotation through regex replacement.
|
||||
//
|
||||
// TODO(alxhub): clean up once fix for TS transformers lands in upstream
|
||||
//
|
||||
// Typescript reference issue: https://github.com/Microsoft/TypeScript/issues/22497
|
||||
|
||||
// Pattern matching all Render3 property names.
|
||||
const R3_DEF_NAME_PATTERN = ['ngInjectableDef'].join('|');
|
||||
|
||||
// Pattern matching `Identifier.property` where property is a Render3 property.
|
||||
const R3_DEF_ACCESS_PATTERN = `[^\\s\\.()[\\]]+\.(${R3_DEF_NAME_PATTERN})`;
|
||||
|
||||
// Pattern matching a source line that contains a Render3 static property assignment.
|
||||
// It declares two matching groups - one for the preceding whitespace, the second for the rest
|
||||
// of the assignment expression.
|
||||
const R3_DEF_LINE_PATTERN = `^(\\s*)(${R3_DEF_ACCESS_PATTERN} = .*)$`;
|
||||
|
||||
// Regex compilation of R3_DEF_LINE_PATTERN. Matching group 1 yields the whitespace preceding the
|
||||
// assignment, matching group 2 gives the rest of the assignment expressions.
|
||||
const R3_MATCH_DEFS = new RegExp(R3_DEF_LINE_PATTERN, 'gmu');
|
||||
|
||||
// Replacement string that complements R3_MATCH_DEFS. It inserts `/** @nocollapse */` before the
|
||||
// assignment but after any indentation. Note that this will mess up any sourcemaps on this line
|
||||
// (though there shouldn't be any, since Render3 properties are synthetic).
|
||||
const R3_NOCOLLAPSE_DEFS = '$1\/** @nocollapse *\/ $2';
|
||||
|
||||
/**
|
||||
* Maximum number of files that are emitable via calling ts.Program.emit
|
||||
|
@ -223,6 +252,10 @@ class AngularCompilerProgram implements Program {
|
|||
this._emitRender2(parameters);
|
||||
}
|
||||
|
||||
private _annotateR3Properties(contents: string): string {
|
||||
return contents.replace(R3_MATCH_DEFS, R3_NOCOLLAPSE_DEFS);
|
||||
}
|
||||
|
||||
private _emitRender3(
|
||||
{emitFlags = EmitFlags.Default, cancellationToken, customTransformers,
|
||||
emitCallback = defaultEmitCallback}: {
|
||||
|
@ -242,6 +275,10 @@ class AngularCompilerProgram implements Program {
|
|||
(outFileName, outData, writeByteOrderMark, onError?, sourceFiles?) => {
|
||||
const sourceFile = sourceFiles && sourceFiles.length == 1 ? sourceFiles[0] : null;
|
||||
let genFile: GeneratedFile|undefined;
|
||||
if (this.options.annotateForClosureCompiler && sourceFile &&
|
||||
TS.test(sourceFile.fileName)) {
|
||||
outData = this._annotateR3Properties(outData);
|
||||
}
|
||||
this.writeFile(outFileName, outData, writeByteOrderMark, onError, undefined, sourceFiles);
|
||||
};
|
||||
|
||||
|
@ -309,6 +346,9 @@ class AngularCompilerProgram implements Program {
|
|||
emittedSourceFiles.push(originalFile);
|
||||
}
|
||||
}
|
||||
if (this.options.annotateForClosureCompiler && TS.test(sourceFile.fileName)) {
|
||||
outData = this._annotateR3Properties(outData);
|
||||
}
|
||||
}
|
||||
this.writeFile(outFileName, outData, writeByteOrderMark, onError, genFile, sourceFiles);
|
||||
};
|
||||
|
|
|
@ -2067,6 +2067,27 @@ describe('ngc transformer command-line', () => {
|
|||
expect(source).toMatch(/ngInjectableDef.*scope: .+\.Module/);
|
||||
});
|
||||
|
||||
it('ngInjectableDef in es5 mode is annotated @nocollapse when closure options are enabled',
|
||||
() => {
|
||||
writeConfig(`{
|
||||
"extends": "./tsconfig-base.json",
|
||||
"angularCompilerOptions": {
|
||||
"annotateForClosureCompiler": true
|
||||
},
|
||||
"files": ["service.ts"]
|
||||
}`);
|
||||
const source = compileService(`
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Module} from './module';
|
||||
|
||||
@Injectable({
|
||||
scope: Module,
|
||||
})
|
||||
export class Service {}
|
||||
`);
|
||||
expect(source).toMatch(/\/\*\* @nocollapse \*\/ Service\.ngInjectableDef =/);
|
||||
});
|
||||
|
||||
it('compiles a useValue InjectableDef', () => {
|
||||
const source = compileService(`
|
||||
import {Injectable} from '@angular/core';
|
||||
|
|
Loading…
Reference in New Issue