import { normalize } from 'path'; import * as ts from 'typescript'; import { AddNodeOperation, ReplaceNodeOperation, StandardTransform, TransformOperation, collectDeepNodes, makeTransform, insertStarImport } from '@ngtools/webpack/src/transformers'; import { AngularCompilerPlugin } from '@ngtools/webpack'; import { findBootstrappedModulePathInSource, findNativeScriptPlatformPathInSource, findBootstrapModuleCallInSource, findNativeScriptPlatformCallInSource, getExpressionName } from '../utils/ast-utils'; export function nsSupportHmrNg(getNgCompiler: () => AngularCompilerPlugin, entryPath: string): ts.TransformerFactory { const standardTransform: StandardTransform = function (sourceFile: ts.SourceFile) { let ops: TransformOperation[] = []; if (!entryPath || normalize(sourceFile.fileName) !== normalize(entryPath)) { return ops; } try { ops = handleHmrSupport(sourceFile); } catch (e) { ops = []; } return ops; }; return makeTransform(standardTransform, () => getNgCompiler().typeChecker); } export function handleHmrSupport(mainFile: ts.SourceFile): TransformOperation[] { const importNodesInFile = collectDeepNodes(mainFile, ts.SyntaxKind.ImportDeclaration); if (!importNodesInFile || !importNodesInFile.length) { return []; } const bootstrapModuleCallNode = findBootstrapModuleCallInSource(mainFile); if (!bootstrapModuleCallNode || !bootstrapModuleCallNode.arguments || !bootstrapModuleCallNode.arguments.length) { return []; } const appModuleName = getExpressionName(bootstrapModuleCallNode.arguments[0]); const nativeScriptPlatformCallNode = findNativeScriptPlatformCallInSource(mainFile); if (!nativeScriptPlatformCallNode || !nativeScriptPlatformCallNode.arguments) { return []; } return handleHmrSupportCore(mainFile, importNodesInFile, appModuleName, nativeScriptPlatformCallNode); } function handleHmrSupportCore(mainFile: ts.SourceFile, importNodesInFile: ts.Node[], appModuleName: string, nativeScriptPlatformCallNode: ts.CallExpression) { const firstImportNode = importNodesInFile[0]; const lastImportNode = importNodesInFile[importNodesInFile.length - 1]; const appModulePath = findBootstrappedModulePathInSource(mainFile); if (!appModuleName || !appModulePath) { return []; } let currentAppOptionsInitializationNode: ts.Expression = ts.createObjectLiteral(); if (nativeScriptPlatformCallNode.arguments.length > 0) { currentAppOptionsInitializationNode = nativeScriptPlatformCallNode.arguments[0]; } const optionsDeclaration = ts.createVariableDeclaration(GeneratedDynamicAppOptions, undefined, ts.createObjectLiteral()); const optionsDeclarationList = ts.createVariableDeclarationList([optionsDeclaration]); const optionsStatement = ts.createVariableStatement(undefined, optionsDeclarationList); const setHmrOptionsNode = ts.createIdentifier(getHmrOptionsCode(appModuleName, appModulePath)); const acceptHmrNode = ts.createIdentifier(getAcceptMainModuleCode(appModulePath)); const objectAssignNode = ts.createPropertyAccess(ts.createIdentifier('Object'), ts.createIdentifier('assign')); const extendAppOptionsNode = ts.createCall(objectAssignNode, undefined, [currentAppOptionsInitializationNode, ts.createIdentifier(GeneratedDynamicAppOptions)]); const newNsDynamicCallArgs = ts.createNodeArray([extendAppOptionsNode, ...nativeScriptPlatformCallNode.arguments.slice(1)]); const nsPlatformPath = findNativeScriptPlatformPathInSource(mainFile); const nsPlatformText = getExpressionName(nativeScriptPlatformCallNode.expression); const newNsDynamicCallNode = ts.createCall(ts.createPropertyAccess(ts.createIdentifier(NsNgPlatformStarImport), ts.createIdentifier(nsPlatformText)), [], newNsDynamicCallArgs); return [...insertStarImport(mainFile, ts.createIdentifier(NsNgPlatformStarImport), nsPlatformPath, firstImportNode, true), new AddNodeOperation(mainFile, lastImportNode, undefined, optionsStatement), new AddNodeOperation(mainFile, lastImportNode, undefined, setHmrOptionsNode), new AddNodeOperation(mainFile, lastImportNode, undefined, acceptHmrNode), new ReplaceNodeOperation(mainFile, nativeScriptPlatformCallNode, newNsDynamicCallNode)]; } export const GeneratedDynamicAppOptions = 'options_Generated'; const NsNgPlatformStarImport = 'nativescript_angular_platform_Generated'; export function getHmrOptionsCode(appModuleName: string, appModulePath: string) { return ` if (module["hot"]) { ${GeneratedDynamicAppOptions} = { hmrOptions: { moduleTypeFactory: function () { return require("${appModulePath}").${appModuleName}; }, livesyncCallback: function (platformReboot) { setTimeout(platformReboot, 0); } } }; } `; } export function getAcceptMainModuleCode(mainModulePath: string) { return ` if (module["hot"]) { module["hot"].accept(["${mainModulePath}"], function () { global["hmrRefresh"]({}); }); } `; }