refactor(compiler): remove old ngtools api and add listLazyRoutes to new api (#19836)
Usages of `NgTools_InternalApi_NG_2` from `@angular/compiler-cli` will now throw an error. Adds `listLazyRoutes` to `@angular/compiler-cli/ngtools2.ts` for getting the lazy routes of a `ng.Program`. PR Close #19836
This commit is contained in:
committed by
Matias Niemelä
parent
5da96c75a2
commit
8d45fefc31
@@ -1,117 +0,0 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
/**
|
||||
* Transform template html and css into executable code.
|
||||
* Intended to be used in a build step.
|
||||
*/
|
||||
import * as compiler from '@angular/compiler';
|
||||
import {readFileSync} from 'fs';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
|
||||
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
||||
import {CompilerOptions} from './transformers/api';
|
||||
|
||||
const GENERATED_META_FILES = /\.json$/;
|
||||
|
||||
const PREAMBLE = `/**
|
||||
* @fileoverview This file is generated by the Angular template compiler.
|
||||
* Do not edit.
|
||||
* @suppress {suspiciousCode,uselessCode,missingProperties,missingOverride}
|
||||
*/
|
||||
/* tslint:disable */
|
||||
|
||||
`;
|
||||
|
||||
export interface CodeGeneratorI18nOptions {
|
||||
i18nFormat: string|null;
|
||||
i18nFile: string|null;
|
||||
locale: string|null;
|
||||
missingTranslation: string|null;
|
||||
}
|
||||
|
||||
// TODO(tbosch): remove this once G3 uses the transformer compiler!
|
||||
export class CodeGenerator {
|
||||
constructor(
|
||||
private options: CompilerOptions, private program: ts.Program, public host: ts.CompilerHost,
|
||||
private compiler: compiler.AotCompiler, private ngCompilerHost: CompilerHost) {}
|
||||
|
||||
codegen(): Promise<string[]> {
|
||||
return this.compiler
|
||||
.analyzeModulesAsync(this.program.getSourceFiles().map(
|
||||
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)))
|
||||
.then(analyzedModules => this.emit(analyzedModules));
|
||||
}
|
||||
|
||||
codegenSync(): string[] {
|
||||
const analyzed = this.compiler.analyzeModulesSync(this.program.getSourceFiles().map(
|
||||
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)));
|
||||
return this.emit(analyzed);
|
||||
}
|
||||
|
||||
private emit(analyzedModules: compiler.NgAnalyzedModules) {
|
||||
const generatedModules = this.compiler.emitAllImpls(analyzedModules);
|
||||
return generatedModules.map(generatedModule => {
|
||||
const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl);
|
||||
const emitPath = this.ngCompilerHost.calculateEmitPath(generatedModule.genFileUrl);
|
||||
const source = generatedModule.source || compiler.toTypeScript(generatedModule, PREAMBLE);
|
||||
this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]);
|
||||
return emitPath;
|
||||
});
|
||||
}
|
||||
|
||||
static create(
|
||||
options: CompilerOptions, i18nOptions: CodeGeneratorI18nOptions, program: ts.Program,
|
||||
tsCompilerHost: ts.CompilerHost, compilerHostContext?: CompilerHostContext,
|
||||
ngCompilerHost?: CompilerHost): CodeGenerator {
|
||||
if (!ngCompilerHost) {
|
||||
const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0;
|
||||
const context = compilerHostContext || new ModuleResolutionHostAdapter(tsCompilerHost);
|
||||
ngCompilerHost = usePathMapping ? new PathMappedCompilerHost(program, options, context) :
|
||||
new CompilerHost(program, options, context);
|
||||
}
|
||||
let transContent: string = '';
|
||||
if (i18nOptions.i18nFile) {
|
||||
if (!i18nOptions.locale) {
|
||||
throw new Error(
|
||||
`The translation file (${i18nOptions.i18nFile}) locale must be provided. Use the --locale option.`);
|
||||
}
|
||||
transContent = readFileSync(i18nOptions.i18nFile, 'utf8');
|
||||
}
|
||||
let missingTranslation = compiler.core.MissingTranslationStrategy.Warning;
|
||||
if (i18nOptions.missingTranslation) {
|
||||
switch (i18nOptions.missingTranslation) {
|
||||
case 'error':
|
||||
missingTranslation = compiler.core.MissingTranslationStrategy.Error;
|
||||
break;
|
||||
case 'warning':
|
||||
missingTranslation = compiler.core.MissingTranslationStrategy.Warning;
|
||||
break;
|
||||
case 'ignore':
|
||||
missingTranslation = compiler.core.MissingTranslationStrategy.Ignore;
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`Unknown option for missingTranslation (${i18nOptions.missingTranslation}). Use either error, warning or ignore.`);
|
||||
}
|
||||
}
|
||||
if (!transContent) {
|
||||
missingTranslation = compiler.core.MissingTranslationStrategy.Ignore;
|
||||
}
|
||||
const {compiler: aotCompiler} = compiler.createAotCompiler(ngCompilerHost, {
|
||||
translations: transContent,
|
||||
i18nFormat: i18nOptions.i18nFormat || undefined,
|
||||
locale: i18nOptions.locale || undefined, missingTranslation,
|
||||
enableLegacyTemplate: options.enableLegacyTemplate === true,
|
||||
enableSummariesForJit: options.enableSummariesForJit !== false,
|
||||
preserveWhitespaces: options.preserveWhitespaces,
|
||||
});
|
||||
return new CodeGenerator(options, program, tsCompilerHost, aotCompiler, ngCompilerHost);
|
||||
}
|
||||
}
|
||||
@@ -1,494 +0,0 @@
|
||||
/**
|
||||
* @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 {AotCompilerHost, StaticSymbol, UrlResolver, createOfflineCompileUrlResolver, syntaxError} from '@angular/compiler';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {CollectorOptions, METADATA_VERSION, MetadataCollector, ModuleMetadata} from './metadata/index';
|
||||
import {CompilerOptions} from './transformers/api';
|
||||
|
||||
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||
const DTS = /\.d\.ts$/;
|
||||
const NODE_MODULES = '/node_modules/';
|
||||
const IS_GENERATED = /\.(ngfactory|ngstyle|ngsummary)$/;
|
||||
const GENERATED_FILES = /\.ngfactory\.ts$|\.ngstyle\.ts$|\.ngsummary\.ts$/;
|
||||
const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.ngstyle\.ts$|\.ngsummary\.ts$/;
|
||||
const SHALLOW_IMPORT = /^((\w|-)+|(@(\w|-)+(\/(\w|-)+)+))$/;
|
||||
|
||||
export interface BaseAotCompilerHostContext extends ts.ModuleResolutionHost {
|
||||
readResource?(fileName: string): Promise<string>|string;
|
||||
}
|
||||
|
||||
export abstract class BaseAotCompilerHost<C extends BaseAotCompilerHostContext> implements
|
||||
AotCompilerHost {
|
||||
private resolverCache = new Map<string, ModuleMetadata[]>();
|
||||
private flatModuleIndexCache = new Map<string, boolean>();
|
||||
private flatModuleIndexNames = new Set<string>();
|
||||
private flatModuleIndexRedirectNames = new Set<string>();
|
||||
|
||||
constructor(protected options: CompilerOptions, protected context: C) {}
|
||||
|
||||
abstract moduleNameToFileName(m: string, containingFile: string): string|null;
|
||||
|
||||
abstract resourceNameToFileName(m: string, containingFile: string): string|null;
|
||||
|
||||
abstract fileNameToModuleName(importedFile: string, containingFile: string): string;
|
||||
|
||||
abstract toSummaryFileName(fileName: string, referringSrcFileName: string): string;
|
||||
|
||||
abstract fromSummaryFileName(fileName: string, referringLibFileName: string): string;
|
||||
|
||||
abstract getMetadataForSourceFile(filePath: string): ModuleMetadata|undefined;
|
||||
|
||||
getMetadataFor(filePath: string): ModuleMetadata[]|undefined {
|
||||
if (!this.context.fileExists(filePath)) {
|
||||
// If the file doesn't exists then we cannot return metadata for the file.
|
||||
// This will occur if the user referenced a declared module for which no file
|
||||
// exists for the module (i.e. jQuery or angularjs).
|
||||
return;
|
||||
}
|
||||
|
||||
if (DTS.test(filePath)) {
|
||||
let metadatas = this.readMetadata(filePath);
|
||||
if (!metadatas) {
|
||||
// If there is a .d.ts file but no metadata file we need to produce a
|
||||
// metadata from the .d.ts file as metadata files capture reexports
|
||||
// (starting with v3).
|
||||
metadatas = [this.upgradeMetadataWithDtsData(
|
||||
{'__symbolic': 'module', 'version': 1, 'metadata': {}}, filePath)];
|
||||
}
|
||||
return metadatas;
|
||||
}
|
||||
|
||||
// Attention: don't cache this, so that e.g. the LanguageService
|
||||
// can read in changes from source files in the metadata!
|
||||
const metadata = this.getMetadataForSourceFile(filePath);
|
||||
return metadata ? [metadata] : [];
|
||||
}
|
||||
|
||||
protected readMetadata(dtsFilePath: string): ModuleMetadata[]|undefined {
|
||||
let metadatas = this.resolverCache.get(dtsFilePath);
|
||||
if (metadatas) {
|
||||
return metadatas;
|
||||
}
|
||||
const metadataPath = dtsFilePath.replace(DTS, '.metadata.json');
|
||||
if (!this.context.fileExists(metadataPath)) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
const metadataOrMetadatas = JSON.parse(this.context.readFile(metadataPath));
|
||||
const metadatas: ModuleMetadata[] = metadataOrMetadatas ?
|
||||
(Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) :
|
||||
[];
|
||||
if (metadatas.length) {
|
||||
let maxMetadata = metadatas.reduce((p, c) => p.version > c.version ? p : c);
|
||||
if (maxMetadata.version < METADATA_VERSION) {
|
||||
metadatas.push(this.upgradeMetadataWithDtsData(maxMetadata, dtsFilePath));
|
||||
}
|
||||
}
|
||||
this.resolverCache.set(dtsFilePath, metadatas);
|
||||
return metadatas;
|
||||
} catch (e) {
|
||||
console.error(`Failed to read JSON file ${metadataPath}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private upgradeMetadataWithDtsData(oldMetadata: ModuleMetadata, dtsFilePath: string):
|
||||
ModuleMetadata {
|
||||
// patch v1 to v3 by adding exports and the `extends` clause.
|
||||
// patch v3 to v4 by adding `interface` symbols for TypeAlias
|
||||
let newMetadata: ModuleMetadata = {
|
||||
'__symbolic': 'module',
|
||||
'version': METADATA_VERSION,
|
||||
'metadata': {...oldMetadata.metadata},
|
||||
};
|
||||
if (oldMetadata.exports) {
|
||||
newMetadata.exports = oldMetadata.exports;
|
||||
}
|
||||
if (oldMetadata.importAs) {
|
||||
newMetadata.importAs = oldMetadata.importAs;
|
||||
}
|
||||
if (oldMetadata.origins) {
|
||||
newMetadata.origins = oldMetadata.origins;
|
||||
}
|
||||
const dtsMetadata = this.getMetadataForSourceFile(dtsFilePath);
|
||||
if (dtsMetadata) {
|
||||
for (let prop in dtsMetadata.metadata) {
|
||||
if (!newMetadata.metadata[prop]) {
|
||||
newMetadata.metadata[prop] = dtsMetadata.metadata[prop];
|
||||
}
|
||||
}
|
||||
|
||||
// Only copy exports from exports from metadata prior to version 3.
|
||||
// Starting with version 3 the collector began collecting exports and
|
||||
// this should be redundant. Also, with bundler will rewrite the exports
|
||||
// which will hoist the exports from modules referenced indirectly causing
|
||||
// the imports to be different than the .d.ts files and using the .d.ts file
|
||||
// exports would cause the StaticSymbolResolver to redirect symbols to the
|
||||
// incorrect location.
|
||||
if ((!oldMetadata.version || oldMetadata.version < 3) && dtsMetadata.exports) {
|
||||
newMetadata.exports = dtsMetadata.exports;
|
||||
}
|
||||
}
|
||||
return newMetadata;
|
||||
}
|
||||
|
||||
loadResource(filePath: string): Promise<string>|string {
|
||||
if (this.context.readResource) return this.context.readResource(filePath);
|
||||
if (!this.context.fileExists(filePath)) {
|
||||
throw syntaxError(`Error: Resource file not found: ${filePath}`);
|
||||
}
|
||||
return this.context.readFile(filePath);
|
||||
}
|
||||
|
||||
loadSummary(filePath: string): string|null {
|
||||
if (this.context.fileExists(filePath)) {
|
||||
return this.context.readFile(filePath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
isSourceFile(filePath: string): boolean {
|
||||
const excludeRegex =
|
||||
this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
|
||||
if (excludeRegex.test(filePath)) {
|
||||
return false;
|
||||
}
|
||||
if (DTS.test(filePath)) {
|
||||
// Check for a bundle index.
|
||||
if (this.hasBundleIndex(filePath)) {
|
||||
const normalFilePath = path.normalize(filePath);
|
||||
return this.flatModuleIndexNames.has(normalFilePath) ||
|
||||
this.flatModuleIndexRedirectNames.has(normalFilePath);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private hasBundleIndex(filePath: string): boolean {
|
||||
const checkBundleIndex = (directory: string): boolean => {
|
||||
let result = this.flatModuleIndexCache.get(directory);
|
||||
if (result == null) {
|
||||
if (path.basename(directory) == 'node_module') {
|
||||
// Don't look outside the node_modules this package is installed in.
|
||||
result = false;
|
||||
} else {
|
||||
// A bundle index exists if the typings .d.ts file has a metadata.json that has an
|
||||
// importAs.
|
||||
try {
|
||||
const packageFile = path.join(directory, 'package.json');
|
||||
if (this.context.fileExists(packageFile)) {
|
||||
// Once we see a package.json file, assume false until it we find the bundle index.
|
||||
result = false;
|
||||
const packageContent: any = JSON.parse(this.context.readFile(packageFile));
|
||||
if (packageContent.typings) {
|
||||
const typings = path.normalize(path.join(directory, packageContent.typings));
|
||||
if (DTS.test(typings)) {
|
||||
const metadataFile = typings.replace(DTS, '.metadata.json');
|
||||
if (this.context.fileExists(metadataFile)) {
|
||||
const metadata = JSON.parse(this.context.readFile(metadataFile));
|
||||
if (metadata.flatModuleIndexRedirect) {
|
||||
this.flatModuleIndexRedirectNames.add(typings);
|
||||
// Note: don't set result = true,
|
||||
// as this would mark this folder
|
||||
// as having a bundleIndex too early without
|
||||
// filling the bundleIndexNames.
|
||||
} else if (metadata.importAs) {
|
||||
this.flatModuleIndexNames.add(typings);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const parent = path.dirname(directory);
|
||||
if (parent != directory) {
|
||||
// Try the parent directory.
|
||||
result = checkBundleIndex(parent);
|
||||
} else {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// If we encounter any errors assume we this isn't a bundle index.
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
this.flatModuleIndexCache.set(directory, result);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
return checkBundleIndex(path.dirname(filePath));
|
||||
}
|
||||
}
|
||||
|
||||
export interface CompilerHostContext extends ts.ModuleResolutionHost {
|
||||
readResource?(fileName: string): Promise<string>|string;
|
||||
assumeFileExists(fileName: string): void;
|
||||
}
|
||||
|
||||
// TODO(tbosch): remove this once G3 uses the transformer compiler!
|
||||
export class CompilerHost extends BaseAotCompilerHost<CompilerHostContext> {
|
||||
protected metadataProvider: MetadataCollector;
|
||||
protected basePath: string;
|
||||
private moduleFileNames = new Map<string, string|null>();
|
||||
private isGenDirChildOfRootDir: boolean;
|
||||
private genDir: string;
|
||||
protected resolveModuleNameHost: CompilerHostContext;
|
||||
private urlResolver: UrlResolver;
|
||||
|
||||
constructor(
|
||||
protected program: ts.Program, options: CompilerOptions, context: CompilerHostContext,
|
||||
collectorOptions?: CollectorOptions) {
|
||||
super(options, context);
|
||||
this.metadataProvider = new MetadataCollector(collectorOptions);
|
||||
// normalize the path so that it never ends with '/'.
|
||||
this.basePath = path.normalize(path.join(this.options.basePath !, '.')).replace(/\\/g, '/');
|
||||
this.genDir = path.normalize(path.join(this.options.genDir !, '.')).replace(/\\/g, '/');
|
||||
|
||||
const genPath: string = path.relative(this.basePath, this.genDir);
|
||||
this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..');
|
||||
|
||||
this.resolveModuleNameHost = Object.create(this.context);
|
||||
|
||||
// When calling ts.resolveModuleName,
|
||||
// additional allow checks for .d.ts files to be done based on
|
||||
// checks for .ngsummary.json files,
|
||||
// so that our codegen depends on fewer inputs and requires to be called
|
||||
// less often.
|
||||
// This is needed as we use ts.resolveModuleName in reflector_host
|
||||
// and it should be able to resolve summary file names.
|
||||
this.resolveModuleNameHost.fileExists = (fileName: string): boolean => {
|
||||
if (this.context.fileExists(fileName)) {
|
||||
return true;
|
||||
}
|
||||
if (DTS.test(fileName)) {
|
||||
const base = fileName.substring(0, fileName.length - 5);
|
||||
return this.context.fileExists(base + '.ngsummary.json');
|
||||
}
|
||||
return false;
|
||||
};
|
||||
this.urlResolver = createOfflineCompileUrlResolver();
|
||||
}
|
||||
|
||||
protected getSourceFile(filePath: string): ts.SourceFile {
|
||||
let sf = this.program.getSourceFile(filePath);
|
||||
if (!sf) {
|
||||
if (this.context.fileExists(filePath)) {
|
||||
const sourceText = this.context.readFile(filePath);
|
||||
sf = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true);
|
||||
} else {
|
||||
throw new Error(`Source file ${filePath} not present in program.`);
|
||||
}
|
||||
}
|
||||
return sf;
|
||||
}
|
||||
|
||||
getMetadataForSourceFile(filePath: string): ModuleMetadata|undefined {
|
||||
return this.metadataProvider.getMetadata(this.getSourceFile(filePath));
|
||||
}
|
||||
|
||||
toSummaryFileName(fileName: string, referringSrcFileName: string): string {
|
||||
return fileName.replace(EXT, '') + '.d.ts';
|
||||
}
|
||||
|
||||
fromSummaryFileName(fileName: string, referringLibFileName: string): string { return fileName; }
|
||||
|
||||
calculateEmitPath(filePath: string): string {
|
||||
// Write codegen in a directory structure matching the sources.
|
||||
let root = this.options.basePath !;
|
||||
for (const eachRootDir of this.options.rootDirs || []) {
|
||||
if (this.options.trace) {
|
||||
console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
|
||||
}
|
||||
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
|
||||
root = eachRootDir;
|
||||
}
|
||||
}
|
||||
|
||||
// transplant the codegen path to be inside the `genDir`
|
||||
let relativePath: string = path.relative(root, filePath);
|
||||
while (relativePath.startsWith('..' + path.sep)) {
|
||||
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
|
||||
// into `genDir`.
|
||||
relativePath = relativePath.substr(3);
|
||||
}
|
||||
|
||||
return path.join(this.options.genDir !, relativePath);
|
||||
}
|
||||
|
||||
// We use absolute paths on disk as canonical.
|
||||
getCanonicalFileName(fileName: string): string { return fileName; }
|
||||
|
||||
moduleNameToFileName(m: string, containingFile: string): string|null {
|
||||
const key = m + ':' + (containingFile || '');
|
||||
let result: string|null = this.moduleFileNames.get(key) || null;
|
||||
if (!result) {
|
||||
if (!containingFile || !containingFile.length) {
|
||||
if (m.indexOf('.') === 0) {
|
||||
throw new Error('Resolution of relative paths requires a containing file.');
|
||||
}
|
||||
// Any containing file gives the same result for absolute imports
|
||||
containingFile = this.getCanonicalFileName(path.join(this.basePath, 'index.ts'));
|
||||
}
|
||||
m = m.replace(EXT, '');
|
||||
const resolved =
|
||||
ts.resolveModuleName(
|
||||
m, containingFile.replace(/\\/g, '/'), this.options, this.resolveModuleNameHost)
|
||||
.resolvedModule;
|
||||
result = resolved ? this.getCanonicalFileName(resolved.resolvedFileName) : null;
|
||||
this.moduleFileNames.set(key, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* We want a moduleId that will appear in import statements in the generated code.
|
||||
* These need to be in a form that system.js can load, so absolute file paths don't work.
|
||||
*
|
||||
* The `containingFile` is always in the `genDir`, where as the `importedFile` can be in
|
||||
* `genDir`, `node_module` or `basePath`. The `importedFile` is either a generated file or
|
||||
* existing file.
|
||||
*
|
||||
* | genDir | node_module | rootDir
|
||||
* --------------+----------+-------------+----------
|
||||
* generated | relative | relative | n/a
|
||||
* existing file | n/a | absolute | relative(*)
|
||||
*
|
||||
* NOTE: (*) the relative path is computed depending on `isGenDirChildOfRootDir`.
|
||||
*/
|
||||
fileNameToModuleName(importedFile: string, containingFile: string): string {
|
||||
// If a file does not yet exist (because we compile it later), we still need to
|
||||
// assume it exists it so that the `resolve` method works!
|
||||
if (importedFile !== containingFile && !this.context.fileExists(importedFile)) {
|
||||
this.context.assumeFileExists(importedFile);
|
||||
}
|
||||
|
||||
containingFile = this.rewriteGenDirPath(containingFile);
|
||||
const containingDir = path.dirname(containingFile);
|
||||
// drop extension
|
||||
importedFile = importedFile.replace(EXT, '');
|
||||
|
||||
const nodeModulesIndex = importedFile.indexOf(NODE_MODULES);
|
||||
const importModule = nodeModulesIndex === -1 ?
|
||||
null :
|
||||
importedFile.substring(nodeModulesIndex + NODE_MODULES.length);
|
||||
const isGeneratedFile = IS_GENERATED.test(importedFile);
|
||||
|
||||
if (isGeneratedFile) {
|
||||
// rewrite to genDir path
|
||||
if (importModule) {
|
||||
// it is generated, therefore we do a relative path to the factory
|
||||
return this.dotRelative(containingDir, this.genDir + NODE_MODULES + importModule);
|
||||
} else {
|
||||
// assume that import is also in `genDir`
|
||||
importedFile = this.rewriteGenDirPath(importedFile);
|
||||
return this.dotRelative(containingDir, importedFile);
|
||||
}
|
||||
} else {
|
||||
// user code import
|
||||
if (importModule) {
|
||||
return importModule;
|
||||
} else {
|
||||
if (!this.isGenDirChildOfRootDir) {
|
||||
// assume that they are on top of each other.
|
||||
importedFile = importedFile.replace(this.basePath, this.genDir);
|
||||
}
|
||||
if (SHALLOW_IMPORT.test(importedFile)) {
|
||||
return importedFile;
|
||||
}
|
||||
return this.dotRelative(containingDir, importedFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resourceNameToFileName(m: string, containingFile: string): string {
|
||||
return this.urlResolver.resolve(containingFile, m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the path into `genDir` folder while preserving the `node_modules` directory.
|
||||
*/
|
||||
private rewriteGenDirPath(filepath: string) {
|
||||
const nodeModulesIndex = filepath.indexOf(NODE_MODULES);
|
||||
if (nodeModulesIndex !== -1) {
|
||||
// If we are in node_modules, transplant them into `genDir`.
|
||||
return path.join(this.genDir, filepath.substring(nodeModulesIndex));
|
||||
} else {
|
||||
// pretend that containing file is on top of the `genDir` to normalize the paths.
|
||||
// we apply the `genDir` => `rootDir` delta through `rootDirPrefix` later.
|
||||
return filepath.replace(this.basePath, this.genDir);
|
||||
}
|
||||
}
|
||||
|
||||
private dotRelative(from: string, to: string): string {
|
||||
const rPath: string = path.relative(from, to).replace(/\\/g, '/');
|
||||
return rPath.startsWith('.') ? rPath : './' + rPath;
|
||||
}
|
||||
}
|
||||
|
||||
export class CompilerHostContextAdapter {
|
||||
protected assumedExists: {[fileName: string]: boolean} = {};
|
||||
|
||||
assumeFileExists(fileName: string): void { this.assumedExists[fileName] = true; }
|
||||
}
|
||||
|
||||
export class ModuleResolutionHostAdapter extends CompilerHostContextAdapter implements
|
||||
CompilerHostContext {
|
||||
public directoryExists: ((directoryName: string) => boolean)|undefined;
|
||||
|
||||
constructor(private host: ts.ModuleResolutionHost) {
|
||||
super();
|
||||
if (host.directoryExists) {
|
||||
this.directoryExists = (directoryName: string) => host.directoryExists !(directoryName);
|
||||
}
|
||||
}
|
||||
|
||||
fileExists(fileName: string): boolean {
|
||||
return this.assumedExists[fileName] || this.host.fileExists(fileName);
|
||||
}
|
||||
|
||||
readFile(fileName: string): string { return this.host.readFile(fileName); }
|
||||
|
||||
readResource(s: string) {
|
||||
if (!this.host.fileExists(s)) {
|
||||
// TODO: We should really have a test for error cases like this!
|
||||
throw new Error(`Compilation failed. Resource file not found: ${s}`);
|
||||
}
|
||||
return Promise.resolve(this.host.readFile(s));
|
||||
}
|
||||
}
|
||||
|
||||
export class NodeCompilerHostContext extends CompilerHostContextAdapter implements
|
||||
CompilerHostContext {
|
||||
fileExists(fileName: string): boolean {
|
||||
return this.assumedExists[fileName] || fs.existsSync(fileName);
|
||||
}
|
||||
|
||||
directoryExists(directoryName: string): boolean {
|
||||
try {
|
||||
return fs.statSync(directoryName).isDirectory();
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
readFile(fileName: string): string { return fs.readFileSync(fileName, 'utf8'); }
|
||||
|
||||
readResource(s: string) {
|
||||
if (!this.fileExists(s)) {
|
||||
// TODO: We should really have a test for error cases like this!
|
||||
throw new Error(`Compilation failed. Resource file not found: ${s}`);
|
||||
}
|
||||
return Promise.resolve(this.readFile(s));
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Extract i18n messages from source code
|
||||
*/
|
||||
// Must be imported first, because Angular decorators throw on load.
|
||||
import 'reflect-metadata';
|
||||
|
||||
import * as compiler from '@angular/compiler';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
|
||||
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
||||
import {CompilerOptions} from './transformers/api';
|
||||
import {i18nExtract, i18nGetExtension, i18nSerialize} from './transformers/program';
|
||||
|
||||
export class Extractor {
|
||||
constructor(
|
||||
private options: CompilerOptions, private ngExtractor: compiler.Extractor,
|
||||
public host: ts.CompilerHost, private ngCompilerHost: CompilerHost,
|
||||
private program: ts.Program) {}
|
||||
|
||||
extract(formatName: string, outFile: string|null): Promise<string[]> {
|
||||
return this.extractBundle().then(
|
||||
bundle => i18nExtract(formatName, outFile, this.host, this.options, bundle));
|
||||
}
|
||||
|
||||
extractBundle(): Promise<compiler.MessageBundle> {
|
||||
const files = this.program.getSourceFiles().map(
|
||||
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName));
|
||||
|
||||
return this.ngExtractor.extract(files);
|
||||
}
|
||||
|
||||
serialize(bundle: compiler.MessageBundle, formatName: string): string {
|
||||
return i18nSerialize(bundle, formatName, this.options);
|
||||
}
|
||||
|
||||
getExtension(formatName: string): string { return i18nGetExtension(formatName); }
|
||||
|
||||
static create(
|
||||
options: CompilerOptions, program: ts.Program, tsCompilerHost: ts.CompilerHost,
|
||||
locale?: string|null, compilerHostContext?: CompilerHostContext,
|
||||
ngCompilerHost?: CompilerHost): Extractor {
|
||||
if (!ngCompilerHost) {
|
||||
const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0;
|
||||
const context = compilerHostContext || new ModuleResolutionHostAdapter(tsCompilerHost);
|
||||
ngCompilerHost = usePathMapping ? new PathMappedCompilerHost(program, options, context) :
|
||||
new CompilerHost(program, options, context);
|
||||
}
|
||||
|
||||
const {extractor: ngExtractor} = compiler.Extractor.create(ngCompilerHost, locale || null);
|
||||
|
||||
return new Extractor(options, ngExtractor, tsCompilerHost, ngCompilerHost, program);
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,10 @@ Angular modules and Typescript as this will indirectly add a dependency
|
||||
to the language service.
|
||||
|
||||
*/
|
||||
export {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter, NodeCompilerHostContext} from './compiler_host';
|
||||
export {DiagnosticTemplateInfo, ExpressionDiagnostic, getExpressionDiagnostics, getExpressionScope, getTemplateExpressionDiagnostics} from './diagnostics/expression_diagnostics';
|
||||
export {AstType, DiagnosticKind, ExpressionDiagnosticsContext, TypeDiagnostic} from './diagnostics/expression_type';
|
||||
export {BuiltinType, DeclarationKind, Definition, Location, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from './diagnostics/symbols';
|
||||
export {getClassFromStaticSymbol, getClassMembers, getClassMembersFromDeclaration, getPipesTable, getSymbolQuery} from './diagnostics/typescript_symbols';
|
||||
export {MetadataCollector, ModuleMetadata} from './metadata';
|
||||
export {CompilerOptions} from './transformers/api';
|
||||
export {MetadataReaderCache, MetadataReaderHost, createMetadataReaderCache, readMetadata} from './transformers/metadata_reader';
|
||||
|
||||
@@ -28,15 +28,15 @@ export function main(
|
||||
let {project, rootNames, options, errors: configErrors, watch, emitFlags} =
|
||||
config || readNgcCommandLineAndConfiguration(args);
|
||||
if (configErrors.length) {
|
||||
return reportErrorsAndExit(configErrors, consoleError);
|
||||
return reportErrorsAndExit(configErrors, /*options*/ undefined, consoleError);
|
||||
}
|
||||
if (watch) {
|
||||
const result = watchMode(project, options, consoleError);
|
||||
return reportErrorsAndExit(result.firstCompileResult, consoleError);
|
||||
return reportErrorsAndExit(result.firstCompileResult, options, consoleError);
|
||||
}
|
||||
const {diagnostics: compileDiags} = performCompilation(
|
||||
{rootNames, options, emitFlags, emitCallback: createEmitCallback(options)});
|
||||
return reportErrorsAndExit(compileDiags, consoleError);
|
||||
return reportErrorsAndExit(compileDiags, options, consoleError);
|
||||
}
|
||||
|
||||
function createEmitCallback(options: api.CompilerOptions): api.TsEmitCallback|undefined {
|
||||
@@ -128,10 +128,17 @@ export function readCommandLineAndConfiguration(
|
||||
}
|
||||
|
||||
function reportErrorsAndExit(
|
||||
allDiagnostics: Diagnostics, consoleError: (s: string) => void = console.error): number {
|
||||
allDiagnostics: Diagnostics, options?: api.CompilerOptions,
|
||||
consoleError: (s: string) => void = console.error): number {
|
||||
const errorsAndWarnings = filterErrorsAndWarnings(allDiagnostics);
|
||||
if (errorsAndWarnings.length) {
|
||||
consoleError(formatDiagnostics(errorsAndWarnings));
|
||||
let currentDir = options ? options.basePath : undefined;
|
||||
const formatHost: ts.FormatDiagnosticsHost = {
|
||||
getCurrentDirectory: () => currentDir || ts.sys.getCurrentDirectory(),
|
||||
getCanonicalFileName: fileName => fileName,
|
||||
getNewLine: () => ts.sys.newLine
|
||||
};
|
||||
consoleError(formatDiagnostics(errorsAndWarnings, formatHost));
|
||||
}
|
||||
return exitCodeFromResult(allDiagnostics);
|
||||
}
|
||||
|
||||
@@ -19,15 +19,11 @@
|
||||
*********************************************************************
|
||||
*/
|
||||
|
||||
import {AotCompilerHost, AotSummaryResolver, StaticReflector, StaticSymbolCache, StaticSymbolResolver} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {CodeGenerator} from './codegen';
|
||||
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
|
||||
import {Extractor} from './extractor';
|
||||
import {listLazyRoutesOfModule} from './ngtools_impl';
|
||||
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
||||
import {CompilerOptions} from './transformers/api';
|
||||
import {CompilerHost, CompilerOptions, LazyRoute} from './transformers/api';
|
||||
import {getOriginalReferences} from './transformers/compiler_host';
|
||||
import {createProgram} from './transformers/entry_points';
|
||||
|
||||
export interface NgTools_InternalApi_NG2_CodeGen_Options {
|
||||
basePath: string;
|
||||
@@ -72,47 +68,16 @@ export interface NgTools_InternalApi_NG2_ExtractI18n_Options {
|
||||
outFile?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A ModuleResolutionHostAdapter that overrides the readResource() method with the one
|
||||
* passed in the interface.
|
||||
*/
|
||||
class CustomLoaderModuleResolutionHostAdapter extends ModuleResolutionHostAdapter {
|
||||
constructor(
|
||||
private _readResource: (path: string) => Promise<string>, host: ts.ModuleResolutionHost) {
|
||||
super(host);
|
||||
}
|
||||
|
||||
readResource(path: string) { return this._readResource(path); }
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecatd Use ngtools_api2 instead!
|
||||
*/
|
||||
export class NgTools_InternalApi_NG_2 {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
static codeGen(options: NgTools_InternalApi_NG2_CodeGen_Options): Promise<any> {
|
||||
const hostContext: CompilerHostContext =
|
||||
new CustomLoaderModuleResolutionHostAdapter(options.readResource, options.host);
|
||||
|
||||
const i18nOptions = {
|
||||
i18nFormat: options.i18nFormat !,
|
||||
i18nFile: options.i18nFile !,
|
||||
locale: options.locale !,
|
||||
missingTranslation: options.missingTranslation !,
|
||||
};
|
||||
const ngOptions = options.angularCompilerOptions;
|
||||
if (ngOptions.enableSummariesForJit === undefined) {
|
||||
// default to false
|
||||
ngOptions.enableSummariesForJit = false;
|
||||
}
|
||||
|
||||
// Create the Code Generator.
|
||||
const codeGenerator =
|
||||
CodeGenerator.create(ngOptions, i18nOptions, options.program, options.host, hostContext);
|
||||
|
||||
return codeGenerator.codegen();
|
||||
throw throwNotSupportedError();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,42 +85,49 @@ export class NgTools_InternalApi_NG_2 {
|
||||
*/
|
||||
static listLazyRoutes(options: NgTools_InternalApi_NG2_ListLazyRoutes_Options):
|
||||
NgTools_InternalApi_NG_2_LazyRouteMap {
|
||||
const angularCompilerOptions = options.angularCompilerOptions;
|
||||
const program = options.program;
|
||||
// TODO(tbosch): Also throwNotSupportedError once Angular CLI 1.5.1 ships,
|
||||
// as we only needed this to support Angular CLI 1.5.0 rc.*
|
||||
const ngProgram = createProgram({
|
||||
rootNames: options.program.getRootFileNames(),
|
||||
options: options.angularCompilerOptions,
|
||||
host: options.host
|
||||
});
|
||||
const lazyRoutes = ngProgram.listLazyRoutes(options.entryModule);
|
||||
|
||||
const moduleResolutionHost = new ModuleResolutionHostAdapter(options.host);
|
||||
const usePathMapping =
|
||||
!!angularCompilerOptions.rootDirs && angularCompilerOptions.rootDirs.length > 0;
|
||||
const ngCompilerHost: AotCompilerHost = usePathMapping ?
|
||||
new PathMappedCompilerHost(program, angularCompilerOptions, moduleResolutionHost) :
|
||||
new CompilerHost(program, angularCompilerOptions, moduleResolutionHost);
|
||||
// reset the referencedFiles that the ng.Program added to the SourceFiles
|
||||
// as the host might be caching the source files!
|
||||
for (const sourceFile of options.program.getSourceFiles()) {
|
||||
const originalReferences = getOriginalReferences(sourceFile);
|
||||
if (originalReferences) {
|
||||
sourceFile.referencedFiles = originalReferences;
|
||||
}
|
||||
}
|
||||
|
||||
const symbolCache = new StaticSymbolCache();
|
||||
const summaryResolver = new AotSummaryResolver(ngCompilerHost, symbolCache);
|
||||
const symbolResolver = new StaticSymbolResolver(ngCompilerHost, symbolCache, summaryResolver);
|
||||
const staticReflector = new StaticReflector(summaryResolver, symbolResolver);
|
||||
const routeMap = listLazyRoutesOfModule(options.entryModule, ngCompilerHost, staticReflector);
|
||||
const result: NgTools_InternalApi_NG_2_LazyRouteMap = {};
|
||||
lazyRoutes.forEach(lazyRoute => {
|
||||
const route = lazyRoute.route;
|
||||
const referencedFilePath = lazyRoute.referencedModule.filePath;
|
||||
if (result[route] && result[route] != referencedFilePath) {
|
||||
throw new Error(
|
||||
`Duplicated path in loadChildren detected: "${route}" is used in 2 loadChildren, ` +
|
||||
`but they point to different modules "(${result[route]} and ` +
|
||||
`"${referencedFilePath}"). Webpack cannot distinguish on context and would fail to ` +
|
||||
'load the proper one.');
|
||||
}
|
||||
result[route] = referencedFilePath;
|
||||
});
|
||||
|
||||
return Object.keys(routeMap).reduce(
|
||||
(acc: NgTools_InternalApi_NG_2_LazyRouteMap, route: string) => {
|
||||
acc[route] = routeMap[route].absoluteFilePath;
|
||||
return acc;
|
||||
},
|
||||
{});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
static extractI18n(options: NgTools_InternalApi_NG2_ExtractI18n_Options): Promise<any> {
|
||||
const hostContext: CompilerHostContext =
|
||||
new CustomLoaderModuleResolutionHostAdapter(options.readResource, options.host);
|
||||
|
||||
// Create the i18n extractor.
|
||||
const locale = options.locale || null;
|
||||
const extractor = Extractor.create(
|
||||
options.angularCompilerOptions, options.program, options.host, locale, hostContext);
|
||||
|
||||
return extractor.extract(options.i18nFormat !, options.outFile || null);
|
||||
throw throwNotSupportedError();
|
||||
}
|
||||
}
|
||||
|
||||
function throwNotSupportedError() {
|
||||
throw new Error(`Please update @angular/cli. Angular 5+ requires at least Angular CLI 1.5+`);
|
||||
}
|
||||
|
||||
@@ -100,6 +100,12 @@ export interface TsEmitArguments {
|
||||
|
||||
export interface TsEmitCallback { (args: TsEmitArguments): ts.EmitResult; }
|
||||
|
||||
export interface LazyRoute {
|
||||
module: {name: string, filePath: string};
|
||||
route: string;
|
||||
referencedModule: {name: string, filePath: string};
|
||||
}
|
||||
|
||||
export interface Program {
|
||||
getTsProgram(): ts.Program;
|
||||
getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken): ts.Diagnostic[];
|
||||
@@ -112,6 +118,7 @@ export interface Program {
|
||||
getNgSemanticDiagnostics(fileName?: string, cancellationToken?: ts.CancellationToken):
|
||||
Diagnostic[];
|
||||
loadNgStructureAsync(): Promise<void>;
|
||||
listLazyRoutes(entryRoute?: string): LazyRoute[];
|
||||
emit({emitFlags, cancellationToken, customTransformers, emitCallback}: {
|
||||
emitFlags?: EmitFlags,
|
||||
cancellationToken?: ts.CancellationToken,
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a private API for the ngtools toolkit.
|
||||
*
|
||||
* This API should be stable for NG 2. It can be removed in NG 4..., but should be replaced by
|
||||
* something else.
|
||||
*/
|
||||
|
||||
/**
|
||||
*********************************************************************
|
||||
* Changes to this file need to be approved by the Angular CLI team. *
|
||||
*********************************************************************
|
||||
*/
|
||||
|
||||
import {AotCompilerHost, StaticReflector, StaticSymbol, core} from '@angular/compiler';
|
||||
|
||||
|
||||
// We cannot depend directly to @angular/router.
|
||||
type Route = any;
|
||||
const ROUTER_MODULE_PATH = '@angular/router';
|
||||
const ROUTER_ROUTES_SYMBOL_NAME = 'ROUTES';
|
||||
|
||||
|
||||
// LazyRoute information between the extractors.
|
||||
export interface LazyRoute {
|
||||
routeDef: RouteDef;
|
||||
absoluteFilePath: string;
|
||||
}
|
||||
export type LazyRouteMap = {
|
||||
[route: string]: LazyRoute
|
||||
};
|
||||
|
||||
// A route definition. Normally the short form 'path/to/module#ModuleClassName' is used by
|
||||
// the user, and this is a helper class to extract information from it.
|
||||
export class RouteDef {
|
||||
private constructor(public readonly path: string, public readonly className: string|null = null) {
|
||||
}
|
||||
|
||||
toString() {
|
||||
return (this.className === null || this.className == 'default') ?
|
||||
this.path :
|
||||
`${this.path}#${this.className}`;
|
||||
}
|
||||
|
||||
static fromString(entry: string): RouteDef {
|
||||
const split = entry.split('#');
|
||||
return new RouteDef(split[0], split[1] || null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function listLazyRoutesOfModule(
|
||||
entryModule: string, host: AotCompilerHost, reflector: StaticReflector): LazyRouteMap {
|
||||
const entryRouteDef = RouteDef.fromString(entryModule);
|
||||
const containingFile = _resolveModule(entryRouteDef.path, entryRouteDef.path, host);
|
||||
const modulePath = `./${containingFile.replace(/^(.*)\//, '')}`;
|
||||
const className = entryRouteDef.className !;
|
||||
|
||||
// List loadChildren of this single module.
|
||||
const appStaticSymbol = reflector.findDeclaration(modulePath, className, containingFile);
|
||||
const ROUTES = reflector.findDeclaration(ROUTER_MODULE_PATH, ROUTER_ROUTES_SYMBOL_NAME);
|
||||
const lazyRoutes: LazyRoute[] =
|
||||
_extractLazyRoutesFromStaticModule(appStaticSymbol, reflector, host, ROUTES);
|
||||
|
||||
const allLazyRoutes = lazyRoutes.reduce(
|
||||
function includeLazyRouteAndSubRoutes(allRoutes: LazyRouteMap, lazyRoute: LazyRoute):
|
||||
LazyRouteMap {
|
||||
const route: string = lazyRoute.routeDef.toString();
|
||||
_assertRoute(allRoutes, lazyRoute);
|
||||
allRoutes[route] = lazyRoute;
|
||||
|
||||
// StaticReflector does not support discovering annotations like `NgModule` on default
|
||||
// exports
|
||||
// Which means: if a default export NgModule was lazy-loaded, we can discover it, but,
|
||||
// we cannot parse its routes to see if they have loadChildren or not.
|
||||
if (!lazyRoute.routeDef.className) {
|
||||
return allRoutes;
|
||||
}
|
||||
|
||||
const lazyModuleSymbol = reflector.findDeclaration(
|
||||
lazyRoute.absoluteFilePath, lazyRoute.routeDef.className || 'default');
|
||||
|
||||
const subRoutes =
|
||||
_extractLazyRoutesFromStaticModule(lazyModuleSymbol, reflector, host, ROUTES);
|
||||
|
||||
return subRoutes.reduce(includeLazyRouteAndSubRoutes, allRoutes);
|
||||
},
|
||||
{});
|
||||
|
||||
return allLazyRoutes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Try to resolve a module, and returns its absolute path.
|
||||
* @private
|
||||
*/
|
||||
function _resolveModule(modulePath: string, containingFile: string, host: AotCompilerHost) {
|
||||
const result = host.moduleNameToFileName(modulePath, containingFile);
|
||||
if (!result) {
|
||||
throw new Error(`Could not resolve "${modulePath}" from "${containingFile}".`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Throw an exception if a route is in a route map, but does not point to the same module.
|
||||
*/
|
||||
function _assertRoute(map: LazyRouteMap, route: LazyRoute) {
|
||||
const r = route.routeDef.toString();
|
||||
if (map[r] && map[r].absoluteFilePath != route.absoluteFilePath) {
|
||||
throw new Error(
|
||||
`Duplicated path in loadChildren detected: "${r}" is used in 2 loadChildren, ` +
|
||||
`but they point to different modules "(${map[r].absoluteFilePath} and ` +
|
||||
`"${route.absoluteFilePath}"). Webpack cannot distinguish on context and would fail to ` +
|
||||
'load the proper one.');
|
||||
}
|
||||
}
|
||||
|
||||
export function flatten<T>(list: Array<T|T[]>): T[] {
|
||||
return list.reduce((flat: any[], item: T | T[]): T[] => {
|
||||
const flatItem = Array.isArray(item) ? flatten(item) : item;
|
||||
return (<T[]>flat).concat(flatItem);
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all the LazyRoutes from a module. This extracts all `loadChildren` keys from this
|
||||
* module and all statically referred modules.
|
||||
* @private
|
||||
*/
|
||||
function _extractLazyRoutesFromStaticModule(
|
||||
staticSymbol: StaticSymbol, reflector: StaticReflector, host: AotCompilerHost,
|
||||
ROUTES: StaticSymbol): LazyRoute[] {
|
||||
const moduleMetadata = _getNgModuleMetadata(staticSymbol, reflector);
|
||||
const imports = flatten(moduleMetadata.imports || []);
|
||||
const allRoutes: any = imports.filter(i => 'providers' in i).reduce((mem: Route[], m: any) => {
|
||||
return mem.concat(_collectRoutes(m.providers || [], reflector, ROUTES));
|
||||
}, _collectRoutes(moduleMetadata.providers || [], reflector, ROUTES));
|
||||
|
||||
const lazyRoutes: LazyRoute[] =
|
||||
_collectLoadChildren(allRoutes).reduce((acc: LazyRoute[], route: string) => {
|
||||
const routeDef = RouteDef.fromString(route);
|
||||
const absoluteFilePath = _resolveModule(routeDef.path, staticSymbol.filePath, host);
|
||||
acc.push({routeDef, absoluteFilePath});
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const importedSymbols =
|
||||
(imports as any[])
|
||||
.filter(i => i instanceof StaticSymbol || i.ngModule instanceof StaticSymbol)
|
||||
.map(i => {
|
||||
if (i instanceof StaticSymbol) return i;
|
||||
return i.ngModule;
|
||||
}) as StaticSymbol[];
|
||||
|
||||
return importedSymbols
|
||||
.reduce(
|
||||
(acc: LazyRoute[], i: StaticSymbol) => {
|
||||
return acc.concat(_extractLazyRoutesFromStaticModule(i, reflector, host, ROUTES));
|
||||
},
|
||||
[])
|
||||
.concat(lazyRoutes);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the NgModule Metadata of a symbol.
|
||||
*/
|
||||
function _getNgModuleMetadata(
|
||||
staticSymbol: StaticSymbol, reflector: StaticReflector): core.NgModule {
|
||||
const ngModules =
|
||||
reflector.annotations(staticSymbol).filter((s: any) => core.createNgModule.isTypeOf(s));
|
||||
if (ngModules.length === 0) {
|
||||
throw new Error(`${staticSymbol.name} is not an NgModule`);
|
||||
}
|
||||
return ngModules[0];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the routes from the provider list.
|
||||
* @private
|
||||
*/
|
||||
function _collectRoutes(
|
||||
providers: any[], reflector: StaticReflector, ROUTES: StaticSymbol): Route[] {
|
||||
return providers.reduce((routeList: Route[], p: any) => {
|
||||
if (p.provide === ROUTES) {
|
||||
return routeList.concat(p.useValue);
|
||||
} else if (Array.isArray(p)) {
|
||||
return routeList.concat(_collectRoutes(p, reflector, ROUTES));
|
||||
} else {
|
||||
return routeList;
|
||||
}
|
||||
}, []);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the loadChildren values of a list of Route.
|
||||
*/
|
||||
function _collectLoadChildren(routes: Route[]): string[] {
|
||||
return routes.reduce((m, r) => {
|
||||
if (r.loadChildren && typeof r.loadChildren === 'string') {
|
||||
return m.concat(r.loadChildren);
|
||||
} else if (Array.isArray(r)) {
|
||||
return m.concat(_collectLoadChildren(r));
|
||||
} else if (r.children) {
|
||||
return m.concat(_collectLoadChildren(r.children));
|
||||
} else {
|
||||
return m;
|
||||
}
|
||||
}, []);
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
/**
|
||||
* @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 {StaticSymbol} from '@angular/compiler';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {CompilerHost, CompilerHostContext} from './compiler_host';
|
||||
import {ModuleMetadata} from './metadata/index';
|
||||
import {CompilerOptions} from './transformers/api';
|
||||
|
||||
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||
const DTS = /\.d\.ts$/;
|
||||
|
||||
/**
|
||||
* This version of the AotCompilerHost expects that the program will be compiled
|
||||
* and executed with a "path mapped" directory structure, where generated files
|
||||
* are in a parallel tree with the sources, and imported using a `./` relative
|
||||
* import. This requires using TS `rootDirs` option and also teaching the module
|
||||
* loader what to do.
|
||||
*/
|
||||
export class PathMappedCompilerHost extends CompilerHost {
|
||||
constructor(program: ts.Program, options: CompilerOptions, context: CompilerHostContext) {
|
||||
super(program, options, context);
|
||||
}
|
||||
|
||||
getCanonicalFileName(fileName: string): string {
|
||||
if (!fileName) return fileName;
|
||||
// NB: the rootDirs should have been sorted longest-first
|
||||
for (const dir of this.options.rootDirs || []) {
|
||||
if (fileName.indexOf(dir) === 0) {
|
||||
fileName = fileName.substring(dir.length);
|
||||
}
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
moduleNameToFileName(m: string, containingFile: string): string|null {
|
||||
if (!containingFile || !containingFile.length) {
|
||||
if (m.indexOf('.') === 0) {
|
||||
throw new Error('Resolution of relative paths requires a containing file.');
|
||||
}
|
||||
// Any containing file gives the same result for absolute imports
|
||||
containingFile = this.getCanonicalFileName(path.join(this.basePath, 'index.ts'));
|
||||
}
|
||||
for (const root of this.options.rootDirs || ['']) {
|
||||
const rootedContainingFile = path.join(root, containingFile);
|
||||
const resolved =
|
||||
ts.resolveModuleName(m, rootedContainingFile, this.options, this.context).resolvedModule;
|
||||
if (resolved) {
|
||||
if (this.options.traceResolution) {
|
||||
console.error('resolve', m, containingFile, '=>', resolved.resolvedFileName);
|
||||
}
|
||||
return this.getCanonicalFileName(resolved.resolvedFileName);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* We want a moduleId that will appear in import statements in the generated code.
|
||||
* These need to be in a form that system.js can load, so absolute file paths don't work.
|
||||
* Relativize the paths by checking candidate prefixes of the absolute path, to see if
|
||||
* they are resolvable by the moduleResolution strategy from the CompilerHost.
|
||||
*/
|
||||
fileNameToModuleName(importedFile: string, containingFile: string): string {
|
||||
if (this.options.traceResolution) {
|
||||
console.error(
|
||||
'getImportPath from containingFile', containingFile, 'to importedFile', importedFile);
|
||||
}
|
||||
|
||||
// If a file does not yet exist (because we compile it later), we still need to
|
||||
// assume it exists so that the `resolve` method works!
|
||||
if (!this.context.fileExists(importedFile)) {
|
||||
if (this.options.rootDirs && this.options.rootDirs.length > 0) {
|
||||
this.context.assumeFileExists(path.join(this.options.rootDirs[0], importedFile));
|
||||
} else {
|
||||
this.context.assumeFileExists(importedFile);
|
||||
}
|
||||
}
|
||||
|
||||
const resolvable = (candidate: string) => {
|
||||
const resolved = this.moduleNameToFileName(candidate, importedFile);
|
||||
return resolved && resolved.replace(EXT, '') === importedFile.replace(EXT, '');
|
||||
};
|
||||
|
||||
const importModuleName = importedFile.replace(EXT, '');
|
||||
const parts = importModuleName.split(path.sep).filter(p => !!p);
|
||||
let foundRelativeImport: string = undefined !;
|
||||
for (let index = parts.length - 1; index >= 0; index--) {
|
||||
let candidate = parts.slice(index, parts.length).join(path.sep);
|
||||
if (resolvable(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
candidate = '.' + path.sep + candidate;
|
||||
if (resolvable(candidate)) {
|
||||
foundRelativeImport = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundRelativeImport) return foundRelativeImport;
|
||||
|
||||
// Try a relative import
|
||||
const candidate = path.relative(path.dirname(containingFile), importModuleName);
|
||||
if (resolvable(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Unable to find any resolvable import for ${importedFile} relative to ${containingFile}`);
|
||||
}
|
||||
|
||||
getMetadataFor(filePath: string): ModuleMetadata[] {
|
||||
for (const root of this.options.rootDirs || []) {
|
||||
const rootedPath = path.join(root, filePath);
|
||||
if (!this.context.fileExists(rootedPath)) {
|
||||
// If the file doesn't exists then we cannot return metadata for the file.
|
||||
// This will occur if the user refernced a declared module for which no file
|
||||
// exists for the module (i.e. jQuery or angularjs).
|
||||
continue;
|
||||
}
|
||||
if (DTS.test(rootedPath)) {
|
||||
const metadatas = this.readMetadata(rootedPath);
|
||||
if (metadatas) {
|
||||
return metadatas;
|
||||
}
|
||||
} else {
|
||||
// Attention: don't cache this, so that e.g. the LanguageService
|
||||
// can read in changes from source files in the metadata!
|
||||
const metadata = this.getMetadataForSourceFile(rootedPath);
|
||||
return metadata ? [metadata] : [];
|
||||
}
|
||||
}
|
||||
return null !;
|
||||
}
|
||||
}
|
||||
@@ -229,6 +229,12 @@ export interface LibrarySummary {
|
||||
sourceFile?: ts.SourceFile;
|
||||
}
|
||||
|
||||
export interface LazyRoute {
|
||||
route: string;
|
||||
module: {name: string, filePath: string};
|
||||
referencedModule: {name: string, filePath: string};
|
||||
}
|
||||
|
||||
export interface Program {
|
||||
/**
|
||||
* Retrieve the TypeScript program used to produce semantic diagnostics and emit the sources.
|
||||
@@ -293,6 +299,14 @@ export interface Program {
|
||||
*/
|
||||
loadNgStructureAsync(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Returns the lazy routes in the program.
|
||||
* @param entryRoute A reference to an NgModule like `someModule#name`. If given,
|
||||
* will recursively analyze routes starting from this symbol only.
|
||||
* Otherwise will list all routes for all NgModules in the program/
|
||||
*/
|
||||
listLazyRoutes(entryRoute?: string): LazyRoute[];
|
||||
|
||||
/**
|
||||
* Emit the files requested by emitFlags implied by the program.
|
||||
*
|
||||
|
||||
@@ -6,19 +6,18 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {EmitterVisitorContext, ExternalReference, GeneratedFile, ParseSourceSpan, TypeScriptEmitter, collectExternalReferences, syntaxError} from '@angular/compiler';
|
||||
import {AotCompilerHost, EmitterVisitorContext, ExternalReference, GeneratedFile, ParseSourceSpan, TypeScriptEmitter, collectExternalReferences, syntaxError} from '@angular/compiler';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {BaseAotCompilerHost} from '../compiler_host';
|
||||
import {TypeCheckHost} from '../diagnostics/translate_diagnostics';
|
||||
import {ModuleMetadata} from '../metadata/index';
|
||||
import {METADATA_VERSION, ModuleMetadata} from '../metadata/index';
|
||||
|
||||
import {CompilerHost, CompilerOptions, LibrarySummary} from './api';
|
||||
import {GENERATED_FILES} from './util';
|
||||
import {MetadataReaderHost, createMetadataReaderCache, readMetadata} from './metadata_reader';
|
||||
import {DTS, GENERATED_FILES} from './util';
|
||||
|
||||
const NODE_MODULES_PACKAGE_NAME = /node_modules\/((\w|-)+|(@(\w|-)+\/(\w|-)+))/;
|
||||
const DTS = /\.d\.ts$/;
|
||||
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||
|
||||
export function createCompilerHost(
|
||||
@@ -48,9 +47,12 @@ export interface CodeGenerator {
|
||||
* - AotCompilerHost for @angular/compiler
|
||||
* - TypeCheckHost for mapping ts errors to ng errors (via translateDiagnostics)
|
||||
*/
|
||||
export class TsCompilerAotCompilerTypeCheckHostAdapter extends
|
||||
BaseAotCompilerHost<CompilerHost> implements ts.CompilerHost,
|
||||
export class TsCompilerAotCompilerTypeCheckHostAdapter implements ts.CompilerHost, AotCompilerHost,
|
||||
TypeCheckHost {
|
||||
private metadataReaderCache = createMetadataReaderCache();
|
||||
private flatModuleIndexCache = new Map<string, boolean>();
|
||||
private flatModuleIndexNames = new Set<string>();
|
||||
private flatModuleIndexRedirectNames = new Set<string>();
|
||||
private rootDirs: string[];
|
||||
private moduleResolutionCache: ts.ModuleResolutionCache;
|
||||
private originalSourceFiles = new Map<string, ts.SourceFile|undefined>();
|
||||
@@ -58,6 +60,8 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
|
||||
private generatedSourceFiles = new Map<string, GenSourceFile>();
|
||||
private generatedCodeFor = new Map<string, string[]>();
|
||||
private emitter = new TypeScriptEmitter();
|
||||
private metadataReaderHost: MetadataReaderHost;
|
||||
|
||||
getCancellationToken: () => ts.CancellationToken;
|
||||
getDefaultLibLocation: () => string;
|
||||
trace: (s: string) => void;
|
||||
@@ -65,10 +69,9 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
|
||||
directoryExists?: (directoryName: string) => boolean;
|
||||
|
||||
constructor(
|
||||
private rootFiles: string[], options: CompilerOptions, context: CompilerHost,
|
||||
private rootFiles: string[], private options: CompilerOptions, private context: CompilerHost,
|
||||
private metadataProvider: MetadataProvider, private codeGenerator: CodeGenerator,
|
||||
private librarySummaries = new Map<string, LibrarySummary>()) {
|
||||
super(options, context);
|
||||
this.moduleResolutionCache = ts.createModuleResolutionCache(
|
||||
this.context.getCurrentDirectory !(), this.context.getCanonicalFileName.bind(this.context));
|
||||
const basePath = this.options.basePath !;
|
||||
@@ -103,6 +106,15 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
|
||||
if (context.fromSummaryFileName) {
|
||||
this.fromSummaryFileName = context.fromSummaryFileName.bind(context);
|
||||
}
|
||||
this.metadataReaderHost = {
|
||||
cacheMetadata: () => true,
|
||||
getSourceFileMetadata: (filePath) => {
|
||||
const sf = this.getOriginalSourceFile(filePath);
|
||||
return sf ? this.metadataProvider.getMetadata(sf) : undefined;
|
||||
},
|
||||
fileExists: (filePath) => this.originalFileExists(filePath),
|
||||
readFile: (filePath) => this.context.readFile(filePath),
|
||||
};
|
||||
}
|
||||
|
||||
private resolveModuleName(moduleName: string, containingFile: string): ts.ResolvedModule
|
||||
@@ -251,14 +263,6 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
|
||||
return sf;
|
||||
}
|
||||
|
||||
getMetadataForSourceFile(filePath: string): ModuleMetadata|undefined {
|
||||
const sf = this.getOriginalSourceFile(filePath);
|
||||
if (!sf) {
|
||||
return undefined;
|
||||
}
|
||||
return this.metadataProvider.getMetadata(sf);
|
||||
}
|
||||
|
||||
updateGeneratedFile(genFile: GeneratedFile): ts.SourceFile {
|
||||
if (!genFile.stmts) {
|
||||
throw new Error(
|
||||
@@ -415,7 +419,10 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
|
||||
if (summary) {
|
||||
return summary.text;
|
||||
}
|
||||
return super.loadSummary(filePath);
|
||||
if (this.originalFileExists(filePath)) {
|
||||
return this.context.readFile(filePath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
isSourceFile(filePath: string): boolean {
|
||||
@@ -424,7 +431,21 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
|
||||
if (this.librarySummaries.has(filePath)) {
|
||||
return false;
|
||||
}
|
||||
return super.isSourceFile(filePath);
|
||||
if (GENERATED_FILES.test(filePath)) {
|
||||
return false;
|
||||
}
|
||||
if (this.options.generateCodeForLibraries === false && DTS.test(filePath)) {
|
||||
return false;
|
||||
}
|
||||
if (DTS.test(filePath)) {
|
||||
// Check for a bundle index.
|
||||
if (this.hasBundleIndex(filePath)) {
|
||||
const normalFilePath = path.normalize(filePath);
|
||||
return this.flatModuleIndexNames.has(normalFilePath) ||
|
||||
this.flatModuleIndexRedirectNames.has(normalFilePath);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
readFile(fileName: string) {
|
||||
@@ -434,6 +455,76 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
|
||||
}
|
||||
return this.context.readFile(fileName);
|
||||
}
|
||||
|
||||
getMetadataFor(filePath: string): ModuleMetadata[]|undefined {
|
||||
return readMetadata(filePath, this.metadataReaderHost, this.metadataReaderCache);
|
||||
}
|
||||
|
||||
loadResource(filePath: string): Promise<string>|string {
|
||||
if (this.context.readResource) return this.context.readResource(filePath);
|
||||
if (!this.originalFileExists(filePath)) {
|
||||
throw syntaxError(`Error: Resource file not found: ${filePath}`);
|
||||
}
|
||||
return this.context.readFile(filePath);
|
||||
}
|
||||
|
||||
private hasBundleIndex(filePath: string): boolean {
|
||||
const checkBundleIndex = (directory: string): boolean => {
|
||||
let result = this.flatModuleIndexCache.get(directory);
|
||||
if (result == null) {
|
||||
if (path.basename(directory) == 'node_module') {
|
||||
// Don't look outside the node_modules this package is installed in.
|
||||
result = false;
|
||||
} else {
|
||||
// A bundle index exists if the typings .d.ts file has a metadata.json that has an
|
||||
// importAs.
|
||||
try {
|
||||
const packageFile = path.join(directory, 'package.json');
|
||||
if (this.originalFileExists(packageFile)) {
|
||||
// Once we see a package.json file, assume false until it we find the bundle index.
|
||||
result = false;
|
||||
const packageContent: any = JSON.parse(this.context.readFile(packageFile));
|
||||
if (packageContent.typings) {
|
||||
const typings = path.normalize(path.join(directory, packageContent.typings));
|
||||
if (DTS.test(typings)) {
|
||||
const metadataFile = typings.replace(DTS, '.metadata.json');
|
||||
if (this.originalFileExists(metadataFile)) {
|
||||
const metadata = JSON.parse(this.context.readFile(metadataFile));
|
||||
if (metadata.flatModuleIndexRedirect) {
|
||||
this.flatModuleIndexRedirectNames.add(typings);
|
||||
// Note: don't set result = true,
|
||||
// as this would mark this folder
|
||||
// as having a bundleIndex too early without
|
||||
// filling the bundleIndexNames.
|
||||
} else if (metadata.importAs) {
|
||||
this.flatModuleIndexNames.add(typings);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const parent = path.dirname(directory);
|
||||
if (parent != directory) {
|
||||
// Try the parent directory.
|
||||
result = checkBundleIndex(parent);
|
||||
} else {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// If we encounter any errors assume we this isn't a bundle index.
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
this.flatModuleIndexCache.set(directory, result);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
return checkBundleIndex(path.dirname(filePath));
|
||||
}
|
||||
|
||||
getDefaultLibFileName = (options: ts.CompilerOptions) =>
|
||||
this.context.getDefaultLibFileName(options)
|
||||
getCurrentDirectory = () => this.context.getCurrentDirectory();
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* @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 ts from 'typescript';
|
||||
|
||||
import {METADATA_VERSION, ModuleMetadata} from '../metadata';
|
||||
|
||||
import {DTS} from './util';
|
||||
|
||||
export interface MetadataReaderHost {
|
||||
getSourceFileMetadata(filePath: string): ModuleMetadata|undefined;
|
||||
cacheMetadata?(fileName: string): boolean;
|
||||
fileExists(filePath: string): boolean;
|
||||
readFile(filePath: string): string;
|
||||
}
|
||||
|
||||
export interface MetadataReaderCache {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
data: Map<string, ModuleMetadata[]|undefined>;
|
||||
}
|
||||
|
||||
export function createMetadataReaderCache(): MetadataReaderCache {
|
||||
const data = new Map<string, ModuleMetadata[]|undefined>();
|
||||
return {data};
|
||||
}
|
||||
|
||||
export function readMetadata(
|
||||
filePath: string, host: MetadataReaderHost, cache?: MetadataReaderCache): ModuleMetadata[]|
|
||||
undefined {
|
||||
let metadatas = cache && cache.data.get(filePath);
|
||||
if (metadatas) {
|
||||
return metadatas;
|
||||
}
|
||||
if (host.fileExists(filePath)) {
|
||||
// If the file doesn't exists then we cannot return metadata for the file.
|
||||
// This will occur if the user referenced a declared module for which no file
|
||||
// exists for the module (i.e. jQuery or angularjs).
|
||||
if (DTS.test(filePath)) {
|
||||
metadatas = readMetadataFile(host, filePath);
|
||||
if (!metadatas) {
|
||||
// If there is a .d.ts file but no metadata file we need to produce a
|
||||
// metadata from the .d.ts file as metadata files capture reexports
|
||||
// (starting with v3).
|
||||
metadatas = [upgradeMetadataWithDtsData(
|
||||
host, {'__symbolic': 'module', 'version': 1, 'metadata': {}}, filePath)];
|
||||
}
|
||||
} else {
|
||||
const metadata = host.getSourceFileMetadata(filePath);
|
||||
metadatas = metadata ? [metadata] : [];
|
||||
}
|
||||
}
|
||||
if (cache && (!host.cacheMetadata || host.cacheMetadata(filePath))) {
|
||||
cache.data.set(filePath, metadatas);
|
||||
}
|
||||
return metadatas;
|
||||
}
|
||||
|
||||
|
||||
function readMetadataFile(host: MetadataReaderHost, dtsFilePath: string): ModuleMetadata[]|
|
||||
undefined {
|
||||
const metadataPath = dtsFilePath.replace(DTS, '.metadata.json');
|
||||
if (!host.fileExists(metadataPath)) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
const metadataOrMetadatas = JSON.parse(host.readFile(metadataPath));
|
||||
const metadatas: ModuleMetadata[] = metadataOrMetadatas ?
|
||||
(Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) :
|
||||
[];
|
||||
if (metadatas.length) {
|
||||
let maxMetadata = metadatas.reduce((p, c) => p.version > c.version ? p : c);
|
||||
if (maxMetadata.version < METADATA_VERSION) {
|
||||
metadatas.push(upgradeMetadataWithDtsData(host, maxMetadata, dtsFilePath));
|
||||
}
|
||||
}
|
||||
return metadatas;
|
||||
} catch (e) {
|
||||
console.error(`Failed to read JSON file ${metadataPath}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
function upgradeMetadataWithDtsData(
|
||||
host: MetadataReaderHost, oldMetadata: ModuleMetadata, dtsFilePath: string): ModuleMetadata {
|
||||
// patch v1 to v3 by adding exports and the `extends` clause.
|
||||
// patch v3 to v4 by adding `interface` symbols for TypeAlias
|
||||
let newMetadata: ModuleMetadata = {
|
||||
'__symbolic': 'module',
|
||||
'version': METADATA_VERSION,
|
||||
'metadata': {...oldMetadata.metadata},
|
||||
};
|
||||
if (oldMetadata.exports) {
|
||||
newMetadata.exports = oldMetadata.exports;
|
||||
}
|
||||
if (oldMetadata.importAs) {
|
||||
newMetadata.importAs = oldMetadata.importAs;
|
||||
}
|
||||
if (oldMetadata.origins) {
|
||||
newMetadata.origins = oldMetadata.origins;
|
||||
}
|
||||
const dtsMetadata = host.getSourceFileMetadata(dtsFilePath);
|
||||
if (dtsMetadata) {
|
||||
for (let prop in dtsMetadata.metadata) {
|
||||
if (!newMetadata.metadata[prop]) {
|
||||
newMetadata.metadata[prop] = dtsMetadata.metadata[prop];
|
||||
}
|
||||
}
|
||||
|
||||
// Only copy exports from exports from metadata prior to version 3.
|
||||
// Starting with version 3 the collector began collecting exports and
|
||||
// this should be redundant. Also, with bundler will rewrite the exports
|
||||
// which will hoist the exports from modules referenced indirectly causing
|
||||
// the imports to be different than the .d.ts files and using the .d.ts file
|
||||
// exports would cause the StaticSymbolResolver to redirect symbols to the
|
||||
// incorrect location.
|
||||
if ((!oldMetadata.version || oldMetadata.version < 3) && dtsMetadata.exports) {
|
||||
newMetadata.exports = dtsMetadata.exports;
|
||||
}
|
||||
}
|
||||
return newMetadata;
|
||||
}
|
||||
@@ -14,12 +14,13 @@ import * as ts from 'typescript';
|
||||
import {TypeCheckHost, translateDiagnostics} from '../diagnostics/translate_diagnostics';
|
||||
import {ModuleMetadata, createBundleIndexHost} from '../metadata/index';
|
||||
|
||||
import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, EmitFlags, LibrarySummary, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api';
|
||||
import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, EmitFlags, LazyRoute, LibrarySummary, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api';
|
||||
import {CodeGenerator, TsCompilerAotCompilerTypeCheckHostAdapter, getOriginalReferences} from './compiler_host';
|
||||
import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions';
|
||||
import {getAngularEmitterTransformFactory} from './node_emitter_transform';
|
||||
import {GENERATED_FILES, StructureIsReused, createMessageDiagnostic, tsStructureIsReused} from './util';
|
||||
|
||||
|
||||
/**
|
||||
* Maximum number of files that are emitable via calling ts.Program.emit
|
||||
* passing individual targetSourceFiles.
|
||||
@@ -50,8 +51,8 @@ class AngularCompilerProgram implements Program {
|
||||
private emittedSourceFiles: ts.SourceFile[]|undefined;
|
||||
|
||||
// Lazily initialized fields
|
||||
private _typeCheckHost: TypeCheckHost;
|
||||
private _compiler: AotCompiler;
|
||||
private _hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter;
|
||||
private _tsProgram: ts.Program;
|
||||
private _analyzedModules: NgAnalyzedModules|undefined;
|
||||
private _structuralDiagnostics: Diagnostic[]|undefined;
|
||||
@@ -167,7 +168,7 @@ class AngularCompilerProgram implements Program {
|
||||
diags.push(...this.tsProgram.getSemanticDiagnostics(sf, cancellationToken));
|
||||
}
|
||||
});
|
||||
const {ng} = translateDiagnostics(this.typeCheckHost, diags);
|
||||
const {ng} = translateDiagnostics(this.hostAdapter, diags);
|
||||
return ng;
|
||||
}
|
||||
|
||||
@@ -175,18 +176,23 @@ class AngularCompilerProgram implements Program {
|
||||
if (this._analyzedModules) {
|
||||
throw new Error('Angular structure already loaded');
|
||||
}
|
||||
const {tmpProgram, sourceFiles, hostAdapter, rootNames} = this._createProgramWithBasicStubs();
|
||||
return this._compiler.loadFilesAsync(sourceFiles)
|
||||
const {tmpProgram, sourceFiles, rootNames} = this._createProgramWithBasicStubs();
|
||||
return this.compiler.loadFilesAsync(sourceFiles)
|
||||
.catch(this.catchAnalysisError.bind(this))
|
||||
.then(analyzedModules => {
|
||||
if (this._analyzedModules) {
|
||||
throw new Error('Angular structure loaded both synchronously and asynchronsly');
|
||||
}
|
||||
this._updateProgramWithTypeCheckStubs(
|
||||
tmpProgram, analyzedModules, hostAdapter, rootNames);
|
||||
this._updateProgramWithTypeCheckStubs(tmpProgram, analyzedModules, rootNames);
|
||||
});
|
||||
}
|
||||
|
||||
listLazyRoutes(route?: string): LazyRoute[] {
|
||||
// Note: Don't analyzedModules if a route is given
|
||||
// to be fast enough.
|
||||
return this.compiler.listLazyRoutes(route, route ? undefined : this.analyzedModules);
|
||||
}
|
||||
|
||||
emit(
|
||||
{emitFlags = EmitFlags.Default, cancellationToken, customTransformers,
|
||||
emitCallback = defaultEmitCallback}: {
|
||||
@@ -341,11 +347,18 @@ class AngularCompilerProgram implements Program {
|
||||
// Private members
|
||||
private get compiler(): AotCompiler {
|
||||
if (!this._compiler) {
|
||||
this.initSync();
|
||||
this._createCompiler();
|
||||
}
|
||||
return this._compiler !;
|
||||
}
|
||||
|
||||
private get hostAdapter(): TsCompilerAotCompilerTypeCheckHostAdapter {
|
||||
if (!this._hostAdapter) {
|
||||
this._createCompiler();
|
||||
}
|
||||
return this._hostAdapter !;
|
||||
}
|
||||
|
||||
private get analyzedModules(): NgAnalyzedModules {
|
||||
if (!this._analyzedModules) {
|
||||
this.initSync();
|
||||
@@ -367,13 +380,6 @@ class AngularCompilerProgram implements Program {
|
||||
return this._tsProgram !;
|
||||
}
|
||||
|
||||
private get typeCheckHost(): TypeCheckHost {
|
||||
if (!this._typeCheckHost) {
|
||||
this.initSync();
|
||||
}
|
||||
return this._typeCheckHost !;
|
||||
}
|
||||
|
||||
private calculateTransforms(
|
||||
genFiles: Map<string, GeneratedFile>,
|
||||
customTransformers?: CustomTransformers): ts.CustomTransformers {
|
||||
@@ -393,19 +399,41 @@ class AngularCompilerProgram implements Program {
|
||||
if (this._analyzedModules) {
|
||||
return;
|
||||
}
|
||||
const {tmpProgram, sourceFiles, hostAdapter, rootNames} = this._createProgramWithBasicStubs();
|
||||
const {tmpProgram, sourceFiles, rootNames} = this._createProgramWithBasicStubs();
|
||||
let analyzedModules: NgAnalyzedModules|null;
|
||||
try {
|
||||
analyzedModules = this._compiler.loadFilesSync(sourceFiles);
|
||||
analyzedModules = this.compiler.loadFilesSync(sourceFiles);
|
||||
} catch (e) {
|
||||
analyzedModules = this.catchAnalysisError(e);
|
||||
}
|
||||
this._updateProgramWithTypeCheckStubs(tmpProgram, analyzedModules, hostAdapter, rootNames);
|
||||
this._updateProgramWithTypeCheckStubs(tmpProgram, analyzedModules, rootNames);
|
||||
}
|
||||
|
||||
private _createCompiler() {
|
||||
const codegen: CodeGenerator = {
|
||||
generateFile: (genFileName, baseFileName) =>
|
||||
this._compiler.emitBasicStub(genFileName, baseFileName),
|
||||
findGeneratedFileNames: (fileName) => this._compiler.findGeneratedFileNames(fileName),
|
||||
};
|
||||
|
||||
this._hostAdapter = new TsCompilerAotCompilerTypeCheckHostAdapter(
|
||||
this.rootNames, this.options, this.host, this.metadataCache, codegen,
|
||||
this.oldProgramLibrarySummaries);
|
||||
const aotOptions = getAotCompilerOptions(this.options);
|
||||
this._structuralDiagnostics = [];
|
||||
const errorCollector = (err: any) => {
|
||||
this._structuralDiagnostics !.push({
|
||||
messageText: err.toString(),
|
||||
category: ts.DiagnosticCategory.Error,
|
||||
source: SOURCE,
|
||||
code: DEFAULT_ERROR_CODE
|
||||
});
|
||||
};
|
||||
this._compiler = createAotCompiler(this._hostAdapter, aotOptions, errorCollector).compiler;
|
||||
}
|
||||
|
||||
private _createProgramWithBasicStubs(): {
|
||||
tmpProgram: ts.Program,
|
||||
hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter,
|
||||
rootNames: string[],
|
||||
sourceFiles: string[],
|
||||
} {
|
||||
@@ -418,58 +446,56 @@ class AngularCompilerProgram implements Program {
|
||||
|
||||
const codegen: CodeGenerator = {
|
||||
generateFile: (genFileName, baseFileName) =>
|
||||
this._compiler.emitBasicStub(genFileName, baseFileName),
|
||||
findGeneratedFileNames: (fileName) => this._compiler.findGeneratedFileNames(fileName),
|
||||
this.compiler.emitBasicStub(genFileName, baseFileName),
|
||||
findGeneratedFileNames: (fileName) => this.compiler.findGeneratedFileNames(fileName),
|
||||
};
|
||||
|
||||
const hostAdapter = new TsCompilerAotCompilerTypeCheckHostAdapter(
|
||||
this.rootNames, this.options, this.host, this.metadataCache, codegen,
|
||||
this.oldProgramLibrarySummaries);
|
||||
const aotOptions = getAotCompilerOptions(this.options);
|
||||
this._compiler = createAotCompiler(hostAdapter, aotOptions).compiler;
|
||||
this._typeCheckHost = hostAdapter;
|
||||
this._structuralDiagnostics = [];
|
||||
|
||||
let rootNames =
|
||||
this.rootNames.filter(fn => !GENERATED_FILES.test(fn) || !hostAdapter.isSourceFile(fn));
|
||||
let rootNames = this.rootNames;
|
||||
if (this.options.generateCodeForLibraries !== false) {
|
||||
// if we should generateCodeForLibraries, enver include
|
||||
// generated files in the program as otherwise we will
|
||||
// ovewrite them and typescript will report the error
|
||||
// TS5055: Cannot write file ... because it would overwrite input file.
|
||||
rootNames = this.rootNames.filter(fn => !GENERATED_FILES.test(fn));
|
||||
}
|
||||
if (this.options.noResolve) {
|
||||
this.rootNames.forEach(rootName => {
|
||||
if (hostAdapter.shouldGenerateFilesFor(rootName)) {
|
||||
rootNames.push(...this._compiler.findGeneratedFileNames(rootName));
|
||||
if (this.hostAdapter.shouldGenerateFilesFor(rootName)) {
|
||||
rootNames.push(...this.compiler.findGeneratedFileNames(rootName));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const tmpProgram = ts.createProgram(rootNames, this.options, hostAdapter, oldTsProgram);
|
||||
const tmpProgram = ts.createProgram(rootNames, this.options, this.hostAdapter, oldTsProgram);
|
||||
const sourceFiles: string[] = [];
|
||||
tmpProgram.getSourceFiles().forEach(sf => {
|
||||
if (hostAdapter.isSourceFile(sf.fileName)) {
|
||||
if (this.hostAdapter.isSourceFile(sf.fileName)) {
|
||||
sourceFiles.push(sf.fileName);
|
||||
}
|
||||
});
|
||||
return {tmpProgram, sourceFiles, hostAdapter, rootNames};
|
||||
return {tmpProgram, sourceFiles, rootNames};
|
||||
}
|
||||
|
||||
private _updateProgramWithTypeCheckStubs(
|
||||
tmpProgram: ts.Program, analyzedModules: NgAnalyzedModules|null,
|
||||
hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter, rootNames: string[]) {
|
||||
tmpProgram: ts.Program, analyzedModules: NgAnalyzedModules|null, rootNames: string[]) {
|
||||
this._analyzedModules = analyzedModules || emptyModules;
|
||||
if (analyzedModules) {
|
||||
tmpProgram.getSourceFiles().forEach(sf => {
|
||||
if (sf.fileName.endsWith('.ngfactory.ts')) {
|
||||
const {generate, baseFileName} = hostAdapter.shouldGenerateFile(sf.fileName);
|
||||
const {generate, baseFileName} = this.hostAdapter.shouldGenerateFile(sf.fileName);
|
||||
if (generate) {
|
||||
// Note: ! is ok as hostAdapter.shouldGenerateFile will always return a basefileName
|
||||
// for .ngfactory.ts files.
|
||||
const genFile = this._compiler.emitTypeCheckStub(sf.fileName, baseFileName !);
|
||||
const genFile = this.compiler.emitTypeCheckStub(sf.fileName, baseFileName !);
|
||||
if (genFile) {
|
||||
hostAdapter.updateGeneratedFile(genFile);
|
||||
this.hostAdapter.updateGeneratedFile(genFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
this._tsProgram = ts.createProgram(rootNames, this.options, hostAdapter, tmpProgram);
|
||||
this._tsProgram = ts.createProgram(rootNames, this.options, this.hostAdapter, tmpProgram);
|
||||
// Note: the new ts program should be completely reusable by TypeScript as:
|
||||
// - we cache all the files in the hostAdapter
|
||||
// - new new stubs use the exactly same imports/exports as the old once (we assert that in
|
||||
|
||||
@@ -11,6 +11,7 @@ import * as ts from 'typescript';
|
||||
import {DEFAULT_ERROR_CODE, Diagnostic, SOURCE} from './api';
|
||||
|
||||
export const GENERATED_FILES = /(.*?)\.(ngfactory|shim\.ngstyle|ngstyle|ngsummary)\.(js|d\.ts|ts)$/;
|
||||
export const DTS = /\.d\.ts$/;
|
||||
|
||||
export const enum StructureIsReused {Not = 0, SafeModules = 1, Completely = 2}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user