Skip to content

Commit

Permalink
refactor(ivy): ngcc - extract file writing out into a class (#29092)
Browse files Browse the repository at this point in the history
This is in preparation of having different file writing strategies.

PR Close #29092
  • Loading branch information
petebacondarwin authored and matsko committed Mar 20, 2019
1 parent a827bc2 commit 849b327
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 17 deletions.
10 changes: 9 additions & 1 deletion packages/compiler-cli/ngcc/src/main.ts
Expand Up @@ -18,6 +18,8 @@ import {EntryPointFormat, EntryPointJsonProperty, SUPPORTED_FORMAT_PROPERTIES, g
import {makeEntryPointBundle} from './packages/entry_point_bundle';
import {EntryPointFinder} from './packages/entry_point_finder';
import {Transformer} from './packages/transformer';
import {FileWriter} from './writing/file_writer';
import {InPlaceFileWriter} from './writing/in_place_file_writer';


/**
Expand Down Expand Up @@ -62,6 +64,7 @@ export function mainNgcc({basePath, targetEntryPointPath,
const host = new DependencyHost();
const resolver = new DependencyResolver(host);
const finder = new EntryPointFinder(resolver);
const fileWriter = getFileWriter();

const absoluteTargetEntryPointPath = targetEntryPointPath ?
AbsoluteFsPath.from(resolve(basePath, targetEntryPointPath)) :
Expand Down Expand Up @@ -116,7 +119,8 @@ export function mainNgcc({basePath, targetEntryPointPath,
compiledFormats.size === 0);
if (bundle) {
console.warn(`Compiling ${entryPoint.name} : ${property} as ${format}`);
transformer.transform(bundle);
const transformedFiles = transformer.transform(bundle);
fileWriter.writeBundle(entryPoint, bundle, transformedFiles);
compiledFormats.add(formatPath);
} else {
console.warn(
Expand All @@ -139,3 +143,7 @@ export function mainNgcc({basePath, targetEntryPointPath,
}
});
}

function getFileWriter(): FileWriter {
return new InPlaceFileWriter();
}
19 changes: 3 additions & 16 deletions packages/compiler-cli/ngcc/src/packages/transformer.ts
Expand Up @@ -5,9 +5,6 @@
* 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 {dirname} from 'canonical-path';
import {existsSync, writeFileSync} from 'fs';
import {mkdir, mv} from 'shelljs';
import * as ts from 'typescript';

import {CompiledFile, DecorationAnalyzer} from '../analysis/decoration_analyzer';
Expand All @@ -22,7 +19,6 @@ import {Esm5Renderer} from '../rendering/esm5_renderer';
import {EsmRenderer} from '../rendering/esm_renderer';
import {FileInfo, Renderer} from '../rendering/renderer';

import {EntryPoint} from './entry_point';
import {EntryPointBundle} from './entry_point_bundle';


Expand Down Expand Up @@ -54,8 +50,9 @@ export class Transformer {
/**
* Transform the source (and typings) files of a bundle.
* @param bundle the bundle to transform.
* @returns information about the files that were transformed.
*/
transform(bundle: EntryPointBundle): void {
transform(bundle: EntryPointBundle): FileInfo[] {
const isCore = bundle.isCore;
const reflectionHost = this.getHost(isCore, bundle);

Expand All @@ -69,8 +66,7 @@ export class Transformer {
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses);

// Write out all the transformed files.
renderedFiles.forEach(file => this.writeFile(file));
return renderedFiles;
}

getHost(isCore: boolean, bundle: EntryPointBundle): NgccReflectionHost {
Expand Down Expand Up @@ -122,15 +118,6 @@ export class Transformer {
return {decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses};
}

writeFile(file: FileInfo): void {
mkdir('-p', dirname(file.path));
const backPath = file.path + '.bak';
if (existsSync(file.path) && !existsSync(backPath)) {
mv(file.path, backPath);
}
writeFileSync(file.path, file.contents, 'utf8');
}
}


Expand Down
19 changes: 19 additions & 0 deletions packages/compiler-cli/ngcc/src/writing/file_writer.ts
@@ -0,0 +1,19 @@

/**
* @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 {EntryPoint} from '../packages/entry_point';
import {EntryPointBundle} from '../packages/entry_point_bundle';
import {FileInfo} from '../rendering/renderer';


/**
* Responsible for writing out the transformed files to disk.
*/
export interface FileWriter {
writeBundle(entryPoint: EntryPoint, bundle: EntryPointBundle, transformedFiles: FileInfo[]): void;
}
40 changes: 40 additions & 0 deletions packages/compiler-cli/ngcc/src/writing/in_place_file_writer.ts
@@ -0,0 +1,40 @@

/**
* @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 {dirname} from 'canonical-path';
import {existsSync, writeFileSync} from 'fs';
import {mkdir, mv} from 'shelljs';

import {EntryPoint} from '../packages/entry_point';
import {EntryPointBundle} from '../packages/entry_point_bundle';
import {FileInfo} from '../rendering/renderer';

import {FileWriter} from './file_writer';

/**
* This FileWriter overwrites the transformed file, in-place, while creating
* a back-up of the original file with an extra `.bak` extension.
*/
export class InPlaceFileWriter implements FileWriter {
writeBundle(_entryPoint: EntryPoint, _bundle: EntryPointBundle, transformedFiles: FileInfo[]) {
transformedFiles.forEach(file => this.writeFileAndBackup(file));
}
protected writeFileAndBackup(file: FileInfo): void {
mkdir('-p', dirname(file.path));
const backPath = file.path + '.__ivy_ngcc_bak';
if (existsSync(backPath)) {
throw new Error(
`Tried to overwrite ${backPath} with an ngcc back up file, which is disallowed.`);
}
if (existsSync(file.path)) {
mv(file.path, backPath);
}
writeFileSync(file.path, file.contents, 'utf8');
}
}
1 change: 1 addition & 0 deletions packages/compiler-cli/ngcc/test/BUILD.bazel
Expand Up @@ -34,6 +34,7 @@ jasmine_node_test(
"//tools/testing:node_no_angular",
"@npm//canonical-path",
"@npm//convert-source-map",
"@npm//shelljs",
],
)

Expand Down
@@ -0,0 +1,87 @@
/**
* @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 {existsSync, readFileSync} from 'fs';
import * as mockFs from 'mock-fs';

import {EntryPoint} from '../../src/packages/entry_point';
import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
import {InPlaceFileWriter} from '../../src/writing/in_place_file_writer';

function createMockFileSystem() {
mockFs({
'/package/path': {
'top-level.js': 'ORIGINAL TOP LEVEL',
'folder-1': {
'file-1.js': 'ORIGINAL FILE 1',
'file-2.js': 'ORIGINAL FILE 2',
},
'folder-2': {
'file-3.js': 'ORIGINAL FILE 3',
'file-4.js': 'ORIGINAL FILE 4',
},
'already-backed-up.js.__ivy_ngcc_bak': 'BACKED UP',
}
});
}

function restoreRealFileSystem() {
mockFs.restore();
}

describe('InPlaceFileWriter', () => {
beforeEach(createMockFileSystem);
afterEach(restoreRealFileSystem);

it('should write all the FileInfo to the disk', () => {
const fileWriter = new InPlaceFileWriter();
fileWriter.writeBundle({} as EntryPoint, {} as EntryPointBundle, [
{path: '/package/path/top-level.js', contents: 'MODIFIED TOP LEVEL'},
{path: '/package/path/folder-1/file-1.js', contents: 'MODIFIED FILE 1'},
{path: '/package/path/folder-2/file-4.js', contents: 'MODIFIED FILE 4'},
{path: '/package/path/folder-3/file-5.js', contents: 'NEW FILE 5'},
]);
expect(readFileSync('/package/path/top-level.js', 'utf8')).toEqual('MODIFIED TOP LEVEL');
expect(readFileSync('/package/path/folder-1/file-1.js', 'utf8')).toEqual('MODIFIED FILE 1');
expect(readFileSync('/package/path/folder-1/file-2.js', 'utf8')).toEqual('ORIGINAL FILE 2');
expect(readFileSync('/package/path/folder-2/file-3.js', 'utf8')).toEqual('ORIGINAL FILE 3');
expect(readFileSync('/package/path/folder-2/file-4.js', 'utf8')).toEqual('MODIFIED FILE 4');
expect(readFileSync('/package/path/folder-3/file-5.js', 'utf8')).toEqual('NEW FILE 5');
});

it('should create backups of all files that previously existed', () => {
const fileWriter = new InPlaceFileWriter();
fileWriter.writeBundle({} as EntryPoint, {} as EntryPointBundle, [
{path: '/package/path/top-level.js', contents: 'MODIFIED TOP LEVEL'},
{path: '/package/path/folder-1/file-1.js', contents: 'MODIFIED FILE 1'},
{path: '/package/path/folder-2/file-4.js', contents: 'MODIFIED FILE 4'},
{path: '/package/path/folder-3/file-5.js', contents: 'NEW FILE 5'},
]);
expect(readFileSync('/package/path/top-level.js.__ivy_ngcc_bak', 'utf8'))
.toEqual('ORIGINAL TOP LEVEL');
expect(readFileSync('/package/path/folder-1/file-1.js.__ivy_ngcc_bak', 'utf8'))
.toEqual('ORIGINAL FILE 1');
expect(existsSync('/package/path/folder-1/file-2.js.__ivy_ngcc_bak')).toBe(false);
expect(existsSync('/package/path/folder-2/file-3.js.__ivy_ngcc_bak')).toBe(false);
expect(readFileSync('/package/path/folder-2/file-4.js.__ivy_ngcc_bak', 'utf8'))
.toEqual('ORIGINAL FILE 4');
expect(existsSync('/package/path/folder-3/file-5.js.__ivy_ngcc_bak')).toBe(false);
});

it('should error if the backup file already exists', () => {
const fileWriter = new InPlaceFileWriter();
expect(
() => fileWriter.writeBundle(
{} as EntryPoint, {} as EntryPointBundle,
[
{path: '/package/path/already-backed-up.js', contents: 'MODIFIED BACKED UP'},
]))
.toThrowError(
'Tried to overwrite /package/path/already-backed-up.js.__ivy_ngcc_bak with an ngcc back up file, which is disallowed.');
});
});

0 comments on commit 849b327

Please sign in to comment.