Skip to content

Commit ee7d134

Browse files
chuckjazjasonaden
authored andcommitted
fix(tsc-wrapped): emit exports metadata in flat modules (#17893)
Fixes: #17888
1 parent 2ab9057 commit ee7d134

File tree

2 files changed

+92
-6
lines changed

2 files changed

+92
-6
lines changed

tools/@angular/tsc-wrapped/src/bundler.ts

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import * as path from 'path';
99
import * as ts from 'typescript';
1010

1111
import {MetadataCollector} from './collector';
12-
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';
12+
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';
13+
1314

1415

1516
// The character set used to produce private names.
@@ -44,6 +45,10 @@ interface Symbol {
4445
// a referenced symbol's value.
4546
referenced?: boolean;
4647

48+
// A symbol is marked as a re-export the symbol was rexported from a module that is
49+
// not part of the flat module bundle.
50+
reexport?: boolean;
51+
4752
// Only valid for referenced canonical symbols. Produces by convertSymbols().
4853
value?: MetadataEntry;
4954

@@ -98,14 +103,19 @@ export class MetadataBundler {
98103
module: s.declaration.module
99104
}));
100105
const origins = Array.from(this.symbolMap.values())
101-
.filter(s => s.referenced)
106+
.filter(s => s.referenced && !s.reexport)
102107
.reduce<{[name: string]: string}>((p, s) => {
103108
p[s.isPrivate ? s.privateName : s.name] = s.declaration.module;
104109
return p;
105110
}, {});
111+
const exports = this.getReExports(exportedSymbols);
106112
return {
107-
metadata:
108-
{__symbolic: 'module', version: VERSION, metadata, origins, importAs: this.importAs},
113+
metadata: {
114+
__symbolic: 'module',
115+
version: VERSION,
116+
exports: exports.length ? exports : undefined, metadata, origins,
117+
importAs: this.importAs
118+
},
109119
privates
110120
};
111121
}
@@ -165,12 +175,19 @@ export class MetadataBundler {
165175
for (const exportDeclaration of module.exports) {
166176
const exportFrom = resolveModule(exportDeclaration.from, moduleName);
167177
// Record all the exports from the module even if we don't use it directly.
168-
this.exportAll(exportFrom);
178+
const exportedSymbols = this.exportAll(exportFrom);
169179
if (exportDeclaration.export) {
170180
// Re-export all the named exports from a module.
171181
for (const exportItem of exportDeclaration.export) {
172182
const name = typeof exportItem == 'string' ? exportItem : exportItem.name;
173183
const exportAs = typeof exportItem == 'string' ? exportItem : exportItem.as;
184+
const symbol = this.symbolOf(exportFrom, name);
185+
if (exportedSymbols && exportedSymbols.length == 1 && exportedSymbols[0].reexport &&
186+
exportedSymbols[0].name == '*') {
187+
// This is a named export from a module we have no metadata about. Record the named
188+
// export as a re-export.
189+
symbol.reexport = true;
190+
}
174191
exportSymbol(this.symbolOf(exportFrom, name), exportAs);
175192
}
176193
} else {
@@ -183,6 +200,15 @@ export class MetadataBundler {
183200
}
184201
}
185202
}
203+
204+
if (!module) {
205+
// If no metadata is found for this import then it is considered external to the
206+
// library and should be recorded as a re-export in the final metadata if it is
207+
// eventually re-exported.
208+
const symbol = this.symbolOf(moduleName, '*');
209+
symbol.reexport = true;
210+
result.push(symbol);
211+
}
186212
this.exports.set(moduleName, result);
187213

188214
return result;
@@ -207,6 +233,7 @@ export class MetadataBundler {
207233
symbol.isPrivate = isPrivate;
208234
symbol.declaration = declaration;
209235
symbol.canonicalSymbol = canonicalSymbol;
236+
symbol.reexport = declaration.reexport;
210237
}
211238

212239
private getEntries(exportedSymbols: Symbol[]): BundleEntries {
@@ -233,7 +260,7 @@ export class MetadataBundler {
233260
exportedSymbols.forEach(symbol => this.convertSymbol(symbol));
234261

235262
Array.from(this.symbolMap.values()).forEach(symbol => {
236-
if (symbol.referenced) {
263+
if (symbol.referenced && !symbol.reexport) {
237264
let name = symbol.name;
238265
if (symbol.isPrivate && !symbol.privateName) {
239266
name = newPrivateName();
@@ -246,6 +273,36 @@ export class MetadataBundler {
246273
return result;
247274
}
248275

276+
private getReExports(exportedSymbols: Symbol[]): ModuleExportMetadata[] {
277+
type ExportClause = {name: string, as: string}[];
278+
const modules = new Map<string, ExportClause>();
279+
const exportAlls = new Set<string>();
280+
for (const symbol of exportedSymbols) {
281+
if (symbol.reexport) {
282+
const declaration = symbol.declaration;
283+
const module = declaration.module;
284+
if (declaration.name == '*') {
285+
// Reexport all the symbols.
286+
exportAlls.add(declaration.module);
287+
} else {
288+
// Re-export the symbol as the exported name.
289+
let entry = modules.get(module);
290+
if (!entry) {
291+
entry = [];
292+
modules.set(module, entry);
293+
}
294+
const as = symbol.name;
295+
const name = declaration.name;
296+
entry.push({name, as});
297+
}
298+
}
299+
}
300+
return [
301+
...Array.from(exportAlls.values()).map(from => ({from})),
302+
...Array.from(modules.entries()).map(([from, exports]) => ({export: exports, from}))
303+
];
304+
}
305+
249306
private convertSymbol(symbol: Symbol) {
250307
const canonicalSymbol = symbol.canonicalSymbol;
251308

tools/@angular/tsc-wrapped/test/bundler_spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,35 @@ describe('metadata bundler', () => {
163163
expect(Object.keys(result.metadata.metadata).sort()).toEqual(['Foo', 'ɵa']);
164164
expect(result.privates).toEqual([{privateName: 'ɵa', name: 'Bar', module: './bar'}]);
165165
});
166+
167+
it('should be able to bundle a library with re-exported symbols', () => {
168+
const host = new MockStringBundlerHost('/', {
169+
'public-api.ts': `
170+
export * from './src/core';
171+
export * from './src/externals';
172+
`,
173+
'src': {
174+
'core.ts': `
175+
export class A {}
176+
export class B extends A {}
177+
`,
178+
'externals.ts': `
179+
export {E, F, G} from 'external_one';
180+
export * from 'external_two';
181+
`
182+
}
183+
});
184+
185+
const bundler = new MetadataBundler('/public-api', undefined, host);
186+
const result = bundler.getMetadataBundle();
187+
expect(result.metadata.exports).toEqual([
188+
{from: 'external_two'}, {
189+
export: [{name: 'E', as: 'E'}, {name: 'F', as: 'F'}, {name: 'G', as: 'G'}],
190+
from: 'external_one'
191+
}
192+
]);
193+
expect(result.metadata.origins['E']).toBeUndefined();
194+
});
166195
});
167196

168197
export class MockStringBundlerHost implements MetadataBundlerHost {

0 commit comments

Comments
 (0)