Skip to content
Permalink
Browse files

feat(compiler-cli): make enableIvy ngtsc/true equivalent (#28616)

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
  • Loading branch information...
filipesilva authored and IgorMinar committed Feb 8, 2019
1 parent a17fd43 commit 1923c2f99cd1e1b008f24df5dcd35e3f19118627
@@ -76,7 +76,7 @@ def _enable_ivy_value(ctx):
if strategy == "legacy":
return False
elif strategy == "aot":
return "ngtsc"
return True
else:
fail("unreachable")

@@ -54,8 +54,7 @@ export function mainDiagnosticsForTest(
}

function createEmitCallback(options: api.CompilerOptions): api.TsEmitCallback|undefined {
const transformDecorators = options.enableIvy !== 'ngtsc' && options.enableIvy !== 'tsc' &&
options.annotationsAs !== 'decorators';
const transformDecorators = !options.enableIvy && options.annotationsAs !== 'decorators';
const transformTypesToClosure = options.annotateForClosureCompiler;
if (!transformDecorators && !transformTypesToClosure) {
return undefined;
@@ -192,7 +191,7 @@ function reportErrorsAndExit(
const errorsAndWarnings = filterErrorsAndWarnings(allDiagnostics);
if (errorsAndWarnings.length) {
const formatHost = getFormatDiagnosticsHost(options);
if (options && (options.enableIvy === true || options.enableIvy === 'ngtsc')) {
if (options && options.enableIvy === true) {
const ngDiagnostics = errorsAndWarnings.filter(api.isNgDiagnostic);
const tsDiagnostics = errorsAndWarnings.filter(api.isTsDiagnostic);
consoleError(replaceTsWithNgInErrors(
@@ -120,6 +120,10 @@ export function calcProjectFileAndBasePath(project: string):

export function createNgCompilerOptions(
basePath: string, config: any, tsOptions: ts.CompilerOptions): api.CompilerOptions {
// enableIvy `ngtsc` is an alias for `true`.
if (config.angularCompilerOptions && config.angularCompilerOptions.enableIvy === 'ngtsc') {
config.angularCompilerOptions.enableIvy = true;
}
return {...tsOptions, ...config.angularCompilerOptions, genDir: basePath, basePath};
}

@@ -194,9 +194,8 @@ export interface CompilerOptions extends ts.CompilerOptions {
* Acceptable values are as follows:
*
* `false` - run ngc normally
* `true` - run ngc with its usual global analysis, but compile decorators to Ivy fields instead
* of running the View Engine compilers
* `ngtsc` - run the ngtsc compiler instead of the normal ngc compiler
* `true` - run the ngtsc compiler instead of the normal ngc compiler
* `ngtsc` - alias for `true`
* `tsc` - behave like plain tsc as much as possible (used for testing JIT code)
*
* @publicApi
@@ -263,11 +263,10 @@ class AngularCompilerProgram implements Program {
emitCallback?: TsEmitCallback,
mergeEmitResultsCallback?: TsMergeEmitResultsCallback,
} = {}): ts.EmitResult {
if (this.options.enableIvy === 'ngtsc' || this.options.enableIvy === 'tsc') {
if (this.options.enableIvy) {
throw new Error('Cannot run legacy compiler in ngtsc mode');
}
return this.options.enableIvy === true ? this._emitRender3(parameters) :
this._emitRender2(parameters);
return this._emitRender2(parameters);
}

private _emitRender3(
@@ -899,7 +898,7 @@ export function createProgram({rootNames, options, host, oldProgram}: {
options: CompilerOptions,
host: CompilerHost, oldProgram?: Program
}): Program {
if (options.enableIvy === 'ngtsc') {
if (options.enableIvy === true) {
return new NgtscProgram(rootNames, options, host, oldProgram);
} else if (options.enableIvy === 'tsc') {
return new TscPassThroughProgram(rootNames, options, host, oldProgram);
@@ -2001,108 +2001,6 @@ describe('ngc transformer command-line', () => {
});
});

describe('ivy', () => {
function emittedFile(name: string): string {
const outputName = path.resolve(outDir, name);
expect(fs.existsSync(outputName)).toBe(true);
return fs.readFileSync(outputName, {encoding: 'UTF-8'});
}

it('should emit the hello world example', () => {
write('tsconfig.json', `{
"extends": "./tsconfig-base.json",
"files": ["hello-world.ts"],
"angularCompilerOptions": {
"enableIvy": true
}
}`);

write('hello-world.ts', `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'hello-world',
template: 'Hello, world!'
})
export class HelloWorldComponent {
}
@NgModule({
declarations: [HelloWorldComponent]
})
export class HelloWorldModule {}
`);
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')]);
expect(exitCode).toBe(0, 'Compile failed');
expect(emittedFile('hello-world.js')).toContain('ngComponentDef');
expect(emittedFile('hello-world.js')).toContain('HelloWorldComponent_Factory');
});

it('should emit an injection of a string token', () => {
write('tsconfig.json', `{
"extends": "./tsconfig-base.json",
"files": ["hello-world.ts"],
"angularCompilerOptions": {
"enableIvy": true
}
}`);

write('hello-world.ts', `
import {Component, Inject, NgModule} from '@angular/core';
@Component({
selector: 'hello-world',
template: 'Hello, world!'
})
export class HelloWorldComponent {
constructor (@Inject('test') private test: string) {}
}
@NgModule({
declarations: [HelloWorldComponent],
providers: [
{provide: 'test', useValue: 'test'}
]
})
export class HelloWorldModule {}
`);
const errors: string[] = [];
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], msg => errors.push(msg));
expect(exitCode).toBe(0, `Compile failed:\n${errors.join('\n ')}`);
expect(emittedFile('hello-world.js')).toContain('ngComponentDef');
});

it('should emit an example that uses the E() instruction', () => {
write('tsconfig.json', `{
"extends": "./tsconfig-base.json",
"files": ["hello-world.ts"],
"angularCompilerOptions": {
"enableIvy": true
}
}`);

write('hello-world.ts', `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'hello-world',
template: '<h1><div style="text-align:center"> Hello, {{name}}! </div></h1> '
})
export class HelloWorldComponent {
name = 'World';
}
@NgModule({declarations: [HelloWorldComponent]})
export class HelloWorldModule {}
`);
const errors: string[] = [];
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], msg => errors.push(msg));
expect(exitCode).toBe(0, `Compile failed:\n${errors.join('\n ')}`);
expect(emittedFile('hello-world.js')).toContain('ngComponentDef');
});
});

describe('tree shakeable services', () => {

function compileService(source: string): string {
@@ -2321,92 +2219,6 @@ describe('ngc transformer command-line', () => {
});
});

describe('ngInjectorDef', () => {
it('is applied with lowered metadata', () => {
writeConfig(`{
"extends": "./tsconfig-base.json",
"files": ["module.ts"],
"angularCompilerOptions": {
"enableIvy": true,
"skipTemplateCodegen": true
}
}`);
write('module.ts', `
import {Injectable, NgModule} from '@angular/core';
@Injectable()
export class ServiceA {}
@Injectable()
export class ServiceB {}
@NgModule()
export class Exported {}
@NgModule({
providers: [ServiceA]
})
export class Imported {
static forRoot() {
console.log('not statically analyzable');
return {
ngModule: Imported,
providers: [] as any,
};
}
}
@NgModule({
providers: [ServiceA, ServiceB],
imports: [Imported.forRoot()],
exports: [Exported],
})
export class Module {}
`);

const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
expect(exitCode).toEqual(0);

const modulePath = path.resolve(outDir, 'module.js');
const moduleSource = fs.readFileSync(modulePath, 'utf8');
expect(moduleSource)
.toContain('var ɵ1 = [ServiceA, ServiceB], ɵ2 = [Imported.forRoot()], ɵ3 = [Exported];');
expect(moduleSource)
.toContain(
'Imported.ngInjectorDef = i0.defineInjector({ factory: function Imported_Factory() { return new Imported(); }, providers: ɵ0, imports: [] });');
expect(moduleSource)
.toContain(
'Module.ngInjectorDef = i0.defineInjector({ factory: function Module_Factory() { return new Module(); }, providers: ɵ1, imports: [ɵ2, ɵ3] });');
});

it('rewrites Injector to INJECTOR in Ivy factory functions ', () => {
writeConfig(`{
"extends": "./tsconfig-base.json",
"files": ["service.ts"],
"angularCompilerOptions": {
"enableIvy": true
}
}`);

write('service.ts', `
import {Injectable, Injector} from '@angular/core';
@Injectable()
export class Service {
constructor(private injector: Injector) {}
}
`);

const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
expect(exitCode).toEqual(0);

const modulePath = path.resolve(outDir, 'service.js');
const moduleSource = fs.readFileSync(modulePath, 'utf8');
expect(moduleSource).not.toMatch(/inject\(i0\.Injector/);
expect(moduleSource).toMatch(/inject\(i0\.INJECTOR/);
});
});

it('libraries should not break strictMetadataEmit', () => {
// first only generate .d.ts / .js / .metadata.json files
writeConfig(`{
@@ -19,7 +19,7 @@ describe('ngtools_api (deprecated)', () => {
beforeEach(() => { testSupport = setup(); });

function createProgram(rootNames: string[]) {
const options = testSupport.createCompilerOptions({enableIvy: ivyEnabled && 'ngtsc'});
const options = testSupport.createCompilerOptions({enableIvy: ivyEnabled});
const host = ts.createCompilerHost(options, true);
const program =
ts.createProgram(rootNames.map(p => path.resolve(testSupport.basePath, p)), options, host);
@@ -70,7 +70,7 @@ export class NgtscTestEnvironment {
"typeRoots": ["node_modules/@types"]
},
"angularCompilerOptions": {
"enableIvy": "ngtsc"
"enableIvy": true
}
}`);

@@ -100,7 +100,7 @@ export class NgtscTestEnvironment {
tsconfig(extraOpts: {[key: string]: string | boolean} = {}, extraRootDirs?: string[]): void {
const tsconfig: {[key: string]: any} = {
extends: './tsconfig-base.json',
angularCompilerOptions: {...extraOpts, enableIvy: 'ngtsc'},
angularCompilerOptions: {...extraOpts, enableIvy: true},
};
if (extraRootDirs !== undefined) {
tsconfig.compilerOptions = {

0 comments on commit 1923c2f

Please sign in to comment.
You can’t perform that action at this time.