Skip to content

Commit 790362e

Browse files
authored
fix: put all ngc files into a single directory (angular#10486)
Prior to this change `ngc` would place generated files which refer to components in the node_modules into the node_module. This is an issue. Now all of the files are forced into a single directory as specified in `tsconfig.json` by the `genDir` option. see: https://docs.google.com/document/d/1OgP1RIpZ-lWUc4113J3w13HTDcW-1-0o7TuGz0tGx0g
1 parent 2eda7a5 commit 790362e

File tree

4 files changed

+181
-48
lines changed

4 files changed

+181
-48
lines changed

modules/@angular/compiler-cli/src/codegen.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,14 @@ export class CodeGenerator {
8080
}
8181
}
8282

83-
return path.join(this.options.genDir, path.relative(root, filePath));
83+
// transplant the codegen path to be inside the `genDir`
84+
var relativePath: string = path.relative(root, filePath);
85+
while (relativePath.startsWith('..' + path.sep)) {
86+
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
87+
// into `genDir`.
88+
relativePath = relativePath.substr(3);
89+
}
90+
return path.join(this.options.genDir, relativePath);
8491
}
8592

8693
codegen(): Promise<any> {

modules/@angular/compiler-cli/src/reflector_host.ts

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {StaticReflectorHost, StaticSymbol} from './static_reflector';
1616

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

2022
export interface ReflectorHostContext {
2123
fileExists(fileName: string): boolean;
@@ -27,10 +29,13 @@ export interface ReflectorHostContext {
2729
export class ReflectorHost implements StaticReflectorHost, ImportGenerator {
2830
private metadataCollector = new MetadataCollector();
2931
private context: ReflectorHostContext;
32+
private isGenDirChildOfRootDir: boolean;
3033
constructor(
3134
private program: ts.Program, private compilerHost: ts.CompilerHost,
3235
private options: AngularCompilerOptions, context?: ReflectorHostContext) {
3336
this.context = context || new NodeReflectorHostContext();
37+
var genPath: string = path.relative(options.basePath, options.genDir);
38+
this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..');
3439
}
3540

3641
angularImportLocations() {
@@ -66,10 +71,19 @@ export class ReflectorHost implements StaticReflectorHost, ImportGenerator {
6671
/**
6772
* We want a moduleId that will appear in import statements in the generated code.
6873
* These need to be in a form that system.js can load, so absolute file paths don't work.
69-
* Relativize the paths by checking candidate prefixes of the absolute path, to see if
70-
* they are resolvable by the moduleResolution strategy from the CompilerHost.
74+
*
75+
* The `containingFile` is always in the `genDir`, where as the `importedFile` can be in
76+
* `genDir`, `node_module` or `basePath`. The `importedFile` is either a generated file or
77+
* existing file.
78+
*
79+
* | genDir | node_module | rootDir
80+
* --------------+----------+-------------+----------
81+
* generated | relative | relative | n/a
82+
* existing file | n/a | absolute | relative(*)
83+
*
84+
* NOTE: (*) the relative path is computed depending on `isGenDirChildOfRootDir`.
7185
*/
72-
getImportPath(containingFile: string, importedFile: string) {
86+
getImportPath(containingFile: string, importedFile: string): string {
7387
importedFile = this.resolveAssetUrl(importedFile, containingFile);
7488
containingFile = this.resolveAssetUrl(containingFile, '');
7589

@@ -79,27 +93,59 @@ export class ReflectorHost implements StaticReflectorHost, ImportGenerator {
7993
this.context.assumeFileExists(importedFile);
8094
}
8195

82-
const importModuleName = importedFile.replace(EXT, '');
83-
const parts = importModuleName.split(path.sep).filter(p => !!p);
84-
85-
for (let index = parts.length - 1; index >= 0; index--) {
86-
let candidate = parts.slice(index, parts.length).join(path.sep);
87-
if (this.resolve('.' + path.sep + candidate, containingFile) === importedFile) {
88-
return `./${candidate}`;
96+
containingFile = this.rewriteGenDirPath(containingFile);
97+
const containingDir = path.dirname(containingFile);
98+
// drop extension
99+
importedFile = importedFile.replace(EXT, '');
100+
101+
var nodeModulesIndex = importedFile.indexOf(NODE_MODULES);
102+
const importModule = nodeModulesIndex === -1 ?
103+
null :
104+
importedFile.substring(nodeModulesIndex + NODE_MODULES.length);
105+
const isGeneratedFile = IS_GENERATED.test(importedFile);
106+
107+
if (isGeneratedFile) {
108+
// rewrite to genDir path
109+
if (importModule) {
110+
// it is generated, therefore we do a relative path to the factory
111+
return this.dotRelative(containingDir, this.options.genDir + NODE_MODULES + importModule);
112+
} else {
113+
// assume that import is also in `genDir`
114+
importedFile = this.rewriteGenDirPath(importedFile);
115+
return this.dotRelative(containingDir, importedFile);
89116
}
90-
if (this.resolve(candidate, containingFile) === importedFile) {
91-
return candidate;
117+
} else {
118+
// user code import
119+
if (importModule) {
120+
return importModule;
121+
} else {
122+
if (!this.isGenDirChildOfRootDir) {
123+
// assume that they are on top of each other.
124+
importedFile = importedFile.replace(this.options.basePath, this.options.genDir);
125+
}
126+
return this.dotRelative(containingDir, importedFile);
92127
}
93128
}
129+
}
94130

95-
// Try a relative import
96-
let candidate = path.relative(path.dirname(containingFile), importModuleName);
97-
if (this.resolve(candidate, containingFile) === importedFile) {
98-
return candidate;
99-
}
131+
private dotRelative(from: string, to: string): string {
132+
var rPath: string = path.relative(from, to);
133+
return rPath.startsWith('.') ? rPath : './' + rPath;
134+
}
100135

101-
throw new Error(
102-
`Unable to find any resolvable import for ${importedFile} relative to ${containingFile}`);
136+
/**
137+
* Moves the path into `genDir` folder while preserving the `node_modules` directory.
138+
*/
139+
private rewriteGenDirPath(filepath: string) {
140+
var nodeModulesIndex = filepath.indexOf(NODE_MODULES);
141+
if (nodeModulesIndex !== -1) {
142+
// If we are in node_modulse, transplant them into `genDir`.
143+
return path.join(this.options.genDir, filepath.substring(nodeModulesIndex));
144+
} else {
145+
// pretend that containing file is on top of the `genDir` to normalize the paths.
146+
// we apply the `genDir` => `rootDir` delta through `rootDirPrefix` later.
147+
return filepath.replace(this.options.basePath, this.options.genDir);
148+
}
103149
}
104150

105151
findDeclaration(

modules/@angular/compiler-cli/test/reflector_host_spec.ts

Lines changed: 107 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ describe('reflector_host', () => {
1717
var context: MockContext;
1818
var host: ts.CompilerHost;
1919
var program: ts.Program;
20-
var reflectorHost: ReflectorHost;
20+
var reflectorNestedGenDir: ReflectorHost;
21+
var reflectorSiblingGenDir: ReflectorHost;
2122

2223
beforeEach(() => {
2324
context = new MockContext('/tmp/src', clone(FILES));
@@ -32,20 +33,95 @@ describe('reflector_host', () => {
3233
if (errors && errors.length) {
3334
throw new Error('Expected no errors');
3435
}
35-
reflectorHost = new ReflectorHost(
36+
reflectorNestedGenDir = new ReflectorHost(
3637
program, host, {
37-
genDir: '/tmp/dist',
38-
basePath: '/tmp/src',
38+
genDir: '/tmp/project/src/gen',
39+
basePath: '/tmp/project/src',
3940
skipMetadataEmit: false,
4041
skipTemplateCodegen: false,
4142
trace: false
4243
},
4344
context);
45+
reflectorSiblingGenDir = new ReflectorHost(
46+
program, host, {
47+
genDir: '/tmp/project/gen',
48+
basePath: '/tmp/project/src',
49+
skipMetadataEmit: false,
50+
skipTemplateCodegen: false,
51+
trace: false
52+
},
53+
context);
54+
});
55+
56+
describe('nestedGenDir', () => {
57+
it('should import node_module from factory', () => {
58+
expect(reflectorNestedGenDir.getImportPath(
59+
'/tmp/project/src/gen/my.ngfactory.ts',
60+
'/tmp/project/node_modules/@angular/core.d.ts'))
61+
.toEqual('@angular/core');
62+
});
63+
64+
it('should import factory from factory', () => {
65+
expect(reflectorNestedGenDir.getImportPath(
66+
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ngfactory.ts'))
67+
.toEqual('./my.other.ngfactory');
68+
expect(reflectorNestedGenDir.getImportPath(
69+
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.css.ts'))
70+
.toEqual('../my.other.css');
71+
expect(reflectorNestedGenDir.getImportPath(
72+
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.css.shim.ts'))
73+
.toEqual('./a/my.other.css.shim');
74+
});
75+
76+
it('should import application from factory', () => {
77+
expect(reflectorNestedGenDir.getImportPath(
78+
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
79+
.toEqual('../my.other');
80+
expect(reflectorNestedGenDir.getImportPath(
81+
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
82+
.toEqual('../../my.other');
83+
expect(reflectorNestedGenDir.getImportPath(
84+
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.ts'))
85+
.toEqual('../a/my.other');
86+
});
87+
});
88+
89+
describe('nestedGenDir', () => {
90+
it('should import node_module from factory', () => {
91+
expect(reflectorSiblingGenDir.getImportPath(
92+
'/tmp/project/src/gen/my.ngfactory.ts',
93+
'/tmp/project/node_modules/@angular/core.d.ts'))
94+
.toEqual('@angular/core');
95+
});
96+
97+
it('should import factory from factory', () => {
98+
expect(reflectorSiblingGenDir.getImportPath(
99+
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ngfactory.ts'))
100+
.toEqual('./my.other.ngfactory');
101+
expect(reflectorSiblingGenDir.getImportPath(
102+
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.css.ts'))
103+
.toEqual('../my.other.css');
104+
expect(reflectorSiblingGenDir.getImportPath(
105+
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.css.shim.ts'))
106+
.toEqual('./a/my.other.css.shim');
107+
});
108+
109+
it('should import application from factory', () => {
110+
expect(reflectorSiblingGenDir.getImportPath(
111+
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
112+
.toEqual('./my.other');
113+
expect(reflectorSiblingGenDir.getImportPath(
114+
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
115+
.toEqual('../my.other');
116+
expect(reflectorSiblingGenDir.getImportPath(
117+
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.ts'))
118+
.toEqual('./a/my.other');
119+
});
44120
});
45121

46122
it('should provide the import locations for angular', () => {
47123
let {coreDecorators, diDecorators, diMetadata, animationMetadata, provider} =
48-
reflectorHost.angularImportLocations();
124+
reflectorNestedGenDir.angularImportLocations();
49125
expect(coreDecorators).toEqual('@angular/core/src/metadata');
50126
expect(diDecorators).toEqual('@angular/core/src/di/decorators');
51127
expect(diMetadata).toEqual('@angular/core/src/di/metadata');
@@ -54,82 +130,86 @@ describe('reflector_host', () => {
54130
});
55131

56132
it('should be able to produce an import from main @angular/core', () => {
57-
expect(reflectorHost.getImportPath('main.ts', 'node_modules/@angular/core.d.ts'))
133+
expect(reflectorNestedGenDir.getImportPath(
134+
'/tmp/project/src/main.ts', '/tmp/project/node_modules/@angular/core.d.ts'))
58135
.toEqual('@angular/core');
59136
});
60137

61-
it('should be ble to produce an import from main to a sub-directory', () => {
62-
expect(reflectorHost.getImportPath('main.ts', 'lib/utils.ts')).toEqual('./lib/utils');
138+
it('should be able to produce an import from main to a sub-directory', () => {
139+
expect(reflectorNestedGenDir.getImportPath('main.ts', 'lib/utils.ts')).toEqual('./lib/utils');
63140
});
64141

65142
it('should be able to produce an import from to a peer file', () => {
66-
expect(reflectorHost.getImportPath('lib/utils.ts', 'lib/collections.ts'))
143+
expect(reflectorNestedGenDir.getImportPath('lib/utils.ts', 'lib/collections.ts'))
67144
.toEqual('./collections');
68145
});
69146

70147
it('should be able to produce an import from to a sibling directory', () => {
71-
expect(reflectorHost.getImportPath('lib2/utils2.ts', 'lib/utils.ts')).toEqual('../lib/utils');
148+
expect(reflectorNestedGenDir.getImportPath('lib2/utils2.ts', 'lib/utils.ts'))
149+
.toEqual('../lib/utils');
72150
});
73151

74152
it('should be able to produce a symbol for an exported symbol', () => {
75-
expect(reflectorHost.findDeclaration('@angular/router-deprecated', 'foo', 'main.ts'))
153+
expect(reflectorNestedGenDir.findDeclaration('@angular/router-deprecated', 'foo', 'main.ts'))
76154
.toBeDefined();
77155
});
78156

79157
it('should be able to produce a symbol for values space only reference', () => {
80-
expect(
81-
reflectorHost.findDeclaration('@angular/router-deprecated/src/providers', 'foo', 'main.ts'))
158+
expect(reflectorNestedGenDir.findDeclaration(
159+
'@angular/router-deprecated/src/providers', 'foo', 'main.ts'))
82160
.toBeDefined();
83161
});
84162

85163

86164
it('should be produce the same symbol if asked twice', () => {
87-
let foo1 = reflectorHost.getStaticSymbol('main.ts', 'foo');
88-
let foo2 = reflectorHost.getStaticSymbol('main.ts', 'foo');
165+
let foo1 = reflectorNestedGenDir.getStaticSymbol('main.ts', 'foo');
166+
let foo2 = reflectorNestedGenDir.getStaticSymbol('main.ts', 'foo');
89167
expect(foo1).toBe(foo2);
90168
});
91169

92170
it('should be able to produce a symbol for a module with no file', () => {
93-
expect(reflectorHost.getStaticSymbol('angularjs', 'SomeAngularSymbol')).toBeDefined();
171+
expect(reflectorNestedGenDir.getStaticSymbol('angularjs', 'SomeAngularSymbol')).toBeDefined();
94172
});
95173

96174
it('should be able to read a metadata file', () => {
97-
expect(reflectorHost.getMetadataFor('node_modules/@angular/core.d.ts'))
175+
expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/core.d.ts'))
98176
.toEqual({__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}});
99177
});
100178

101179
it('should be able to read metadata from an otherwise unused .d.ts file ', () => {
102-
expect(reflectorHost.getMetadataFor('node_modules/@angular/unused.d.ts')).toBeUndefined();
180+
expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/unused.d.ts'))
181+
.toBeUndefined();
103182
});
104183

105184
it('should return undefined for missing modules', () => {
106-
expect(reflectorHost.getMetadataFor('node_modules/@angular/missing.d.ts')).toBeUndefined();
185+
expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/missing.d.ts'))
186+
.toBeUndefined();
107187
});
108188

109189
it('should be able to trace a named export', () => {
110-
const symbol =
111-
reflectorHost.findDeclaration('./reexport/reexport.d.ts', 'One', '/tmp/src/main.ts');
190+
const symbol = reflectorNestedGenDir.findDeclaration(
191+
'./reexport/reexport.d.ts', 'One', '/tmp/src/main.ts');
112192
expect(symbol.name).toEqual('One');
113193
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
114194
});
115195

116196
it('should be able to trace a renamed export', () => {
117-
const symbol =
118-
reflectorHost.findDeclaration('./reexport/reexport.d.ts', 'Four', '/tmp/src/main.ts');
197+
const symbol = reflectorNestedGenDir.findDeclaration(
198+
'./reexport/reexport.d.ts', 'Four', '/tmp/src/main.ts');
119199
expect(symbol.name).toEqual('Three');
120200
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
121201
});
122202

123203
it('should be able to trace an export * export', () => {
124-
const symbol =
125-
reflectorHost.findDeclaration('./reexport/reexport.d.ts', 'Five', '/tmp/src/main.ts');
204+
const symbol = reflectorNestedGenDir.findDeclaration(
205+
'./reexport/reexport.d.ts', 'Five', '/tmp/src/main.ts');
126206
expect(symbol.name).toEqual('Five');
127207
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin5.d.ts');
128208
});
129209

130210
it('should be able to trace a multi-level re-export', () => {
131-
const symbol =
132-
reflectorHost.findDeclaration('./reexport/reexport.d.ts', 'Thirty', '/tmp/src/main.ts');
211+
const symbol = reflectorNestedGenDir.findDeclaration(
212+
'./reexport/reexport.d.ts', 'Thirty', '/tmp/src/main.ts');
133213
expect(symbol.name).toEqual('Thirty');
134214
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin30.d.ts');
135215
});

modules/@angular/compiler/src/output/ts_emitter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor
254254
}
255255

256256
getBuiltinMethodName(method: o.BuiltinMethod): string {
257-
var name: any /** TODO #9100 */;
257+
var name: string;
258258
switch (method) {
259259
case o.BuiltinMethod.ConcatArray:
260260
name = 'concat';

0 commit comments

Comments
 (0)