angular/packages/compiler-cli/ngcc/src/rendering/commonjs_rendering_formatter.ts
George Kalpakas 17d5e2bc99 refactor(ngcc): share code between CommonJsReflectionHost and UmdReflectionHost (#34512)
While different, CommonJS and UMD have a lot in common regarding the
their exports are constructed. Therefore, there was some code
duplication between `CommonJsReflectionHost` and `UmdReflectionHost`.

This commit extracts some of the common bits into a separate file as
helpers to allow reusing the code in both `ReflectionHost`s.

PR Close #34512
2020-01-08 15:00:49 -08:00

88 lines
3.4 KiB
TypeScript

/**
* @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 {dirname, relative} from 'canonical-path';
import * as ts from 'typescript';
import MagicString from 'magic-string';
import {Reexport} from '../../../src/ngtsc/imports';
import {Import, ImportManager} from '../../../src/ngtsc/translator';
import {ExportInfo} from '../analysis/private_declarations_analyzer';
import {isRequireCall} from '../host/commonjs_umd_utils';
import {NgccReflectionHost} from '../host/ngcc_host';
import {Esm5RenderingFormatter} from './esm5_rendering_formatter';
import {stripExtension} from './utils';
/**
* A RenderingFormatter that works with CommonJS files, instead of `import` and `export` statements
* the module is an IIFE with a factory function call with dependencies, which are defined in a
* wrapper function for AMD, CommonJS and global module formats.
*/
export class CommonJsRenderingFormatter extends Esm5RenderingFormatter {
constructor(protected commonJsHost: NgccReflectionHost, isCore: boolean) {
super(commonJsHost, isCore);
}
/**
* Add the imports below any in situ imports as `require` calls.
*/
addImports(output: MagicString, imports: Import[], file: ts.SourceFile): void {
// Avoid unnecessary work if there are no imports to add.
if (imports.length === 0) {
return;
}
const insertionPoint = this.findEndOfImports(file);
const renderedImports =
imports.map(i => `var ${i.qualifier} = require('${i.specifier}');\n`).join('');
output.appendLeft(insertionPoint, renderedImports);
}
/**
* Add the exports to the bottom of the file.
*/
addExports(
output: MagicString, entryPointBasePath: string, exports: ExportInfo[],
importManager: ImportManager, file: ts.SourceFile): void {
exports.forEach(e => {
const basePath = stripExtension(e.from);
const relativePath = './' + relative(dirname(entryPointBasePath), basePath);
const namedImport = entryPointBasePath !== basePath ?
importManager.generateNamedImport(relativePath, e.identifier) :
{symbol: e.identifier, moduleImport: null};
const importNamespace = namedImport.moduleImport ? `${namedImport.moduleImport}.` : '';
const exportStr = `\nexports.${e.identifier} = ${importNamespace}${namedImport.symbol};`;
output.append(exportStr);
});
}
addDirectExports(
output: MagicString, exports: Reexport[], importManager: ImportManager,
file: ts.SourceFile): void {
for (const e of exports) {
const namedImport = importManager.generateNamedImport(e.fromModule, e.symbolName);
const importNamespace = namedImport.moduleImport ? `${namedImport.moduleImport}.` : '';
const exportStr = `\nexports.${e.asAlias} = ${importNamespace}${namedImport.symbol};`;
output.append(exportStr);
}
}
protected findEndOfImports(sf: ts.SourceFile): number {
for (const statement of sf.statements) {
if (ts.isExpressionStatement(statement) && isRequireCall(statement.expression)) {
continue;
}
const declarations = ts.isVariableStatement(statement) ?
Array.from(statement.declarationList.declarations) :
[];
if (declarations.some(d => !d.initializer || !isRequireCall(d.initializer))) {
return statement.getStart();
}
}
return 0;
}
}