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.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.