diff --git a/src/aot/aot-compiler.ts b/src/aot/aot-compiler.ts index d1b6409d..5dc26b50 100644 --- a/src/aot/aot-compiler.ts +++ b/src/aot/aot-compiler.ts @@ -111,6 +111,7 @@ export class AotCompiler { try { modifiedFileContent = replaceBootstrap(mainFile.path, mainFile.content, AppNgModuleTokens[0], AppNgModuleTokens[1]); } catch (ex) { + Logger.debug(`Failed to parse bootstrap: `, ex.message); Logger.warn(`Failed to parse and update ${this.options.entryPoint} content for AoT compilation. For now, the default fallback content will be used instead. Please consider updating ${this.options.entryPoint} with the content from the following link: diff --git a/src/aot/optimization.ts b/src/aot/optimization.ts index 9d3af19e..d4f25d1d 100644 --- a/src/aot/optimization.ts +++ b/src/aot/optimization.ts @@ -3,7 +3,8 @@ import { removeDecorators } from '../util/typescript-utils'; export function optimizeJavascript(filePath: string, fileContent: string) { fileContent = removeDecorators(filePath, fileContent); fileContent = purgeDecoratorStatements(filePath, fileContent, ['@angular']); - fileContent = purgeCtorStatements(filePath, fileContent, ['@angular']); + // TODO - needs more testing to fully understand + // fileContent = purgeCtorStatements(filePath, fileContent, ['@angular']); fileContent = purgeKnownContent(filePath, fileContent, ['@angular']); return fileContent; diff --git a/src/aot/utils.ts b/src/aot/utils.ts index 82656b4b..81065bc6 100644 --- a/src/aot/utils.ts +++ b/src/aot/utils.ts @@ -7,7 +7,7 @@ import { ScriptTarget } from 'typescript'; -import { getTypescriptSourceFile, findNodes, replaceImportModuleSpecifier, replaceNamedImport, replaceNode } from '../util/typescript-utils'; +import { appendBefore, checkIfFunctionIsCalled, getTypescriptSourceFile, findNodes, insertNamedImportIfNeeded, replaceImportModuleSpecifier, replaceNamedImport, replaceNode } from '../util/typescript-utils'; export function getFallbackMainContent() { return ` @@ -86,6 +86,34 @@ function replaceBootstrapModuleFactory(filePath: string, fileContent: string) { return modifiedContent; } +function getPlatformBrowserFunctionNode(filePath: string, fileContent: string) { + let modifiedFileContent = fileContent; + const sourceFile = getTypescriptSourceFile(filePath, modifiedFileContent, ScriptTarget.Latest, false); + const allCalls = findNodes(sourceFile, sourceFile, SyntaxKind.CallExpression, true) as CallExpression[]; + const callsToPlatformBrowser = allCalls.filter(call => call.expression && call.expression.kind === SyntaxKind.Identifier && (call.expression as Identifier).text === 'platformBrowser'); + const toAppend = `enableProdMode();\n`; + if (callsToPlatformBrowser.length) { + modifiedFileContent = appendBefore(filePath, modifiedFileContent, callsToPlatformBrowser[0].expression, toAppend); + } else { + // just throw it at the bottom + modifiedFileContent + toAppend; + } + return modifiedFileContent; +} + +function importAndEnableProdMode(filePath: string, fileContent: string) { + let modifiedFileContent = fileContent; + modifiedFileContent = insertNamedImportIfNeeded(filePath, modifiedFileContent, 'enableProdMode', '@angular/core'); + + const isCalled = checkIfFunctionIsCalled(filePath, modifiedFileContent, 'enableProdMode'); + if (!isCalled) { + // go ahead and insert this + modifiedFileContent = getPlatformBrowserFunctionNode(filePath, modifiedFileContent); + } + + return modifiedFileContent; +} + export function replaceBootstrap(filePath: string, fileContent: string, appNgModulePath: string, appNgModuleClassName: string) { if (!fileContent.match(/\bbootstrapModule\b/)) { throw new Error(`Could not find bootstrapModule in ${filePath}`); @@ -103,7 +131,6 @@ export function replaceBootstrap(filePath: string, fileContent: string, appNgMod let modifiedFileContent = fileContent; modifiedFileContent = replaceNgModuleClassName(filePath, modifiedFileContent, appNgModuleClassName); - modifiedFileContent = replacePlatformBrowser(filePath, modifiedFileContent); modifiedFileContent = replaceBootstrapModuleFactory(filePath, modifiedFileContent); @@ -112,5 +139,8 @@ export function replaceBootstrap(filePath: string, fileContent: string, appNgMod modifiedFileContent = replaceImportModuleSpecifier(filePath, modifiedFileContent, '@angular/platform-browser-dynamic', '@angular/platform-browser'); modifiedFileContent = replaceImportModuleSpecifier(filePath, modifiedFileContent, originalImport, ngFactryImport); + // check if prod mode is imported and enabled + modifiedFileContent = importAndEnableProdMode(filePath, modifiedFileContent); + return modifiedFileContent; } diff --git a/src/util/typescript-utils.ts b/src/util/typescript-utils.ts index f0c895b3..f076dce5 100644 --- a/src/util/typescript-utils.ts +++ b/src/util/typescript-utils.ts @@ -1,5 +1,7 @@ import { + CallExpression, createSourceFile, + Identifier, ImportClause, ImportDeclaration, ImportSpecifier, @@ -58,12 +60,17 @@ export function appendAfter(source: string, node: Node, toAppend: string): strin return stringSplice(source, node.getEnd(), 0, toAppend); } -export function insertNamedImport(filePath: string, fileContent: string, namedImport: string, fromPath: string) { +export function appendBefore(filePath: string, fileContent: string, node: Node, toAppend: string): string { + const sourceFile = getTypescriptSourceFile(filePath, fileContent, ScriptTarget.Latest, false); + return stringSplice(fileContent, node.getStart(sourceFile), 0, toAppend); +} + +export function insertNamedImportIfNeeded(filePath: string, fileContent: string, namedImport: string, fromModule: string) { const sourceFile = getTypescriptSourceFile(filePath, fileContent, ScriptTarget.Latest, false); const allImports = findNodes(sourceFile, sourceFile, SyntaxKind.ImportDeclaration); const maybeImports = allImports.filter((node: ImportDeclaration) => { return node.moduleSpecifier.kind === SyntaxKind.StringLiteral - && (node.moduleSpecifier as StringLiteral).text === fromPath; + && (node.moduleSpecifier as StringLiteral).text === fromModule; }).filter((node: ImportDeclaration) => { // Remove import statements that are either `import 'XYZ'` or `import * as X from 'XYZ'`. const clause = node.importClause as ImportClause; @@ -93,7 +100,7 @@ export function insertNamedImport(filePath: string, fileContent: string, namedIm } else { // Find the last import and insert after. fileContent = appendAfter(fileContent, allImports[allImports.length - 1], - `import {${namedImport}} from '${fromPath}';`); + `\nimport { ${namedImport} } from '${fromModule}';`); } return fileContent; @@ -131,3 +138,10 @@ export function replaceImportModuleSpecifier(filePath: string, fileContent: stri }); return modifiedContent; } + +export function checkIfFunctionIsCalled(filePath: string, fileContent: string, functionName: string) { + const sourceFile = getTypescriptSourceFile(filePath, fileContent, ScriptTarget.Latest, false); + const allCalls = findNodes(sourceFile, sourceFile, SyntaxKind.CallExpression, true) as CallExpression[]; + const functionCallList = allCalls.filter(call => call.expression && call.expression.kind === SyntaxKind.Identifier && (call.expression as Identifier).text === functionName); + return functionCallList.length > 0; +} \ No newline at end of file