Skip to content

Commit 6304e79

Browse files
petebacondarwinFrederikSchlemmer
authored andcommitted
feat(ivy): find all packages to be compiled by ngcc (angular#25406)
PR Close angular#25406
1 parent bb26325 commit 6304e79

File tree

4 files changed

+92
-20
lines changed

4 files changed

+92
-20
lines changed

integration/ngcc/test.sh

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
#!/bin/bash
22

3-
set -x
3+
set -e -x
44

55
PATH=$PATH:$(npm bin)
66

7-
ivy-ngcc node_modules/@angular/common
8-
cp -r node_modules_ngtsc/* node_modules/
7+
ivy-ngcc fesm2015,esm2015
98
ngc -p tsconfig-app.json
109

10+
# Look for correct output
11+
grep "directives: \[\S*\.NgIf\]" dist/src/main.js > /dev/null

packages/compiler-cli/src/ngcc/src/main.ts

+72-8
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,87 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import {resolve} from 'path';
8+
import {existsSync, lstatSync, readFileSync, readdirSync} from 'fs';
9+
import {posix as path} from 'path';
10+
911
import {PackageTransformer} from './transform/package_transformer';
1012

1113
export function mainNgcc(args: string[]): number {
12-
const packagePaths = args[0] ? [resolve(args[0])] : [];
13-
const formats = args[1] ? [args[1]] : ['fesm2015', 'esm2015', 'fesm5', 'esm5'];
14-
15-
// TODO: find all the package types to transform
16-
// TODO: error/warning logging/handling etc
14+
const formats = args[0] ? args[0].split(',') : ['fesm2015', 'esm2015', 'fesm5', 'esm5'];
15+
const packagePaths = args[1] ? [path.resolve(args[1])] : findPackagesToCompile();
16+
const targetPath = args[2] ? args[2] : 'node_modules';
1717

1818
const transformer = new PackageTransformer();
1919
packagePaths.forEach(packagePath => {
2020
formats.forEach(format => {
21-
console.warn(`Compiling ${packagePath}:${format}`);
22-
transformer.transform(packagePath, format);
21+
// TODO: remove before flight
22+
console.warn(`Compiling ${packagePath} : ${format}`);
23+
transformer.transform(packagePath, format, targetPath);
2324
});
2425
});
2526

2627
return 0;
2728
}
29+
30+
// TODO - consider nested node_modules
31+
32+
/**
33+
* Check whether the given folder needs to be included in the ngcc compilation.
34+
* We do not care about folders that are:
35+
*
36+
* - symlinks
37+
* - node_modules
38+
* - do not contain a package.json
39+
* - do not have a typings property in package.json
40+
* - do not have an appropriate metadata.json file
41+
*
42+
* @param folderPath The absolute path to the folder.
43+
*/
44+
function hasMetadataFile(folderPath: string): boolean {
45+
const folderName = path.basename(folderPath);
46+
if (folderName === 'node_modules' || lstatSync(folderPath).isSymbolicLink()) {
47+
return false;
48+
}
49+
const packageJsonPath = path.join(folderPath, 'package.json');
50+
if (!existsSync(packageJsonPath)) {
51+
return false;
52+
}
53+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
54+
if (!packageJson.typings) {
55+
return false;
56+
}
57+
// TODO: avoid if packageJson contains built marker
58+
const metadataPath =
59+
path.join(folderPath, packageJson.typings.replace(/\.d\.ts$/, '.metadata.json'));
60+
return existsSync(metadataPath);
61+
}
62+
63+
/**
64+
* Look for packages that need to be compiled.
65+
* The function will recurse into folders that start with `@...`, e.g. `@angular/...`.
66+
* Without an argument it starts at `node_modules`.
67+
*/
68+
function findPackagesToCompile(folder: string = 'node_modules'): string[] {
69+
const fullPath = path.resolve(folder);
70+
const packagesToCompile: string[] = [];
71+
readdirSync(fullPath)
72+
.filter(p => !p.startsWith('.'))
73+
.filter(p => lstatSync(path.join(fullPath, p)).isDirectory())
74+
.forEach(p => {
75+
const packagePath = path.join(fullPath, p);
76+
if (p.startsWith('@')) {
77+
packagesToCompile.push(...findPackagesToCompile(packagePath));
78+
} else {
79+
packagesToCompile.push(packagePath);
80+
}
81+
});
82+
83+
return packagesToCompile.filter(path => recursiveDirTest(path, hasMetadataFile));
84+
}
85+
86+
function recursiveDirTest(dir: string, test: (dir: string) => boolean): boolean {
87+
return test(dir) || readdirSync(dir).some(segment => {
88+
const fullPath = path.join(dir, segment);
89+
return lstatSync(fullPath).isDirectory() && recursiveDirTest(fullPath, test);
90+
});
91+
}

packages/compiler-cli/src/ngcc/src/transform/package_transformer.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import {readFileSync, writeFileSync} from 'fs';
8+
import {existsSync, readFileSync, writeFileSync} from 'fs';
99
import {dirname, relative, resolve} from 'path';
10-
import {mkdir} from 'shelljs';
10+
import {mkdir, mv} from 'shelljs';
1111
import * as ts from 'typescript';
1212

1313
import {DtsFileTransformer} from '../../../ngtsc/transform';
@@ -49,9 +49,9 @@ import {getEntryPoints} from './utils';
4949
* - Some formats may contain multiple "modules" in a single file.
5050
*/
5151
export class PackageTransformer {
52-
transform(packagePath: string, format: string): void {
52+
transform(packagePath: string, format: string, targetPath: string = 'node_modules'): void {
5353
const sourceNodeModules = this.findNodeModulesPath(packagePath);
54-
const targetNodeModules = sourceNodeModules.replace(/node_modules$/, 'node_modules_ngtsc');
54+
const targetNodeModules = resolve(sourceNodeModules, '..', targetPath);
5555
const entryPoints = getEntryPoints(packagePath, format);
5656

5757
entryPoints.forEach(entryPoint => {
@@ -63,6 +63,7 @@ export class PackageTransformer {
6363
};
6464

6565
// Create the TS program and necessary helpers.
66+
// TODO : create a custom compiler host that reads from .bak files if available.
6667
const host = ts.createCompilerHost(options);
6768
const packageProgram = ts.createProgram([entryPoint.entryFileName], options, host);
6869
const typeChecker = packageProgram.getTypeChecker();
@@ -195,6 +196,10 @@ export class PackageTransformer {
195196

196197
writeFile(file: FileInfo): void {
197198
mkdir('-p', dirname(file.path));
199+
const backPath = file.path + '.bak';
200+
if (existsSync(file.path) && !existsSync(backPath)) {
201+
mv(file.path, backPath);
202+
}
198203
writeFileSync(file.path, file.contents, 'utf8');
199204
}
200205
}

packages/compiler-cli/test/ngcc/ngcc_spec.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {mainNgcc} from '../../src/ngcc/src/main';
1212

1313
import {TestSupport, isInBazel, setup} from '../test_support';
1414

15+
const OUTPUT_PATH = 'node_modules_ngtsc';
16+
1517
describe('ngcc behavioral tests', () => {
1618
if (!isInBazel()) {
1719
// These tests should be excluded from the non-Bazel build.
@@ -26,7 +28,7 @@ describe('ngcc behavioral tests', () => {
2628
const {cp, mkdir, rm, set} = require('shelljs');
2729

2830
const tempRootDir = join(tmpdir(), 'ngcc-spec', format);
29-
const outputDir = 'node_modules_ngtsc';
31+
const outputDir = OUTPUT_PATH;
3032

3133
set('-e');
3234
rm('-rf', tempRootDir);
@@ -44,7 +46,7 @@ describe('ngcc behavioral tests', () => {
4446
const commonPath = join(support.basePath, 'node_modules/@angular/common');
4547
const format = 'fesm2015';
4648

47-
expect(mainNgcc([commonPath, format])).toBe(0);
49+
expect(mainNgcc([format, commonPath, OUTPUT_PATH])).toBe(0);
4850

4951
onSpecCompleted(format);
5052
});
@@ -53,7 +55,7 @@ describe('ngcc behavioral tests', () => {
5355
const commonPath = join(support.basePath, 'node_modules/@angular/common');
5456
const format = 'fesm5';
5557

56-
expect(mainNgcc([commonPath, format])).toBe(0);
58+
expect(mainNgcc([format, commonPath, OUTPUT_PATH])).toBe(0);
5759

5860
onSpecCompleted(format);
5961
});
@@ -62,7 +64,7 @@ describe('ngcc behavioral tests', () => {
6264
const commonPath = join(support.basePath, 'node_modules/@angular/common');
6365
const format = 'esm2015';
6466

65-
expect(mainNgcc([commonPath, format])).toBe(0);
67+
expect(mainNgcc([format, commonPath, OUTPUT_PATH])).toBe(0);
6668

6769
onSpecCompleted(format);
6870
});
@@ -71,7 +73,7 @@ describe('ngcc behavioral tests', () => {
7173
const commonPath = join(support.basePath, 'node_modules/@angular/common');
7274
const format = 'esm5';
7375

74-
expect(mainNgcc([commonPath, format])).toBe(0);
76+
expect(mainNgcc([format, commonPath, OUTPUT_PATH])).toBe(0);
7577

7678
onSpecCompleted(format);
7779
});

0 commit comments

Comments
 (0)