fix(ngcc): do not emit ES2015 code in ES5 files (#33514)
Previously, ngcc's `Renderer` would add some constants in the processed files which were emitted as ES2015 code (e.g. `const` declarations). This would result in invalid ES5 generated code that would break when run on browsers that do not support the emitted format. This commit fixes it by adding a `printStatement()` method to `RenderingFormatter`, which can convert statements to JavaScript code in a suitable format for the corresponding `RenderingFormatter`. Additionally, the `translateExpression()` and `translateStatement()` ngtsc helper methods are augmented to accept an extra hint to know whether the code needs to be translated to ES5 format or not. Fixes #32665 PR Close #33514
This commit is contained in:

committed by
Kara Erickson

parent
21bd8c9a2e
commit
06e36e5972
@ -10,6 +10,7 @@ ts_library(
|
||||
exclude = ["integration/**/*.ts"],
|
||||
),
|
||||
deps = [
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/ngcc",
|
||||
"//packages/compiler-cli/ngcc/test/helpers",
|
||||
"//packages/compiler-cli/src/ngtsc/diagnostics",
|
||||
|
@ -147,6 +147,38 @@ runInEachFileSystem(() => {
|
||||
'{ bar: [{ type: Input }] });');
|
||||
});
|
||||
|
||||
it('should not add `const` in ES5 generated code', () => {
|
||||
genNodeModules({
|
||||
'test-package': {
|
||||
'/index.ts': `
|
||||
import {Directive, Input, NgModule} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[foo]',
|
||||
host: {bar: ''},
|
||||
})
|
||||
export class FooDirective {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [FooDirective],
|
||||
})
|
||||
export class FooModule {}
|
||||
`,
|
||||
},
|
||||
});
|
||||
|
||||
mainNgcc({
|
||||
basePath: '/node_modules',
|
||||
targetEntryPointPath: 'test-package',
|
||||
propertiesToConsider: ['main'],
|
||||
});
|
||||
|
||||
const jsContents = fs.readFile(_(`/node_modules/test-package/index.js`));
|
||||
expect(jsContents).not.toMatch(/\bconst \w+\s*=/);
|
||||
expect(jsContents).toMatch(/\bvar _c0 =/);
|
||||
});
|
||||
|
||||
describe('in async mode', () => {
|
||||
it('should run ngcc without errors for fesm2015', async() => {
|
||||
const promise = mainNgcc({
|
||||
|
@ -5,6 +5,7 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {DeclareVarStmt, LiteralExpr, StmtModifier} from '@angular/compiler';
|
||||
import MagicString from 'magic-string';
|
||||
import * as ts from 'typescript';
|
||||
import {NoopImportRewriter} from '../../../src/ngtsc/imports';
|
||||
@ -518,5 +519,19 @@ SOME DEFINITION TEXT
|
||||
expect(output.toString()).toContain(`function C() {\n }\n return C;`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('printStatement', () => {
|
||||
it('should transpile code to ES5', () => {
|
||||
const {renderer, sourceFile, importManager} = setup(PROGRAM);
|
||||
|
||||
const stmt1 = new DeclareVarStmt('foo', new LiteralExpr(42), null, [StmtModifier.Static]);
|
||||
const stmt2 = new DeclareVarStmt('bar', new LiteralExpr(true));
|
||||
const stmt3 = new DeclareVarStmt('baz', new LiteralExpr('qux'), undefined, []);
|
||||
|
||||
expect(renderer.printStatement(stmt1, sourceFile, importManager)).toBe('var foo = 42;');
|
||||
expect(renderer.printStatement(stmt2, sourceFile, importManager)).toBe('var bar = true;');
|
||||
expect(renderer.printStatement(stmt3, sourceFile, importManager)).toBe('var baz = "qux";');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -53,6 +53,7 @@ class TestRenderingFormatter implements RenderingFormatter {
|
||||
importManager: ImportManager): void {
|
||||
output.prepend('\n// ADD MODUlE WITH PROVIDERS PARAMS\n');
|
||||
}
|
||||
printStatement(): string { return 'IGNORED'; }
|
||||
}
|
||||
|
||||
function createTestRenderer(
|
||||
@ -87,6 +88,7 @@ function createTestRenderer(
|
||||
spyOn(testFormatter, 'removeDecorators').and.callThrough();
|
||||
spyOn(testFormatter, 'rewriteSwitchableDeclarations').and.callThrough();
|
||||
spyOn(testFormatter, 'addModuleWithProvidersParams').and.callThrough();
|
||||
spyOn(testFormatter, 'printStatement').and.callThrough();
|
||||
|
||||
const renderer = new DtsRenderer(testFormatter, fs, logger, host, bundle);
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {DeclareVarStmt, LiteralExpr, StmtModifier} from '@angular/compiler';
|
||||
import MagicString from 'magic-string';
|
||||
import * as ts from 'typescript';
|
||||
import {NoopImportRewriter} from '../../../src/ngtsc/imports';
|
||||
@ -545,5 +546,19 @@ SOME DEFINITION TEXT
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('printStatement', () => {
|
||||
it('should transpile code to ES5', () => {
|
||||
const {renderer, sourceFile, importManager} = setup(PROGRAM);
|
||||
|
||||
const stmt1 = new DeclareVarStmt('foo', new LiteralExpr(42), null, [StmtModifier.Static]);
|
||||
const stmt2 = new DeclareVarStmt('bar', new LiteralExpr(true));
|
||||
const stmt3 = new DeclareVarStmt('baz', new LiteralExpr('qux'), undefined, []);
|
||||
|
||||
expect(renderer.printStatement(stmt1, sourceFile, importManager)).toBe('var foo = 42;');
|
||||
expect(renderer.printStatement(stmt2, sourceFile, importManager)).toBe('var bar = true;');
|
||||
expect(renderer.printStatement(stmt3, sourceFile, importManager)).toBe('var baz = "qux";');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -5,6 +5,7 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {DeclareVarStmt, LiteralExpr, StmtModifier} from '@angular/compiler';
|
||||
import MagicString from 'magic-string';
|
||||
import * as ts from 'typescript';
|
||||
import {NoopImportRewriter} from '../../../src/ngtsc/imports';
|
||||
@ -623,5 +624,19 @@ export { D };
|
||||
static withProviders2(): (ModuleWithProviders)&{ngModule:ExternalModule};`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('printStatement', () => {
|
||||
it('should transpile code to ES2015', () => {
|
||||
const {renderer, sourceFile, importManager} = setup([PROGRAM]);
|
||||
|
||||
const stmt1 = new DeclareVarStmt('foo', new LiteralExpr(42), null, [StmtModifier.Final]);
|
||||
const stmt2 = new DeclareVarStmt('bar', new LiteralExpr(true));
|
||||
const stmt3 = new DeclareVarStmt('baz', new LiteralExpr('qux'), undefined, []);
|
||||
|
||||
expect(renderer.printStatement(stmt1, sourceFile, importManager)).toBe('const foo = 42;');
|
||||
expect(renderer.printStatement(stmt2, sourceFile, importManager)).toBe('var bar = true;');
|
||||
expect(renderer.printStatement(stmt3, sourceFile, importManager)).toBe('var baz = "qux";');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -5,14 +5,15 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {Statement} from '@angular/compiler';
|
||||
import MagicString from 'magic-string';
|
||||
import * as ts from 'typescript';
|
||||
import {fromObject, generateMapFileComment, SourceMapConverter} from 'convert-source-map';
|
||||
import {absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
|
||||
import {TestFile, runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
|
||||
import {Reexport} from '../../../src/ngtsc/imports';
|
||||
import {NOOP_DEFAULT_IMPORT_RECORDER, Reexport} from '../../../src/ngtsc/imports';
|
||||
import {loadTestFiles} from '../../../test/helpers';
|
||||
import {Import, ImportManager} from '../../../src/ngtsc/translator';
|
||||
import {Import, ImportManager, translateStatement} from '../../../src/ngtsc/translator';
|
||||
import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
|
||||
import {CompiledClass} from '../../src/analysis/types';
|
||||
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
|
||||
@ -27,6 +28,8 @@ import {RenderingFormatter, RedundantDecoratorMap} from '../../src/rendering/ren
|
||||
import {makeTestEntryPointBundle, getRootFiles} from '../helpers/utils';
|
||||
|
||||
class TestRenderingFormatter implements RenderingFormatter {
|
||||
private printer = ts.createPrinter({newLine: ts.NewLineKind.LineFeed});
|
||||
|
||||
addImports(output: MagicString, imports: Import[], sf: ts.SourceFile) {
|
||||
output.prepend('\n// ADD IMPORTS\n');
|
||||
}
|
||||
@ -56,6 +59,13 @@ class TestRenderingFormatter implements RenderingFormatter {
|
||||
importManager: ImportManager): void {
|
||||
output.prepend('\n// ADD MODUlE WITH PROVIDERS PARAMS\n');
|
||||
}
|
||||
printStatement(stmt: Statement, sourceFile: ts.SourceFile, importManager: ImportManager): string {
|
||||
const node = translateStatement(
|
||||
stmt, importManager, NOOP_DEFAULT_IMPORT_RECORDER, ts.ScriptTarget.ES2015);
|
||||
const code = this.printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
||||
|
||||
return `// TRANSPILED\n${code}`;
|
||||
}
|
||||
}
|
||||
|
||||
function createTestRenderer(
|
||||
@ -92,6 +102,7 @@ function createTestRenderer(
|
||||
spyOn(testFormatter, 'removeDecorators').and.callThrough();
|
||||
spyOn(testFormatter, 'rewriteSwitchableDeclarations').and.callThrough();
|
||||
spyOn(testFormatter, 'addModuleWithProvidersParams').and.callThrough();
|
||||
spyOn(testFormatter, 'printStatement').and.callThrough();
|
||||
|
||||
const renderer = new Renderer(host, testFormatter, fs, logger, bundle);
|
||||
|
||||
@ -200,8 +211,9 @@ runInEachFileSystem(() => {
|
||||
renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
const addDefinitionsSpy = testFormatter.addDefinitions as jasmine.Spy;
|
||||
expect(addDefinitionsSpy.calls.first().args[2])
|
||||
.toEqual(`A.ɵfac = function A_Factory(t) { return new (t || A)(); };
|
||||
expect(addDefinitionsSpy.calls.first().args[2]).toEqual(`// TRANSPILED
|
||||
A.ɵfac = function A_Factory(t) { return new (t || A)(); };
|
||||
// TRANSPILED
|
||||
A.ɵcmp = ɵngcc0.ɵɵdefineComponent({ type: A, selectors: [["a"]], decls: 1, vars: 1, template: function A_Template(rf, ctx) { if (rf & 1) {
|
||||
ɵngcc0.ɵɵtext(0);
|
||||
} if (rf & 2) {
|
||||
@ -209,8 +221,8 @@ A.ɵcmp = ɵngcc0.ɵɵdefineComponent({ type: A, selectors: [["a"]], decls: 1, v
|
||||
} }, encapsulation: 2 });`);
|
||||
|
||||
const addAdjacentStatementsSpy = testFormatter.addAdjacentStatements as jasmine.Spy;
|
||||
expect(addAdjacentStatementsSpy.calls.first().args[2])
|
||||
.toEqual(`/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(A, [{
|
||||
expect(addAdjacentStatementsSpy.calls.first().args[2]).toEqual(`// TRANSPILED
|
||||
/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(A, [{
|
||||
type: Component,
|
||||
args: [{ selector: 'a', template: '{{ person!.name }}' }]
|
||||
}], null, null);`);
|
||||
@ -243,8 +255,9 @@ A.ɵcmp = ɵngcc0.ɵɵdefineComponent({ type: A, selectors: [["a"]], decls: 1, v
|
||||
name: 'A',
|
||||
decorators: [jasmine.objectContaining({name: 'Directive'})]
|
||||
}));
|
||||
expect(addDefinitionsSpy.calls.first().args[2])
|
||||
.toEqual(`A.ɵfac = function A_Factory(t) { return new (t || A)(); };
|
||||
expect(addDefinitionsSpy.calls.first().args[2]).toEqual(`// TRANSPILED
|
||||
A.ɵfac = function A_Factory(t) { return new (t || A)(); };
|
||||
// TRANSPILED
|
||||
A.ɵdir = ɵngcc0.ɵɵdefineDirective({ type: A, selectors: [["", "a", ""]] });`);
|
||||
});
|
||||
|
||||
@ -260,8 +273,8 @@ A.ɵdir = ɵngcc0.ɵɵdefineDirective({ type: A, selectors: [["", "a", ""]] });`
|
||||
expect(addAdjacentStatementsSpy.calls.first().args[1])
|
||||
.toEqual(jasmine.objectContaining(
|
||||
{name: 'A', decorators: [jasmine.objectContaining({name: 'Directive'})]}));
|
||||
expect(addAdjacentStatementsSpy.calls.first().args[2])
|
||||
.toEqual(`/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(A, [{
|
||||
expect(addAdjacentStatementsSpy.calls.first().args[2]).toEqual(`// TRANSPILED
|
||||
/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(A, [{
|
||||
type: Directive,
|
||||
args: [{ selector: '[a]' }]
|
||||
}], null, null);`);
|
||||
@ -271,7 +284,7 @@ A.ɵdir = ɵngcc0.ɵɵdefineDirective({ type: A, selectors: [["", "a", ""]] });`
|
||||
() => {
|
||||
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
|
||||
testFormatter} = createTestRenderer('test-package', [INPUT_PROGRAM]);
|
||||
const result = renderer.renderProgram(
|
||||
renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
const removeDecoratorsSpy = testFormatter.removeDecorators as jasmine.Spy;
|
||||
expect(removeDecoratorsSpy.calls.first().args[0].toString())
|
||||
@ -467,9 +480,9 @@ A.ɵdir = ɵngcc0.ɵɵdefineDirective({ type: A, selectors: [["", "a", ""]] });`
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
|
||||
const addDefinitionsSpy = testFormatter.addDefinitions as jasmine.Spy;
|
||||
expect(addDefinitionsSpy.calls.first().args[2])
|
||||
.toEqual(
|
||||
`UndecoratedBase.ɵfac = function UndecoratedBase_Factory(t) { return new (t || UndecoratedBase)(); };
|
||||
expect(addDefinitionsSpy.calls.first().args[2]).toEqual(`// TRANSPILED
|
||||
UndecoratedBase.ɵfac = function UndecoratedBase_Factory(t) { return new (t || UndecoratedBase)(); };
|
||||
// TRANSPILED
|
||||
UndecoratedBase.ɵdir = ɵngcc0.ɵɵdefineDirective({ type: UndecoratedBase, viewQuery: function UndecoratedBase_Query(rf, ctx) { if (rf & 1) {
|
||||
ɵngcc0.ɵɵstaticViewQuery(_c0, true);
|
||||
} if (rf & 2) {
|
||||
|
@ -5,6 +5,7 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {DeclareVarStmt, LiteralExpr, StmtModifier} from '@angular/compiler';
|
||||
import MagicString from 'magic-string';
|
||||
import * as ts from 'typescript';
|
||||
import {NoopImportRewriter} from '../../../src/ngtsc/imports';
|
||||
@ -592,5 +593,19 @@ SOME DEFINITION TEXT
|
||||
expect(output.toString()).toContain(`function C() {\n }\n return C;`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('printStatement', () => {
|
||||
it('should transpile code to ES5', () => {
|
||||
const {renderer, sourceFile, importManager} = setup(PROGRAM);
|
||||
|
||||
const stmt1 = new DeclareVarStmt('foo', new LiteralExpr(42), null, [StmtModifier.Static]);
|
||||
const stmt2 = new DeclareVarStmt('bar', new LiteralExpr(true));
|
||||
const stmt3 = new DeclareVarStmt('baz', new LiteralExpr('qux'), undefined, []);
|
||||
|
||||
expect(renderer.printStatement(stmt1, sourceFile, importManager)).toBe('var foo = 42;');
|
||||
expect(renderer.printStatement(stmt2, sourceFile, importManager)).toBe('var bar = true;');
|
||||
expect(renderer.printStatement(stmt3, sourceFile, importManager)).toBe('var baz = "qux";');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user