Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions modules/@angular/compiler-cli/integrationtest/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
"moduleResolution": "node",
"rootDir": "",
"declaration": true,
"lib": ["es6", "dom"],
"baseUrl": "."
"lib": ["es6", "dom"]
}
}
6 changes: 2 additions & 4 deletions modules/@angular/compiler-cli/src/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@ import * as ts from 'typescript';

import {CompileMetadataResolver, DirectiveNormalizer, DomElementSchemaRegistry, HtmlParser, Lexer, NgModuleCompiler, Parser, StyleCompiler, TemplateParser, TypeScriptEmitter, ViewCompiler} from './compiler_private';
import {Console} from './core_private';
import {ReflectorHost, ReflectorHostContext} from './reflector_host';
import {GENERATED_FILES, ReflectorHost, ReflectorHostContext} from './reflector_host';
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
import {StaticReflector, StaticSymbol} from './static_reflector';

const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;

const PREAMBLE = `/**
* This file is generated by the Angular 2 template compiler.
* Do not edit.
Expand Down Expand Up @@ -69,7 +67,7 @@ export class CodeGenerator {
}

// Write codegen in a directory structure matching the sources.
private calculateEmitPath(filePath: string) {
private calculateEmitPath(filePath: string): string {
let root = this.options.basePath;
for (let eachRootDir of this.options.rootDirs || []) {
if (this.options.trace) {
Expand Down
97 changes: 49 additions & 48 deletions modules/@angular/compiler-cli/src/reflector_host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ import {StaticReflectorHost, StaticSymbol} from './static_reflector';

const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
const DTS = /\.d\.ts$/;
const NODE_MODULES = path.sep + 'node_modules' + path.sep;
const IS_GENERATED = /\.(ngfactory|css(\.shim)?)$/;
export const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;

export interface ReflectorHostContext {
fileExists(fileName: string): boolean;
Expand Down Expand Up @@ -58,6 +57,9 @@ export class ReflectorHost implements StaticReflectorHost, ImportGenerator {
private resolve(m: string, containingFile: string) {
const resolved =
ts.resolveModuleName(m, containingFile, this.options, this.context).resolvedModule;
if (this.options.traceResolution) {
console.log('resolve', m, containingFile, '=>', resolved);
}
return resolved ? resolved.resolvedFileName : null;
};

Expand All @@ -79,8 +81,8 @@ export class ReflectorHost implements StaticReflectorHost, ImportGenerator {
* These need to be in a form that system.js can load, so absolute file paths don't work.
*
* The `containingFile` is always in the `genDir`, where as the `importedFile` can be in
* `genDir`, `node_module` or `basePath`. The `importedFile` is either a generated file or
* existing file.
* `genDir`, `node_module` or `rootDir`/`rootDirs`.
* The `importedFile` is either a generated file or an existing file.
*
* | genDir | node_module | rootDir
* --------------+----------+-------------+----------
Expand All @@ -93,67 +95,66 @@ export class ReflectorHost implements StaticReflectorHost, ImportGenerator {
importedFile = this.resolveAssetUrl(importedFile, containingFile);
containingFile = this.resolveAssetUrl(containingFile, '');

if (this.options.traceResolution) {
console.log(
'getImportPath from containingFile', containingFile, 'to importedFile', importedFile);
}

// If a file does not yet exist (because we compile it later), we still need to
// assume it exists it so that the `resolve` method works!
if (!this.compilerHost.fileExists(importedFile)) {
this.context.assumeFileExists(importedFile);
}

containingFile = this.rewriteGenDirPath(containingFile);
const containingDir = path.dirname(containingFile);
// drop extension
importedFile = importedFile.replace(EXT, '');

var nodeModulesIndex = importedFile.indexOf(NODE_MODULES);
const importModule = nodeModulesIndex === -1 ?
null :
importedFile.substring(nodeModulesIndex + NODE_MODULES.length);
const isGeneratedFile = IS_GENERATED.test(importedFile);

if (isGeneratedFile) {
// rewrite to genDir path
if (importModule) {
// it is generated, therefore we do a relative path to the factory
return this.dotRelative(containingDir, this.genDir + NODE_MODULES + importModule);
} else {
// assume that import is also in `genDir`
importedFile = this.rewriteGenDirPath(importedFile);
return this.dotRelative(containingDir, importedFile);
let importModuleName = importedFile.replace(EXT, '');
const parts = importModuleName.split(path.sep).filter(p => !!p);
let foundRelativeImport: string;
for (let index = parts.length - 1; index >= 0; index--) {
let candidate = parts.slice(index, parts.length).join(path.sep);
if (this.resolve(candidate, containingFile) === importedFile) {
return candidate;
}
} else {
// user code import
if (importModule) {
return importModule;
} else {
if (!this.isGenDirChildOfRootDir) {
// assume that they are on top of each other.
importedFile = importedFile.replace(this.basePath, this.genDir);
candidate = '.' + path.sep + candidate;
if (this.resolve(candidate, containingFile) === importedFile) {
if (this.options.writeImportsForRootDirs) {
foundRelativeImport = candidate;
} else {
foundRelativeImport = this.fixupGendirRelativePath(containingFile, importedFile);
}
return this.dotRelative(containingDir, importedFile);
}
}

if (foundRelativeImport) return foundRelativeImport;

// Try a relative import
let candidate = path.relative(path.dirname(containingFile), importModuleName);
if (this.resolve(candidate, containingFile) === importedFile) {
return this.fixupGendirRelativePath(containingFile, importedFile);
}

throw new Error(
`Unable to find any resolvable import for ${importedFile} relative to ${containingFile}`);
}

private fixupGendirRelativePath(containingFile: string, importedFile: string) {
let importModuleName = importedFile.replace(EXT, '');

if (!this.options.writeImportsForRootDirs && this.isGenDirChildOfRootDir) {
if (GENERATED_FILES.test(importedFile)) {
importModuleName = importModuleName.replace(this.basePath, this.genDir);
}
if (GENERATED_FILES.test(containingFile)) {
containingFile = containingFile.replace(this.basePath, this.genDir);
}
}
return this.dotRelative(path.dirname(containingFile), importModuleName);
}

private dotRelative(from: string, to: string): string {
var rPath: string = path.relative(from, to);
return rPath.startsWith('.') ? rPath : './' + rPath;
}

/**
* Moves the path into `genDir` folder while preserving the `node_modules` directory.
*/
private rewriteGenDirPath(filepath: string) {
var nodeModulesIndex = filepath.indexOf(NODE_MODULES);
if (nodeModulesIndex !== -1) {
// If we are in node_modulse, transplant them into `genDir`.
return path.join(this.genDir, filepath.substring(nodeModulesIndex));
} else {
// pretend that containing file is on top of the `genDir` to normalize the paths.
// we apply the `genDir` => `rootDir` delta through `rootDirPrefix` later.
return filepath.replace(this.basePath, this.genDir);
}
}

findDeclaration(
module: string, symbolName: string, containingFile: string,
containingModule?: string): StaticSymbol {
Expand Down
2 changes: 2 additions & 0 deletions modules/@angular/compiler-cli/test/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export interface Directory { [name: string]: Entry; }
export class MockContext implements ReflectorHostContext {
constructor(public currentDirectory: string, private files: Entry) {}

trace(s: string) { console.log(s); }

fileExists(fileName: string): boolean { return typeof this.getEntry(fileName) === 'string'; }

directoryExists(path: string): boolean { return typeof this.getEntry(path) === 'object'; }
Expand Down
89 changes: 62 additions & 27 deletions modules/@angular/compiler-cli/test/reflector_host_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ describe('reflector_host', () => {
var reflectorNestedGenDir: ReflectorHost;
var reflectorSiblingGenDir: ReflectorHost;

const DEBUG = false;

beforeEach(() => {
context = new MockContext('/tmp/src', clone(FILES));
host = new MockCompilerHost(context);
Expand All @@ -35,86 +37,107 @@ describe('reflector_host', () => {
}
reflectorNestedGenDir = new ReflectorHost(
program, host, {
genDir: '/tmp/project/src/gen/',
basePath: '/tmp/project/src',
// Intentional trailing slash, check for regression of #10533
genDir: '/tmp/src/gen/',
basePath: '/tmp/src',
skipMetadataEmit: false,
skipTemplateCodegen: false,
trace: false
},
context);
reflectorSiblingGenDir = new ReflectorHost(
program, host, {
genDir: '/tmp/project/gen',
basePath: '/tmp/project/src/',
genDir: '/tmp/gen',
// Intentional trailing slash, check for regression of #10533
basePath: '/tmp/src/',
skipMetadataEmit: false,
skipTemplateCodegen: false,
trace: false
},
context);

});

describe('path mapping', () => {
it('should use rootDirs for calculating relative imports', () => {
const reflectorHost = new ReflectorHost(
program, host, {
genDir: '/tmp/gen',
basePath: '/tmp/src/',
skipMetadataEmit: false,
skipTemplateCodegen: false,
trace: false,
traceResolution: DEBUG,
rootDirs: ['/tmp/src/', '/tmp/genfiles/'],
writeImportsForRootDirs: true,
},
context);
expect(reflectorHost.getImportPath(
'/tmp/src/pathmapping/bootstrap.ts', '/tmp/genfiles/pathmapping/comp.d.ts'))
.toEqual('./comp');
});
});

describe('nestedGenDir', () => {
describe('nested genDir', () => {
it('should import node_module from factory', () => {
expect(reflectorNestedGenDir.getImportPath(
'/tmp/project/src/gen/my.ngfactory.ts',
'/tmp/project/node_modules/@angular/core.d.ts'))
'/tmp/src/gen/my.ngfactory.ts', '/tmp/src/node_modules/@angular/core.d.ts'))
.toEqual('@angular/core');
});

it('should import factory from factory', () => {
expect(reflectorNestedGenDir.getImportPath(
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ngfactory.ts'))
'/tmp/src/my.ngfactory.ts', '/tmp/src/my.other.ngfactory.ts'))
.toEqual('./my.other.ngfactory');
expect(reflectorNestedGenDir.getImportPath(
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.css.ts'))
'/tmp/src/a/my.ngfactory.ts', '/tmp/src/my.other.css.ts'))
.toEqual('../my.other.css');
expect(reflectorNestedGenDir.getImportPath(
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.css.shim.ts'))
'/tmp/src/my.ngfactory.ts', '/tmp/src/a/my.other.css.shim.ts'))
.toEqual('./a/my.other.css.shim');
});

it('should import application from factory', () => {
expect(reflectorNestedGenDir.getImportPath(
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
expect(
reflectorNestedGenDir.getImportPath('/tmp/src/my.ngfactory.ts', '/tmp/src/my.other.ts'))
.toEqual('../my.other');
expect(reflectorNestedGenDir.getImportPath(
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
expect(
reflectorNestedGenDir.getImportPath('/tmp/src/a/my.ngfactory.ts', '/tmp/src/my.other.ts'))
.toEqual('../../my.other');
expect(reflectorNestedGenDir.getImportPath(
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.ts'))
expect(
reflectorNestedGenDir.getImportPath('/tmp/src/my.ngfactory.ts', '/tmp/src/a/my.other.ts'))
.toEqual('../a/my.other');
});
});

describe('nestedGenDir', () => {
describe('sibling genDir', () => {
it('should import node_module from factory', () => {
expect(reflectorSiblingGenDir.getImportPath(
'/tmp/project/src/gen/my.ngfactory.ts',
'/tmp/project/node_modules/@angular/core.d.ts'))
'/tmp/src/gen/my.ngfactory.ts', '/tmp/src/node_modules/@angular/core.d.ts'))
.toEqual('@angular/core');
});

it('should import factory from factory', () => {
expect(reflectorSiblingGenDir.getImportPath(
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ngfactory.ts'))
'/tmp/src/my.ngfactory.ts', '/tmp/src/my.other.ngfactory.ts'))
.toEqual('./my.other.ngfactory');
expect(reflectorSiblingGenDir.getImportPath(
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.css.ts'))
'/tmp/src/a/my.ngfactory.ts', '/tmp/src/my.other.css.ts'))
.toEqual('../my.other.css');
expect(reflectorSiblingGenDir.getImportPath(
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.css.shim.ts'))
'/tmp/src/my.ngfactory.ts', '/tmp/src/a/my.other.css.shim.ts'))
.toEqual('./a/my.other.css.shim');
});

it('should import application from factory', () => {
expect(reflectorSiblingGenDir.getImportPath(
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
expect(
reflectorSiblingGenDir.getImportPath('/tmp/src/my.ngfactory.ts', '/tmp/src/my.other.ts'))
.toEqual('./my.other');
expect(reflectorSiblingGenDir.getImportPath(
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
'/tmp/src/a/my.ngfactory.ts', '/tmp/src/my.other.ts'))
.toEqual('../my.other');
expect(reflectorSiblingGenDir.getImportPath(
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.ts'))
'/tmp/src/my.ngfactory.ts', '/tmp/src/a/my.other.ts'))
.toEqual('./a/my.other');
});
});
Expand All @@ -131,7 +154,7 @@ describe('reflector_host', () => {

it('should be able to produce an import from main @angular/core', () => {
expect(reflectorNestedGenDir.getImportPath(
'/tmp/project/src/main.ts', '/tmp/project/node_modules/@angular/core.d.ts'))
'/tmp/src/main.ts', '/tmp/src/node_modules/@angular/core.d.ts'))
.toEqual('@angular/core');
});

Expand Down Expand Up @@ -295,6 +318,11 @@ const FILES: Entry = {
})
}
},
'pathmapping': {'bootstrap.ts': `import {a} from './comp.d.ts';`},
'a': {
'my.other.css.shim.ts': dummyModule,
},
'my.other.ts': dummyModule,
'node_modules': {
'@angular': {
'core.d.ts': dummyModule,
Expand All @@ -304,6 +332,13 @@ const FILES: Entry = {
'unused.d.ts': dummyModule
}
}
},
'genfiles': {
'pathmapping': {
'comp.d.ts': `
export declare let a: string;
`
}
}
}
};
Expand Down
12 changes: 12 additions & 0 deletions tools/@angular/tsc-wrapped/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ interface Options extends ts.CompilerOptions {

// Whether to embed debug information in the compiled templates
debug?: boolean;

// Starting with TypeScript 1.9, the 'rootDirs' option can be used
// to allow multiple source directories to have relative imports
// between them.
// This option causes generated code to use imports relative to the
// current directory, and requires you configure the 'rootDirs' to
// include both the genDir and rootDir.
// However, due to https://github.com/Microsoft/TypeScript/issues/8245
// note that using this option does not lay out into a flat directory
// with application and generated sources side-by-side, so you must
// teach your module loader how to resolve such imports as well.
writeImportsForRootDirs?: boolean;
}

export default Options;