
committed by
Jason Aden

parent
7fec1771fc
commit
f6aa60c03c
@ -9,7 +9,7 @@ import MagicString from 'magic-string';
|
||||
import * as ts from 'typescript';
|
||||
import {PathSegment, AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||
import {isDtsPath} from '../../../src/ngtsc/util/src/typescript';
|
||||
import {Import} from '../../../src/ngtsc/translator';
|
||||
import {Import, ImportManager} from '../../../src/ngtsc/translator';
|
||||
import {CompiledClass} from '../analysis/decoration_analyzer';
|
||||
import {ExportInfo} from '../analysis/private_declarations_analyzer';
|
||||
import {FileSystem} from '../file_system/file_system';
|
||||
@ -35,7 +35,9 @@ export class EsmRenderer extends Renderer {
|
||||
output.appendLeft(insertionPoint, renderedImports);
|
||||
}
|
||||
|
||||
addExports(output: MagicString, entryPointBasePath: AbsoluteFsPath, exports: ExportInfo[]): void {
|
||||
addExports(
|
||||
output: MagicString, entryPointBasePath: AbsoluteFsPath, exports: ExportInfo[],
|
||||
importManager: ImportManager, file: ts.SourceFile): void {
|
||||
exports.forEach(e => {
|
||||
let exportFrom = '';
|
||||
const isDtsFile = isDtsPath(entryPointBasePath);
|
||||
|
@ -127,6 +127,7 @@ export abstract class Renderer {
|
||||
sourceFile: ts.SourceFile, compiledFile: CompiledFile|undefined,
|
||||
switchMarkerAnalysis: SwitchMarkerAnalysis|undefined,
|
||||
privateDeclarationsAnalyses: PrivateDeclarationsAnalyses): FileInfo[] {
|
||||
const isEntryPoint = sourceFile === this.bundle.src.file;
|
||||
const input = this.extractSourceMap(sourceFile);
|
||||
const outputText = new MagicString(input.source);
|
||||
|
||||
@ -135,11 +136,11 @@ export abstract class Renderer {
|
||||
outputText, switchMarkerAnalysis.sourceFile, switchMarkerAnalysis.declarations);
|
||||
}
|
||||
|
||||
if (compiledFile) {
|
||||
const importManager = new ImportManager(
|
||||
this.getImportRewriter(this.bundle.src.r3SymbolsFile, this.bundle.isFlatCore),
|
||||
IMPORT_PREFIX);
|
||||
const importManager = new ImportManager(
|
||||
this.getImportRewriter(this.bundle.src.r3SymbolsFile, this.bundle.isFlatCore),
|
||||
IMPORT_PREFIX);
|
||||
|
||||
if (compiledFile) {
|
||||
// TODO: remove constructor param metadata and property decorators (we need info from the
|
||||
// handlers to do this)
|
||||
const decoratorsToRemove = this.computeDecoratorsToRemove(compiledFile.compiledClasses);
|
||||
@ -154,19 +155,24 @@ export abstract class Renderer {
|
||||
outputText,
|
||||
renderConstantPool(compiledFile.sourceFile, compiledFile.constantPool, importManager),
|
||||
compiledFile.sourceFile);
|
||||
|
||||
this.addImports(
|
||||
outputText, importManager.getAllImports(compiledFile.sourceFile.fileName),
|
||||
compiledFile.sourceFile);
|
||||
}
|
||||
|
||||
// Add exports to the entry-point file
|
||||
if (sourceFile === this.bundle.src.file) {
|
||||
if (isEntryPoint) {
|
||||
const entryPointBasePath = stripExtension(this.bundle.src.path);
|
||||
this.addExports(outputText, entryPointBasePath, privateDeclarationsAnalyses);
|
||||
this.addExports(
|
||||
outputText, entryPointBasePath, privateDeclarationsAnalyses, importManager, sourceFile);
|
||||
}
|
||||
|
||||
return this.renderSourceAndMap(sourceFile, input, outputText);
|
||||
if (isEntryPoint || compiledFile) {
|
||||
this.addImports(outputText, importManager.getAllImports(sourceFile.fileName), sourceFile);
|
||||
}
|
||||
|
||||
if (compiledFile || switchMarkerAnalysis || isEntryPoint) {
|
||||
return this.renderSourceAndMap(sourceFile, input, outputText);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
renderDtsFile(dtsFile: ts.SourceFile, renderInfo: DtsRenderInfo): FileInfo[] {
|
||||
@ -189,7 +195,9 @@ export abstract class Renderer {
|
||||
this.addModuleWithProvidersParams(outputText, renderInfo.moduleWithProviders, importManager);
|
||||
this.addImports(outputText, importManager.getAllImports(dtsFile.fileName), dtsFile);
|
||||
|
||||
this.addExports(outputText, AbsoluteFsPath.fromSourceFile(dtsFile), renderInfo.privateExports);
|
||||
this.addExports(
|
||||
outputText, AbsoluteFsPath.fromSourceFile(dtsFile), renderInfo.privateExports,
|
||||
importManager, dtsFile);
|
||||
|
||||
|
||||
return this.renderSourceAndMap(dtsFile, input, outputText);
|
||||
@ -251,7 +259,8 @@ export abstract class Renderer {
|
||||
void;
|
||||
protected abstract addImports(output: MagicString, imports: Import[], sf: ts.SourceFile): void;
|
||||
protected abstract addExports(
|
||||
output: MagicString, entryPointBasePath: AbsoluteFsPath, exports: ExportInfo[]): void;
|
||||
output: MagicString, entryPointBasePath: AbsoluteFsPath, exports: ExportInfo[],
|
||||
importManager: ImportManager, file: ts.SourceFile): void;
|
||||
protected abstract addDefinitions(
|
||||
output: MagicString, compiledClass: CompiledClass, definitions: string): void;
|
||||
protected abstract removeDecorators(
|
||||
|
207
packages/compiler-cli/ngcc/src/rendering/umd_renderer.ts
Normal file
207
packages/compiler-cli/ngcc/src/rendering/umd_renderer.ts
Normal file
@ -0,0 +1,207 @@
|
||||
/**
|
||||
* @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 {Import, ImportManager} from '../../../src/ngtsc/translator';
|
||||
import {ExportInfo} from '../analysis/private_declarations_analyzer';
|
||||
import {FileSystem} from '../file_system/file_system';
|
||||
import {UmdReflectionHost} from '../host/umd_host';
|
||||
import {Logger} from '../logging/logger';
|
||||
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
||||
import {Esm5Renderer} from './esm5_renderer';
|
||||
import {stripExtension} from './renderer';
|
||||
|
||||
type CommonJsConditional = ts.ConditionalExpression & {whenTrue: ts.CallExpression};
|
||||
type AmdConditional = ts.ConditionalExpression & {whenTrue: ts.CallExpression};
|
||||
|
||||
export class UmdRenderer extends Esm5Renderer {
|
||||
constructor(
|
||||
fs: FileSystem, logger: Logger, protected umdHost: UmdReflectionHost, isCore: boolean,
|
||||
bundle: EntryPointBundle) {
|
||||
super(fs, logger, umdHost, isCore, bundle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the imports at the top of the file
|
||||
*/
|
||||
addImports(output: MagicString, imports: Import[], file: ts.SourceFile): void {
|
||||
// Assume there is only one UMD module in the file
|
||||
const umdModule = this.umdHost.getUmdModule(file);
|
||||
if (!umdModule) {
|
||||
return;
|
||||
}
|
||||
|
||||
const wrapperFunction = umdModule.wrapperFn;
|
||||
|
||||
// We need to add new `require()` calls for each import in the CommonJS initializer
|
||||
renderCommonJsDependencies(output, wrapperFunction, imports);
|
||||
renderAmdDependencies(output, wrapperFunction, imports);
|
||||
renderGlobalDependencies(output, wrapperFunction, imports);
|
||||
renderFactoryParameters(output, wrapperFunction, imports);
|
||||
}
|
||||
|
||||
addExports(
|
||||
output: MagicString, entryPointBasePath: string, exports: ExportInfo[],
|
||||
importManager: ImportManager, file: ts.SourceFile): void {
|
||||
const umdModule = this.umdHost.getUmdModule(file);
|
||||
if (!umdModule) {
|
||||
return;
|
||||
}
|
||||
const factoryFunction = umdModule.factoryFn;
|
||||
const lastStatement =
|
||||
factoryFunction.body.statements[factoryFunction.body.statements.length - 1];
|
||||
const insertionPoint =
|
||||
lastStatement ? lastStatement.getEnd() : factoryFunction.body.getEnd() - 1;
|
||||
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.appendRight(insertionPoint, exportStr);
|
||||
});
|
||||
}
|
||||
|
||||
addConstants(output: MagicString, constants: string, file: ts.SourceFile): void {
|
||||
if (constants === '') {
|
||||
return;
|
||||
}
|
||||
const umdModule = this.umdHost.getUmdModule(file);
|
||||
if (!umdModule) {
|
||||
return;
|
||||
}
|
||||
const factoryFunction = umdModule.factoryFn;
|
||||
const firstStatement = factoryFunction.body.statements[0];
|
||||
const insertionPoint =
|
||||
firstStatement ? firstStatement.getStart() : factoryFunction.body.getStart() + 1;
|
||||
output.appendLeft(insertionPoint, '\n' + constants + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
function renderCommonJsDependencies(
|
||||
output: MagicString, wrapperFunction: ts.FunctionExpression, imports: Import[]) {
|
||||
const conditional = find(wrapperFunction.body.statements[0], isCommonJSConditional);
|
||||
if (!conditional) {
|
||||
return;
|
||||
}
|
||||
const factoryCall = conditional.whenTrue;
|
||||
const injectionPoint = factoryCall.getEnd() -
|
||||
1; // Backup one char to account for the closing parenthesis on the call
|
||||
imports.forEach(i => output.appendLeft(injectionPoint, `,require('${i.specifier}')`));
|
||||
}
|
||||
|
||||
function renderAmdDependencies(
|
||||
output: MagicString, wrapperFunction: ts.FunctionExpression, imports: Import[]) {
|
||||
const conditional = find(wrapperFunction.body.statements[0], isAmdConditional);
|
||||
if (!conditional) {
|
||||
return;
|
||||
}
|
||||
const dependencyArray = conditional.whenTrue.arguments[1];
|
||||
if (!dependencyArray || !ts.isArrayLiteralExpression(dependencyArray)) {
|
||||
return;
|
||||
}
|
||||
const injectionPoint = dependencyArray.getEnd() -
|
||||
1; // Backup one char to account for the closing square bracket on the array
|
||||
imports.forEach(i => output.appendLeft(injectionPoint, `,'${i.specifier}'`));
|
||||
}
|
||||
|
||||
function renderGlobalDependencies(
|
||||
output: MagicString, wrapperFunction: ts.FunctionExpression, imports: Import[]) {
|
||||
const globalFactoryCall = find(wrapperFunction.body.statements[0], isGlobalFactoryCall);
|
||||
if (!globalFactoryCall) {
|
||||
return;
|
||||
}
|
||||
const injectionPoint = globalFactoryCall.getEnd() -
|
||||
1; // Backup one char to account for the closing parenthesis on the call
|
||||
imports.forEach(i => output.appendLeft(injectionPoint, `,global.${getGlobalIdentifier(i)}`));
|
||||
}
|
||||
|
||||
function renderFactoryParameters(
|
||||
output: MagicString, wrapperFunction: ts.FunctionExpression, imports: Import[]) {
|
||||
const wrapperCall = wrapperFunction.parent as ts.CallExpression;
|
||||
const secondArgument = wrapperCall.arguments[1];
|
||||
if (!secondArgument) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Be resilient to the factory being inside parentheses
|
||||
const factoryFunction =
|
||||
ts.isParenthesizedExpression(secondArgument) ? secondArgument.expression : secondArgument;
|
||||
if (!ts.isFunctionExpression(factoryFunction)) {
|
||||
return;
|
||||
}
|
||||
const parameters = factoryFunction.parameters;
|
||||
const injectionPoint = parameters[parameters.length - 1].getEnd();
|
||||
imports.forEach(i => output.appendLeft(injectionPoint, `,${i.qualifier}`));
|
||||
}
|
||||
|
||||
function isCommonJSConditional(value: ts.Node): value is CommonJsConditional {
|
||||
if (!ts.isConditionalExpression(value)) {
|
||||
return false;
|
||||
}
|
||||
if (!ts.isBinaryExpression(value.condition) ||
|
||||
value.condition.operatorToken.kind !== ts.SyntaxKind.AmpersandAmpersandToken) {
|
||||
return false;
|
||||
}
|
||||
if (!oneOfBinaryConditions(value.condition, (exp) => isTypeOf(exp, 'exports', 'module'))) {
|
||||
return false;
|
||||
}
|
||||
if (!ts.isCallExpression(value.whenTrue) || !ts.isIdentifier(value.whenTrue.expression)) {
|
||||
return false;
|
||||
}
|
||||
return value.whenTrue.expression.text === 'factory';
|
||||
}
|
||||
|
||||
function isAmdConditional(value: ts.Node): value is AmdConditional {
|
||||
if (!ts.isConditionalExpression(value)) {
|
||||
return false;
|
||||
}
|
||||
if (!ts.isBinaryExpression(value.condition) ||
|
||||
value.condition.operatorToken.kind !== ts.SyntaxKind.AmpersandAmpersandToken) {
|
||||
return false;
|
||||
}
|
||||
if (!oneOfBinaryConditions(value.condition, (exp) => isTypeOf(exp, 'define'))) {
|
||||
return false;
|
||||
}
|
||||
if (!ts.isCallExpression(value.whenTrue) || !ts.isIdentifier(value.whenTrue.expression)) {
|
||||
return false;
|
||||
}
|
||||
return value.whenTrue.expression.text === 'define';
|
||||
}
|
||||
|
||||
function isGlobalFactoryCall(value: ts.Node): value is ts.CallExpression {
|
||||
if (ts.isCallExpression(value) && !!value.parent) {
|
||||
// Be resilient to the value being inside parentheses
|
||||
const expression = ts.isParenthesizedExpression(value.parent) ? value.parent : value;
|
||||
return !!expression.parent && ts.isConditionalExpression(expression.parent) &&
|
||||
expression.parent.whenFalse === expression;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getGlobalIdentifier(i: Import) {
|
||||
return i.specifier.replace('@angular/', 'ng.').replace(/^\//, '');
|
||||
}
|
||||
|
||||
function find<T>(node: ts.Node, test: (node: ts.Node) => node is ts.Node & T): T|undefined {
|
||||
return test(node) ? node : node.forEachChild(child => find<T>(child, test));
|
||||
}
|
||||
|
||||
function oneOfBinaryConditions(
|
||||
node: ts.BinaryExpression, test: (expression: ts.Expression) => boolean) {
|
||||
return test(node.left) || test(node.right);
|
||||
}
|
||||
|
||||
function isTypeOf(node: ts.Expression, ...types: string[]): boolean {
|
||||
return ts.isBinaryExpression(node) && ts.isTypeOfExpression(node.left) &&
|
||||
ts.isIdentifier(node.left.expression) && types.indexOf(node.left.expression.text) !== -1;
|
||||
}
|
Reference in New Issue
Block a user