diff --git a/tools/@angular/tsc-wrapped/src/bundler.ts b/tools/@angular/tsc-wrapped/src/bundler.ts index d47fede4fe89b..b599ddf5602f1 100644 --- a/tools/@angular/tsc-wrapped/src/bundler.ts +++ b/tools/@angular/tsc-wrapped/src/bundler.ts @@ -9,7 +9,8 @@ import * as path from 'path'; import * as ts from 'typescript'; import {MetadataCollector} from './collector'; -import {ClassMetadata, ConstructorMetadata, FunctionMetadata, MemberMetadata, MetadataEntry, MetadataError, MetadataImportedSymbolReferenceExpression, MetadataMap, MetadataObject, MetadataSymbolicExpression, MetadataSymbolicReferenceExpression, MetadataValue, MethodMetadata, ModuleMetadata, VERSION, isClassMetadata, isConstructorMetadata, isFunctionMetadata, isInterfaceMetadata, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataImportedSymbolReferenceExpression, isMetadataModuleReferenceExpression, isMetadataSymbolicExpression, isMethodMetadata} from './schema'; +import {ClassMetadata, ConstructorMetadata, FunctionMetadata, MemberMetadata, MetadataEntry, MetadataError, MetadataImportedSymbolReferenceExpression, MetadataMap, MetadataObject, MetadataSymbolicExpression, MetadataSymbolicReferenceExpression, MetadataValue, MethodMetadata, ModuleExportMetadata, ModuleMetadata, VERSION, isClassMetadata, isConstructorMetadata, isFunctionMetadata, isInterfaceMetadata, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataImportedSymbolReferenceExpression, isMetadataModuleReferenceExpression, isMetadataSymbolicExpression, isMethodMetadata} from './schema'; + // The character set used to produce private names. @@ -44,6 +45,10 @@ interface Symbol { // a referenced symbol's value. referenced?: boolean; + // A symbol is marked as a re-export the symbol was rexported from a module that is + // not part of the flat module bundle. + reexport?: boolean; + // Only valid for referenced canonical symbols. Produces by convertSymbols(). value?: MetadataEntry; @@ -98,14 +103,19 @@ export class MetadataBundler { module: s.declaration.module })); const origins = Array.from(this.symbolMap.values()) - .filter(s => s.referenced) + .filter(s => s.referenced && !s.reexport) .reduce<{[name: string]: string}>((p, s) => { p[s.isPrivate ? s.privateName : s.name] = s.declaration.module; return p; }, {}); + const exports = this.getReExports(exportedSymbols); return { - metadata: - {__symbolic: 'module', version: VERSION, metadata, origins, importAs: this.importAs}, + metadata: { + __symbolic: 'module', + version: VERSION, + exports: exports.length ? exports : undefined, metadata, origins, + importAs: this.importAs + }, privates }; } @@ -165,12 +175,19 @@ export class MetadataBundler { for (const exportDeclaration of module.exports) { const exportFrom = resolveModule(exportDeclaration.from, moduleName); // Record all the exports from the module even if we don't use it directly. - this.exportAll(exportFrom); + const exportedSymbols = this.exportAll(exportFrom); if (exportDeclaration.export) { // Re-export all the named exports from a module. for (const exportItem of exportDeclaration.export) { const name = typeof exportItem == 'string' ? exportItem : exportItem.name; const exportAs = typeof exportItem == 'string' ? exportItem : exportItem.as; + const symbol = this.symbolOf(exportFrom, name); + if (exportedSymbols && exportedSymbols.length == 1 && exportedSymbols[0].reexport && + exportedSymbols[0].name == '*') { + // This is a named export from a module we have no metadata about. Record the named + // export as a re-export. + symbol.reexport = true; + } exportSymbol(this.symbolOf(exportFrom, name), exportAs); } } else { @@ -183,6 +200,15 @@ export class MetadataBundler { } } } + + if (!module) { + // If no metadata is found for this import then it is considered external to the + // library and should be recorded as a re-export in the final metadata if it is + // eventually re-exported. + const symbol = this.symbolOf(moduleName, '*'); + symbol.reexport = true; + result.push(symbol); + } this.exports.set(moduleName, result); return result; @@ -207,6 +233,7 @@ export class MetadataBundler { symbol.isPrivate = isPrivate; symbol.declaration = declaration; symbol.canonicalSymbol = canonicalSymbol; + symbol.reexport = declaration.reexport; } private getEntries(exportedSymbols: Symbol[]): BundleEntries { @@ -233,7 +260,7 @@ export class MetadataBundler { exportedSymbols.forEach(symbol => this.convertSymbol(symbol)); Array.from(this.symbolMap.values()).forEach(symbol => { - if (symbol.referenced) { + if (symbol.referenced && !symbol.reexport) { let name = symbol.name; if (symbol.isPrivate && !symbol.privateName) { name = newPrivateName(); @@ -246,6 +273,36 @@ export class MetadataBundler { return result; } + private getReExports(exportedSymbols: Symbol[]): ModuleExportMetadata[] { + type ExportClause = {name: string, as: string}[]; + const modules = new Map(); + const exportAlls = new Set(); + for (const symbol of exportedSymbols) { + if (symbol.reexport) { + const declaration = symbol.declaration; + const module = declaration.module; + if (declaration.name == '*') { + // Reexport all the symbols. + exportAlls.add(declaration.module); + } else { + // Re-export the symbol as the exported name. + let entry = modules.get(module); + if (!entry) { + entry = []; + modules.set(module, entry); + } + const as = symbol.name; + const name = declaration.name; + entry.push({name, as}); + } + } + } + return [ + ...Array.from(exportAlls.values()).map(from => ({from})), + ...Array.from(modules.entries()).map(([from, exports]) => ({export: exports, from})) + ]; + } + private convertSymbol(symbol: Symbol) { const canonicalSymbol = symbol.canonicalSymbol; diff --git a/tools/@angular/tsc-wrapped/test/bundler_spec.ts b/tools/@angular/tsc-wrapped/test/bundler_spec.ts index 90569628f4dfc..54ba8dca60c03 100644 --- a/tools/@angular/tsc-wrapped/test/bundler_spec.ts +++ b/tools/@angular/tsc-wrapped/test/bundler_spec.ts @@ -163,6 +163,35 @@ describe('metadata bundler', () => { expect(Object.keys(result.metadata.metadata).sort()).toEqual(['Foo', 'ɵa']); expect(result.privates).toEqual([{privateName: 'ɵa', name: 'Bar', module: './bar'}]); }); + + it('should be able to bundle a library with re-exported symbols', () => { + const host = new MockStringBundlerHost('/', { + 'public-api.ts': ` + export * from './src/core'; + export * from './src/externals'; + `, + 'src': { + 'core.ts': ` + export class A {} + export class B extends A {} + `, + 'externals.ts': ` + export {E, F, G} from 'external_one'; + export * from 'external_two'; + ` + } + }); + + const bundler = new MetadataBundler('/public-api', undefined, host); + const result = bundler.getMetadataBundle(); + expect(result.metadata.exports).toEqual([ + {from: 'external_two'}, { + export: [{name: 'E', as: 'E'}, {name: 'F', as: 'F'}, {name: 'G', as: 'G'}], + from: 'external_one' + } + ]); + expect(result.metadata.origins['E']).toBeUndefined(); + }); }); export class MockStringBundlerHost implements MetadataBundlerHost {