fix(compiler): allow to use flat modules and summaries

The combination of flat modules, flat module redirects and summaries
lead to errors before.
This commit is contained in:
Tobias Bosch 2017-09-28 09:39:16 -07:00 committed by Victor Berchet
parent 14e8e88022
commit ec2be5dccb
8 changed files with 147 additions and 96 deletions

View File

@ -47,23 +47,6 @@ export abstract class BaseAotCompilerHost<C extends BaseAotCompilerHostContext>
abstract getMetadataForSourceFile(filePath: string): ModuleMetadata|undefined; abstract getMetadataForSourceFile(filePath: string): ModuleMetadata|undefined;
protected getImportAs(fileName: string): string|undefined {
// Note: `importAs` can only be in .metadata.json files
// So it is enough to call this.readMetadata, and we get the
// benefit that this is cached.
if (DTS.test(fileName)) {
const metadatas = this.readMetadata(fileName);
if (metadatas) {
for (const metadata of metadatas) {
if (metadata.importAs) {
return metadata.importAs;
}
}
}
}
return undefined;
}
getMetadataFor(filePath: string): ModuleMetadata[]|undefined { getMetadataFor(filePath: string): ModuleMetadata[]|undefined {
if (!this.context.fileExists(filePath)) { if (!this.context.fileExists(filePath)) {
// If the file doesn't exists then we cannot return metadata for the file. // If the file doesn't exists then we cannot return metadata for the file.
@ -384,11 +367,6 @@ export class CompilerHost extends BaseAotCompilerHost<CompilerHostContext> {
* NOTE: (*) the relative path is computed depending on `isGenDirChildOfRootDir`. * NOTE: (*) the relative path is computed depending on `isGenDirChildOfRootDir`.
*/ */
fileNameToModuleName(importedFile: string, containingFile: string): string { fileNameToModuleName(importedFile: string, containingFile: string): string {
const importAs = this.getImportAs(importedFile);
if (importAs) {
return importAs;
}
// If a file does not yet exist (because we compile it later), we still need to // 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! // assume it exists it so that the `resolve` method works!
if (importedFile !== containingFile && !this.context.fileExists(importedFile)) { if (importedFile !== containingFile && !this.context.fileExists(importedFile)) {

View File

@ -174,10 +174,6 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
'fileNameToModuleName from containingFile', containingFile, 'to importedFile', 'fileNameToModuleName from containingFile', containingFile, 'to importedFile',
importedFile); importedFile);
} }
const importAs = this.getImportAs(importedFile);
if (importAs) {
return importAs;
}
// drop extension // drop extension
importedFile = importedFile.replace(EXT, ''); importedFile = importedFile.replace(EXT, '');

View File

@ -833,60 +833,6 @@ describe('ngc transformer command-line', () => {
shouldExist('index.metadata.json'); shouldExist('index.metadata.json');
}); });
it('should use the importAs for flat libraries instead of deep imports', () => {
// compile the flat module
writeFlatModule('index.js');
expect(main(['-p', basePath], errorSpy)).toBe(0);
// move the flat module output into node_modules
const flatModuleNodeModulesPath = path.resolve(basePath, 'node_modules', 'flat_module');
fs.renameSync(outDir, flatModuleNodeModulesPath);
fs.renameSync(
path.resolve(basePath, 'src/flat.component.html'),
path.resolve(flatModuleNodeModulesPath, 'src/flat.component.html'));
// add a package.json
fs.writeFileSync(
path.resolve(flatModuleNodeModulesPath, 'package.json'), `{"typings": "./index.d.ts"}`);
// and remove the sources.
fs.renameSync(path.resolve(basePath, 'src'), path.resolve(basePath, 'flat_module_src'));
fs.unlinkSync(path.resolve(basePath, 'public-api.ts'));
writeConfig(`
{
"extends": "./tsconfig-base.json",
"files": ["index.ts"]
}
`);
write('index.ts', `
import {NgModule} from '@angular/core';
import {FlatModule} from 'flat_module';
@NgModule({
imports: [FlatModule]
})
export class MyModule {}
`);
expect(main(['-p', basePath], errorSpy)).toBe(0);
shouldExist('index.js');
const summary =
fs.readFileSync(path.resolve(basePath, 'built', 'index.ngsummary.json')).toString();
// reference to the module itself
expect(summary).toMatch(/"filePath":"flat_module"/);
// no reference to a deep file
expect(summary).not.toMatch(/"filePath":"flat_module\//);
const factory =
fs.readFileSync(path.resolve(basePath, 'built', 'index.ngfactory.js')).toString();
// reference to the module itself
expect(factory).toMatch(/from "flat_module"/);
// no reference to a deep file
expect(factory).not.toMatch(/from "flat_module\//);
});
describe('with tree example', () => { describe('with tree example', () => {
beforeEach(() => { beforeEach(() => {
writeConfig(); writeConfig();
@ -1050,6 +996,107 @@ describe('ngc transformer command-line', () => {
outDir = path.resolve(basePath, 'built'); outDir = path.resolve(basePath, 'built');
shouldExist('app/main.js'); shouldExist('app/main.js');
}); });
it('shoud be able to compile libraries with summaries and flat modules', () => {
writeFiles();
compile();
// libraries
// make `shouldExist` / `shouldNotExist` relative to `node_modules`
outDir = path.resolve(basePath, 'node_modules');
shouldExist('flat_module/index.ngfactory.js');
shouldExist('flat_module/index.ngsummary.json');
// app
// make `shouldExist` / `shouldNotExist` relative to `built`
outDir = path.resolve(basePath, 'built');
shouldExist('app/main.ngfactory.js');
const factory = fs.readFileSync(path.resolve(outDir, 'app/main.ngfactory.js')).toString();
// reference to the module itself
expect(factory).toMatch(/from "flat_module"/);
// no reference to a deep file
expect(factory).not.toMatch(/from "flat_module\//);
function writeFiles() {
createFlatModuleInNodeModules();
// Angular + flat module
write('tsconfig-lib.json', `{
"extends": "./tsconfig-base.json",
"angularCompilerOptions": {
"generateCodeForLibraries": true
},
"compilerOptions": {
"outDir": "."
},
"include": ["node_modules/@angular/core/**/*", "node_modules/flat_module/**/*"],
"exclude": [
"node_modules/@angular/core/test/**",
"node_modules/@angular/core/testing/**"
]
}`);
// Application
write('app/tsconfig-app.json', `{
"extends": "../tsconfig-base.json",
"angularCompilerOptions": {
"generateCodeForLibraries": false
},
"compilerOptions": {
"rootDir": ".",
"outDir": "../built/app"
}
}`);
write('app/main.ts', `
import {NgModule} from '@angular/core';
import {FlatModule} from 'flat_module';
@NgModule({
imports: [FlatModule]
})
export class AppModule {}
`);
}
function createFlatModuleInNodeModules() {
// compile the flat module
writeFlatModule('index.js');
expect(main(['-p', basePath], errorSpy)).toBe(0);
// move the flat module output into node_modules
const flatModuleNodeModulesPath = path.resolve(basePath, 'node_modules', 'flat_module');
fs.renameSync(outDir, flatModuleNodeModulesPath);
fs.renameSync(
path.resolve(basePath, 'src/flat.component.html'),
path.resolve(flatModuleNodeModulesPath, 'src/flat.component.html'));
// and remove the sources.
fs.renameSync(path.resolve(basePath, 'src'), path.resolve(basePath, 'flat_module_src'));
fs.unlinkSync(path.resolve(basePath, 'public-api.ts'));
// add a flatModuleIndexRedirect
write('node_modules/flat_module/redirect.metadata.json', `{
"__symbolic": "module",
"version": 3,
"metadata": {},
"exports": [
{
"from": "./index"
}
],
"flatModuleIndexRedirect": true,
"importAs": "flat_module"
}`);
write('node_modules/flat_module/redirect.d.ts', `export * from './index';`);
// add a package.json to use the redirect
write('node_modules/flat_module/package.json', `{"typings": "./redirect.d.ts"}`);
}
function compile() {
expect(main(['-p', path.join(basePath, 'tsconfig-lib.json')], errorSpy)).toBe(0);
expect(main(['-p', path.join(basePath, 'app', 'tsconfig-app.json')], errorSpy)).toBe(0);
}
});
}); });
describe('expression lowering', () => { describe('expression lowering', () => {

View File

@ -164,10 +164,15 @@ export class StaticSymbolResolver {
* Converts a file path to a module name that can be used as an `import`. * Converts a file path to a module name that can be used as an `import`.
*/ */
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string { fileNameToModuleName(importedFilePath: string, containingFilePath: string): string {
return this.knownFileNameToModuleNames.get(importedFilePath) || return this.summaryResolver.getKnownModuleName(importedFilePath) ||
this.knownFileNameToModuleNames.get(importedFilePath) ||
this.host.fileNameToModuleName(importedFilePath, containingFilePath); this.host.fileNameToModuleName(importedFilePath, containingFilePath);
} }
getKnownModuleName(filePath: string): string|null {
return this.knownFileNameToModuleNames.get(filePath) || null;
}
recordImportAs(sourceSymbol: StaticSymbol, targetSymbol: StaticSymbol) { recordImportAs(sourceSymbol: StaticSymbol, targetSymbol: StaticSymbol) {
sourceSymbol.assertNoMembers(); sourceSymbol.assertNoMembers();
targetSymbol.assertNoMembers(); targetSymbol.assertNoMembers();
@ -292,7 +297,11 @@ export class StaticSymbolResolver {
this.resolvedFilePaths.add(filePath); this.resolvedFilePaths.add(filePath);
const resolvedSymbols: ResolvedStaticSymbol[] = []; const resolvedSymbols: ResolvedStaticSymbol[] = [];
const metadata = this.getModuleMetadata(filePath); const metadata = this.getModuleMetadata(filePath);
if (metadata['importAs']) {
// Index bundle indices should use the importAs module name defined
// in the bundle.
this.knownFileNameToModuleNames.set(filePath, metadata['importAs']);
}
// handle the symbols in one of the re-export location // handle the symbols in one of the re-export location
if (metadata['exports']) { if (metadata['exports']) {
for (const moduleExport of metadata['exports']) { for (const moduleExport of metadata['exports']) {

View File

@ -45,6 +45,7 @@ export class AotSummaryResolver implements SummaryResolver<StaticSymbol> {
private loadedFilePaths = new Map<string, boolean>(); private loadedFilePaths = new Map<string, boolean>();
// Note: this will only contain StaticSymbols without members! // Note: this will only contain StaticSymbols without members!
private importAs = new Map<StaticSymbol, StaticSymbol>(); private importAs = new Map<StaticSymbol, StaticSymbol>();
private knownFileNameToModuleNames = new Map<string, string>();
constructor(private host: AotSummaryResolverHost, private staticSymbolCache: StaticSymbolCache) {} constructor(private host: AotSummaryResolverHost, private staticSymbolCache: StaticSymbolCache) {}
@ -85,6 +86,13 @@ export class AotSummaryResolver implements SummaryResolver<StaticSymbol> {
return this.importAs.get(staticSymbol) !; return this.importAs.get(staticSymbol) !;
} }
/**
* Converts a file path to a module name that can be used as an `import`.
*/
getKnownModuleName(importedFilePath: string): string|null {
return this.knownFileNameToModuleNames.get(importedFilePath) || null;
}
addSummary(summary: Summary<StaticSymbol>) { this.summaryCache.set(summary.symbol, summary); } addSummary(summary: Summary<StaticSymbol>) { this.summaryCache.set(summary.symbol, summary); }
private _loadSummaryFile(filePath: string): boolean { private _loadSummaryFile(filePath: string): boolean {
@ -105,9 +113,12 @@ export class AotSummaryResolver implements SummaryResolver<StaticSymbol> {
hasSummary = json != null; hasSummary = json != null;
this.loadedFilePaths.set(filePath, hasSummary); this.loadedFilePaths.set(filePath, hasSummary);
if (json) { if (json) {
const {summaries, importAs} = const {moduleName, summaries, importAs} =
deserializeSummaries(this.staticSymbolCache, this, filePath, json); deserializeSummaries(this.staticSymbolCache, this, filePath, json);
summaries.forEach((summary) => this.summaryCache.set(summary.symbol, summary)); summaries.forEach((summary) => this.summaryCache.set(summary.symbol, summary));
if (moduleName) {
this.knownFileNameToModuleNames.set(filePath, moduleName);
}
importAs.forEach((importAs) => { importAs.forEach((importAs) => {
this.importAs.set( this.importAs.set(
importAs.symbol, importAs.symbol,

View File

@ -21,7 +21,7 @@ export function serializeSummaries(
metadata: CompileNgModuleMetadata | CompileDirectiveMetadata | CompilePipeMetadata | metadata: CompileNgModuleMetadata | CompileDirectiveMetadata | CompilePipeMetadata |
CompileTypeMetadata CompileTypeMetadata
}[]): {json: string, exportAs: {symbol: StaticSymbol, exportAs: string}[]} { }[]): {json: string, exportAs: {symbol: StaticSymbol, exportAs: string}[]} {
const toJsonSerializer = new ToJsonSerializer(symbolResolver, summaryResolver); const toJsonSerializer = new ToJsonSerializer(symbolResolver, summaryResolver, srcFileName);
const forJitSerializer = new ForJitSerializer(forJitCtx, symbolResolver); const forJitSerializer = new ForJitSerializer(forJitCtx, symbolResolver);
// for symbols, we use everything except for the class metadata itself // for symbols, we use everything except for the class metadata itself
@ -43,15 +43,18 @@ export function serializeSummaries(
} }
}); });
const {json, exportAs} = toJsonSerializer.serialize(srcFileName); const {json, exportAs} = toJsonSerializer.serialize();
forJitSerializer.serialize(exportAs); forJitSerializer.serialize(exportAs);
return {json, exportAs}; return {json, exportAs};
} }
export function deserializeSummaries( export function deserializeSummaries(
symbolCache: StaticSymbolCache, summaryResolver: SummaryResolver<StaticSymbol>, symbolCache: StaticSymbolCache, summaryResolver: SummaryResolver<StaticSymbol>,
libraryFileName: string, json: string): libraryFileName: string, json: string): {
{summaries: Summary<StaticSymbol>[], importAs: {symbol: StaticSymbol, importAs: string}[]} { moduleName: string | null,
summaries: Summary<StaticSymbol>[],
importAs: {symbol: StaticSymbol, importAs: string}[]
} {
const deserializer = new FromJsonDeserializer(symbolCache, summaryResolver); const deserializer = new FromJsonDeserializer(symbolCache, summaryResolver);
return deserializer.deserialize(libraryFileName, json); return deserializer.deserialize(libraryFileName, json);
} }
@ -82,13 +85,15 @@ class ToJsonSerializer extends ValueTransformer {
// StaticSymbols, but otherwise has the same shape as the original objects. // StaticSymbols, but otherwise has the same shape as the original objects.
private processedSummaryBySymbol = new Map<StaticSymbol, any>(); private processedSummaryBySymbol = new Map<StaticSymbol, any>();
private processedSummaries: any[] = []; private processedSummaries: any[] = [];
private moduleName: string|null;
unprocessedSymbolSummariesBySymbol = new Map<StaticSymbol, Summary<StaticSymbol>>(); unprocessedSymbolSummariesBySymbol = new Map<StaticSymbol, Summary<StaticSymbol>>();
constructor( constructor(
private symbolResolver: StaticSymbolResolver, private symbolResolver: StaticSymbolResolver,
private summaryResolver: SummaryResolver<StaticSymbol>) { private summaryResolver: SummaryResolver<StaticSymbol>, private srcFileName: string) {
super(); super();
this.moduleName = symbolResolver.getKnownModuleName(srcFileName);
} }
addSummary(summary: Summary<StaticSymbol>) { addSummary(summary: Summary<StaticSymbol>) {
@ -147,10 +152,10 @@ class ToJsonSerializer extends ValueTransformer {
} }
} }
serialize(srcFileName: string): serialize(): {json: string, exportAs: {symbol: StaticSymbol, exportAs: string}[]} {
{json: string, exportAs: {symbol: StaticSymbol, exportAs: string}[]} {
const exportAs: {symbol: StaticSymbol, exportAs: string}[] = []; const exportAs: {symbol: StaticSymbol, exportAs: string}[] = [];
const json = JSON.stringify({ const json = JSON.stringify({
moduleName: this.moduleName,
summaries: this.processedSummaries, summaries: this.processedSummaries,
symbols: this.symbols.map((symbol, index) => { symbols: this.symbols.map((symbol, index) => {
symbol.assertNoMembers(); symbol.assertNoMembers();
@ -165,7 +170,7 @@ class ToJsonSerializer extends ValueTransformer {
return { return {
__symbol: index, __symbol: index,
name: symbol.name, name: symbol.name,
filePath: this.summaryResolver.toSummaryFileName(symbol.filePath, srcFileName), filePath: this.summaryResolver.toSummaryFileName(symbol.filePath, this.srcFileName),
importAs: importAs importAs: importAs
}; };
}) })
@ -368,9 +373,12 @@ class FromJsonDeserializer extends ValueTransformer {
super(); super();
} }
deserialize(libraryFileName: string, json: string): deserialize(libraryFileName: string, json: string): {
{summaries: Summary<StaticSymbol>[], importAs: {symbol: StaticSymbol, importAs: string}[]} { moduleName: string | null,
const data: {summaries: any[], symbols: any[]} = JSON.parse(json); summaries: Summary<StaticSymbol>[],
importAs: {symbol: StaticSymbol, importAs: string}[]
} {
const data: {moduleName: string | null, summaries: any[], symbols: any[]} = JSON.parse(json);
const importAs: {symbol: StaticSymbol, importAs: string}[] = []; const importAs: {symbol: StaticSymbol, importAs: string}[] = [];
this.symbols = []; this.symbols = [];
data.symbols.forEach((serializedSymbol) => { data.symbols.forEach((serializedSymbol) => {
@ -383,7 +391,7 @@ class FromJsonDeserializer extends ValueTransformer {
} }
}); });
const summaries = visitValue(data.summaries, this, null); const summaries = visitValue(data.summaries, this, null);
return {summaries, importAs}; return {moduleName: data.moduleName, summaries, importAs};
} }
visitStringMap(map: {[key: string]: any}, context: any): any { visitStringMap(map: {[key: string]: any}, context: any): any {

View File

@ -21,6 +21,7 @@ export abstract class SummaryResolver<T> {
abstract resolveSummary(reference: T): Summary<T>|null; abstract resolveSummary(reference: T): Summary<T>|null;
abstract getSymbolsOf(filePath: string): T[]|null; abstract getSymbolsOf(filePath: string): T[]|null;
abstract getImportAs(reference: T): T; abstract getImportAs(reference: T): T;
abstract getKnownModuleName(fileName: string): string|null;
abstract addSummary(summary: Summary<T>): void; abstract addSummary(summary: Summary<T>): void;
} }
@ -35,5 +36,6 @@ export class JitSummaryResolver implements SummaryResolver<Type> {
} }
getSymbolsOf(): Type[] { return []; } getSymbolsOf(): Type[] { return []; }
getImportAs(reference: Type): Type { return reference; } getImportAs(reference: Type): Type { return reference; }
getKnownModuleName(fileName: string) { return null; }
addSummary(summary: Summary<Type>) { this._summaries.set(summary.symbol, summary); } addSummary(summary: Summary<Type>) { this._summaries.set(summary.symbol, summary); }
} }

View File

@ -406,7 +406,7 @@ export class MockSummaryResolver implements SummaryResolver<StaticSymbol> {
const entry = this.importAs.find(entry => entry.symbol === symbol); const entry = this.importAs.find(entry => entry.symbol === symbol);
return entry ? entry.importAs : undefined !; return entry ? entry.importAs : undefined !;
} }
getKnownModuleName(fileName: string): string|null { return null; }
isLibraryFile(filePath: string): boolean { return filePath.endsWith('.d.ts'); } isLibraryFile(filePath: string): boolean { return filePath.endsWith('.d.ts'); }
toSummaryFileName(filePath: string): string { return filePath.replace(/(\.d)?\.ts$/, '.d.ts'); } toSummaryFileName(filePath: string): string { return filePath.replace(/(\.d)?\.ts$/, '.d.ts'); }
fromSummaryFileName(filePath: string): string { return filePath; } fromSummaryFileName(filePath: string): string { return filePath; }