feat(ivy): implement ngcc build marker (#25557)

`ngcc` adds marker files to each folder that has been
compiled, containing the version of the ngcc used.

When compiling, it will ignore folders that contain these
marker files, as long as the version matches.

PR Close #25557
This commit is contained in:
Pete Bacon Darwin 2018-08-17 22:00:00 +01:00 committed by Misko Hevery
parent 2a672a97ab
commit b0cb134815
4 changed files with 123 additions and 18 deletions

View File

@ -5,7 +5,14 @@ set -e -x
PATH=$PATH:$(npm bin)
ivy-ngcc fesm2015,esm2015
# Did it add the appropriate build markers?
ls node_modules/@angular/common | grep __modified_by_ngcc_for_fesm2015
if [[ $? != 0 ]]; then exit 1; fi
ls node_modules/@angular/common | grep __modified_by_ngcc_for_esm2015
if [[ $? != 0 ]]; then exit 1; fi
ngc -p tsconfig-app.json
# Look for correct output
grep "directives: \[\S*\.NgIf\]" dist/src/main.js > /dev/null
# Did it compile the main.ts correctly?
grep "directives: \[\S*\.NgIf\]" dist/src/main.js
if [[ $? != 0 ]]; then exit 1; fi

View File

@ -25,9 +25,7 @@ import {Esm2015Renderer} from '../rendering/esm2015_renderer';
import {Esm5Renderer} from '../rendering/esm5_renderer';
import {FileInfo, Renderer} from '../rendering/renderer';
import {getEntryPoints} from './utils';
import {checkMarkerFile, findAllPackageJsonFiles, getEntryPoints, writeMarkerFile} from './utils';
/**
* A Package is stored in a directory on disk and that directory can contain one or more package
@ -52,7 +50,11 @@ export class PackageTransformer {
transform(packagePath: string, format: string, targetPath: string = 'node_modules'): void {
const sourceNodeModules = this.findNodeModulesPath(packagePath);
const targetNodeModules = resolve(sourceNodeModules, '..', targetPath);
const entryPoints = getEntryPoints(packagePath, format);
const packageJsonPaths =
findAllPackageJsonFiles(packagePath)
// Ignore paths that have been built already
.filter(packageJsonPath => !checkMarkerFile(packageJsonPath, format));
const entryPoints = getEntryPoints(packageJsonPaths, format);
entryPoints.forEach(entryPoint => {
const outputFiles: FileInfo[] = [];
@ -102,6 +104,9 @@ export class PackageTransformer {
// Write out all the transformed files.
outputFiles.forEach(file => this.writeFile(file));
});
// Write the built-with-ngcc markers
packageJsonPaths.forEach(packageJsonPath => { writeMarkerFile(packageJsonPath, format); });
}
getHost(format: string, program: ts.Program, dtsMapper: DtsMapper): NgccReflectionHost {

View File

@ -6,12 +6,14 @@
* found in the LICENSE file at https://angular.io/license
*/
import {readFileSync} from 'fs';
import {dirname, relative, resolve} from 'path';
import {existsSync, readFileSync, writeFileSync} from 'fs';
import {dirname, resolve} from 'path';
import {find} from 'shelljs';
import {isDefined} from '../utils';
export const NGCC_VERSION = '0.0.0-PLACEHOLDER';
/**
* Represents an entry point to a package or sub-package.
*
@ -29,7 +31,7 @@ export class EntryPoint {
* @param relativeEntryPath The relative path to the entry point file.
* @param relativeDtsEntryPath The relative path to the `.d.ts` entry point file.
*/
constructor(packageRoot: string, relativeEntryPath: string, relativeDtsEntryPath: string) {
constructor(public packageRoot: string, relativeEntryPath: string, relativeDtsEntryPath: string) {
this.entryFileName = resolve(packageRoot, relativeEntryPath);
this.entryRoot = dirname(this.entryFileName);
const dtsEntryFileName = resolve(packageRoot, relativeDtsEntryPath);
@ -63,15 +65,14 @@ export function findAllPackageJsonFiles(rootDirectory: string): string[] {
}
/**
* Identify the entry points of a package.
* Identify the entry points of a collection of package.json files.
*
* @param packageDirectory The absolute path to the root directory that contains the package.
* @param packageJsonPaths A collection of absolute paths to the package.json files.
* @param format The format of the entry points to look for within the package.
*
* @returns A collection of `EntryPoint`s that correspond to entry points for the package.
*/
export function getEntryPoints(packageDirectory: string, format: string): EntryPoint[] {
const packageJsonPaths = findAllPackageJsonFiles(packageDirectory);
export function getEntryPoints(packageJsonPaths: string[], format: string): EntryPoint[] {
const entryPoints =
packageJsonPaths
.map(packageJsonPath => {
@ -86,3 +87,26 @@ export function getEntryPoints(packageDirectory: string, format: string): EntryP
.filter(isDefined);
return entryPoints;
}
function getMarkerPath(packageJsonPath: string, format: string) {
return resolve(dirname(packageJsonPath), `__modified_by_ngcc_for_${format}__`);
}
export function checkMarkerFile(packageJsonPath: string, format: string) {
const markerPath = getMarkerPath(packageJsonPath, format);
const markerExists = existsSync(markerPath);
if (markerExists) {
const previousVersion = readFileSync(markerPath, 'utf8');
if (previousVersion !== NGCC_VERSION) {
throw new Error(
'The ngcc compiler has changed since the last ngcc build.\n' +
'Please completely remove `node_modules` and try again.');
}
}
return markerExists;
}
export function writeMarkerFile(packageJsonPath: string, format: string) {
const markerPath = getMarkerPath(packageJsonPath, format);
writeFileSync(markerPath, NGCC_VERSION, 'utf8');
}

View File

@ -6,8 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {existsSync, readFileSync, writeFileSync} from 'fs';
import * as mockFs from 'mock-fs';
import {EntryPoint, findAllPackageJsonFiles, getEntryPoints} from '../../src/transform/utils';
import {EntryPoint, checkMarkerFile, findAllPackageJsonFiles, getEntryPoints, writeMarkerFile} from '../../src/transform/utils';
function createMockFileSystem() {
mockFs({
@ -95,6 +97,11 @@ describe('EntryPoint', () => {
const entryPoint = new EntryPoint('/foo/bar', '../baz/qux/../quux.js', '/typings/foo/bar.d.ts');
expect(entryPoint.entryFileName).toBe('/foo/baz/quux.js');
});
it('should expose the package root for the entry point file', () => {
const entryPoint = new EntryPoint('/foo/bar', '../baz/qux/../quux.js', '/typings/foo/bar.d.ts');
expect(entryPoint.packageRoot).toBe('/foo/bar');
});
});
describe('findAllPackageJsonFiles()', () => {
@ -138,7 +145,14 @@ describe('getEntryPoints()', () => {
afterEach(restoreRealFileSystem);
it('should return the entry points for the specified format from each `package.json`', () => {
const entryPoints = getEntryPoints('/node_modules/@angular/common', 'fesm2015');
const entryPoints = getEntryPoints(
[
'/node_modules/@angular/common/package.json',
'/node_modules/@angular/common/http/package.json',
'/node_modules/@angular/common/http/testing/package.json',
'/node_modules/@angular/common/testing/package.json'
],
'fesm2015');
entryPoints.forEach(ep => expect(ep).toEqual(jasmine.any(EntryPoint)));
const sortedPaths = entryPoints.map(x => x.entryFileName).sort();
@ -151,17 +165,18 @@ describe('getEntryPoints()', () => {
});
it('should return an empty array if there are no matching `package.json` files', () => {
const entryPoints = getEntryPoints('/node_modules/@angular/other', 'fesm2015');
const entryPoints = getEntryPoints([], 'fesm2015');
expect(entryPoints).toEqual([]);
});
it('should return an empty array if there are no matching formats', () => {
const entryPoints = getEntryPoints('/node_modules/@angular/common', 'fesm3000');
const entryPoints = getEntryPoints(['/node_modules/@angular/common/package.json'], 'fesm3000');
expect(entryPoints).toEqual([]);
});
it('should return an entry point even if the typings are not specified', () => {
const entryPoints = getEntryPoints('/node_modules/@angular/no-typings', 'fesm2015');
const entryPoints =
getEntryPoints(['/node_modules/@angular/no-typings/package.json'], 'fesm2015');
expect(entryPoints.length).toEqual(1);
expect(entryPoints[0].entryFileName)
.toEqual('/node_modules/@angular/no-typings/fesm2015/index.js');
@ -169,3 +184,57 @@ describe('getEntryPoints()', () => {
expect(entryPoints[0].dtsEntryRoot).toEqual(entryPoints[0].entryRoot);
});
});
describe('Marker files', () => {
beforeEach(createMockFileSystem);
afterEach(restoreRealFileSystem);
describe('writeMarkerFile', () => {
it('should write a file containing the version placeholder', () => {
expect(existsSync('/node_modules/@angular/common/__modified_by_ngcc_for_fesm2015__'))
.toBe(false);
expect(existsSync('/node_modules/@angular/common/__modified_by_ngcc_for_esm5__')).toBe(false);
writeMarkerFile('/node_modules/@angular/common/package.json', 'fesm2015');
expect(existsSync('/node_modules/@angular/common/__modified_by_ngcc_for_fesm2015__'))
.toBe(true);
expect(existsSync('/node_modules/@angular/common/__modified_by_ngcc_for_esm5__')).toBe(false);
expect(
readFileSync('/node_modules/@angular/common/__modified_by_ngcc_for_fesm2015__', 'utf8'))
.toEqual('0.0.0-PLACEHOLDER');
writeMarkerFile('/node_modules/@angular/common/package.json', 'esm5');
expect(existsSync('/node_modules/@angular/common/__modified_by_ngcc_for_fesm2015__'))
.toBe(true);
expect(existsSync('/node_modules/@angular/common/__modified_by_ngcc_for_esm5__')).toBe(true);
expect(
readFileSync('/node_modules/@angular/common/__modified_by_ngcc_for_fesm2015__', 'utf8'))
.toEqual('0.0.0-PLACEHOLDER');
expect(readFileSync('/node_modules/@angular/common/__modified_by_ngcc_for_esm5__', 'utf8'))
.toEqual('0.0.0-PLACEHOLDER');
});
});
describe('checkMarkerFile', () => {
it('should return false if the marker file does not exist', () => {
expect(checkMarkerFile('/node_modules/@angular/common/package.json', 'fesm2015')).toBe(false);
});
it('should return true if the marker file exists and contains the correct version', () => {
writeFileSync(
'/node_modules/@angular/common/__modified_by_ngcc_for_fesm2015__', '0.0.0-PLACEHOLDER',
'utf8');
expect(checkMarkerFile('/node_modules/@angular/common/package.json', 'fesm2015')).toBe(true);
});
it('should throw if the marker file exists but contains the wrong version', () => {
writeFileSync(
'/node_modules/@angular/common/__modified_by_ngcc_for_fesm2015__', 'WRONG_VERSION',
'utf8');
expect(() => checkMarkerFile('/node_modules/@angular/common/package.json', 'fesm2015'))
.toThrowError(
'The ngcc compiler has changed since the last ngcc build.\n' +
'Please completely remove `node_modules` and try again.');
});
});
});