feat(ivy): first steps towards ngtsc mode (#23455)

This commit adds a new compiler pipeline that isn't dependent on global
analysis, referred to as 'ngtsc'. This new compiler is accessed by
running ngc with "enableIvy" set to "ngtsc". It reuses the same initialization
logic but creates a new implementation of Program which does not perform the
global-level analysis that AngularCompilerProgram does. It will be the
foundation for the production Ivy compiler.

PR Close #23455
This commit is contained in:
Alex Rickabaugh
2018-04-06 09:53:10 -07:00
committed by Igor Minar
parent f567e1898f
commit ab5bc42da0
44 changed files with 2827 additions and 10 deletions

View File

@ -0,0 +1,27 @@
load("//tools:defaults.bzl", "ts_library", "ts_web_test")
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
ts_library(
name = "ngtsc_lib",
testonly = 1,
srcs = [
"ngtsc_spec.ts",
],
deps = [
"//packages/compiler",
"//packages/compiler-cli",
"//packages/compiler-cli/test:test_utils",
],
)
jasmine_node_test(
name = "ngtsc",
bootstrap = ["angular/tools/testing/init_node_no_angular_spec.js"],
data = [
"//packages/compiler-cli/test/ngtsc/fake_core:npm_package",
],
deps = [
":ngtsc_lib",
"//tools/testing:node_no_angular",
],
)

View File

@ -0,0 +1,22 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ts_library", "ng_package")
ts_library(
name = "fake_core",
srcs = [
"index.ts",
],
module_name = "@angular/core",
)
ng_package(
name = "npm_package",
srcs = [
"package.json",
],
entry_point = "packages/fake_core/index.js",
deps = [
":fake_core",
],
)

View File

@ -0,0 +1,13 @@
`fake_core` is a library designed to expose some of the same symbols as `@angular/core`, without
requiring compilation of the whole of `@angular/core`. This enables unit tests for the compiler to
be written without incurring long rebuilds for every change.
* `@angular/core` is compiled with `@angular/compiler-cli`, and therefore has an implicit dependency
on it. Therefore core must be rebuilt if the compiler changes.
* Tests for the compiler which intend to build code that depends on `@angular/core` must have
a data dependency on `@angular/core`. Therefore core must be built to run the compiler tests, and
thus rebuilt if the compiler changes.
This rebuild cycle is expensive and slow. `fake_core` avoids this by exposing a subset of the
`@angular/core` API, which enables applications to be built by the ngtsc compiler without
needing a full version of core present at compile time.

View File

@ -0,0 +1,25 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* 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
*/
type FnWithArg<T> = (arg?: any) => T;
function callableClassDecorator(): FnWithArg<(clazz: any) => any> {
return null !;
}
function callableParamDecorator(): FnWithArg<(a: any, b: any, c: any) => void> {
return null !;
}
export const Injectable = callableClassDecorator();
export const NgModule = callableClassDecorator();
export const Inject = callableParamDecorator();
export const Self = callableParamDecorator();
export const SkipSelf = callableParamDecorator();
export const Optional = callableParamDecorator();

View File

@ -0,0 +1,13 @@
{
"name": "@angular/core",
"version": "0.0.0-FAKE-FOR-TESTS",
"description": "Fake version of core for ngtsc tests",
"main": "./bundles/fake_core.umd.js",
"module": "./fesm5/fake_core.js",
"es2015": "./fesm2015/fake_core.js",
"esm5": "./esm5/index.js",
"esm2015": "./esm2015/index.js",
"fesm5": "./fesm5/fake_core.js",
"fesm2015": "./fesm2015/fake_core.js",
"typings": "./index.d.ts"
}

View File

@ -0,0 +1,125 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* 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 * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import {main, readCommandLineAndConfiguration, watchMode} from '../../src/main';
import {TestSupport, isInBazel, makeTempDir, setup} from '../test_support';
function setupFakeCore(support: TestSupport): void {
const fakeCore = path.join(
process.env.TEST_SRCDIR, 'angular/packages/compiler-cli/test/ngtsc/fake_core/npm_package');
const nodeModulesPath = path.join(support.basePath, 'node_modules');
const angularCoreDirectory = path.join(nodeModulesPath, '@angular/core');
fs.symlinkSync(fakeCore, angularCoreDirectory);
}
function getNgRootDir() {
const moduleFilename = module.filename.replace(/\\/g, '/');
const distIndex = moduleFilename.indexOf('/dist/all');
return moduleFilename.substr(0, distIndex);
}
describe('ngtsc behavioral tests', () => {
if (!isInBazel()) {
// These tests should be excluded from the non-Bazel build.
return;
}
let basePath: string;
let outDir: string;
let write: (fileName: string, content: string) => void;
let errorSpy: jasmine.Spy&((s: string) => void);
function shouldExist(fileName: string) {
if (!fs.existsSync(path.resolve(outDir, fileName))) {
throw new Error(`Expected ${fileName} to be emitted (outDir: ${outDir})`);
}
}
function shouldNotExist(fileName: string) {
if (fs.existsSync(path.resolve(outDir, fileName))) {
throw new Error(`Did not expect ${fileName} to be emitted (outDir: ${outDir})`);
}
}
function getContents(fileName: string): string {
shouldExist(fileName);
const modulePath = path.resolve(outDir, fileName);
return fs.readFileSync(modulePath, 'utf8');
}
function writeConfig(
tsconfig: string =
'{"extends": "./tsconfig-base.json", "angularCompilerOptions": {"enableIvy": "ngtsc"}}') {
write('tsconfig.json', tsconfig);
}
beforeEach(() => {
errorSpy = jasmine.createSpy('consoleError').and.callFake(console.error);
const support = setup();
basePath = support.basePath;
outDir = path.join(basePath, 'built');
process.chdir(basePath);
write = (fileName: string, content: string) => { support.write(fileName, content); };
setupFakeCore(support);
write('tsconfig-base.json', `{
"compilerOptions": {
"experimentalDecorators": true,
"skipLibCheck": true,
"noImplicitAny": true,
"types": [],
"outDir": "built",
"rootDir": ".",
"baseUrl": ".",
"declaration": true,
"target": "es5",
"module": "es2015",
"moduleResolution": "node",
"lib": ["es6", "dom"],
"typeRoots": ["node_modules/@types"]
},
"angularCompilerOptions": {
"enableIvy": "ngtsc"
}
}`);
});
it('should compile without errors', () => {
writeConfig();
write('test.ts', `
import {Injectable} from '@angular/core';
@Injectable()
export class Dep {}
@Injectable()
export class Service {
constructor(dep: Dep) {}
}
`);
const exitCode = main(['-p', basePath], errorSpy);
expect(errorSpy).not.toHaveBeenCalled();
expect(exitCode).toBe(0);
const jsContents = getContents('test.js');
expect(jsContents).toContain('Dep.ngInjectableDef =');
expect(jsContents).toContain('Service.ngInjectableDef =');
expect(jsContents).not.toContain('__decorate');
const dtsContents = getContents('test.d.ts');
expect(dtsContents).toContain('static ngInjectableDef: i0.InjectableDef<Dep>;');
expect(dtsContents).toContain('static ngInjectableDef: i0.InjectableDef<Service>;');
});
});