diff --git a/packages/compiler-cli/ngcc/src/host/umd_host.ts b/packages/compiler-cli/ngcc/src/host/umd_host.ts index e074f28b03..85368cdab3 100644 --- a/packages/compiler-cli/ngcc/src/host/umd_host.ts +++ b/packages/compiler-cli/ngcc/src/host/umd_host.ts @@ -145,11 +145,25 @@ export class UmdReflectionHost extends Esm5ReflectionHost { private extractUmdReexports(statement: ReexportStatement, containingFile: ts.SourceFile): UmdExportDeclaration[] { - const importParameter = this.findUmdImportParameter(statement.expression.arguments[0]); - const importPath = importParameter && this.getUmdImportPath(importParameter); + const reexportArg = statement.expression.arguments[0]; + + const requireCall = isRequireCall(reexportArg) ? + reexportArg : + ts.isIdentifier(reexportArg) ? this.findRequireCallReference(reexportArg) : null; + + let importPath: string|null = null; + + if (requireCall !== null) { + importPath = requireCall.arguments[0].text; + } else if (ts.isIdentifier(reexportArg)) { + const importParameter = this.findUmdImportParameter(reexportArg); + importPath = importParameter && this.getUmdImportPath(importParameter); + } + if (importPath === null) { return []; } + const importedFile = this.resolveModuleName(importPath, containingFile); if (importedFile === undefined) { return []; @@ -182,6 +196,14 @@ export class UmdReflectionHost extends Esm5ReflectionHost { return declaration && ts.isParameter(declaration) ? declaration : null; } + private findRequireCallReference(id: ts.Identifier): RequireCall|null { + const symbol = id && this.checker.getSymbolAtLocation(id) || null; + const declaration = symbol && symbol.valueDeclaration; + const initializer = + declaration && ts.isVariableDeclaration(declaration) && declaration.initializer || null; + return initializer && isRequireCall(initializer) ? initializer : null; + } + private getUmdImportedDeclaration(id: ts.Identifier): Declaration|null { const importInfo = this.getImportOfIdentifier(id); if (importInfo === null) { @@ -277,13 +299,12 @@ interface UmdExportDeclaration { declaration: Declaration; } -type ReexportStatement = ts.ExpressionStatement & {expression: {arguments: [ts.Identifier]}}; +type ReexportStatement = ts.ExpressionStatement & {expression: ts.CallExpression}; function isReexportStatement(statement: ts.Statement): statement is ReexportStatement { return ts.isExpressionStatement(statement) && ts.isCallExpression(statement.expression) && ts.isIdentifier(statement.expression.expression) && statement.expression.expression.text === '__export' && - statement.expression.arguments.length === 1 && - ts.isIdentifier(statement.expression.arguments[0]); + statement.expression.arguments.length === 1; } function getRequiredModulePath(wrapperFn: ts.FunctionExpression, paramIndex: number): string { @@ -315,7 +336,8 @@ function getRequiredModulePath(wrapperFn: ts.FunctionExpression, paramIndex: num } } -function isRequireCall(node: ts.Node): node is ts.CallExpression { +type RequireCall = ts.CallExpression & {arguments: [ts.StringLiteral]}; +function isRequireCall(node: ts.Node): node is RequireCall { return ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === 'require' && node.arguments.length === 1; } diff --git a/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts b/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts index 695503d298..f1f2dd3a78 100644 --- a/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts @@ -564,10 +564,10 @@ runInEachFileSystem(() => { name: _('/index.js'), contents: ` (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('./a_module'), require('./b_module'), require('./wildcard_reexports')) : - typeof define === 'function' && define.amd ? define('index', ['exports', './a_module', './b_module', './wildcard_reexports], factory) : - (factory(global.index, global.a_module, global.b_module, global.wildcard_reexports)); - }(this, (function (exports, a_module, b_module, wildcard_reexports) { 'use strict'; + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('./a_module'), require('./b_module'), require('./wildcard_reexports'), require('./wildcard_reexports_with_require')) : + typeof define === 'function' && define.amd ? define('index', ['exports', './a_module', './b_module', './wildcard_reexports', './wildcard_reexports_with_require'], factory) : + (factory(global.index, global.a_module, global.b_module, global.wildcard_reexports, global.wildcard_reexports_with_require)); + }(this, (function (exports, a_module, b_module, wildcard_reexports, wildcard_reexports_with_require) { 'use strict'; }))); ` }, @@ -633,8 +633,23 @@ runInEachFileSystem(() => { function __export(m) { for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; } -__export(b_module); -__export(xtra_module); + __export(b_module); + __export(xtra_module); +})));`, + }, + { + name: _('/wildcard_reexports_with_require.js'), + contents: ` +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(require, exports) : + typeof define === 'function' && define.amd ? define('wildcard_reexports_with_require', ['require', 'exports'], factory); +}(this, (function (require, exports) { 'use strict'; + function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; + } + var b_module = require('./b_module'); + __export(b_module); + __export(require('./xtra_module')); })));`, } ]; @@ -1960,6 +1975,34 @@ __export(xtra_module); ]); }); + it('should handle wildcard re-exports of other modules using `require()` calls', () => { + loadFakeCore(getFileSystem()); + loadTestFiles(EXPORTS_FILES); + const bundle = makeTestBundleProgram(_('/index.js')); + const host = new UmdReflectionHost(new MockLogger(), false, bundle); + const file = getSourceFileOrError(bundle.program, _('/wildcard_reexports_with_require.js')); + const exportDeclarations = host.getExportsOfModule(file); + expect(exportDeclarations).not.toBe(null); + expect(Array.from(exportDeclarations !.entries()) + .map(entry => [entry[0], entry[1].node !.getText(), entry[1].viaModule])) + .toEqual([ + ['Directive', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], + ['a', `a = 'a'`, _('/b_module')], + ['b', `b = a_module.a`, _('/b_module')], + ['c', `a = 'a'`, _('/b_module')], + ['d', `b = a_module.a`, _('/b_module')], + ['e', `e = 'e'`, _('/b_module')], + ['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], + [ + 'SomeClass', + `SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n }())`, + _('/b_module') + ], + ['xtra1', `xtra1 = 'xtra1'`, _('/xtra_module')], + ['xtra2', `xtra2 = 'xtra2'`, _('/xtra_module')], + ]); + }); + it('should return the class symbol for an ES5 class (outer variable declaration)', () => { loadTestFiles([SIMPLE_CLASS_FILE]); const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);