refactor(ivy): ngcc - implement abstract FileSystem (#29643)
This commit introduces a new interface, which abstracts access to the underlying `FileSystem`. There is initially one concrete implementation, `NodeJsFileSystem`, which is simply wrapping the `fs` library of NodeJs. Going forward, we can provide a `MockFileSystem` for test, which should allow us to stop using `mock-fs` for most of the unit tests. We could also implement a `CachedFileSystem` that may improve the performance of ngcc. PR Close #29643
This commit is contained in:

committed by
Andrew Kushnir

parent
1fd2cc6340
commit
16d7dde2ad
@ -6,9 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {ConstantPool} from '@angular/compiler';
|
||||
import {NOOP_PERF_RECORDER} from '@angular/compiler-cli/src/ngtsc/perf';
|
||||
import * as path from 'canonical-path';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {BaseDefDecoratorHandler, ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, PipeDecoratorHandler, ReferencesRegistry, ResourceLoader} from '../../../src/ngtsc/annotations';
|
||||
@ -19,6 +17,7 @@ import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator';
|
||||
import {AbsoluteFsPath, LogicalFileSystem} from '../../../src/ngtsc/path';
|
||||
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../../src/ngtsc/scope';
|
||||
import {CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../../src/ngtsc/transform';
|
||||
import {FileSystem} from '../file_system/file_system';
|
||||
import {DecoratedClass} from '../host/decorated_class';
|
||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||
import {isDefined} from '../utils';
|
||||
@ -53,9 +52,10 @@ export interface MatchingHandler<A, M> {
|
||||
* Simple class that resolves and loads files directly from the filesystem.
|
||||
*/
|
||||
class NgccResourceLoader implements ResourceLoader {
|
||||
constructor(private fs: FileSystem) {}
|
||||
canPreload = false;
|
||||
preload(): undefined|Promise<void> { throw new Error('Not implemented.'); }
|
||||
load(url: string): string { return fs.readFileSync(url, 'utf8'); }
|
||||
load(url: string): string { return this.fs.readFile(AbsoluteFsPath.resolve(url)); }
|
||||
resolve(url: string, containingFile: string): string {
|
||||
return path.resolve(path.dirname(containingFile), url);
|
||||
}
|
||||
@ -65,7 +65,7 @@ class NgccResourceLoader implements ResourceLoader {
|
||||
* This Analyzer will analyze the files that have decorated classes that need to be transformed.
|
||||
*/
|
||||
export class DecorationAnalyzer {
|
||||
resourceManager = new NgccResourceLoader();
|
||||
resourceManager = new NgccResourceLoader(this.fs);
|
||||
metaRegistry = new LocalMetadataRegistry();
|
||||
dtsMetaReader = new DtsMetadataReader(this.typeChecker, this.reflectionHost);
|
||||
fullMetaReader = new CompoundMetadataReader([this.metaRegistry, this.dtsMetaReader]);
|
||||
@ -73,8 +73,8 @@ export class DecorationAnalyzer {
|
||||
new LocalIdentifierStrategy(),
|
||||
new AbsoluteModuleStrategy(this.program, this.typeChecker, this.options, this.host),
|
||||
// TODO(alxhub): there's no reason why ngcc needs the "logical file system" logic here, as ngcc
|
||||
// projects only ever have one rootDir. Instead, ngcc should just switch its emitted imort based
|
||||
// on whether a bestGuessOwningModule is present in the Reference.
|
||||
// projects only ever have one rootDir. Instead, ngcc should just switch its emitted import
|
||||
// based on whether a bestGuessOwningModule is present in the Reference.
|
||||
new LogicalProjectStrategy(this.typeChecker, new LogicalFileSystem(this.rootDirs)),
|
||||
]);
|
||||
dtsModuleScopeResolver =
|
||||
@ -110,7 +110,7 @@ export class DecorationAnalyzer {
|
||||
];
|
||||
|
||||
constructor(
|
||||
private program: ts.Program, private options: ts.CompilerOptions,
|
||||
private fs: FileSystem, private program: ts.Program, private options: ts.CompilerOptions,
|
||||
private host: ts.CompilerHost, private typeChecker: ts.TypeChecker,
|
||||
private reflectionHost: NgccReflectionHost, private referencesRegistry: ReferencesRegistry,
|
||||
private rootDirs: AbsoluteFsPath[], private isCore: boolean) {}
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||
import {Declaration} from '../../../src/ngtsc/reflection';
|
||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||
import {hasNameIdentifier, isDefined} from '../utils';
|
||||
@ -14,8 +15,8 @@ import {NgccReferencesRegistry} from './ngcc_references_registry';
|
||||
|
||||
export interface ExportInfo {
|
||||
identifier: string;
|
||||
from: string;
|
||||
dtsFrom?: string|null;
|
||||
from: AbsoluteFsPath;
|
||||
dtsFrom?: AbsoluteFsPath|null;
|
||||
alias?: string|null;
|
||||
}
|
||||
export type PrivateDeclarationsAnalyses = ExportInfo[];
|
||||
@ -93,11 +94,12 @@ export class PrivateDeclarationsAnalyzer {
|
||||
});
|
||||
|
||||
return Array.from(privateDeclarations.keys()).map(id => {
|
||||
const from = id.getSourceFile().fileName;
|
||||
const from = AbsoluteFsPath.fromSourceFile(id.getSourceFile());
|
||||
const declaration = privateDeclarations.get(id) !;
|
||||
const alias = exportAliasDeclarations.get(id) || null;
|
||||
const dtsDeclaration = this.host.getDtsDeclaration(declaration.node);
|
||||
const dtsFrom = dtsDeclaration && dtsDeclaration.getSourceFile().fileName;
|
||||
const dtsFrom =
|
||||
dtsDeclaration && AbsoluteFsPath.fromSourceFile(dtsDeclaration.getSourceFile());
|
||||
|
||||
return {identifier: id.text, from, dtsFrom, alias};
|
||||
});
|
||||
|
@ -5,12 +5,10 @@
|
||||
* 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 * as fs from 'fs';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||
|
||||
import {FileSystem} from '../file_system/file_system';
|
||||
import {DependencyHost, DependencyInfo} from './dependency_host';
|
||||
import {ModuleResolver, ResolvedDeepImport, ResolvedRelativeModule} from './module_resolver';
|
||||
|
||||
@ -19,7 +17,7 @@ import {ModuleResolver, ResolvedDeepImport, ResolvedRelativeModule} from './modu
|
||||
* Helper functions for computing dependencies.
|
||||
*/
|
||||
export class EsmDependencyHost implements DependencyHost {
|
||||
constructor(private moduleResolver: ModuleResolver) {}
|
||||
constructor(private fs: FileSystem, private moduleResolver: ModuleResolver) {}
|
||||
|
||||
/**
|
||||
* Find all the dependencies for the entry-point at the given path.
|
||||
@ -54,7 +52,7 @@ export class EsmDependencyHost implements DependencyHost {
|
||||
private recursivelyFindDependencies(
|
||||
file: AbsoluteFsPath, dependencies: Set<AbsoluteFsPath>, missing: Set<string>,
|
||||
deepImports: Set<string>, alreadySeen: Set<AbsoluteFsPath>): void {
|
||||
const fromContents = fs.readFileSync(file, 'utf8');
|
||||
const fromContents = this.fs.readFile(file);
|
||||
if (!this.hasImportOrReexportStatements(fromContents)) {
|
||||
return;
|
||||
}
|
||||
|
@ -6,12 +6,10 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||
import {FileSystem} from '../file_system/file_system';
|
||||
import {PathMappings, isRelativePath} from '../utils';
|
||||
|
||||
|
||||
/**
|
||||
* This is a very cut-down implementation of the TypeScript module resolution strategy.
|
||||
*
|
||||
@ -28,7 +26,9 @@ import {PathMappings, isRelativePath} from '../utils';
|
||||
export class ModuleResolver {
|
||||
private pathMappings: ProcessedPathMapping[];
|
||||
|
||||
constructor(pathMappings?: PathMappings, private relativeExtensions = ['.js', '/index.js']) {
|
||||
constructor(private fs: FileSystem, pathMappings?: PathMappings, private relativeExtensions = [
|
||||
'.js', '/index.js'
|
||||
]) {
|
||||
this.pathMappings = pathMappings ? this.processPathMappings(pathMappings) : [];
|
||||
}
|
||||
|
||||
@ -139,11 +139,11 @@ export class ModuleResolver {
|
||||
* to the `path` and checking if the file exists on disk.
|
||||
* @returns An absolute path to the first matching existing file, or `null` if none exist.
|
||||
*/
|
||||
private resolvePath(path: string, postFixes: string[]): AbsoluteFsPath|null {
|
||||
private resolvePath(path: AbsoluteFsPath, postFixes: string[]): AbsoluteFsPath|null {
|
||||
for (const postFix of postFixes) {
|
||||
const testPath = path + postFix;
|
||||
if (fs.existsSync(testPath)) {
|
||||
return AbsoluteFsPath.from(testPath);
|
||||
const testPath = AbsoluteFsPath.fromUnchecked(path + postFix);
|
||||
if (this.fs.exists(testPath)) {
|
||||
return testPath;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@ -155,7 +155,7 @@ export class ModuleResolver {
|
||||
* This is achieved by checking for the existence of `${modulePath}/package.json`.
|
||||
*/
|
||||
private isEntryPoint(modulePath: AbsoluteFsPath): boolean {
|
||||
return fs.existsSync(AbsoluteFsPath.join(modulePath, 'package.json'));
|
||||
return this.fs.exists(AbsoluteFsPath.join(modulePath, 'package.json'));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -227,7 +227,7 @@ export class ModuleResolver {
|
||||
let folder = path;
|
||||
while (folder !== '/') {
|
||||
folder = AbsoluteFsPath.dirname(folder);
|
||||
if (fs.existsSync(AbsoluteFsPath.join(folder, 'package.json'))) {
|
||||
if (this.fs.exists(AbsoluteFsPath.join(folder, 'package.json'))) {
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
|
38
packages/compiler-cli/ngcc/src/file_system/file_system.ts
Normal file
38
packages/compiler-cli/ngcc/src/file_system/file_system.ts
Normal file
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @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 {AbsoluteFsPath, PathSegment} from '../../../src/ngtsc/path';
|
||||
|
||||
/**
|
||||
* A basic interface to abstract the underlying file-system.
|
||||
*
|
||||
* This makes it easier to provide mock file-systems in unit tests,
|
||||
* but also to create clever file-systems that have features such as caching.
|
||||
*/
|
||||
export interface FileSystem {
|
||||
exists(path: AbsoluteFsPath): boolean;
|
||||
readFile(path: AbsoluteFsPath): string;
|
||||
writeFile(path: AbsoluteFsPath, data: string): void;
|
||||
readdir(path: AbsoluteFsPath): PathSegment[];
|
||||
lstat(path: AbsoluteFsPath): FileStats;
|
||||
stat(path: AbsoluteFsPath): FileStats;
|
||||
pwd(): AbsoluteFsPath;
|
||||
copyFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void;
|
||||
moveFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void;
|
||||
ensureDir(path: AbsoluteFsPath): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about an object in the FileSystem.
|
||||
* This is analogous to the `fs.Stats` class in Node.js.
|
||||
*/
|
||||
export interface FileStats {
|
||||
isFile(): boolean;
|
||||
isDirectory(): boolean;
|
||||
isSymbolicLink(): boolean;
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* @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 * as fs from 'fs';
|
||||
import {cp, mkdir, mv} from 'shelljs';
|
||||
import {AbsoluteFsPath, PathSegment} from '../../../src/ngtsc/path';
|
||||
import {FileSystem} from './file_system';
|
||||
|
||||
/**
|
||||
* A wrapper around the Node.js file-system (i.e the `fs` package).
|
||||
*/
|
||||
export class NodeJSFileSystem implements FileSystem {
|
||||
exists(path: AbsoluteFsPath): boolean { return fs.existsSync(path); }
|
||||
readFile(path: AbsoluteFsPath): string { return fs.readFileSync(path, 'utf8'); }
|
||||
writeFile(path: AbsoluteFsPath, data: string): void {
|
||||
return fs.writeFileSync(path, data, 'utf8');
|
||||
}
|
||||
readdir(path: AbsoluteFsPath): PathSegment[] { return fs.readdirSync(path) as PathSegment[]; }
|
||||
lstat(path: AbsoluteFsPath): fs.Stats { return fs.lstatSync(path); }
|
||||
stat(path: AbsoluteFsPath): fs.Stats { return fs.statSync(path); }
|
||||
pwd() { return AbsoluteFsPath.fromUnchecked(process.cwd()); }
|
||||
copyFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void { cp(from, to); }
|
||||
moveFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void { mv(from, to); }
|
||||
ensureDir(path: AbsoluteFsPath): void { mkdir('-p', path); }
|
||||
}
|
@ -5,15 +5,13 @@
|
||||
* 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 {resolve} from 'canonical-path';
|
||||
import {readFileSync} from 'fs';
|
||||
|
||||
import {AbsoluteFsPath} from '../../src/ngtsc/path';
|
||||
|
||||
import {DependencyResolver} from './dependencies/dependency_resolver';
|
||||
import {EsmDependencyHost} from './dependencies/esm_dependency_host';
|
||||
import {ModuleResolver} from './dependencies/module_resolver';
|
||||
import {FileSystem} from './file_system/file_system';
|
||||
import {NodeJSFileSystem} from './file_system/node_js_file_system';
|
||||
import {ConsoleLogger, LogLevel} from './logging/console_logger';
|
||||
import {Logger} from './logging/logger';
|
||||
import {hasBeenProcessed, markAsProcessed} from './packages/build_marker';
|
||||
@ -26,8 +24,6 @@ import {FileWriter} from './writing/file_writer';
|
||||
import {InPlaceFileWriter} from './writing/in_place_file_writer';
|
||||
import {NewEntryPointFileWriter} from './writing/new_entry_point_file_writer';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The options to configure the ngcc compiler.
|
||||
*/
|
||||
@ -80,20 +76,20 @@ export function mainNgcc(
|
||||
{basePath, targetEntryPointPath, propertiesToConsider = SUPPORTED_FORMAT_PROPERTIES,
|
||||
compileAllFormats = true, createNewEntryPointFormats = false,
|
||||
logger = new ConsoleLogger(LogLevel.info), pathMappings}: NgccOptions): void {
|
||||
const transformer = new Transformer(logger);
|
||||
const moduleResolver = new ModuleResolver(pathMappings);
|
||||
const host = new EsmDependencyHost(moduleResolver);
|
||||
const fs = new NodeJSFileSystem();
|
||||
const transformer = new Transformer(fs, logger);
|
||||
const moduleResolver = new ModuleResolver(fs, pathMappings);
|
||||
const host = new EsmDependencyHost(fs, moduleResolver);
|
||||
const resolver = new DependencyResolver(logger, host);
|
||||
const finder = new EntryPointFinder(logger, resolver);
|
||||
const fileWriter = getFileWriter(createNewEntryPointFormats);
|
||||
const finder = new EntryPointFinder(fs, logger, resolver);
|
||||
const fileWriter = getFileWriter(fs, createNewEntryPointFormats);
|
||||
|
||||
const absoluteTargetEntryPointPath = targetEntryPointPath ?
|
||||
AbsoluteFsPath.from(resolve(basePath, targetEntryPointPath)) :
|
||||
undefined;
|
||||
const absoluteTargetEntryPointPath =
|
||||
targetEntryPointPath ? AbsoluteFsPath.resolve(basePath, targetEntryPointPath) : undefined;
|
||||
|
||||
if (absoluteTargetEntryPointPath &&
|
||||
hasProcessedTargetEntryPoint(
|
||||
absoluteTargetEntryPointPath, propertiesToConsider, compileAllFormats)) {
|
||||
fs, absoluteTargetEntryPointPath, propertiesToConsider, compileAllFormats)) {
|
||||
logger.info('The target entry-point has already been processed');
|
||||
return;
|
||||
}
|
||||
@ -102,7 +98,7 @@ export function mainNgcc(
|
||||
AbsoluteFsPath.from(basePath), absoluteTargetEntryPointPath, pathMappings);
|
||||
|
||||
if (absoluteTargetEntryPointPath && entryPoints.length === 0) {
|
||||
markNonAngularPackageAsProcessed(absoluteTargetEntryPointPath, propertiesToConsider);
|
||||
markNonAngularPackageAsProcessed(fs, absoluteTargetEntryPointPath, propertiesToConsider);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -138,8 +134,8 @@ export function mainNgcc(
|
||||
// the property as processed even if its underlying format has been built already.
|
||||
if (!compiledFormats.has(formatPath) && (compileAllFormats || isFirstFormat)) {
|
||||
const bundle = makeEntryPointBundle(
|
||||
entryPoint.path, formatPath, entryPoint.typings, isCore, property, format, processDts,
|
||||
pathMappings);
|
||||
fs, entryPoint.path, formatPath, entryPoint.typings, isCore, property, format,
|
||||
processDts, pathMappings);
|
||||
if (bundle) {
|
||||
logger.info(`Compiling ${entryPoint.name} : ${property} as ${format}`);
|
||||
const transformedFiles = transformer.transform(bundle);
|
||||
@ -156,9 +152,9 @@ export function mainNgcc(
|
||||
// Either this format was just compiled or its underlying format was compiled because of a
|
||||
// previous property.
|
||||
if (compiledFormats.has(formatPath)) {
|
||||
markAsProcessed(entryPointPackageJson, entryPointPackageJsonPath, property);
|
||||
markAsProcessed(fs, entryPointPackageJson, entryPointPackageJsonPath, property);
|
||||
if (processDts) {
|
||||
markAsProcessed(entryPointPackageJson, entryPointPackageJsonPath, 'typings');
|
||||
markAsProcessed(fs, entryPointPackageJson, entryPointPackageJsonPath, 'typings');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -170,14 +166,15 @@ export function mainNgcc(
|
||||
});
|
||||
}
|
||||
|
||||
function getFileWriter(createNewEntryPointFormats: boolean): FileWriter {
|
||||
return createNewEntryPointFormats ? new NewEntryPointFileWriter() : new InPlaceFileWriter();
|
||||
function getFileWriter(fs: FileSystem, createNewEntryPointFormats: boolean): FileWriter {
|
||||
return createNewEntryPointFormats ? new NewEntryPointFileWriter(fs) : new InPlaceFileWriter(fs);
|
||||
}
|
||||
|
||||
function hasProcessedTargetEntryPoint(
|
||||
targetPath: AbsoluteFsPath, propertiesToConsider: string[], compileAllFormats: boolean) {
|
||||
const packageJsonPath = AbsoluteFsPath.from(resolve(targetPath, 'package.json'));
|
||||
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
||||
fs: FileSystem, targetPath: AbsoluteFsPath, propertiesToConsider: string[],
|
||||
compileAllFormats: boolean) {
|
||||
const packageJsonPath = AbsoluteFsPath.resolve(targetPath, 'package.json');
|
||||
const packageJson = JSON.parse(fs.readFile(packageJsonPath));
|
||||
|
||||
for (const property of propertiesToConsider) {
|
||||
if (packageJson[property]) {
|
||||
@ -205,11 +202,12 @@ function hasProcessedTargetEntryPoint(
|
||||
* So mark all formats in this entry-point as processed so that clients of ngcc can avoid
|
||||
* triggering ngcc for this entry-point in the future.
|
||||
*/
|
||||
function markNonAngularPackageAsProcessed(path: AbsoluteFsPath, propertiesToConsider: string[]) {
|
||||
const packageJsonPath = AbsoluteFsPath.from(resolve(path, 'package.json'));
|
||||
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
||||
function markNonAngularPackageAsProcessed(
|
||||
fs: FileSystem, path: AbsoluteFsPath, propertiesToConsider: string[]) {
|
||||
const packageJsonPath = AbsoluteFsPath.resolve(path, 'package.json');
|
||||
const packageJson = JSON.parse(fs.readFile(packageJsonPath));
|
||||
propertiesToConsider.forEach(formatProperty => {
|
||||
if (packageJson[formatProperty])
|
||||
markAsProcessed(packageJson, packageJsonPath, formatProperty as EntryPointJsonProperty);
|
||||
markAsProcessed(fs, packageJson, packageJsonPath, formatProperty as EntryPointJsonProperty);
|
||||
});
|
||||
}
|
||||
|
@ -6,10 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {writeFileSync} from 'fs';
|
||||
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||
|
||||
import {FileSystem} from '../file_system/file_system';
|
||||
import {EntryPointJsonProperty, EntryPointPackageJson} from './entry_point';
|
||||
|
||||
export const NGCC_VERSION = '0.0.0-PLACEHOLDER';
|
||||
@ -49,9 +47,9 @@ export function hasBeenProcessed(
|
||||
* @param format the property in the package.json of the format for which we are writing the marker.
|
||||
*/
|
||||
export function markAsProcessed(
|
||||
packageJson: EntryPointPackageJson, packageJsonPath: AbsoluteFsPath,
|
||||
fs: FileSystem, packageJson: EntryPointPackageJson, packageJsonPath: AbsoluteFsPath,
|
||||
format: EntryPointJsonProperty) {
|
||||
if (!packageJson.__processed_by_ivy_ngcc__) packageJson.__processed_by_ivy_ngcc__ = {};
|
||||
packageJson.__processed_by_ivy_ngcc__[format] = NGCC_VERSION;
|
||||
writeFileSync(packageJsonPath, JSON.stringify(packageJson), 'utf8');
|
||||
fs.writeFile(packageJsonPath, JSON.stringify(packageJson));
|
||||
}
|
||||
|
@ -5,10 +5,11 @@
|
||||
* 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, resolve} from 'canonical-path';
|
||||
import {existsSync, lstatSync, readdirSync} from 'fs';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||
import {FileSystem} from '../file_system/file_system';
|
||||
|
||||
/**
|
||||
* An entry point bundle contains one or two programs, e.g. `src` and `dts`,
|
||||
* that are compiled via TypeScript.
|
||||
@ -21,9 +22,9 @@ export interface BundleProgram {
|
||||
program: ts.Program;
|
||||
options: ts.CompilerOptions;
|
||||
host: ts.CompilerHost;
|
||||
path: string;
|
||||
path: AbsoluteFsPath;
|
||||
file: ts.SourceFile;
|
||||
r3SymbolsPath: string|null;
|
||||
r3SymbolsPath: AbsoluteFsPath|null;
|
||||
r3SymbolsFile: ts.SourceFile|null;
|
||||
}
|
||||
|
||||
@ -31,9 +32,10 @@ export interface BundleProgram {
|
||||
* Create a bundle program.
|
||||
*/
|
||||
export function makeBundleProgram(
|
||||
isCore: boolean, path: string, r3FileName: string, options: ts.CompilerOptions,
|
||||
host: ts.CompilerHost): BundleProgram {
|
||||
const r3SymbolsPath = isCore ? findR3SymbolsPath(dirname(path), r3FileName) : null;
|
||||
fs: FileSystem, isCore: boolean, path: AbsoluteFsPath, r3FileName: string,
|
||||
options: ts.CompilerOptions, host: ts.CompilerHost): BundleProgram {
|
||||
const r3SymbolsPath =
|
||||
isCore ? findR3SymbolsPath(fs, AbsoluteFsPath.dirname(path), r3FileName) : null;
|
||||
const rootPaths = r3SymbolsPath ? [path, r3SymbolsPath] : [path];
|
||||
const program = ts.createProgram(rootPaths, options, host);
|
||||
const file = program.getSourceFile(path) !;
|
||||
@ -45,26 +47,28 @@ export function makeBundleProgram(
|
||||
/**
|
||||
* Search the given directory hierarchy to find the path to the `r3_symbols` file.
|
||||
*/
|
||||
export function findR3SymbolsPath(directory: string, filename: string): string|null {
|
||||
const r3SymbolsFilePath = resolve(directory, filename);
|
||||
if (existsSync(r3SymbolsFilePath)) {
|
||||
export function findR3SymbolsPath(
|
||||
fs: FileSystem, directory: AbsoluteFsPath, filename: string): AbsoluteFsPath|null {
|
||||
const r3SymbolsFilePath = AbsoluteFsPath.resolve(directory, filename);
|
||||
if (fs.exists(r3SymbolsFilePath)) {
|
||||
return r3SymbolsFilePath;
|
||||
}
|
||||
|
||||
const subDirectories =
|
||||
readdirSync(directory)
|
||||
fs.readdir(directory)
|
||||
// Not interested in hidden files
|
||||
.filter(p => !p.startsWith('.'))
|
||||
// Ignore node_modules
|
||||
.filter(p => p !== 'node_modules')
|
||||
// Only interested in directories (and only those that are not symlinks)
|
||||
.filter(p => {
|
||||
const stat = lstatSync(resolve(directory, p));
|
||||
const stat = fs.lstat(AbsoluteFsPath.resolve(directory, p));
|
||||
return stat.isDirectory() && !stat.isSymbolicLink();
|
||||
});
|
||||
|
||||
for (const subDirectory of subDirectories) {
|
||||
const r3SymbolsFilePath = findR3SymbolsPath(resolve(directory, subDirectory, ), filename);
|
||||
const r3SymbolsFilePath =
|
||||
findR3SymbolsPath(fs, AbsoluteFsPath.resolve(directory, subDirectory), filename);
|
||||
if (r3SymbolsFilePath) {
|
||||
return r3SymbolsFilePath;
|
||||
}
|
||||
|
@ -5,14 +5,10 @@
|
||||
* 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 * as path from 'canonical-path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||
import {FileSystem} from '../file_system/file_system';
|
||||
import {Logger} from '../logging/logger';
|
||||
|
||||
|
||||
/**
|
||||
* The possible values for the format of an entry-point.
|
||||
*/
|
||||
@ -70,13 +66,14 @@ export const SUPPORTED_FORMAT_PROPERTIES: EntryPointJsonProperty[] =
|
||||
* @returns An entry-point if it is valid, `null` otherwise.
|
||||
*/
|
||||
export function getEntryPointInfo(
|
||||
logger: Logger, packagePath: AbsoluteFsPath, entryPointPath: AbsoluteFsPath): EntryPoint|null {
|
||||
const packageJsonPath = path.resolve(entryPointPath, 'package.json');
|
||||
if (!fs.existsSync(packageJsonPath)) {
|
||||
fs: FileSystem, logger: Logger, packagePath: AbsoluteFsPath,
|
||||
entryPointPath: AbsoluteFsPath): EntryPoint|null {
|
||||
const packageJsonPath = AbsoluteFsPath.resolve(entryPointPath, 'package.json');
|
||||
if (!fs.exists(packageJsonPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const entryPointPackageJson = loadEntryPointPackage(logger, packageJsonPath);
|
||||
const entryPointPackageJson = loadEntryPointPackage(fs, logger, packageJsonPath);
|
||||
if (!entryPointPackageJson) {
|
||||
return null;
|
||||
}
|
||||
@ -90,15 +87,15 @@ export function getEntryPointInfo(
|
||||
|
||||
// Also there must exist a `metadata.json` file next to the typings entry-point.
|
||||
const metadataPath =
|
||||
path.resolve(entryPointPath, typings.replace(/\.d\.ts$/, '') + '.metadata.json');
|
||||
AbsoluteFsPath.resolve(entryPointPath, typings.replace(/\.d\.ts$/, '') + '.metadata.json');
|
||||
|
||||
const entryPointInfo: EntryPoint = {
|
||||
name: entryPointPackageJson.name,
|
||||
packageJson: entryPointPackageJson,
|
||||
package: packagePath,
|
||||
path: entryPointPath,
|
||||
typings: AbsoluteFsPath.from(path.resolve(entryPointPath, typings)),
|
||||
compiledByAngular: fs.existsSync(metadataPath),
|
||||
typings: AbsoluteFsPath.resolve(entryPointPath, typings),
|
||||
compiledByAngular: fs.exists(metadataPath),
|
||||
};
|
||||
|
||||
return entryPointInfo;
|
||||
@ -136,10 +133,10 @@ export function getEntryPointFormat(property: string): EntryPointFormat|undefine
|
||||
* @param packageJsonPath the absolute path to the package.json file.
|
||||
* @returns JSON from the package.json file if it is valid, `null` otherwise.
|
||||
*/
|
||||
function loadEntryPointPackage(logger: Logger, packageJsonPath: string): EntryPointPackageJson|
|
||||
null {
|
||||
function loadEntryPointPackage(
|
||||
fs: FileSystem, logger: Logger, packageJsonPath: AbsoluteFsPath): EntryPointPackageJson|null {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||
return JSON.parse(fs.readFile(packageJsonPath));
|
||||
} catch (e) {
|
||||
// We may have run into a package.json with unexpected symbols
|
||||
logger.warn(`Failed to read entry point info from ${packageJsonPath} with error ${e}.`);
|
||||
|
@ -5,14 +5,14 @@
|
||||
* 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 {resolve} from 'canonical-path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||
import {FileSystem} from '../file_system/file_system';
|
||||
import {PathMappings} from '../utils';
|
||||
|
||||
import {BundleProgram, makeBundleProgram} from './bundle_program';
|
||||
import {EntryPointFormat, EntryPointJsonProperty} from './entry_point';
|
||||
import {NgccCompilerHost} from './ngcc_compiler_host';
|
||||
|
||||
/**
|
||||
* A bundle of files and paths (and TS programs) that correspond to a particular
|
||||
@ -38,25 +38,27 @@ export interface EntryPointBundle {
|
||||
* @param transformDts Whether to transform the typings along with this bundle.
|
||||
*/
|
||||
export function makeEntryPointBundle(
|
||||
entryPointPath: string, formatPath: string, typingsPath: string, isCore: boolean,
|
||||
formatProperty: EntryPointJsonProperty, format: EntryPointFormat, transformDts: boolean,
|
||||
pathMappings?: PathMappings): EntryPointBundle|null {
|
||||
fs: FileSystem, entryPointPath: string, formatPath: string, typingsPath: string,
|
||||
isCore: boolean, formatProperty: EntryPointJsonProperty, format: EntryPointFormat,
|
||||
transformDts: boolean, pathMappings?: PathMappings): EntryPointBundle|null {
|
||||
// Create the TS program and necessary helpers.
|
||||
const options: ts.CompilerOptions = {
|
||||
allowJs: true,
|
||||
maxNodeModuleJsDepth: Infinity,
|
||||
noLib: true,
|
||||
rootDir: entryPointPath, ...pathMappings
|
||||
};
|
||||
const host = ts.createCompilerHost(options);
|
||||
const host = new NgccCompilerHost(fs, options);
|
||||
const rootDirs = [AbsoluteFsPath.from(entryPointPath)];
|
||||
|
||||
// Create the bundle programs, as necessary.
|
||||
const src = makeBundleProgram(
|
||||
isCore, resolve(entryPointPath, formatPath), 'r3_symbols.js', options, host);
|
||||
const dts = transformDts ?
|
||||
makeBundleProgram(
|
||||
isCore, resolve(entryPointPath, typingsPath), 'r3_symbols.d.ts', options, host) :
|
||||
null;
|
||||
fs, isCore, AbsoluteFsPath.resolve(entryPointPath, formatPath), 'r3_symbols.js', options,
|
||||
host);
|
||||
const dts = transformDts ? makeBundleProgram(
|
||||
fs, isCore, AbsoluteFsPath.resolve(entryPointPath, typingsPath),
|
||||
'r3_symbols.d.ts', options, host) :
|
||||
null;
|
||||
const isFlatCore = isCore && src.r3SymbolsFile === null;
|
||||
|
||||
return {format, formatProperty, rootDirs, isCore, isFlatCore, src, dts};
|
||||
|
@ -5,20 +5,16 @@
|
||||
* 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 * as path from 'canonical-path';
|
||||
import * as fs from 'fs';
|
||||
import {join, resolve} from 'path';
|
||||
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||
import {DependencyResolver, SortedEntryPointsInfo} from '../dependencies/dependency_resolver';
|
||||
import {FileSystem} from '../file_system/file_system';
|
||||
import {Logger} from '../logging/logger';
|
||||
import {PathMappings} from '../utils';
|
||||
|
||||
import {EntryPoint, getEntryPointInfo} from './entry_point';
|
||||
|
||||
|
||||
export class EntryPointFinder {
|
||||
constructor(private logger: Logger, private resolver: DependencyResolver) {}
|
||||
constructor(
|
||||
private fs: FileSystem, private logger: Logger, private resolver: DependencyResolver) {}
|
||||
/**
|
||||
* Search the given directory, and sub-directories, for Angular package entry points.
|
||||
* @param sourceDirectory An absolute path to the directory to search for entry points.
|
||||
@ -59,9 +55,9 @@ export class EntryPointFinder {
|
||||
AbsoluteFsPath[] {
|
||||
const basePaths = [sourceDirectory];
|
||||
if (pathMappings) {
|
||||
const baseUrl = AbsoluteFsPath.from(resolve(pathMappings.baseUrl));
|
||||
const baseUrl = AbsoluteFsPath.resolve(pathMappings.baseUrl);
|
||||
values(pathMappings.paths).forEach(paths => paths.forEach(path => {
|
||||
basePaths.push(AbsoluteFsPath.fromUnchecked(join(baseUrl, extractPathPrefix(path))));
|
||||
basePaths.push(AbsoluteFsPath.join(baseUrl, extractPathPrefix(path)));
|
||||
}));
|
||||
}
|
||||
basePaths.sort(); // Get the paths in order with the shorter ones first.
|
||||
@ -75,29 +71,29 @@ export class EntryPointFinder {
|
||||
*/
|
||||
private walkDirectoryForEntryPoints(sourceDirectory: AbsoluteFsPath): EntryPoint[] {
|
||||
const entryPoints: EntryPoint[] = [];
|
||||
fs.readdirSync(sourceDirectory)
|
||||
this.fs
|
||||
.readdir(sourceDirectory)
|
||||
// Not interested in hidden files
|
||||
.filter(p => !p.startsWith('.'))
|
||||
// Ignore node_modules
|
||||
.filter(p => p !== 'node_modules')
|
||||
// Only interested in directories (and only those that are not symlinks)
|
||||
.filter(p => {
|
||||
const stat = fs.lstatSync(path.resolve(sourceDirectory, p));
|
||||
const stat = this.fs.lstat(AbsoluteFsPath.resolve(sourceDirectory, p));
|
||||
return stat.isDirectory() && !stat.isSymbolicLink();
|
||||
})
|
||||
.forEach(p => {
|
||||
// Either the directory is a potential package or a namespace containing packages (e.g
|
||||
// `@angular`).
|
||||
const packagePath = AbsoluteFsPath.from(path.join(sourceDirectory, p));
|
||||
const packagePath = AbsoluteFsPath.join(sourceDirectory, p);
|
||||
if (p.startsWith('@')) {
|
||||
entryPoints.push(...this.walkDirectoryForEntryPoints(packagePath));
|
||||
} else {
|
||||
entryPoints.push(...this.getEntryPointsForPackage(packagePath));
|
||||
|
||||
// Also check for any nested node_modules in this package
|
||||
const nestedNodeModulesPath =
|
||||
AbsoluteFsPath.from(path.resolve(packagePath, 'node_modules'));
|
||||
if (fs.existsSync(nestedNodeModulesPath)) {
|
||||
const nestedNodeModulesPath = AbsoluteFsPath.resolve(packagePath, 'node_modules');
|
||||
if (this.fs.exists(nestedNodeModulesPath)) {
|
||||
entryPoints.push(...this.walkDirectoryForEntryPoints(nestedNodeModulesPath));
|
||||
}
|
||||
}
|
||||
@ -114,14 +110,14 @@ export class EntryPointFinder {
|
||||
const entryPoints: EntryPoint[] = [];
|
||||
|
||||
// Try to get an entry point from the top level package directory
|
||||
const topLevelEntryPoint = getEntryPointInfo(this.logger, packagePath, packagePath);
|
||||
const topLevelEntryPoint = getEntryPointInfo(this.fs, this.logger, packagePath, packagePath);
|
||||
if (topLevelEntryPoint !== null) {
|
||||
entryPoints.push(topLevelEntryPoint);
|
||||
}
|
||||
|
||||
// Now search all the directories of this package for possible entry points
|
||||
this.walkDirectory(packagePath, subdir => {
|
||||
const subEntryPoint = getEntryPointInfo(this.logger, packagePath, subdir);
|
||||
const subEntryPoint = getEntryPointInfo(this.fs, this.logger, packagePath, subdir);
|
||||
if (subEntryPoint !== null) {
|
||||
entryPoints.push(subEntryPoint);
|
||||
}
|
||||
@ -137,19 +133,19 @@ export class EntryPointFinder {
|
||||
* @param fn the function to apply to each directory.
|
||||
*/
|
||||
private walkDirectory(dir: AbsoluteFsPath, fn: (dir: AbsoluteFsPath) => void) {
|
||||
return fs
|
||||
.readdirSync(dir)
|
||||
return this.fs
|
||||
.readdir(dir)
|
||||
// Not interested in hidden files
|
||||
.filter(p => !p.startsWith('.'))
|
||||
// Ignore node_modules
|
||||
.filter(p => p !== 'node_modules')
|
||||
// Only interested in directories (and only those that are not symlinks)
|
||||
.filter(p => {
|
||||
const stat = fs.lstatSync(path.resolve(dir, p));
|
||||
const stat = this.fs.lstat(AbsoluteFsPath.resolve(dir, p));
|
||||
return stat.isDirectory() && !stat.isSymbolicLink();
|
||||
})
|
||||
.forEach(subDir => {
|
||||
const resolvedSubDir = AbsoluteFsPath.from(path.resolve(dir, subDir));
|
||||
const resolvedSubDir = AbsoluteFsPath.resolve(dir, subDir);
|
||||
fn(resolvedSubDir);
|
||||
this.walkDirectory(resolvedSubDir, fn);
|
||||
});
|
||||
|
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* @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 * as os from 'os';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||
import {FileSystem} from '../file_system/file_system';
|
||||
|
||||
export class NgccCompilerHost implements ts.CompilerHost {
|
||||
private _caseSensitive = this.fs.exists(AbsoluteFsPath.fromUnchecked(__filename.toUpperCase()));
|
||||
|
||||
constructor(private fs: FileSystem, private options: ts.CompilerOptions) {}
|
||||
|
||||
getSourceFile(fileName: string, languageVersion: ts.ScriptTarget): ts.SourceFile|undefined {
|
||||
const text = this.readFile(fileName);
|
||||
return text !== undefined ? ts.createSourceFile(fileName, text, languageVersion) : undefined;
|
||||
}
|
||||
|
||||
getDefaultLibFileName(options: ts.CompilerOptions): string {
|
||||
return this.getDefaultLibLocation() + '/' + ts.getDefaultLibFileName(options);
|
||||
}
|
||||
|
||||
getDefaultLibLocation(): string {
|
||||
const nodeLibPath = AbsoluteFsPath.fromUnchecked(require.resolve('typescript'));
|
||||
return AbsoluteFsPath.join(nodeLibPath, '..');
|
||||
}
|
||||
|
||||
writeFile(fileName: string, data: string): void {
|
||||
this.fs.writeFile(AbsoluteFsPath.fromUnchecked(fileName), data);
|
||||
}
|
||||
|
||||
getCurrentDirectory(): string { return this.fs.pwd(); }
|
||||
|
||||
getCanonicalFileName(fileName: string): string {
|
||||
return this.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
|
||||
}
|
||||
|
||||
useCaseSensitiveFileNames(): boolean { return this._caseSensitive; }
|
||||
|
||||
getNewLine(): string {
|
||||
switch (this.options.newLine) {
|
||||
case ts.NewLineKind.CarriageReturnLineFeed:
|
||||
return '\r\n';
|
||||
case ts.NewLineKind.LineFeed:
|
||||
return '\n';
|
||||
default:
|
||||
return os.EOL;
|
||||
}
|
||||
}
|
||||
|
||||
fileExists(fileName: string): boolean {
|
||||
return this.fs.exists(AbsoluteFsPath.fromUnchecked(fileName));
|
||||
}
|
||||
|
||||
readFile(fileName: string): string|undefined {
|
||||
if (!this.fileExists(fileName)) {
|
||||
return undefined;
|
||||
}
|
||||
return this.fs.readFile(AbsoluteFsPath.fromUnchecked(fileName));
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ import {ModuleWithProvidersAnalyses, ModuleWithProvidersAnalyzer} from '../analy
|
||||
import {NgccReferencesRegistry} from '../analysis/ngcc_references_registry';
|
||||
import {ExportInfo, PrivateDeclarationsAnalyzer} from '../analysis/private_declarations_analyzer';
|
||||
import {SwitchMarkerAnalyses, SwitchMarkerAnalyzer} from '../analysis/switch_marker_analyzer';
|
||||
import {FileSystem} from '../file_system/file_system';
|
||||
import {Esm2015ReflectionHost} from '../host/esm2015_host';
|
||||
import {Esm5ReflectionHost} from '../host/esm5_host';
|
||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||
@ -46,7 +47,7 @@ import {EntryPointBundle} from './entry_point_bundle';
|
||||
* - Some formats may contain multiple "modules" in a single file.
|
||||
*/
|
||||
export class Transformer {
|
||||
constructor(private logger: Logger) {}
|
||||
constructor(private fs: FileSystem, private logger: Logger) {}
|
||||
|
||||
/**
|
||||
* Transform the source (and typings) files of a bundle.
|
||||
@ -85,9 +86,9 @@ export class Transformer {
|
||||
getRenderer(host: NgccReflectionHost, isCore: boolean, bundle: EntryPointBundle): Renderer {
|
||||
switch (bundle.format) {
|
||||
case 'esm2015':
|
||||
return new EsmRenderer(this.logger, host, isCore, bundle);
|
||||
return new EsmRenderer(this.fs, this.logger, host, isCore, bundle);
|
||||
case 'esm5':
|
||||
return new Esm5Renderer(this.logger, host, isCore, bundle);
|
||||
return new Esm5Renderer(this.fs, this.logger, host, isCore, bundle);
|
||||
default:
|
||||
throw new Error(`Renderer for "${bundle.format}" not yet implemented.`);
|
||||
}
|
||||
@ -102,8 +103,8 @@ export class Transformer {
|
||||
const switchMarkerAnalyses = switchMarkerAnalyzer.analyzeProgram(bundle.src.program);
|
||||
|
||||
const decorationAnalyzer = new DecorationAnalyzer(
|
||||
bundle.src.program, bundle.src.options, bundle.src.host, typeChecker, reflectionHost,
|
||||
referencesRegistry, bundle.rootDirs, isCore);
|
||||
this.fs, bundle.src.program, bundle.src.options, bundle.src.host, typeChecker,
|
||||
reflectionHost, referencesRegistry, bundle.rootDirs, isCore);
|
||||
const decorationAnalyses = decorationAnalyzer.analyzeProgram();
|
||||
|
||||
const moduleWithProvidersAnalyzer =
|
||||
|
@ -5,18 +5,21 @@
|
||||
* 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 * as ts from 'typescript';
|
||||
import MagicString from 'magic-string';
|
||||
import * as ts from 'typescript';
|
||||
import {CompiledClass} from '../analysis/decoration_analyzer';
|
||||
import {FileSystem} from '../file_system/file_system';
|
||||
import {getIifeBody} from '../host/esm5_host';
|
||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||
import {CompiledClass} from '../analysis/decoration_analyzer';
|
||||
import {EsmRenderer} from './esm_renderer';
|
||||
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
||||
import {Logger} from '../logging/logger';
|
||||
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
||||
import {EsmRenderer} from './esm_renderer';
|
||||
|
||||
export class Esm5Renderer extends EsmRenderer {
|
||||
constructor(logger: Logger, host: NgccReflectionHost, isCore: boolean, bundle: EntryPointBundle) {
|
||||
super(logger, host, isCore, bundle);
|
||||
constructor(
|
||||
fs: FileSystem, logger: Logger, host: NgccReflectionHost, isCore: boolean,
|
||||
bundle: EntryPointBundle) {
|
||||
super(fs, logger, host, isCore, bundle);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,20 +5,23 @@
|
||||
* 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, relative} from 'canonical-path';
|
||||
import MagicString from 'magic-string';
|
||||
import * as ts from 'typescript';
|
||||
import {NgccReflectionHost, POST_R3_MARKER, PRE_R3_MARKER, SwitchableVariableDeclaration} from '../host/ngcc_host';
|
||||
import {CompiledClass} from '../analysis/decoration_analyzer';
|
||||
import {RedundantDecoratorMap, Renderer, stripExtension} from './renderer';
|
||||
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
||||
import {ExportInfo} from '../analysis/private_declarations_analyzer';
|
||||
import {PathSegment, AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||
import {isDtsPath} from '../../../src/ngtsc/util/src/typescript';
|
||||
import {CompiledClass} from '../analysis/decoration_analyzer';
|
||||
import {ExportInfo} from '../analysis/private_declarations_analyzer';
|
||||
import {FileSystem} from '../file_system/file_system';
|
||||
import {NgccReflectionHost, POST_R3_MARKER, PRE_R3_MARKER, SwitchableVariableDeclaration} from '../host/ngcc_host';
|
||||
import {Logger} from '../logging/logger';
|
||||
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
||||
import {RedundantDecoratorMap, Renderer, stripExtension} from './renderer';
|
||||
|
||||
export class EsmRenderer extends Renderer {
|
||||
constructor(logger: Logger, host: NgccReflectionHost, isCore: boolean, bundle: EntryPointBundle) {
|
||||
super(logger, host, isCore, bundle);
|
||||
constructor(
|
||||
fs: FileSystem, logger: Logger, host: NgccReflectionHost, isCore: boolean,
|
||||
bundle: EntryPointBundle) {
|
||||
super(fs, logger, host, isCore, bundle);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -33,7 +36,7 @@ export class EsmRenderer extends Renderer {
|
||||
output.appendLeft(insertionPoint, renderedImports);
|
||||
}
|
||||
|
||||
addExports(output: MagicString, entryPointBasePath: string, exports: ExportInfo[]): void {
|
||||
addExports(output: MagicString, entryPointBasePath: AbsoluteFsPath, exports: ExportInfo[]): void {
|
||||
exports.forEach(e => {
|
||||
let exportFrom = '';
|
||||
const isDtsFile = isDtsPath(entryPointBasePath);
|
||||
@ -41,7 +44,8 @@ export class EsmRenderer extends Renderer {
|
||||
|
||||
if (from) {
|
||||
const basePath = stripExtension(from);
|
||||
const relativePath = './' + relative(dirname(entryPointBasePath), basePath);
|
||||
const relativePath =
|
||||
'./' + PathSegment.relative(AbsoluteFsPath.dirname(entryPointBasePath), basePath);
|
||||
exportFrom = entryPointBasePath !== basePath ? ` from '${relativePath}'` : '';
|
||||
}
|
||||
|
||||
|
@ -6,25 +6,26 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {ConstantPool, Expression, Statement, WrappedNodeExpr, WritePropExpr} from '@angular/compiler';
|
||||
import {SourceMapConverter, commentRegex, fromJSON, fromMapFileSource, fromObject, fromSource, generateMapFileComment, mapFileCommentRegex, removeComments, removeMapFileComments} from 'convert-source-map';
|
||||
import {readFileSync, statSync} from 'fs';
|
||||
import {SourceMapConverter, commentRegex, fromJSON, fromObject, fromSource, generateMapFileComment, mapFileCommentRegex, removeComments, removeMapFileComments} from 'convert-source-map';
|
||||
import MagicString from 'magic-string';
|
||||
import {basename, dirname, relative, resolve} from 'canonical-path';
|
||||
import {SourceMapConsumer, SourceMapGenerator, RawSourceMap} from 'source-map';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {NoopImportRewriter, ImportRewriter, R3SymbolsImportRewriter, NOOP_DEFAULT_IMPORT_RECORDER} from '@angular/compiler-cli/src/ngtsc/imports';
|
||||
import {CompileResult} from '@angular/compiler-cli/src/ngtsc/transform';
|
||||
import {NoopImportRewriter, ImportRewriter, R3SymbolsImportRewriter, NOOP_DEFAULT_IMPORT_RECORDER} from '../../../src/ngtsc/imports';
|
||||
import {AbsoluteFsPath, PathSegment} from '../../../src/ngtsc/path';
|
||||
import {CompileResult} from '../../../src/ngtsc/transform';
|
||||
import {translateStatement, translateType, ImportManager} from '../../../src/ngtsc/translator';
|
||||
import {NgccFlatImportRewriter} from './ngcc_import_rewriter';
|
||||
|
||||
import {CompiledClass, CompiledFile, DecorationAnalyses} from '../analysis/decoration_analyzer';
|
||||
import {ModuleWithProvidersInfo, ModuleWithProvidersAnalyses} from '../analysis/module_with_providers_analyzer';
|
||||
import {PrivateDeclarationsAnalyses, ExportInfo} from '../analysis/private_declarations_analyzer';
|
||||
import {SwitchMarkerAnalyses, SwitchMarkerAnalysis} from '../analysis/switch_marker_analyzer';
|
||||
import {IMPORT_PREFIX} from '../constants';
|
||||
import {FileSystem} from '../file_system/file_system';
|
||||
import {NgccReflectionHost, SwitchableVariableDeclaration} from '../host/ngcc_host';
|
||||
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
||||
import {Logger} from '../logging/logger';
|
||||
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
||||
import {NgccFlatImportRewriter} from './ngcc_import_rewriter';
|
||||
|
||||
interface SourceMapInfo {
|
||||
source: string;
|
||||
@ -39,7 +40,7 @@ export interface FileInfo {
|
||||
/**
|
||||
* Path to where the file should be written.
|
||||
*/
|
||||
path: string;
|
||||
path: AbsoluteFsPath;
|
||||
/**
|
||||
* The contents of the file to be be written.
|
||||
*/
|
||||
@ -81,8 +82,8 @@ export const RedundantDecoratorMap = Map;
|
||||
*/
|
||||
export abstract class Renderer {
|
||||
constructor(
|
||||
protected logger: Logger, protected host: NgccReflectionHost, protected isCore: boolean,
|
||||
protected bundle: EntryPointBundle) {}
|
||||
protected fs: FileSystem, protected logger: Logger, protected host: NgccReflectionHost,
|
||||
protected isCore: boolean, protected bundle: EntryPointBundle) {}
|
||||
|
||||
renderProgram(
|
||||
decorationAnalyses: DecorationAnalyses, switchMarkerAnalyses: SwitchMarkerAnalyses,
|
||||
@ -189,7 +190,7 @@ export abstract class Renderer {
|
||||
this.addModuleWithProvidersParams(outputText, renderInfo.moduleWithProviders, importManager);
|
||||
this.addImports(outputText, importManager.getAllImports(dtsFile.fileName), dtsFile);
|
||||
|
||||
this.addExports(outputText, dtsFile.fileName, renderInfo.privateExports);
|
||||
this.addExports(outputText, AbsoluteFsPath.fromSourceFile(dtsFile), renderInfo.privateExports);
|
||||
|
||||
|
||||
return this.renderSourceAndMap(dtsFile, input, outputText);
|
||||
@ -207,11 +208,12 @@ export abstract class Renderer {
|
||||
importManager: ImportManager): void {
|
||||
moduleWithProviders.forEach(info => {
|
||||
const ngModuleName = info.ngModule.node.name.text;
|
||||
const declarationFile = info.declaration.getSourceFile().fileName;
|
||||
const ngModuleFile = info.ngModule.node.getSourceFile().fileName;
|
||||
const declarationFile = AbsoluteFsPath.fromSourceFile(info.declaration.getSourceFile());
|
||||
const ngModuleFile = AbsoluteFsPath.fromSourceFile(info.ngModule.node.getSourceFile());
|
||||
const importPath = info.ngModule.viaModule ||
|
||||
(declarationFile !== ngModuleFile ?
|
||||
stripExtension(`./${relative(dirname(declarationFile), ngModuleFile)}`) :
|
||||
stripExtension(
|
||||
`./${PathSegment.relative(AbsoluteFsPath.dirname(declarationFile), ngModuleFile)}`) :
|
||||
null);
|
||||
const ngModule = getImportString(importManager, importPath, ngModuleName);
|
||||
|
||||
@ -252,7 +254,7 @@ export abstract class Renderer {
|
||||
output: MagicString, imports: {specifier: string, qualifier: string}[],
|
||||
sf: ts.SourceFile): void;
|
||||
protected abstract addExports(
|
||||
output: MagicString, entryPointBasePath: string, exports: ExportInfo[]): void;
|
||||
output: MagicString, entryPointBasePath: AbsoluteFsPath, exports: ExportInfo[]): void;
|
||||
protected abstract addDefinitions(
|
||||
output: MagicString, compiledClass: CompiledClass, definitions: string): void;
|
||||
protected abstract removeDecorators(
|
||||
@ -288,7 +290,7 @@ export abstract class Renderer {
|
||||
*/
|
||||
protected extractSourceMap(file: ts.SourceFile): SourceMapInfo {
|
||||
const inline = commentRegex.test(file.text);
|
||||
const external = mapFileCommentRegex.test(file.text);
|
||||
const external = mapFileCommentRegex.exec(file.text);
|
||||
|
||||
if (inline) {
|
||||
const inlineSourceMap = fromSource(file.text);
|
||||
@ -300,17 +302,22 @@ export abstract class Renderer {
|
||||
} else if (external) {
|
||||
let externalSourceMap: SourceMapConverter|null = null;
|
||||
try {
|
||||
externalSourceMap = fromMapFileSource(file.text, dirname(file.fileName));
|
||||
const fileName = external[1] || external[2];
|
||||
const filePath = AbsoluteFsPath.resolve(
|
||||
AbsoluteFsPath.dirname(AbsoluteFsPath.fromSourceFile(file)), fileName);
|
||||
const mappingFile = this.fs.readFile(filePath);
|
||||
externalSourceMap = fromJSON(mappingFile);
|
||||
} catch (e) {
|
||||
if (e.code === 'ENOENT') {
|
||||
this.logger.warn(
|
||||
`The external map file specified in the source code comment "${e.path}" was not found on the file system.`);
|
||||
const mapPath = file.fileName + '.map';
|
||||
if (basename(e.path) !== basename(mapPath) && statSync(mapPath).isFile()) {
|
||||
const mapPath = AbsoluteFsPath.fromUnchecked(file.fileName + '.map');
|
||||
if (PathSegment.basename(e.path) !== PathSegment.basename(mapPath) &&
|
||||
this.fs.stat(mapPath).isFile()) {
|
||||
this.logger.warn(
|
||||
`Guessing the map file name from the source file name: "${basename(mapPath)}"`);
|
||||
`Guessing the map file name from the source file name: "${PathSegment.basename(mapPath)}"`);
|
||||
try {
|
||||
externalSourceMap = fromObject(JSON.parse(readFileSync(mapPath, 'utf8')));
|
||||
externalSourceMap = fromObject(JSON.parse(this.fs.readFile(mapPath)));
|
||||
} catch (e) {
|
||||
this.logger.error(e);
|
||||
}
|
||||
@ -333,9 +340,9 @@ export abstract class Renderer {
|
||||
*/
|
||||
protected renderSourceAndMap(
|
||||
sourceFile: ts.SourceFile, input: SourceMapInfo, output: MagicString): FileInfo[] {
|
||||
const outputPath = sourceFile.fileName;
|
||||
const outputMapPath = `${outputPath}.map`;
|
||||
const relativeSourcePath = basename(outputPath);
|
||||
const outputPath = AbsoluteFsPath.fromSourceFile(sourceFile);
|
||||
const outputMapPath = AbsoluteFsPath.fromUnchecked(`${outputPath}.map`);
|
||||
const relativeSourcePath = PathSegment.basename(outputPath);
|
||||
const relativeMapPath = `${relativeSourcePath}.map`;
|
||||
|
||||
const outputMap = output.generateMap({
|
||||
@ -502,8 +509,8 @@ export function renderDefinitions(
|
||||
return definitions;
|
||||
}
|
||||
|
||||
export function stripExtension(filePath: string): string {
|
||||
return filePath.replace(/\.(js|d\.ts)$/, '');
|
||||
export function stripExtension<T extends string>(filePath: T): T {
|
||||
return filePath.replace(/\.(js|d\.ts)$/, '') as T;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -6,15 +6,11 @@
|
||||
* 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 {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||
import {FileSystem} from '../file_system/file_system';
|
||||
import {EntryPoint} from '../packages/entry_point';
|
||||
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
||||
import {FileInfo} from '../rendering/renderer';
|
||||
|
||||
import {FileWriter} from './file_writer';
|
||||
|
||||
/**
|
||||
@ -22,19 +18,22 @@ import {FileWriter} from './file_writer';
|
||||
* a back-up of the original file with an extra `.bak` extension.
|
||||
*/
|
||||
export class InPlaceFileWriter implements FileWriter {
|
||||
constructor(protected fs: FileSystem) {}
|
||||
|
||||
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)) {
|
||||
this.fs.ensureDir(AbsoluteFsPath.dirname(file.path));
|
||||
const backPath = AbsoluteFsPath.fromUnchecked(`${file.path}.__ivy_ngcc_bak`);
|
||||
if (this.fs.exists(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);
|
||||
if (this.fs.exists(file.path)) {
|
||||
this.fs.moveFile(file.path, backPath);
|
||||
}
|
||||
writeFileSync(file.path, file.contents, 'utf8');
|
||||
this.fs.writeFile(file.path, file.contents);
|
||||
}
|
||||
}
|
||||
|
@ -6,12 +6,7 @@
|
||||
* 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, join, relative} from 'canonical-path';
|
||||
import {writeFileSync} from 'fs';
|
||||
import {cp, mkdir} from 'shelljs';
|
||||
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||
import {AbsoluteFsPath, PathSegment} from '../../../src/ngtsc/path';
|
||||
import {isDtsPath} from '../../../src/ngtsc/util/src/typescript';
|
||||
import {EntryPoint, EntryPointJsonProperty} from '../packages/entry_point';
|
||||
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
||||
@ -32,7 +27,7 @@ const NGCC_DIRECTORY = '__ivy_ngcc__';
|
||||
export class NewEntryPointFileWriter extends InPlaceFileWriter {
|
||||
writeBundle(entryPoint: EntryPoint, bundle: EntryPointBundle, transformedFiles: FileInfo[]) {
|
||||
// The new folder is at the root of the overall package
|
||||
const ngccFolder = AbsoluteFsPath.fromUnchecked(join(entryPoint.package, NGCC_DIRECTORY));
|
||||
const ngccFolder = AbsoluteFsPath.join(entryPoint.package, NGCC_DIRECTORY);
|
||||
this.copyBundle(bundle, entryPoint.package, ngccFolder);
|
||||
transformedFiles.forEach(file => this.writeFile(file, entryPoint.package, ngccFolder));
|
||||
this.updatePackageJson(entryPoint, bundle.formatProperty, ngccFolder);
|
||||
@ -41,12 +36,13 @@ export class NewEntryPointFileWriter extends InPlaceFileWriter {
|
||||
protected copyBundle(
|
||||
bundle: EntryPointBundle, packagePath: AbsoluteFsPath, ngccFolder: AbsoluteFsPath) {
|
||||
bundle.src.program.getSourceFiles().forEach(sourceFile => {
|
||||
const relativePath = relative(packagePath, sourceFile.fileName);
|
||||
const relativePath =
|
||||
PathSegment.relative(packagePath, AbsoluteFsPath.fromSourceFile(sourceFile));
|
||||
const isOutsidePackage = relativePath.startsWith('..');
|
||||
if (!sourceFile.isDeclarationFile && !isOutsidePackage) {
|
||||
const newFilePath = join(ngccFolder, relativePath);
|
||||
mkdir('-p', dirname(newFilePath));
|
||||
cp(sourceFile.fileName, newFilePath);
|
||||
const newFilePath = AbsoluteFsPath.join(ngccFolder, relativePath);
|
||||
this.fs.ensureDir(AbsoluteFsPath.dirname(newFilePath));
|
||||
this.fs.copyFile(AbsoluteFsPath.fromSourceFile(sourceFile), newFilePath);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -57,19 +53,24 @@ export class NewEntryPointFileWriter extends InPlaceFileWriter {
|
||||
// This is either `.d.ts` or `.d.ts.map` file
|
||||
super.writeFileAndBackup(file);
|
||||
} else {
|
||||
const relativePath = relative(packagePath, file.path);
|
||||
const newFilePath = join(ngccFolder, relativePath);
|
||||
mkdir('-p', dirname(newFilePath));
|
||||
writeFileSync(newFilePath, file.contents, 'utf8');
|
||||
const relativePath = PathSegment.relative(packagePath, file.path);
|
||||
const newFilePath = AbsoluteFsPath.join(ngccFolder, relativePath);
|
||||
this.fs.ensureDir(AbsoluteFsPath.dirname(newFilePath));
|
||||
this.fs.writeFile(newFilePath, file.contents);
|
||||
}
|
||||
}
|
||||
|
||||
protected updatePackageJson(
|
||||
entryPoint: EntryPoint, formatProperty: EntryPointJsonProperty, ngccFolder: AbsoluteFsPath) {
|
||||
const formatPath = join(entryPoint.path, entryPoint.packageJson[formatProperty] !);
|
||||
const newFormatPath = join(ngccFolder, relative(entryPoint.package, formatPath));
|
||||
const formatPath =
|
||||
AbsoluteFsPath.join(entryPoint.path, entryPoint.packageJson[formatProperty] !);
|
||||
const newFormatPath =
|
||||
AbsoluteFsPath.join(ngccFolder, PathSegment.relative(entryPoint.package, formatPath));
|
||||
const newFormatProperty = formatProperty + '_ivy_ngcc';
|
||||
(entryPoint.packageJson as any)[newFormatProperty] = relative(entryPoint.path, newFormatPath);
|
||||
writeFileSync(join(entryPoint.path, 'package.json'), JSON.stringify(entryPoint.packageJson));
|
||||
(entryPoint.packageJson as any)[newFormatProperty] =
|
||||
PathSegment.relative(entryPoint.path, newFormatPath);
|
||||
this.fs.writeFile(
|
||||
AbsoluteFsPath.join(entryPoint.path, 'package.json'),
|
||||
JSON.stringify(entryPoint.packageJson));
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user